alecAbstraction Layer for Escape Codes |
git clone git://git.dimitrijedobrota.com/alec.git |
Log | Files | Refs | README | LICENSE | HACKING | CONTRIBUTING | CODE_OF_CONDUCT | BUILDING |
alec.rules.hpp (10284B)
0 #pragma once 1 2 #include <algorithm> 3 #include <array> 4 #include <cassert> 5 #include <cstdint> 6 #include <optional> 7 #include <stdexcept> 8 #include <string> 9 10 #include <sys/ioctl.h> 11 #include <termios.h> 12 #include <unistd.h> 13 14 namespace alec 15 { 16 17 enum Ctrl : std::uint8_t 18 { 19 BELL = 0x07, 20 BS = 0x08, 21 HT = 0x09, 22 LF = 0x0A, 23 VT = 0x0B, 24 FF = 0x0C, 25 CR = 0x0D, 26 ESC = 0x1B, 27 DEL = 0x7F, 28 }; 29 30 enum class Color : std::uint8_t 31 { 32 BLACK = 0, 33 RED = 1, 34 GREEN = 2, 35 YELLOW = 3, 36 BLUE = 4, 37 MAGENTA = 5, 38 CYAN = 6, 39 WHITE = 7, 40 DEFAULT = 9, 41 }; 42 43 enum class Decor : std::uint8_t 44 { 45 RESET = 0, 46 BOLD = 1, 47 DIM = 2, 48 ITALIC = 3, 49 UNDERLINE = 4, 50 BLINK = 5, 51 INVERSE = 7, 52 HIDE = 8, 53 STRIKE = 9, 54 }; 55 56 enum class Motion : std::uint8_t 57 { 58 END = 0, 59 BEGIN = 1, 60 WHOLE = 2, 61 }; 62 63 namespace details 64 { 65 66 template<std::size_t n> 67 struct string_literal 68 { 69 constexpr string_literal(const char (&str)[n]) // NOLINT 70 : m_value(std::to_array(str)) 71 { 72 } // NOLINT 73 74 constexpr std::size_t size() const { return n; } 75 constexpr const char* data() const { return m_value.data(); } 76 77 std::array<char, n> m_value; 78 }; 79 80 namespace helper 81 { 82 template<std::size_t n> 83 static constexpr std::size_t size(string_literal<n> /*val*/) 84 { 85 return n; 86 } 87 static constexpr std::size_t size(char /*val*/) 88 { 89 return 1; 90 } 91 static constexpr std::size_t size(int val) 92 { 93 std::size_t len = 1; 94 while ((val /= 10) != 0) { 95 len++; 96 } 97 return len; 98 } 99 100 template<std::size_t n> 101 static constexpr char* append(char* ptr, string_literal<n> val) 102 { 103 std::copy_n(val.data(), n, ptr); 104 return ptr + n; // NOLINT 105 } 106 107 static constexpr char* append(char* ptr, char val) 108 { 109 *ptr++ = val; // NOLINT 110 return ptr; 111 } 112 113 static constexpr char* append(char* ptr, int val) 114 { 115 char* tmp = ptr += size(val); // NOLINT 116 do { // NOLINT 117 *--tmp = '0' + static_cast<char>(val % 10); // NOLINT 118 } while ((val /= 10) != 0); 119 return ptr; 120 } 121 122 static constexpr std::string make(auto... args) 123 { 124 std::string res((helper::size(args) + ... + 2), 0); 125 res[0] = Ctrl::ESC, res[1] = '['; 126 auto* ptr = res.data() + 2; // NOLINT 127 ((ptr = helper::append(ptr, args)), ...); 128 return res; 129 } 130 131 template<auto... args> 132 struct escape_t 133 { 134 static constexpr const auto value = []() 135 { 136 std::array<char, (helper::size(args) + ... + 3)> arr = {Ctrl::ESC, '[', 0}; 137 auto* ptr = arr.data() + 2; 138 ((ptr = helper::append(ptr, args)), ...); 139 return arr; 140 }(); 141 static constexpr auto data = value.data(); 142 }; 143 144 } // namespace helper 145 146 template<auto... args> 147 static constexpr auto escape = alec::details::helper::escape_t<args...>::data; 148 149 template<details::string_literal... strs> 150 static constexpr auto escape_literal = escape<strs...>; 151 152 } // namespace details 153 154 // Tamplate parameter constraints 155 156 template<int n> 157 concept limit_256_v = n >= 0 && n < 256; 158 159 template<int n> 160 concept limit_pos_v = n >= 0; 161 162 static constexpr bool limit_pos(int n) 163 { 164 return n >= 0; 165 } 166 static constexpr bool limit_256(int n) 167 { 168 return n >= 0 && n < 256; 169 } 170 171 /*%%*//* 172 // NOLINTBEGIN (*cast*) 173 174 // Move cursor up/down/frwd/back 175 176 cursor_up 177 int cnt 178 limit_pos 179 cnt, 'A' 180 181 cursor_down 182 int cnt 183 limit_pos 184 cnt, 'B' 185 186 cursor_frwd 187 int cnt 188 limit_pos 189 cnt, 'C' 190 191 cursor_back 192 int cnt 193 limit_pos 194 cnt, 'D' 195 196 // Move cursor to the next/prev line 197 198 cursor_line_next 199 int cnt 200 limit_pos 201 cnt, 'E' 202 203 cursor_line_prev 204 int cnt 205 limit_pos 206 cnt, 'F' 207 208 // Set cursor to specific column 209 210 cursor_column 211 int col 212 limit_pos 213 col, 'G' 214 215 // Erase functions 216 217 erase_display 218 Motion mtn 219 | 220 int(mtn), 'J' 221 222 erase_line 223 Motion mtn 224 | 225 int(mtn), 'K' 226 227 // Scroll up/down 228 229 scroll_up 230 int cnt 231 limit_pos 232 cnt, 'S' 233 234 scroll_down 235 int cnt 236 limit_pos 237 cnt, 'T' 238 239 // Set cursor to a specific position 240 241 cursor_position 242 int row, int col 243 limit_pos 244 row, ';', col, 'H' 245 246 // color 247 248 // palet colors 249 250 foreground 251 Color color 252 | 253 int(color) + 30, 'm' 254 255 background 256 Color color 257 | 258 int(color) + 40, 'm' 259 260 // 256-color palette 261 262 foreground 263 int idx 264 limit_256 265 38, ';', 5, ';', idx, 'm' 266 267 background 268 int idx 269 limit_256 270 48, ';', 5, ';', idx, 'm' 271 272 // RGB colors 273 274 foreground 275 int red, int green, int blue 276 limit_256 277 38, ';', 2, ';', red, ';', green, ';', blue, 'm' 278 279 background 280 int red, int green, int blue 281 limit_256 282 48, ';', 2, ';', red, ';', green, ';', blue, 'm' 283 284 // Set/reset text decorators 285 286 decor_set 287 Decor decor 288 | 289 int(decor), 'm' 290 291 decor_reset 292 Decor decor 293 | 294 int(decor) + 20, 'm' 295 296 // Save/restore cursor position; 297 298 cursor_save 299 | 300 | 301 's' 302 303 cursor_restore 304 | 305 | 306 'u' 307 308 // Set screen modes 309 310 screen_mode_set 311 int mode 312 limit_pos 313 '=', mode, 'h' 314 315 screen_mode_reset 316 int mode 317 limit_pos 318 '=', mode, 'l' 319 320 // Private screen modes supported by most terminals 321 322 // Save/restore screen 323 324 screen_save 325 | 326 | 327 "?47h" 328 329 screen_restore 330 | 331 | 332 "?47l" 333 334 // Show/hide cursor 335 336 cursor_show 337 | 338 | 339 "?25h" 340 341 cursor_hide 342 | 343 | 344 "?25l" 345 346 // Enable/disable alternate buffer 347 348 abuf_enable 349 | 350 | 351 "?1049h" 352 353 abuf_disable 354 | 355 | 356 "?1049l" 357 358 // Enable/disable bracketed paste mode 359 360 paste_enable 361 | 362 | 363 "?2004h" 364 365 paste_disable 366 | 367 | 368 "?2004l" 369 370 // NOLINTEND (*cast*) 371 *//*%%*/ 372 373 class runtime_error : public std::runtime_error 374 { 375 public: 376 explicit runtime_error(const std::string& err) 377 : std::runtime_error(err) 378 { 379 } 380 }; 381 382 enum error_code_t // NOLINT 383 { 384 FDNTERM, 385 TERMIOSRD, 386 TERMIOSWR, 387 BUFFULL, 388 CHARRD, 389 IOCTL, 390 SCREENSZ 391 }; 392 393 template<error_code_t e> 394 class error : public runtime_error 395 { 396 public: 397 explicit error() 398 : runtime_error(error_get_message(e)) 399 { 400 } 401 402 private: 403 static std::string error_get_message(error_code_t error) 404 { 405 switch (error) { 406 case error_code_t::FDNTERM: 407 return "File descriptor is not associated with a terminal"; 408 case error_code_t::TERMIOSRD: 409 return "Can't read termios"; 410 case error_code_t::TERMIOSWR: 411 return "Can't write termios"; 412 case error_code_t::BUFFULL: 413 return "Buffer is full"; 414 case error_code_t::CHARRD: 415 return "Can't read character"; 416 case error_code_t::IOCTL: 417 return "ioctl error"; 418 case error_code_t::SCREENSZ: 419 return "Can't determine the screen size"; 420 } 421 422 return "alec error, should not happen..."; 423 } 424 }; 425 426 class buffer 427 { 428 public: 429 explicit buffer(int fdsc) 430 : m_fd(fdsc) 431 , m_orig_termios() 432 { 433 if (isatty(m_fd) == 0) { 434 throw error<error_code_t::FDNTERM>(); 435 } 436 437 if (tcgetattr(m_fd, &m_orig_termios) == -1) { 438 throw error<error_code_t::TERMIOSRD>(); 439 } 440 441 struct termios raw = m_orig_termios; 442 443 // NOLINTBEGIN 444 raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); 445 raw.c_oflag &= ~(OPOST); 446 raw.c_cflag |= (CS8); 447 raw.c_lflag &= ~(ECHO | ICANON | IEXTEN); // | ISIG 448 raw.c_cc[VMIN] = 0; 449 raw.c_cc[VTIME] = 1; 450 // NOLINTEND 451 452 /* put terminal in raw mode after flushing */ 453 if (tcsetattr(m_fd, TCSAFLUSH, &raw) < 0) { 454 throw error<error_code_t::TERMIOSWR>(); 455 } 456 } 457 458 buffer(const buffer&) = delete; 459 buffer& operator=(const buffer&) = delete; 460 461 buffer(buffer&&) = default; 462 buffer& operator=(buffer&&) = default; 463 464 ~buffer() { tcsetattr(m_fd, TCSAFLUSH, &m_orig_termios); } 465 466 uint8_t read() 467 { 468 if (m_start == m_end && get() == 0) { 469 return 0; 470 } 471 472 uint8_t chr = m_buffer[m_start]; // NOLINT 473 m_start = (m_start + 1) % m_buffer.size(); 474 return chr; 475 } 476 477 uint8_t read_blocking() 478 { 479 while (m_start == m_end) { 480 get(); 481 } 482 483 uint8_t chr = m_buffer[m_start]; // NOLINT 484 m_start = (m_start + 1) % m_buffer.size(); 485 return chr; 486 } 487 488 void flush() 489 { 490 while (get() != 0) { 491 } 492 } 493 494 private: 495 size_t get() 496 { 497 ssize_t scnt = -1; 498 if ((m_end + 1) % m_buffer.size() == m_start) { 499 throw error<error_code_t::BUFFULL>(); 500 } 501 502 if (m_start <= m_end) { 503 scnt = ::read(m_fd, m_buffer.data() + m_end, m_buffer.size() - m_end); 504 } else { 505 scnt = ::read(m_fd, m_buffer.data() + m_end, m_start - m_end); 506 } 507 508 if (scnt == -1) { 509 throw error<error_code_t::CHARRD>(); 510 } 511 512 const auto cnt = static_cast<size_t>(scnt); 513 m_end = (m_end + cnt) % m_buffer.size(); 514 return cnt; 515 } 516 517 std::array<uint8_t, 1024> m_buffer = {0}; 518 int m_fd = 0; 519 520 uint64_t m_start = 0; 521 uint64_t m_end = 0; 522 523 struct termios m_orig_termios; 524 }; 525 526 inline auto& get_buffer() 527 { 528 static std::optional<buffer> ibuf; 529 return ibuf; 530 } 531 532 inline void init_buffer(int fdsc) 533 { 534 get_buffer().emplace(fdsc); 535 } 536 537 inline void dest_buffer() 538 { 539 get_buffer().reset(); 540 } 541 542 inline std::pair<std::uint16_t, std::uint16_t> get_screen_size() 543 { 544 #ifdef TIOCGSIZE 545 struct ttysize tts = {}; 546 if (ioctl(STDIN_FILENO, TIOCGSIZE, &tts) == -1) { // NOLINT 547 throw error<error_code_t::IOCTL>(); 548 } 549 return {tts.ts_cols, tts.ts_lines}; 550 #elif defined(TIOCGWINSZ) 551 struct winsize tts = {}; 552 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &tts) == -1) { // NOLINT 553 throw error<error_code_t::IOCTL>(); 554 } 555 return {tts.ws_col, tts.ws_row}; 556 #endif /* TIOCGSIZE */ 557 558 throw error<error_code_t::SCREENSZ>(); 559 } 560 561 class event 562 { 563 public: 564 enum class Type : std::uint8_t 565 { 566 NONE = 0, 567 KEY = 1, 568 RESIZE = 2, 569 MOUSE = 3, 570 }; 571 572 enum class Mod : std::uint8_t 573 { 574 ALT = 1, 575 CTRL = 2, 576 SHIFT = 4, 577 MOTION = 8, 578 }; 579 580 event(Type type, uint8_t mod_mask, uint8_t key) // NOLINT 581 : m_type(type) 582 , m_mod_mask(mod_mask) 583 , m_key(key) 584 { 585 } 586 587 const auto& type() const { return m_type; } 588 auto& type() { return m_type; } 589 590 const auto& key() const { return m_key; } 591 auto& key() { return m_key; } 592 593 const auto& mod_mask() const { return m_mod_mask; } 594 auto& mod_mask() { return m_mod_mask; } 595 596 bool is_set(uint8_t mask) const { return mask == (m_mod_mask & mask); } 597 598 private: 599 Type m_type = Type::NONE; 600 uint8_t m_mod_mask = 0; 601 uint8_t m_key = 0; 602 }; 603 604 inline event get_event() 605 { 606 const auto chr = get_buffer().value().read(); 607 return {chr != 0 ? event::Type::KEY : event::Type::NONE, 0, chr}; 608 } 609 610 } // namespace alec