1 /* 2 * ***************************************************************************** 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (c) 2018-2020 Gavin D. Howard and contributors. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions are met: 10 * 11 * * Redistributions of source code must retain the above copyright notice, this 12 * list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright notice, 15 * this list of conditions and the following disclaimer in the documentation 16 * and/or other materials provided with the distribution. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 * POSSIBILITY OF SUCH DAMAGE. 29 * 30 * ***************************************************************************** 31 * 32 * Adapted from the following: 33 * 34 * linenoise.c -- guerrilla line editing library against the idea that a 35 * line editing lib needs to be 20,000 lines of C code. 36 * 37 * You can find the original source code at: 38 * http://github.com/antirez/linenoise 39 * 40 * You can find the fork that this code is based on at: 41 * https://github.com/rain-1/linenoise-mob 42 * 43 * ------------------------------------------------------------------------ 44 * 45 * This code is also under the following license: 46 * 47 * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com> 48 * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> 49 * 50 * Redistribution and use in source and binary forms, with or without 51 * modification, are permitted provided that the following conditions are 52 * met: 53 * 54 * * Redistributions of source code must retain the above copyright 55 * notice, this list of conditions and the following disclaimer. 56 * 57 * * Redistributions in binary form must reproduce the above copyright 58 * notice, this list of conditions and the following disclaimer in the 59 * documentation and/or other materials provided with the distribution. 60 * 61 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 62 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 63 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 64 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 65 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 66 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 67 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 68 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 69 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 70 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 71 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 72 * 73 * ------------------------------------------------------------------------ 74 * 75 * Does a number of crazy assumptions that happen to be true in 99.9999% of 76 * the 2010 UNIX computers around. 77 * 78 * References: 79 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html 80 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html 81 * 82 * Todo list: 83 * - Filter bogus Ctrl+<char> combinations. 84 * - Win32 support 85 * 86 * Bloat: 87 * - History search like Ctrl+r in readline? 88 * 89 * List of escape sequences used by this program, we do everything just 90 * with three sequences. In order to be so cheap we may have some 91 * flickering effect with some slow terminal, but the lesser sequences 92 * the more compatible. 93 * 94 * EL (Erase Line) 95 * Sequence: ESC [ n K 96 * Effect: if n is 0 or missing, clear from cursor to end of line 97 * Effect: if n is 1, clear from beginning of line to cursor 98 * Effect: if n is 2, clear entire line 99 * 100 * CUF (CUrsor Forward) 101 * Sequence: ESC [ n C 102 * Effect: moves cursor forward n chars 103 * 104 * CUB (CUrsor Backward) 105 * Sequence: ESC [ n D 106 * Effect: moves cursor backward n chars 107 * 108 * The following is used to get the terminal width if getting 109 * the width with the TIOCGWINSZ ioctl fails 110 * 111 * DSR (Device Status Report) 112 * Sequence: ESC [ 6 n 113 * Effect: reports the current cusor position as ESC [ n ; m R 114 * where n is the row and m is the column 115 * 116 * When multi line mode is enabled, we also use two additional escape 117 * sequences. However multi line editing is disabled by default. 118 * 119 * CUU (CUrsor Up) 120 * Sequence: ESC [ n A 121 * Effect: moves cursor up of n chars. 122 * 123 * CUD (CUrsor Down) 124 * Sequence: ESC [ n B 125 * Effect: moves cursor down of n chars. 126 * 127 * When bc_history_clearScreen() is called, two additional escape sequences 128 * are used in order to clear the screen and position the cursor at home 129 * position. 130 * 131 * CUP (CUrsor Position) 132 * Sequence: ESC [ H 133 * Effect: moves the cursor to upper left corner 134 * 135 * ED (Erase Display) 136 * Sequence: ESC [ 2 J 137 * Effect: clear the whole screen 138 * 139 * ***************************************************************************** 140 * 141 * Code for line history. 142 * 143 */ 144 145 #if BC_ENABLE_HISTORY 146 147 #include <assert.h> 148 #include <stdlib.h> 149 #include <errno.h> 150 #include <string.h> 151 #include <strings.h> 152 #include <ctype.h> 153 154 #include <signal.h> 155 156 #include <termios.h> 157 #include <unistd.h> 158 #include <sys/stat.h> 159 #include <sys/types.h> 160 #include <sys/ioctl.h> 161 #include <sys/select.h> 162 163 #include <vector.h> 164 #include <history.h> 165 #include <read.h> 166 #include <file.h> 167 #include <vm.h> 168 169 static void bc_history_add(BcHistory *h, char *line); 170 static void bc_history_add_empty(BcHistory *h); 171 172 /** 173 * Check if the code is a wide character. 174 */ 175 static bool bc_history_wchar(uint32_t cp) { 176 177 size_t i; 178 179 for (i = 0; i < bc_history_wchars_len; ++i) { 180 181 // Ranges are listed in ascending order. Therefore, once the 182 // whole range is higher than the codepoint we're testing, the 183 // codepoint won't be found in any remaining range => bail early. 184 if (bc_history_wchars[i][0] > cp) return false; 185 186 // Test this range. 187 if (bc_history_wchars[i][0] <= cp && cp <= bc_history_wchars[i][1]) 188 return true; 189 } 190 191 return false; 192 } 193 194 /** 195 * Check if the code is a combining character. 196 */ 197 static bool bc_history_comboChar(uint32_t cp) { 198 199 size_t i; 200 201 for (i = 0; i < bc_history_combo_chars_len; ++i) { 202 203 // Combining chars are listed in ascending order, so once we pass 204 // the codepoint of interest, we know it's not a combining char. 205 if (bc_history_combo_chars[i] > cp) return false; 206 if (bc_history_combo_chars[i] == cp) return true; 207 } 208 209 return false; 210 } 211 212 /** 213 * Get length of previous UTF8 character. 214 */ 215 static size_t bc_history_prevCharLen(const char *buf, size_t pos) { 216 size_t end = pos; 217 for (pos -= 1; pos < end && (buf[pos] & 0xC0) == 0x80; --pos); 218 return end - (pos >= end ? 0 : pos); 219 } 220 221 /** 222 * Convert UTF-8 to Unicode code point. 223 */ 224 static size_t bc_history_codePoint(const char *s, size_t len, uint32_t *cp) { 225 226 if (len) { 227 228 uchar byte = (uchar) s[0]; 229 230 if ((byte & 0x80) == 0) { 231 *cp = byte; 232 return 1; 233 } 234 else if ((byte & 0xE0) == 0xC0) { 235 236 if (len >= 2) { 237 *cp = (((uint32_t) (s[0] & 0x1F)) << 6) | 238 ((uint32_t) (s[1] & 0x3F)); 239 return 2; 240 } 241 } 242 else if ((byte & 0xF0) == 0xE0) { 243 244 if (len >= 3) { 245 *cp = (((uint32_t) (s[0] & 0x0F)) << 12) | 246 (((uint32_t) (s[1] & 0x3F)) << 6) | 247 ((uint32_t) (s[2] & 0x3F)); 248 return 3; 249 } 250 } 251 else if ((byte & 0xF8) == 0xF0) { 252 253 if (len >= 4) { 254 *cp = (((uint32_t) (s[0] & 0x07)) << 18) | 255 (((uint32_t) (s[1] & 0x3F)) << 12) | 256 (((uint32_t) (s[2] & 0x3F)) << 6) | 257 ((uint32_t) (s[3] & 0x3F)); 258 return 4; 259 } 260 } 261 else { 262 *cp = 0xFFFD; 263 return 1; 264 } 265 } 266 267 *cp = 0; 268 269 return 1; 270 } 271 272 /** 273 * Get length of next grapheme. 274 */ 275 static size_t bc_history_nextLen(const char *buf, size_t buf_len, 276 size_t pos, size_t *col_len) 277 { 278 uint32_t cp; 279 size_t beg = pos; 280 size_t len = bc_history_codePoint(buf + pos, buf_len - pos, &cp); 281 282 if (bc_history_comboChar(cp)) { 283 // Currently unreachable? 284 return 0; 285 } 286 287 if (col_len != NULL) *col_len = bc_history_wchar(cp) ? 2 : 1; 288 289 pos += len; 290 291 while (pos < buf_len) { 292 293 len = bc_history_codePoint(buf + pos, buf_len - pos, &cp); 294 295 if (!bc_history_comboChar(cp)) return pos - beg; 296 297 pos += len; 298 } 299 300 return pos - beg; 301 } 302 303 /** 304 * Get length of previous grapheme. 305 */ 306 static size_t bc_history_prevLen(const char *buf, size_t pos, size_t *col_len) { 307 308 size_t end = pos; 309 310 while (pos > 0) { 311 312 uint32_t cp; 313 size_t len = bc_history_prevCharLen(buf, pos); 314 315 pos -= len; 316 bc_history_codePoint(buf + pos, len, &cp); 317 318 if (!bc_history_comboChar(cp)) { 319 if (col_len != NULL) *col_len = 1 + (bc_history_wchar(cp) != 0); 320 return end - pos; 321 } 322 } 323 324 // Currently unreachable? 325 return 0; 326 } 327 328 static ssize_t bc_history_read(char *buf, size_t n) { 329 330 ssize_t ret; 331 332 BC_SIG_LOCK; 333 334 do { 335 ret = read(STDIN_FILENO, buf, n); 336 } while (ret == EINTR); 337 338 BC_SIG_UNLOCK; 339 340 return ret; 341 } 342 343 /** 344 * Read a Unicode code point from a file. 345 */ 346 static BcStatus bc_history_readCode(char *buf, size_t buf_len, 347 uint32_t *cp, size_t *nread) 348 { 349 ssize_t n; 350 351 assert(buf_len >= 1); 352 353 n = bc_history_read(buf, 1); 354 if (BC_ERR(n <= 0)) goto err; 355 356 uchar byte = (uchar) buf[0]; 357 358 if ((byte & 0x80) != 0) { 359 360 if ((byte & 0xE0) == 0xC0) { 361 assert(buf_len >= 2); 362 n = bc_history_read(buf + 1, 1); 363 if (BC_ERR(n <= 0)) goto err; 364 } 365 else if ((byte & 0xF0) == 0xE0) { 366 assert(buf_len >= 3); 367 n = bc_history_read(buf + 1, 2); 368 if (BC_ERR(n <= 0)) goto err; 369 } 370 else if ((byte & 0xF8) == 0xF0) { 371 assert(buf_len >= 3); 372 n = bc_history_read(buf + 1, 3); 373 if (BC_ERR(n <= 0)) goto err; 374 } 375 else { 376 n = -1; 377 goto err; 378 } 379 } 380 381 *nread = bc_history_codePoint(buf, buf_len, cp); 382 383 return BC_STATUS_SUCCESS; 384 385 err: 386 if (BC_ERR(n < 0)) bc_vm_err(BC_ERR_FATAL_IO_ERR); 387 else *nread = (size_t) n; 388 return BC_STATUS_EOF; 389 } 390 391 /** 392 * Get column length from begining of buffer to current byte position. 393 */ 394 static size_t bc_history_colPos(const char *buf, size_t buf_len, size_t pos) { 395 396 size_t ret = 0, off = 0; 397 398 while (off < pos) { 399 400 size_t col_len, len; 401 402 len = bc_history_nextLen(buf, buf_len, off, &col_len); 403 404 off += len; 405 ret += col_len; 406 } 407 408 return ret; 409 } 410 411 /** 412 * Return true if the terminal name is in the list of terminals we know are 413 * not able to understand basic escape sequences. 414 */ 415 static inline bool bc_history_isBadTerm(void) { 416 417 size_t i; 418 char *term = getenv("TERM"); 419 420 if (term == NULL) return false; 421 422 for (i = 0; bc_history_bad_terms[i]; ++i) { 423 if (!strcasecmp(term, bc_history_bad_terms[i])) return true; 424 } 425 426 return false; 427 } 428 429 /** 430 * Raw mode: 1960's black magic. 431 */ 432 static void bc_history_enableRaw(BcHistory *h) { 433 434 struct termios raw; 435 int err; 436 437 assert(BC_TTYIN); 438 439 if (h->rawMode) return; 440 441 BC_SIG_LOCK; 442 443 if (BC_ERR(tcgetattr(STDIN_FILENO, &h->orig_termios) == -1)) 444 bc_vm_err(BC_ERR_FATAL_IO_ERR); 445 446 BC_SIG_UNLOCK; 447 448 // Modify the original mode. 449 raw = h->orig_termios; 450 451 // Input modes: no break, no CR to NL, no parity check, no strip char, 452 // no start/stop output control. 453 raw.c_iflag &= (unsigned int) (~(BRKINT | ICRNL | INPCK | ISTRIP | IXON)); 454 455 // Control modes - set 8 bit chars. 456 raw.c_cflag |= (CS8); 457 458 // Local modes - choing off, canonical off, no extended functions, 459 // no signal chars (^Z,^C). 460 raw.c_lflag &= (unsigned int) (~(ECHO | ICANON | IEXTEN | ISIG)); 461 462 // Control chars - set return condition: min number of bytes and timer. 463 // We want read to give every single byte, w/o timeout (1 byte, no timer). 464 raw.c_cc[VMIN] = 1; 465 raw.c_cc[VTIME] = 0; 466 467 BC_SIG_LOCK; 468 469 // Put terminal in raw mode after flushing. 470 do { 471 err = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); 472 } while (BC_ERR(err < 0) && errno == EINTR); 473 474 BC_SIG_UNLOCK; 475 476 if (BC_ERR(err < 0)) bc_vm_err(BC_ERR_FATAL_IO_ERR); 477 478 h->rawMode = true; 479 } 480 481 static void bc_history_disableRaw(BcHistory *h) { 482 483 sig_atomic_t lock; 484 485 // Don't even check the return value as it's too late. 486 if (!h->rawMode) return; 487 488 BC_SIG_TRYLOCK(lock); 489 490 if (BC_ERR(tcsetattr(STDIN_FILENO, TCSAFLUSH, &h->orig_termios) != -1)) 491 h->rawMode = false; 492 493 BC_SIG_TRYUNLOCK(lock); 494 } 495 496 /** 497 * Use the ESC [6n escape sequence to query the horizontal cursor position 498 * and return it. On error -1 is returned, on success the position of the 499 * cursor. 500 */ 501 static size_t bc_history_cursorPos(void) { 502 503 char buf[BC_HIST_SEQ_SIZE]; 504 char *ptr, *ptr2; 505 size_t cols, rows, i; 506 507 // Report cursor location. 508 bc_file_write(&vm.fout, "\x1b[6n", 4); 509 bc_file_flush(&vm.fout); 510 511 // Read the response: ESC [ rows ; cols R. 512 for (i = 0; i < sizeof(buf) - 1; ++i) { 513 if (bc_history_read(buf + i, 1) != 1 || buf[i] == 'R') break; 514 } 515 516 buf[i] = '\0'; 517 518 if (BC_ERR(buf[0] != BC_ACTION_ESC || buf[1] != '[')) return SIZE_MAX; 519 520 // Parse it. 521 ptr = buf + 2; 522 rows = strtoul(ptr, &ptr2, 10); 523 524 if (BC_ERR(!rows || ptr2[0] != ';')) return SIZE_MAX; 525 526 ptr = ptr2 + 1; 527 cols = strtoul(ptr, NULL, 10); 528 529 if (BC_ERR(!cols)) return SIZE_MAX; 530 531 return cols <= UINT16_MAX ? cols : 0; 532 } 533 534 /** 535 * Try to get the number of columns in the current terminal, or assume 80 536 * if it fails. 537 */ 538 static size_t bc_history_columns(void) { 539 540 struct winsize ws; 541 int ret; 542 543 BC_SIG_LOCK; 544 545 ret = ioctl(vm.fout.fd, TIOCGWINSZ, &ws); 546 547 BC_SIG_UNLOCK; 548 549 if (BC_ERR(ret == -1 || !ws.ws_col)) { 550 551 // Calling ioctl() failed. Try to query the terminal itself. 552 size_t start, cols; 553 554 // Get the initial position so we can restore it later. 555 start = bc_history_cursorPos(); 556 if (BC_ERR(start == SIZE_MAX)) return BC_HIST_DEF_COLS; 557 558 // Go to right margin and get position. 559 bc_file_write(&vm.fout, "\x1b[999C", 6); 560 bc_file_flush(&vm.fout); 561 cols = bc_history_cursorPos(); 562 if (BC_ERR(cols == SIZE_MAX)) return BC_HIST_DEF_COLS; 563 564 // Restore position. 565 if (cols > start) { 566 bc_file_printf(&vm.fout, "\x1b[%zuD", cols - start); 567 bc_file_flush(&vm.fout); 568 } 569 570 return cols; 571 } 572 573 return ws.ws_col; 574 } 575 576 #if BC_ENABLE_PROMPT 577 /** 578 * Check if text is an ANSI escape sequence. 579 */ 580 static bool bc_history_ansiEscape(const char *buf, size_t buf_len, size_t *len) 581 { 582 if (buf_len > 2 && !memcmp("\033[", buf, 2)) { 583 584 size_t off = 2; 585 586 while (off < buf_len) { 587 588 char c = buf[off++]; 589 590 if ((c >= 'A' && c <= 'K' && c != 'I') || 591 c == 'S' || c == 'T' || c == 'f' || c == 'm') 592 { 593 *len = off; 594 return true; 595 } 596 } 597 } 598 599 return false; 600 } 601 602 /** 603 * Get column length of prompt text. 604 */ 605 static size_t bc_history_promptColLen(const char *prompt, size_t plen) { 606 607 char buf[BC_HIST_MAX_LINE + 1]; 608 size_t buf_len = 0, off = 0; 609 610 while (off < plen) { 611 612 size_t len; 613 614 if (bc_history_ansiEscape(prompt + off, plen - off, &len)) { 615 off += len; 616 continue; 617 } 618 619 buf[buf_len++] = prompt[off++]; 620 } 621 622 return bc_history_colPos(buf, buf_len, buf_len); 623 } 624 #endif // BC_ENABLE_PROMPT 625 626 /** 627 * Rewrites the currently edited line accordingly to the buffer content, 628 * cursor position, and number of columns of the terminal. 629 */ 630 static void bc_history_refresh(BcHistory *h) { 631 632 char* buf = h->buf.v; 633 size_t colpos, len = BC_HIST_BUF_LEN(h), pos = h->pos; 634 635 bc_file_flush(&vm.fout); 636 637 while(h->pcol + bc_history_colPos(buf, len, pos) >= h->cols) { 638 639 size_t chlen = bc_history_nextLen(buf, len, 0, NULL); 640 641 buf += chlen; 642 len -= chlen; 643 pos -= chlen; 644 } 645 646 while (h->pcol + bc_history_colPos(buf, len, len) > h->cols) 647 len -= bc_history_prevLen(buf, len, NULL); 648 649 // Cursor to left edge. 650 bc_file_write(&vm.fout, "\r", 1); 651 652 // Write the prompt, if desired. 653 #if BC_ENABLE_PROMPT 654 if (BC_USE_PROMPT) bc_file_write(&vm.fout, h->prompt, h->plen); 655 #endif // BC_ENABLE_PROMPT 656 657 bc_file_write(&vm.fout, buf, BC_HIST_BUF_LEN(h)); 658 659 // Erase to right. 660 bc_file_write(&vm.fout, "\x1b[0K", 4); 661 662 // Move cursor to original position. 663 colpos = bc_history_colPos(buf, len, pos) + h->pcol; 664 665 if (colpos) bc_file_printf(&vm.fout, "\r\x1b[%zuC", colpos); 666 667 bc_file_flush(&vm.fout); 668 } 669 670 /** 671 * Insert the character 'c' at cursor current position. 672 */ 673 static void bc_history_edit_insert(BcHistory *h, const char *cbuf, size_t clen) 674 { 675 bc_vec_grow(&h->buf, clen); 676 677 if (h->pos == BC_HIST_BUF_LEN(h)) { 678 679 size_t colpos = 0, len; 680 681 memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen); 682 683 h->pos += clen; 684 h->buf.len += clen - 1; 685 bc_vec_pushByte(&h->buf, '\0'); 686 687 len = BC_HIST_BUF_LEN(h); 688 #if BC_ENABLE_PROMPT 689 colpos = bc_history_promptColLen(h->prompt, h->plen); 690 #endif // BC_ENABLE_PROMPT 691 colpos += bc_history_colPos(h->buf.v, len, len); 692 693 if (colpos < h->cols) { 694 695 // Avoid a full update of the line in the trivial case. 696 bc_file_write(&vm.fout, cbuf, clen); 697 bc_file_flush(&vm.fout); 698 } 699 else bc_history_refresh(h); 700 } 701 else { 702 703 size_t amt = BC_HIST_BUF_LEN(h) - h->pos; 704 705 memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt); 706 memcpy(h->buf.v + h->pos, cbuf, clen); 707 708 h->pos += clen; 709 h->buf.len += clen; 710 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; 711 712 bc_history_refresh(h); 713 } 714 } 715 716 /** 717 * Move cursor to the left. 718 */ 719 static void bc_history_edit_left(BcHistory *h) { 720 721 if (h->pos <= 0) return; 722 723 h->pos -= bc_history_prevLen(h->buf.v, h->pos, NULL); 724 725 bc_history_refresh(h); 726 } 727 728 /** 729 * Move cursor on the right. 730 */ 731 static void bc_history_edit_right(BcHistory *h) { 732 733 if (h->pos == BC_HIST_BUF_LEN(h)) return; 734 735 h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); 736 737 bc_history_refresh(h); 738 } 739 740 /** 741 * Move cursor to the end of the current word. 742 */ 743 static void bc_history_edit_wordEnd(BcHistory *h) { 744 745 size_t len = BC_HIST_BUF_LEN(h); 746 747 if (!len || h->pos >= len) return; 748 749 while (h->pos < len && isspace(h->buf.v[h->pos])) h->pos += 1; 750 while (h->pos < len && !isspace(h->buf.v[h->pos])) h->pos += 1; 751 752 bc_history_refresh(h); 753 } 754 755 /** 756 * Move cursor to the start of the current word. 757 */ 758 static void bc_history_edit_wordStart(BcHistory *h) { 759 760 size_t len = BC_HIST_BUF_LEN(h); 761 762 if (!len) return; 763 764 while (h->pos > 0 && isspace(h->buf.v[h->pos - 1])) h->pos -= 1; 765 while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1])) h->pos -= 1; 766 767 bc_history_refresh(h); 768 } 769 770 /** 771 * Move cursor to the start of the line. 772 */ 773 static void bc_history_edit_home(BcHistory *h) { 774 775 if (!h->pos) return; 776 777 h->pos = 0; 778 779 bc_history_refresh(h); 780 } 781 782 /** 783 * Move cursor to the end of the line. 784 */ 785 static void bc_history_edit_end(BcHistory *h) { 786 787 if (h->pos == BC_HIST_BUF_LEN(h)) return; 788 789 h->pos = BC_HIST_BUF_LEN(h); 790 791 bc_history_refresh(h); 792 } 793 794 /** 795 * Substitute the currently edited line with the next or previous history 796 * entry as specified by 'dir' (direction). 797 */ 798 static void bc_history_edit_next(BcHistory *h, bool dir) { 799 800 const char *dup, *str; 801 802 if (h->history.len <= 1) return; 803 804 BC_SIG_LOCK; 805 806 if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v); 807 else dup = ""; 808 809 // Update the current history entry before 810 // overwriting it with the next one. 811 bc_vec_replaceAt(&h->history, h->history.len - 1 - h->idx, &dup); 812 813 BC_SIG_UNLOCK; 814 815 // Show the new entry. 816 h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX); 817 818 if (h->idx == SIZE_MAX) { 819 h->idx = 0; 820 return; 821 } 822 else if (h->idx >= h->history.len) { 823 h->idx = h->history.len - 1; 824 return; 825 } 826 827 str = *((char**) bc_vec_item(&h->history, h->history.len - 1 - h->idx)); 828 bc_vec_string(&h->buf, strlen(str), str); 829 assert(h->buf.len > 0); 830 831 h->pos = BC_HIST_BUF_LEN(h); 832 833 bc_history_refresh(h); 834 } 835 836 /** 837 * Delete the character at the right of the cursor without altering the cursor 838 * position. Basically this is what happens with the "Delete" keyboard key. 839 */ 840 static void bc_history_edit_delete(BcHistory *h) { 841 842 size_t chlen, len = BC_HIST_BUF_LEN(h); 843 844 if (!len || h->pos >= len) return; 845 846 chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL); 847 848 memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen); 849 850 h->buf.len -= chlen; 851 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; 852 853 bc_history_refresh(h); 854 } 855 856 static void bc_history_edit_backspace(BcHistory *h) { 857 858 size_t chlen, len = BC_HIST_BUF_LEN(h); 859 860 if (!h->pos || !len) return; 861 862 chlen = bc_history_prevLen(h->buf.v, h->pos, NULL); 863 864 memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos); 865 866 h->pos -= chlen; 867 h->buf.len -= chlen; 868 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; 869 870 bc_history_refresh(h); 871 } 872 873 /** 874 * Delete the previous word, maintaining the cursor at the start of the 875 * current word. 876 */ 877 static void bc_history_edit_deletePrevWord(BcHistory *h) { 878 879 size_t diff, old_pos = h->pos; 880 881 while (h->pos > 0 && h->buf.v[h->pos - 1] == ' ') --h->pos; 882 while (h->pos > 0 && h->buf.v[h->pos - 1] != ' ') --h->pos; 883 884 diff = old_pos - h->pos; 885 memmove(h->buf.v + h->pos, h->buf.v + old_pos, 886 BC_HIST_BUF_LEN(h) - old_pos + 1); 887 h->buf.len -= diff; 888 889 bc_history_refresh(h); 890 } 891 892 /** 893 * Delete the next word, maintaining the cursor at the same position. 894 */ 895 static void bc_history_edit_deleteNextWord(BcHistory *h) { 896 897 size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h); 898 899 while (next_end < len && h->buf.v[next_end] == ' ') ++next_end; 900 while (next_end < len && h->buf.v[next_end] != ' ') ++next_end; 901 902 memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end); 903 904 h->buf.len -= next_end - h->pos; 905 906 bc_history_refresh(h); 907 } 908 909 static void bc_history_swap(BcHistory *h) { 910 911 size_t pcl, ncl; 912 char auxb[5]; 913 914 pcl = bc_history_prevLen(h->buf.v, h->pos, NULL); 915 ncl = bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); 916 917 // To perform a swap we need: 918 // * nonzero char length to the left 919 // * not at the end of the line 920 if (pcl && h->pos != BC_HIST_BUF_LEN(h) && pcl < 5 && ncl < 5) { 921 922 memcpy(auxb, h->buf.v + h->pos - pcl, pcl); 923 memcpy(h->buf.v + h->pos - pcl, h->buf.v + h->pos, ncl); 924 memcpy(h->buf.v + h->pos - pcl + ncl, auxb, pcl); 925 926 h->pos += -pcl + ncl; 927 928 bc_history_refresh(h); 929 } 930 } 931 932 /** 933 * Handle escape sequences. 934 */ 935 static void bc_history_escape(BcHistory *h) { 936 937 char c, seq[3]; 938 939 if (BC_ERR(BC_HIST_READ(seq, 1))) return; 940 941 c = seq[0]; 942 943 // ESC ? sequences. 944 if (c != '[' && c != 'O') { 945 if (c == 'f') bc_history_edit_wordEnd(h); 946 else if (c == 'b') bc_history_edit_wordStart(h); 947 else if (c == 'd') bc_history_edit_deleteNextWord(h); 948 } 949 else { 950 951 if (BC_ERR(BC_HIST_READ(seq + 1, 1))) bc_vm_err(BC_ERR_FATAL_IO_ERR); 952 953 // ESC [ sequences. 954 if (c == '[') { 955 956 c = seq[1]; 957 958 if (c >= '0' && c <= '9') { 959 960 // Extended escape, read additional byte. 961 if (BC_ERR(BC_HIST_READ(seq + 2, 1))) 962 bc_vm_err(BC_ERR_FATAL_IO_ERR); 963 964 if (seq[2] == '~' && c == '3') bc_history_edit_delete(h); 965 else if(seq[2] == ';') { 966 967 if (BC_ERR(BC_HIST_READ(seq, 2))) 968 bc_vm_err(BC_ERR_FATAL_IO_ERR); 969 970 if (seq[0] != '5') return; 971 else if (seq[1] == 'C') bc_history_edit_wordEnd(h); 972 else if (seq[1] == 'D') bc_history_edit_wordStart(h); 973 } 974 } 975 else { 976 977 switch(c) { 978 979 // Up. 980 case 'A': 981 { 982 bc_history_edit_next(h, BC_HIST_PREV); 983 break; 984 } 985 986 // Down. 987 case 'B': 988 { 989 bc_history_edit_next(h, BC_HIST_NEXT); 990 break; 991 } 992 993 // Right. 994 case 'C': 995 { 996 bc_history_edit_right(h); 997 break; 998 } 999 1000 // Left. 1001 case 'D': 1002 { 1003 bc_history_edit_left(h); 1004 break; 1005 } 1006 1007 // Home. 1008 case 'H': 1009 case '1': 1010 { 1011 bc_history_edit_home(h); 1012 break; 1013 } 1014 1015 // End. 1016 case 'F': 1017 case '4': 1018 { 1019 bc_history_edit_end(h); 1020 break; 1021 } 1022 1023 case 'd': 1024 { 1025 bc_history_edit_deleteNextWord(h); 1026 break; 1027 } 1028 } 1029 } 1030 } 1031 // ESC O sequences. 1032 else if (c == 'O') { 1033 1034 switch (seq[1]) { 1035 1036 case 'A': 1037 { 1038 bc_history_edit_next(h, BC_HIST_PREV); 1039 break; 1040 } 1041 1042 case 'B': 1043 { 1044 bc_history_edit_next(h, BC_HIST_NEXT); 1045 break; 1046 } 1047 1048 case 'C': 1049 { 1050 bc_history_edit_right(h); 1051 break; 1052 } 1053 1054 case 'D': 1055 { 1056 bc_history_edit_left(h); 1057 break; 1058 } 1059 1060 case 'F': 1061 { 1062 bc_history_edit_end(h); 1063 break; 1064 } 1065 1066 case 'H': 1067 { 1068 bc_history_edit_home(h); 1069 break; 1070 } 1071 } 1072 } 1073 } 1074 } 1075 1076 static void bc_history_reset(BcHistory *h) { 1077 1078 h->oldcolpos = h->pos = h->idx = 0; 1079 h->cols = bc_history_columns(); 1080 1081 // The latest history entry is always our current buffer, that 1082 // initially is just an empty string. 1083 bc_history_add_empty(h); 1084 1085 // Buffer starts empty. 1086 bc_vec_empty(&h->buf); 1087 } 1088 1089 static void bc_history_printCtrl(BcHistory *h, unsigned int c) { 1090 1091 char str[3] = "^A"; 1092 const char newline[2] = "\n"; 1093 1094 str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A); 1095 1096 bc_vec_concat(&h->buf, str); 1097 1098 bc_history_refresh(h); 1099 1100 bc_vec_npop(&h->buf, sizeof(str)); 1101 bc_vec_pushByte(&h->buf, '\0'); 1102 1103 if (c != BC_ACTION_CTRL_C && c != BC_ACTION_CTRL_D) { 1104 bc_file_write(&vm.fout, newline, sizeof(newline) - 1); 1105 bc_history_refresh(h); 1106 } 1107 } 1108 1109 /** 1110 * This function is the core of the line editing capability of bc history. 1111 * It expects 'fd' to be already in "raw mode" so that every key pressed 1112 * will be returned ASAP to read(). 1113 */ 1114 static BcStatus bc_history_edit(BcHistory *h, const char *prompt) { 1115 1116 bc_history_reset(h); 1117 1118 #if BC_ENABLE_PROMPT 1119 if (BC_USE_PROMPT) { 1120 1121 h->prompt = prompt; 1122 h->plen = strlen(prompt); 1123 h->pcol = bc_history_promptColLen(prompt, h->plen); 1124 1125 bc_file_write(&vm.fout, prompt, h->plen); 1126 bc_file_flush(&vm.fout); 1127 } 1128 #endif // BC_ENABLE_PROMPT 1129 1130 for (;;) { 1131 1132 BcStatus s; 1133 // Large enough for any encoding? 1134 char cbuf[32]; 1135 unsigned int c = 0; 1136 size_t nread = 0; 1137 1138 s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread); 1139 if (BC_ERR(s)) return s; 1140 1141 switch (c) { 1142 1143 case BC_ACTION_LINE_FEED: 1144 case BC_ACTION_ENTER: 1145 { 1146 bc_vec_pop(&h->history); 1147 return s; 1148 } 1149 1150 case BC_ACTION_TAB: 1151 { 1152 memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1); 1153 bc_history_edit_insert(h, cbuf, bc_history_tab_len); 1154 break; 1155 } 1156 1157 case BC_ACTION_CTRL_C: 1158 { 1159 bc_history_printCtrl(h, c); 1160 bc_file_write(&vm.fout, vm.sigmsg, vm.siglen); 1161 bc_file_write(&vm.fout, bc_program_ready_msg, 1162 bc_program_ready_msg_len); 1163 bc_history_reset(h); 1164 bc_history_refresh(h); 1165 break; 1166 } 1167 1168 case BC_ACTION_BACKSPACE: 1169 case BC_ACTION_CTRL_H: 1170 { 1171 bc_history_edit_backspace(h); 1172 break; 1173 } 1174 1175 // Act as end-of-file. 1176 case BC_ACTION_CTRL_D: 1177 { 1178 bc_history_printCtrl(h, c); 1179 return BC_STATUS_EOF; 1180 } 1181 1182 // Swaps current character with previous. 1183 case BC_ACTION_CTRL_T: 1184 { 1185 bc_history_swap(h); 1186 break; 1187 } 1188 1189 case BC_ACTION_CTRL_B: 1190 { 1191 bc_history_edit_left(h); 1192 break; 1193 } 1194 1195 case BC_ACTION_CTRL_F: 1196 { 1197 bc_history_edit_right(h); 1198 break; 1199 } 1200 1201 case BC_ACTION_CTRL_P: 1202 { 1203 bc_history_edit_next(h, BC_HIST_PREV); 1204 break; 1205 } 1206 1207 case BC_ACTION_CTRL_N: 1208 { 1209 bc_history_edit_next(h, BC_HIST_NEXT); 1210 break; 1211 } 1212 1213 case BC_ACTION_ESC: 1214 { 1215 bc_history_escape(h); 1216 break; 1217 } 1218 1219 // Delete the whole line. 1220 case BC_ACTION_CTRL_U: 1221 { 1222 bc_vec_string(&h->buf, 0, ""); 1223 h->pos = 0; 1224 1225 bc_history_refresh(h); 1226 1227 break; 1228 } 1229 1230 // Delete from current to end of line. 1231 case BC_ACTION_CTRL_K: 1232 { 1233 bc_vec_npop(&h->buf, h->buf.len - h->pos); 1234 bc_vec_pushByte(&h->buf, '\0'); 1235 bc_history_refresh(h); 1236 break; 1237 } 1238 1239 // Go to the start of the line. 1240 case BC_ACTION_CTRL_A: 1241 { 1242 bc_history_edit_home(h); 1243 break; 1244 } 1245 1246 // Go to the end of the line. 1247 case BC_ACTION_CTRL_E: 1248 { 1249 bc_history_edit_end(h); 1250 break; 1251 } 1252 1253 // Clear screen. 1254 case BC_ACTION_CTRL_L: 1255 { 1256 bc_file_write(&vm.fout, "\x1b[H\x1b[2J", 7); 1257 bc_history_refresh(h); 1258 break; 1259 } 1260 1261 // Delete previous word. 1262 case BC_ACTION_CTRL_W: 1263 { 1264 bc_history_edit_deletePrevWord(h); 1265 break; 1266 } 1267 1268 default: 1269 { 1270 if (c >= BC_ACTION_CTRL_A && c <= BC_ACTION_CTRL_Z) 1271 bc_history_printCtrl(h, c); 1272 else bc_history_edit_insert(h, cbuf, nread); 1273 break; 1274 } 1275 } 1276 } 1277 1278 return BC_STATUS_SUCCESS; 1279 } 1280 1281 static inline bool bc_history_stdinHasData(BcHistory *h) { 1282 int n; 1283 return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 || 1284 (ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0); 1285 } 1286 1287 /** 1288 * This function calls the line editing function bc_history_edit() 1289 * using the STDIN file descriptor set in raw mode. 1290 */ 1291 static BcStatus bc_history_raw(BcHistory *h, const char *prompt) { 1292 1293 BcStatus s; 1294 1295 assert(vm.fout.len == 0); 1296 1297 bc_history_enableRaw(h); 1298 1299 s = bc_history_edit(h, prompt); 1300 1301 h->stdin_has_data = bc_history_stdinHasData(h); 1302 if (!h->stdin_has_data) bc_history_disableRaw(h); 1303 1304 bc_file_write(&vm.fout, "\n", 1); 1305 bc_file_flush(&vm.fout); 1306 1307 return s; 1308 } 1309 1310 BcStatus bc_history_line(BcHistory *h, BcVec *vec, const char *prompt) { 1311 1312 BcStatus s; 1313 char* line; 1314 1315 s = bc_history_raw(h, prompt); 1316 assert(!s || s == BC_STATUS_EOF); 1317 1318 bc_vec_string(vec, BC_HIST_BUF_LEN(h), h->buf.v); 1319 1320 if (h->buf.v[0]) { 1321 1322 BC_SIG_LOCK; 1323 1324 line = bc_vm_strdup(h->buf.v); 1325 1326 BC_SIG_UNLOCK; 1327 1328 bc_history_add(h, line); 1329 } 1330 else bc_history_add_empty(h); 1331 1332 bc_vec_concat(vec, "\n"); 1333 1334 return s; 1335 } 1336 1337 static void bc_history_add(BcHistory *h, char *line) { 1338 1339 if (h->history.len) { 1340 1341 char *s = *((char**) bc_vec_item_rev(&h->history, 0)); 1342 1343 if (!strcmp(s, line)) { 1344 1345 BC_SIG_LOCK; 1346 1347 free(line); 1348 1349 BC_SIG_UNLOCK; 1350 1351 return; 1352 } 1353 } 1354 1355 bc_vec_push(&h->history, &line); 1356 } 1357 1358 static void bc_history_add_empty(BcHistory *h) { 1359 1360 const char *line = ""; 1361 1362 if (h->history.len) { 1363 1364 char *s = *((char**) bc_vec_item_rev(&h->history, 0)); 1365 1366 if (!s[0]) return; 1367 } 1368 1369 bc_vec_push(&h->history, &line); 1370 } 1371 1372 static void bc_history_string_free(void *str) { 1373 char *s = *((char**) str); 1374 BC_SIG_ASSERT_LOCKED; 1375 if (s[0]) free(s); 1376 } 1377 1378 void bc_history_init(BcHistory *h) { 1379 1380 BC_SIG_ASSERT_LOCKED; 1381 1382 bc_vec_init(&h->buf, sizeof(char), NULL); 1383 bc_vec_init(&h->history, sizeof(char*), bc_history_string_free); 1384 1385 FD_ZERO(&h->rdset); 1386 FD_SET(STDIN_FILENO, &h->rdset); 1387 h->ts.tv_sec = 0; 1388 h->ts.tv_nsec = 0; 1389 1390 sigemptyset(&h->sigmask); 1391 sigaddset(&h->sigmask, SIGINT); 1392 1393 h->rawMode = h->stdin_has_data = false; 1394 h->badTerm = bc_history_isBadTerm(); 1395 } 1396 1397 void bc_history_free(BcHistory *h) { 1398 BC_SIG_ASSERT_LOCKED; 1399 bc_history_disableRaw(h); 1400 #ifndef NDEBUG 1401 bc_vec_free(&h->buf); 1402 bc_vec_free(&h->history); 1403 #endif // NDEBUG 1404 } 1405 1406 /** 1407 * This special mode is used by bc history in order to print scan codes 1408 * on screen for debugging / development purposes. 1409 */ 1410 #if BC_DEBUG_CODE 1411 void bc_history_printKeyCodes(BcHistory *h) { 1412 1413 char quit[4]; 1414 1415 bc_vm_printf("Linenoise key codes debugging mode.\n" 1416 "Press keys to see scan codes. " 1417 "Type 'quit' at any time to exit.\n"); 1418 1419 bc_history_enableRaw(h); 1420 memset(quit, ' ', 4); 1421 1422 while(true) { 1423 1424 char c; 1425 ssize_t nread; 1426 1427 nread = bc_history_read(&c, 1); 1428 if (nread <= 0) continue; 1429 1430 // Shift string to left. 1431 memmove(quit, quit + 1, sizeof(quit) - 1); 1432 1433 // Insert current char on the right. 1434 quit[sizeof(quit) - 1] = c; 1435 if (!memcmp(quit, "quit", sizeof(quit))) break; 1436 1437 bc_vm_printf("'%c' %lu (type quit to exit)\n", 1438 isprint(c) ? c : '?', (unsigned long) c); 1439 1440 // Go left edge manually, we are in raw mode. 1441 bc_vm_putchar('\r'); 1442 bc_file_flush(&vm.fout); 1443 } 1444 1445 bc_history_disableRaw(h); 1446 } 1447 #endif // BC_DEBUG_CODE 1448 1449 #endif // BC_ENABLE_HISTORY 1450