1 /* 2 * ***************************************************************************** 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (c) 2018-2021 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_fatalError(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_fatalError(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_fatalError(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, bc_flush_none, "\x1b[6n", 4); 509 bc_file_flush(&vm.fout, bc_flush_none); 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, bc_flush_none, "\x1b[999C", 6); 560 bc_file_flush(&vm.fout, bc_flush_none); 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, bc_flush_none); 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, bc_flush_none); 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, bc_flush_none, "\r", 1); 651 652 // Take the extra stuff into account. 653 if (h->extras.len > 1) { 654 len += h->extras.len - 1; 655 pos += h->extras.len - 1; 656 bc_file_write(&vm.fout, bc_flush_none, h->extras.v, h->extras.len - 1); 657 } 658 659 // Write the prompt, if desired. 660 #if BC_ENABLE_PROMPT 661 if (BC_USE_PROMPT) 662 bc_file_write(&vm.fout, bc_flush_none, h->prompt, h->plen); 663 #endif // BC_ENABLE_PROMPT 664 665 bc_file_write(&vm.fout, bc_flush_none, buf, BC_HIST_BUF_LEN(h)); 666 667 // Erase to right. 668 bc_file_write(&vm.fout, bc_flush_none, "\x1b[0K", 4); 669 670 // Move cursor to original position. 671 colpos = bc_history_colPos(buf, len, pos) + h->pcol; 672 673 if (colpos) bc_file_printf(&vm.fout, "\r\x1b[%zuC", colpos); 674 675 bc_file_flush(&vm.fout, bc_flush_none); 676 } 677 678 /** 679 * Insert the character 'c' at cursor current position. 680 */ 681 static void bc_history_edit_insert(BcHistory *h, const char *cbuf, size_t clen) 682 { 683 bc_vec_grow(&h->buf, clen); 684 685 if (h->pos == BC_HIST_BUF_LEN(h)) { 686 687 size_t colpos = 0, len; 688 689 memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen); 690 691 h->pos += clen; 692 h->buf.len += clen - 1; 693 bc_vec_pushByte(&h->buf, '\0'); 694 695 len = BC_HIST_BUF_LEN(h) + h->extras.len - 1; 696 #if BC_ENABLE_PROMPT 697 colpos = bc_history_promptColLen(h->prompt, h->plen); 698 #endif // BC_ENABLE_PROMPT 699 colpos += bc_history_colPos(h->buf.v, len, len); 700 701 if (colpos < h->cols) { 702 703 // Avoid a full update of the line in the trivial case. 704 bc_file_write(&vm.fout, bc_flush_none, cbuf, clen); 705 bc_file_flush(&vm.fout, bc_flush_none); 706 } 707 else bc_history_refresh(h); 708 } 709 else { 710 711 size_t amt = BC_HIST_BUF_LEN(h) - h->pos; 712 713 memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt); 714 memcpy(h->buf.v + h->pos, cbuf, clen); 715 716 h->pos += clen; 717 h->buf.len += clen; 718 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; 719 720 bc_history_refresh(h); 721 } 722 } 723 724 /** 725 * Move cursor to the left. 726 */ 727 static void bc_history_edit_left(BcHistory *h) { 728 729 if (h->pos <= 0) return; 730 731 h->pos -= bc_history_prevLen(h->buf.v, h->pos, NULL); 732 733 bc_history_refresh(h); 734 } 735 736 /** 737 * Move cursor on the right. 738 */ 739 static void bc_history_edit_right(BcHistory *h) { 740 741 if (h->pos == BC_HIST_BUF_LEN(h)) return; 742 743 h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); 744 745 bc_history_refresh(h); 746 } 747 748 /** 749 * Move cursor to the end of the current word. 750 */ 751 static void bc_history_edit_wordEnd(BcHistory *h) { 752 753 size_t len = BC_HIST_BUF_LEN(h); 754 755 if (!len || h->pos >= len) return; 756 757 while (h->pos < len && isspace(h->buf.v[h->pos])) h->pos += 1; 758 while (h->pos < len && !isspace(h->buf.v[h->pos])) h->pos += 1; 759 760 bc_history_refresh(h); 761 } 762 763 /** 764 * Move cursor to the start of the current word. 765 */ 766 static void bc_history_edit_wordStart(BcHistory *h) { 767 768 size_t len = BC_HIST_BUF_LEN(h); 769 770 if (!len) return; 771 772 while (h->pos > 0 && isspace(h->buf.v[h->pos - 1])) h->pos -= 1; 773 while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1])) h->pos -= 1; 774 775 bc_history_refresh(h); 776 } 777 778 /** 779 * Move cursor to the start of the line. 780 */ 781 static void bc_history_edit_home(BcHistory *h) { 782 783 if (!h->pos) return; 784 785 h->pos = 0; 786 787 bc_history_refresh(h); 788 } 789 790 /** 791 * Move cursor to the end of the line. 792 */ 793 static void bc_history_edit_end(BcHistory *h) { 794 795 if (h->pos == BC_HIST_BUF_LEN(h)) return; 796 797 h->pos = BC_HIST_BUF_LEN(h); 798 799 bc_history_refresh(h); 800 } 801 802 /** 803 * Substitute the currently edited line with the next or previous history 804 * entry as specified by 'dir' (direction). 805 */ 806 static void bc_history_edit_next(BcHistory *h, bool dir) { 807 808 const char *dup, *str; 809 810 if (h->history.len <= 1) return; 811 812 BC_SIG_LOCK; 813 814 if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v); 815 else dup = ""; 816 817 // Update the current history entry before 818 // overwriting it with the next one. 819 bc_vec_replaceAt(&h->history, h->history.len - 1 - h->idx, &dup); 820 821 BC_SIG_UNLOCK; 822 823 // Show the new entry. 824 h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX); 825 826 if (h->idx == SIZE_MAX) { 827 h->idx = 0; 828 return; 829 } 830 else if (h->idx >= h->history.len) { 831 h->idx = h->history.len - 1; 832 return; 833 } 834 835 str = *((char**) bc_vec_item(&h->history, h->history.len - 1 - h->idx)); 836 bc_vec_string(&h->buf, strlen(str), str); 837 assert(h->buf.len > 0); 838 839 h->pos = BC_HIST_BUF_LEN(h); 840 841 bc_history_refresh(h); 842 } 843 844 /** 845 * Delete the character at the right of the cursor without altering the cursor 846 * position. Basically this is what happens with the "Delete" keyboard key. 847 */ 848 static void bc_history_edit_delete(BcHistory *h) { 849 850 size_t chlen, len = BC_HIST_BUF_LEN(h); 851 852 if (!len || h->pos >= len) return; 853 854 chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL); 855 856 memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen); 857 858 h->buf.len -= chlen; 859 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; 860 861 bc_history_refresh(h); 862 } 863 864 static void bc_history_edit_backspace(BcHistory *h) { 865 866 size_t chlen, len = BC_HIST_BUF_LEN(h); 867 868 if (!h->pos || !len) return; 869 870 chlen = bc_history_prevLen(h->buf.v, h->pos, NULL); 871 872 memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos); 873 874 h->pos -= chlen; 875 h->buf.len -= chlen; 876 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; 877 878 bc_history_refresh(h); 879 } 880 881 /** 882 * Delete the previous word, maintaining the cursor at the start of the 883 * current word. 884 */ 885 static void bc_history_edit_deletePrevWord(BcHistory *h) { 886 887 size_t diff, old_pos = h->pos; 888 889 while (h->pos > 0 && h->buf.v[h->pos - 1] == ' ') --h->pos; 890 while (h->pos > 0 && h->buf.v[h->pos - 1] != ' ') --h->pos; 891 892 diff = old_pos - h->pos; 893 memmove(h->buf.v + h->pos, h->buf.v + old_pos, 894 BC_HIST_BUF_LEN(h) - old_pos + 1); 895 h->buf.len -= diff; 896 897 bc_history_refresh(h); 898 } 899 900 /** 901 * Delete the next word, maintaining the cursor at the same position. 902 */ 903 static void bc_history_edit_deleteNextWord(BcHistory *h) { 904 905 size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h); 906 907 while (next_end < len && h->buf.v[next_end] == ' ') ++next_end; 908 while (next_end < len && h->buf.v[next_end] != ' ') ++next_end; 909 910 memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end); 911 912 h->buf.len -= next_end - h->pos; 913 914 bc_history_refresh(h); 915 } 916 917 static void bc_history_swap(BcHistory *h) { 918 919 size_t pcl, ncl; 920 char auxb[5]; 921 922 pcl = bc_history_prevLen(h->buf.v, h->pos, NULL); 923 ncl = bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); 924 925 // To perform a swap we need: 926 // * nonzero char length to the left 927 // * not at the end of the line 928 if (pcl && h->pos != BC_HIST_BUF_LEN(h) && pcl < 5 && ncl < 5) { 929 930 memcpy(auxb, h->buf.v + h->pos - pcl, pcl); 931 memcpy(h->buf.v + h->pos - pcl, h->buf.v + h->pos, ncl); 932 memcpy(h->buf.v + h->pos - pcl + ncl, auxb, pcl); 933 934 h->pos += -pcl + ncl; 935 936 bc_history_refresh(h); 937 } 938 } 939 940 /** 941 * Handle escape sequences. 942 */ 943 static void bc_history_escape(BcHistory *h) { 944 945 char c, seq[3]; 946 947 if (BC_ERR(BC_HIST_READ(seq, 1))) return; 948 949 c = seq[0]; 950 951 // ESC ? sequences. 952 if (c != '[' && c != 'O') { 953 if (c == 'f') bc_history_edit_wordEnd(h); 954 else if (c == 'b') bc_history_edit_wordStart(h); 955 else if (c == 'd') bc_history_edit_deleteNextWord(h); 956 } 957 else { 958 959 if (BC_ERR(BC_HIST_READ(seq + 1, 1))) 960 bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); 961 962 // ESC [ sequences. 963 if (c == '[') { 964 965 c = seq[1]; 966 967 if (c >= '0' && c <= '9') { 968 969 // Extended escape, read additional byte. 970 if (BC_ERR(BC_HIST_READ(seq + 2, 1))) 971 bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); 972 973 if (seq[2] == '~' && c == '3') bc_history_edit_delete(h); 974 else if(seq[2] == ';') { 975 976 if (BC_ERR(BC_HIST_READ(seq, 2))) 977 bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); 978 979 if (seq[0] != '5') return; 980 else if (seq[1] == 'C') bc_history_edit_wordEnd(h); 981 else if (seq[1] == 'D') bc_history_edit_wordStart(h); 982 } 983 } 984 else { 985 986 switch(c) { 987 988 // Up. 989 case 'A': 990 { 991 bc_history_edit_next(h, BC_HIST_PREV); 992 break; 993 } 994 995 // Down. 996 case 'B': 997 { 998 bc_history_edit_next(h, BC_HIST_NEXT); 999 break; 1000 } 1001 1002 // Right. 1003 case 'C': 1004 { 1005 bc_history_edit_right(h); 1006 break; 1007 } 1008 1009 // Left. 1010 case 'D': 1011 { 1012 bc_history_edit_left(h); 1013 break; 1014 } 1015 1016 // Home. 1017 case 'H': 1018 case '1': 1019 { 1020 bc_history_edit_home(h); 1021 break; 1022 } 1023 1024 // End. 1025 case 'F': 1026 case '4': 1027 { 1028 bc_history_edit_end(h); 1029 break; 1030 } 1031 1032 case 'd': 1033 { 1034 bc_history_edit_deleteNextWord(h); 1035 break; 1036 } 1037 } 1038 } 1039 } 1040 // ESC O sequences. 1041 else if (c == 'O') { 1042 1043 switch (seq[1]) { 1044 1045 case 'A': 1046 { 1047 bc_history_edit_next(h, BC_HIST_PREV); 1048 break; 1049 } 1050 1051 case 'B': 1052 { 1053 bc_history_edit_next(h, BC_HIST_NEXT); 1054 break; 1055 } 1056 1057 case 'C': 1058 { 1059 bc_history_edit_right(h); 1060 break; 1061 } 1062 1063 case 'D': 1064 { 1065 bc_history_edit_left(h); 1066 break; 1067 } 1068 1069 case 'F': 1070 { 1071 bc_history_edit_end(h); 1072 break; 1073 } 1074 1075 case 'H': 1076 { 1077 bc_history_edit_home(h); 1078 break; 1079 } 1080 } 1081 } 1082 } 1083 } 1084 1085 static void bc_history_reset(BcHistory *h) { 1086 1087 h->oldcolpos = h->pos = h->idx = 0; 1088 h->cols = bc_history_columns(); 1089 1090 // The latest history entry is always our current buffer, that 1091 // initially is just an empty string. 1092 bc_history_add_empty(h); 1093 1094 // Buffer starts empty. 1095 bc_vec_empty(&h->buf); 1096 } 1097 1098 static void bc_history_printCtrl(BcHistory *h, unsigned int c) { 1099 1100 char str[3] = "^A"; 1101 const char newline[2] = "\n"; 1102 1103 str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A); 1104 1105 bc_vec_concat(&h->buf, str); 1106 1107 bc_history_refresh(h); 1108 1109 bc_vec_npop(&h->buf, sizeof(str)); 1110 bc_vec_pushByte(&h->buf, '\0'); 1111 1112 if (c != BC_ACTION_CTRL_C && c != BC_ACTION_CTRL_D) { 1113 bc_file_write(&vm.fout, bc_flush_none, newline, sizeof(newline) - 1); 1114 bc_history_refresh(h); 1115 } 1116 } 1117 1118 /** 1119 * This function is the core of the line editing capability of bc history. 1120 * It expects 'fd' to be already in "raw mode" so that every key pressed 1121 * will be returned ASAP to read(). 1122 */ 1123 static BcStatus bc_history_edit(BcHistory *h, const char *prompt) { 1124 1125 bc_history_reset(h); 1126 1127 // Don't write the saved output the first time. This is because it has 1128 // already been written to output. In other words, don't uncomment the 1129 // line below or add anything like it. 1130 // bc_file_write(&vm.fout, bc_flush_none, h->extras.v, h->extras.len - 1); 1131 1132 #if BC_ENABLE_PROMPT 1133 if (BC_USE_PROMPT) { 1134 1135 h->prompt = prompt; 1136 h->plen = strlen(prompt); 1137 h->pcol = bc_history_promptColLen(prompt, h->plen); 1138 1139 bc_file_write(&vm.fout, bc_flush_none, prompt, h->plen); 1140 bc_file_flush(&vm.fout, bc_flush_none); 1141 } 1142 #endif // BC_ENABLE_PROMPT 1143 1144 for (;;) { 1145 1146 BcStatus s; 1147 // Large enough for any encoding? 1148 char cbuf[32]; 1149 unsigned int c = 0; 1150 size_t nread = 0; 1151 1152 s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread); 1153 if (BC_ERR(s)) return s; 1154 1155 switch (c) { 1156 1157 case BC_ACTION_LINE_FEED: 1158 case BC_ACTION_ENTER: 1159 { 1160 bc_vec_pop(&h->history); 1161 return s; 1162 } 1163 1164 case BC_ACTION_TAB: 1165 { 1166 memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1); 1167 bc_history_edit_insert(h, cbuf, bc_history_tab_len); 1168 break; 1169 } 1170 1171 case BC_ACTION_CTRL_C: 1172 { 1173 bc_history_printCtrl(h, c); 1174 bc_file_write(&vm.fout, bc_flush_none, vm.sigmsg, vm.siglen); 1175 bc_file_write(&vm.fout, bc_flush_none, bc_program_ready_msg, 1176 bc_program_ready_msg_len); 1177 bc_history_reset(h); 1178 bc_history_refresh(h); 1179 break; 1180 } 1181 1182 case BC_ACTION_BACKSPACE: 1183 case BC_ACTION_CTRL_H: 1184 { 1185 bc_history_edit_backspace(h); 1186 break; 1187 } 1188 1189 // Act as end-of-file. 1190 case BC_ACTION_CTRL_D: 1191 { 1192 bc_history_printCtrl(h, c); 1193 return BC_STATUS_EOF; 1194 } 1195 1196 // Swaps current character with previous. 1197 case BC_ACTION_CTRL_T: 1198 { 1199 bc_history_swap(h); 1200 break; 1201 } 1202 1203 case BC_ACTION_CTRL_B: 1204 { 1205 bc_history_edit_left(h); 1206 break; 1207 } 1208 1209 case BC_ACTION_CTRL_F: 1210 { 1211 bc_history_edit_right(h); 1212 break; 1213 } 1214 1215 case BC_ACTION_CTRL_P: 1216 { 1217 bc_history_edit_next(h, BC_HIST_PREV); 1218 break; 1219 } 1220 1221 case BC_ACTION_CTRL_N: 1222 { 1223 bc_history_edit_next(h, BC_HIST_NEXT); 1224 break; 1225 } 1226 1227 case BC_ACTION_ESC: 1228 { 1229 bc_history_escape(h); 1230 break; 1231 } 1232 1233 // Delete the whole line. 1234 case BC_ACTION_CTRL_U: 1235 { 1236 bc_vec_string(&h->buf, 0, ""); 1237 h->pos = 0; 1238 1239 bc_history_refresh(h); 1240 1241 break; 1242 } 1243 1244 // Delete from current to end of line. 1245 case BC_ACTION_CTRL_K: 1246 { 1247 bc_vec_npop(&h->buf, h->buf.len - h->pos); 1248 bc_vec_pushByte(&h->buf, '\0'); 1249 bc_history_refresh(h); 1250 break; 1251 } 1252 1253 // Go to the start of the line. 1254 case BC_ACTION_CTRL_A: 1255 { 1256 bc_history_edit_home(h); 1257 break; 1258 } 1259 1260 // Go to the end of the line. 1261 case BC_ACTION_CTRL_E: 1262 { 1263 bc_history_edit_end(h); 1264 break; 1265 } 1266 1267 // Clear screen. 1268 case BC_ACTION_CTRL_L: 1269 { 1270 bc_file_write(&vm.fout, bc_flush_none, "\x1b[H\x1b[2J", 7); 1271 bc_history_refresh(h); 1272 break; 1273 } 1274 1275 // Delete previous word. 1276 case BC_ACTION_CTRL_W: 1277 { 1278 bc_history_edit_deletePrevWord(h); 1279 break; 1280 } 1281 1282 default: 1283 { 1284 if (c >= BC_ACTION_CTRL_A && c <= BC_ACTION_CTRL_Z) { 1285 bc_history_printCtrl(h, c); 1286 if (c == BC_ACTION_CTRL_Z) raise(SIGTSTP); 1287 if (c == BC_ACTION_CTRL_S) raise(SIGSTOP); 1288 } 1289 else bc_history_edit_insert(h, cbuf, nread); 1290 break; 1291 } 1292 } 1293 } 1294 1295 return BC_STATUS_SUCCESS; 1296 } 1297 1298 static inline bool bc_history_stdinHasData(BcHistory *h) { 1299 int n; 1300 return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 || 1301 (ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0); 1302 } 1303 1304 /** 1305 * This function calls the line editing function bc_history_edit() 1306 * using the STDIN file descriptor set in raw mode. 1307 */ 1308 static BcStatus bc_history_raw(BcHistory *h, const char *prompt) { 1309 1310 BcStatus s; 1311 1312 assert(vm.fout.len == 0); 1313 1314 bc_history_enableRaw(h); 1315 1316 s = bc_history_edit(h, prompt); 1317 1318 h->stdin_has_data = bc_history_stdinHasData(h); 1319 if (!h->stdin_has_data) bc_history_disableRaw(h); 1320 1321 bc_file_write(&vm.fout, bc_flush_none, "\n", 1); 1322 bc_file_flush(&vm.fout, bc_flush_none); 1323 1324 return s; 1325 } 1326 1327 BcStatus bc_history_line(BcHistory *h, BcVec *vec, const char *prompt) { 1328 1329 BcStatus s; 1330 char* line; 1331 1332 s = bc_history_raw(h, prompt); 1333 assert(!s || s == BC_STATUS_EOF); 1334 1335 bc_vec_string(vec, BC_HIST_BUF_LEN(h), h->buf.v); 1336 1337 if (h->buf.v[0]) { 1338 1339 BC_SIG_LOCK; 1340 1341 line = bc_vm_strdup(h->buf.v); 1342 1343 BC_SIG_UNLOCK; 1344 1345 bc_history_add(h, line); 1346 } 1347 else bc_history_add_empty(h); 1348 1349 bc_vec_concat(vec, "\n"); 1350 1351 return s; 1352 } 1353 1354 static void bc_history_add(BcHistory *h, char *line) { 1355 1356 if (h->history.len) { 1357 1358 char *s = *((char**) bc_vec_item_rev(&h->history, 0)); 1359 1360 if (!strcmp(s, line)) { 1361 1362 BC_SIG_LOCK; 1363 1364 free(line); 1365 1366 BC_SIG_UNLOCK; 1367 1368 return; 1369 } 1370 } 1371 1372 bc_vec_push(&h->history, &line); 1373 } 1374 1375 static void bc_history_add_empty(BcHistory *h) { 1376 1377 const char *line = ""; 1378 1379 if (h->history.len) { 1380 1381 char *s = *((char**) bc_vec_item_rev(&h->history, 0)); 1382 1383 if (!s[0]) return; 1384 } 1385 1386 bc_vec_push(&h->history, &line); 1387 } 1388 1389 static void bc_history_string_free(void *str) { 1390 char *s = *((char**) str); 1391 BC_SIG_ASSERT_LOCKED; 1392 if (s[0]) free(s); 1393 } 1394 1395 void bc_history_init(BcHistory *h) { 1396 1397 BC_SIG_ASSERT_LOCKED; 1398 1399 bc_vec_init(&h->buf, sizeof(char), NULL); 1400 bc_vec_init(&h->history, sizeof(char*), bc_history_string_free); 1401 bc_vec_init(&h->extras, sizeof(char), NULL); 1402 1403 FD_ZERO(&h->rdset); 1404 FD_SET(STDIN_FILENO, &h->rdset); 1405 h->ts.tv_sec = 0; 1406 h->ts.tv_nsec = 0; 1407 1408 sigemptyset(&h->sigmask); 1409 sigaddset(&h->sigmask, SIGINT); 1410 1411 h->rawMode = h->stdin_has_data = false; 1412 h->badTerm = bc_history_isBadTerm(); 1413 } 1414 1415 void bc_history_free(BcHistory *h) { 1416 BC_SIG_ASSERT_LOCKED; 1417 bc_history_disableRaw(h); 1418 #ifndef NDEBUG 1419 bc_vec_free(&h->buf); 1420 bc_vec_free(&h->history); 1421 bc_vec_free(&h->extras); 1422 #endif // NDEBUG 1423 } 1424 1425 /** 1426 * This special mode is used by bc history in order to print scan codes 1427 * on screen for debugging / development purposes. 1428 */ 1429 #if BC_DEBUG_CODE 1430 void bc_history_printKeyCodes(BcHistory *h) { 1431 1432 char quit[4]; 1433 1434 bc_vm_printf("Linenoise key codes debugging mode.\n" 1435 "Press keys to see scan codes. " 1436 "Type 'quit' at any time to exit.\n"); 1437 1438 bc_history_enableRaw(h); 1439 memset(quit, ' ', 4); 1440 1441 while(true) { 1442 1443 char c; 1444 ssize_t nread; 1445 1446 nread = bc_history_read(&c, 1); 1447 if (nread <= 0) continue; 1448 1449 // Shift string to left. 1450 memmove(quit, quit + 1, sizeof(quit) - 1); 1451 1452 // Insert current char on the right. 1453 quit[sizeof(quit) - 1] = c; 1454 if (!memcmp(quit, "quit", sizeof(quit))) break; 1455 1456 bc_vm_printf("'%c' %lu (type quit to exit)\n", 1457 isprint(c) ? c : '?', (unsigned long) c); 1458 1459 // Go left edge manually, we are in raw mode. 1460 bc_vm_putchar('\r', bc_flush_none); 1461 bc_file_flush(&vm.fout, bc_flush_none); 1462 } 1463 1464 bc_history_disableRaw(h); 1465 } 1466 #endif // BC_DEBUG_CODE 1467 1468 #endif // BC_ENABLE_HISTORY 1469