alec

Abstraction 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 (10286B)


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