2 * *****************************************************************************
4 * SPDX-License-Identifier: BSD-2-Clause
6 * Copyright (c) 2018-2020 Gavin D. Howard and contributors.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions are met:
11 * * Redistributions of source code must retain the above copyright notice, this
12 * list of conditions and the following disclaimer.
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.
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.
30 * *****************************************************************************
32 * Adapted from the following:
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.
37 * You can find the original source code at:
38 * http://github.com/antirez/linenoise
40 * You can find the fork that this code is based on at:
41 * https://github.com/rain-1/linenoise-mob
43 * ------------------------------------------------------------------------
45 * This code is also under the following license:
47 * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
48 * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
50 * Redistribution and use in source and binary forms, with or without
51 * modification, are permitted provided that the following conditions are
54 * * Redistributions of source code must retain the above copyright
55 * notice, this list of conditions and the following disclaimer.
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.
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.
73 * ------------------------------------------------------------------------
75 * Does a number of crazy assumptions that happen to be true in 99.9999% of
76 * the 2010 UNIX computers around.
79 * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
80 * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
83 * - Filter bogus Ctrl+<char> combinations.
87 * - History search like Ctrl+r in readline?
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.
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
100 * CUF (CUrsor Forward)
101 * Sequence: ESC [ n C
102 * Effect: moves cursor forward n chars
104 * CUB (CUrsor Backward)
105 * Sequence: ESC [ n D
106 * Effect: moves cursor backward n chars
108 * The following is used to get the terminal width if getting
109 * the width with the TIOCGWINSZ ioctl fails
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
116 * When multi line mode is enabled, we also use two additional escape
117 * sequences. However multi line editing is disabled by default.
120 * Sequence: ESC [ n A
121 * Effect: moves cursor up of n chars.
124 * Sequence: ESC [ n B
125 * Effect: moves cursor down of n chars.
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
131 * CUP (CUrsor Position)
133 * Effect: moves the cursor to upper left corner
136 * Sequence: ESC [ 2 J
137 * Effect: clear the whole screen
139 * *****************************************************************************
141 * Code for line history.
145 #if BC_ENABLE_HISTORY
158 #include <sys/stat.h>
159 #include <sys/types.h>
160 #include <sys/ioctl.h>
161 #include <sys/select.h>
169 static void bc_history_add(BcHistory *h, char *line);
170 static void bc_history_add_empty(BcHistory *h);
173 * Check if the code is a wide character.
175 static bool bc_history_wchar(uint32_t cp) {
179 for (i = 0; i < bc_history_wchars_len; ++i) {
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;
187 if (bc_history_wchars[i][0] <= cp && cp <= bc_history_wchars[i][1])
195 * Check if the code is a combining character.
197 static bool bc_history_comboChar(uint32_t cp) {
201 for (i = 0; i < bc_history_combo_chars_len; ++i) {
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;
213 * Get length of previous UTF8 character.
215 static size_t bc_history_prevCharLen(const char *buf, size_t pos) {
217 for (pos -= 1; pos < end && (buf[pos] & 0xC0) == 0x80; --pos);
218 return end - (pos >= end ? 0 : pos);
222 * Convert UTF-8 to Unicode code point.
224 static size_t bc_history_codePoint(const char *s, size_t len, uint32_t *cp) {
228 uchar byte = (uchar) s[0];
230 if ((byte & 0x80) == 0) {
234 else if ((byte & 0xE0) == 0xC0) {
237 *cp = (((uint32_t) (s[0] & 0x1F)) << 6) |
238 ((uint32_t) (s[1] & 0x3F));
242 else if ((byte & 0xF0) == 0xE0) {
245 *cp = (((uint32_t) (s[0] & 0x0F)) << 12) |
246 (((uint32_t) (s[1] & 0x3F)) << 6) |
247 ((uint32_t) (s[2] & 0x3F));
251 else if ((byte & 0xF8) == 0xF0) {
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));
273 * Get length of next grapheme.
275 static size_t bc_history_nextLen(const char *buf, size_t buf_len,
276 size_t pos, size_t *col_len)
280 size_t len = bc_history_codePoint(buf + pos, buf_len - pos, &cp);
282 if (bc_history_comboChar(cp)) {
283 // Currently unreachable?
287 if (col_len != NULL) *col_len = bc_history_wchar(cp) ? 2 : 1;
291 while (pos < buf_len) {
293 len = bc_history_codePoint(buf + pos, buf_len - pos, &cp);
295 if (!bc_history_comboChar(cp)) return pos - beg;
304 * Get length of previous grapheme.
306 static size_t bc_history_prevLen(const char *buf, size_t pos, size_t *col_len) {
313 size_t len = bc_history_prevCharLen(buf, pos);
316 bc_history_codePoint(buf + pos, len, &cp);
318 if (!bc_history_comboChar(cp)) {
319 if (col_len != NULL) *col_len = 1 + (bc_history_wchar(cp) != 0);
324 // Currently unreachable?
328 static ssize_t bc_history_read(char *buf, size_t n) {
335 ret = read(STDIN_FILENO, buf, n);
336 } while (ret == EINTR);
344 * Read a Unicode code point from a file.
346 static BcStatus bc_history_readCode(char *buf, size_t buf_len,
347 uint32_t *cp, size_t *nread)
351 assert(buf_len >= 1);
353 n = bc_history_read(buf, 1);
354 if (BC_ERR(n <= 0)) goto err;
356 uchar byte = (uchar) buf[0];
358 if ((byte & 0x80) != 0) {
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;
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;
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;
381 *nread = bc_history_codePoint(buf, buf_len, cp);
383 return BC_STATUS_SUCCESS;
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;
392 * Get column length from begining of buffer to current byte position.
394 static size_t bc_history_colPos(const char *buf, size_t buf_len, size_t pos) {
396 size_t ret = 0, off = 0;
402 len = bc_history_nextLen(buf, buf_len, off, &col_len);
412 * Return true if the terminal name is in the list of terminals we know are
413 * not able to understand basic escape sequences.
415 static inline bool bc_history_isBadTerm(void) {
418 char *term = getenv("TERM");
420 if (term == NULL) return false;
422 for (i = 0; bc_history_bad_terms[i]; ++i) {
423 if (!strcasecmp(term, bc_history_bad_terms[i])) return true;
430 * Raw mode: 1960's black magic.
432 static void bc_history_enableRaw(BcHistory *h) {
439 if (h->rawMode) return;
443 if (BC_ERR(tcgetattr(STDIN_FILENO, &h->orig_termios) == -1))
444 bc_vm_err(BC_ERR_FATAL_IO_ERR);
448 // Modify the original mode.
449 raw = h->orig_termios;
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));
455 // Control modes - set 8 bit chars.
456 raw.c_cflag |= (CS8);
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));
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).
469 // Put terminal in raw mode after flushing.
471 err = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);
472 } while (BC_ERR(err < 0) && errno == EINTR);
476 if (BC_ERR(err < 0)) bc_vm_err(BC_ERR_FATAL_IO_ERR);
481 static void bc_history_disableRaw(BcHistory *h) {
485 // Don't even check the return value as it's too late.
486 if (!h->rawMode) return;
488 BC_SIG_TRYLOCK(lock);
490 if (BC_ERR(tcsetattr(STDIN_FILENO, TCSAFLUSH, &h->orig_termios) != -1))
493 BC_SIG_TRYUNLOCK(lock);
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
501 static size_t bc_history_cursorPos(void) {
503 char buf[BC_HIST_SEQ_SIZE];
505 size_t cols, rows, i;
507 // Report cursor location.
508 bc_file_write(&vm.fout, "\x1b[6n", 4);
509 bc_file_flush(&vm.fout);
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;
518 if (BC_ERR(buf[0] != BC_ACTION_ESC || buf[1] != '[')) return SIZE_MAX;
522 rows = strtoul(ptr, &ptr2, 10);
524 if (BC_ERR(!rows || ptr2[0] != ';')) return SIZE_MAX;
527 cols = strtoul(ptr, NULL, 10);
529 if (BC_ERR(!cols)) return SIZE_MAX;
531 return cols <= UINT16_MAX ? cols : 0;
535 * Try to get the number of columns in the current terminal, or assume 80
538 static size_t bc_history_columns(void) {
545 ret = ioctl(vm.fout.fd, TIOCGWINSZ, &ws);
549 if (BC_ERR(ret == -1 || !ws.ws_col)) {
551 // Calling ioctl() failed. Try to query the terminal itself.
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;
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;
566 bc_file_printf(&vm.fout, "\x1b[%zuD", cols - start);
567 bc_file_flush(&vm.fout);
578 * Check if text is an ANSI escape sequence.
580 static bool bc_history_ansiEscape(const char *buf, size_t buf_len, size_t *len)
582 if (buf_len > 2 && !memcmp("\033[", buf, 2)) {
586 while (off < buf_len) {
590 if ((c >= 'A' && c <= 'K' && c != 'I') ||
591 c == 'S' || c == 'T' || c == 'f' || c == 'm')
603 * Get column length of prompt text.
605 static size_t bc_history_promptColLen(const char *prompt, size_t plen) {
607 char buf[BC_HIST_MAX_LINE + 1];
608 size_t buf_len = 0, off = 0;
614 if (bc_history_ansiEscape(prompt + off, plen - off, &len)) {
619 buf[buf_len++] = prompt[off++];
622 return bc_history_colPos(buf, buf_len, buf_len);
624 #endif // BC_ENABLE_PROMPT
627 * Rewrites the currently edited line accordingly to the buffer content,
628 * cursor position, and number of columns of the terminal.
630 static void bc_history_refresh(BcHistory *h) {
632 char* buf = h->buf.v;
633 size_t colpos, len = BC_HIST_BUF_LEN(h), pos = h->pos;
635 bc_file_flush(&vm.fout);
637 while(h->pcol + bc_history_colPos(buf, len, pos) >= h->cols) {
639 size_t chlen = bc_history_nextLen(buf, len, 0, NULL);
646 while (h->pcol + bc_history_colPos(buf, len, len) > h->cols)
647 len -= bc_history_prevLen(buf, len, NULL);
649 // Cursor to left edge.
650 bc_file_write(&vm.fout, "\r", 1);
652 // Write the prompt, if desired.
654 if (BC_USE_PROMPT) bc_file_write(&vm.fout, h->prompt, h->plen);
655 #endif // BC_ENABLE_PROMPT
657 bc_file_write(&vm.fout, buf, BC_HIST_BUF_LEN(h));
660 bc_file_write(&vm.fout, "\x1b[0K", 4);
662 // Move cursor to original position.
663 colpos = bc_history_colPos(buf, len, pos) + h->pcol;
665 if (colpos) bc_file_printf(&vm.fout, "\r\x1b[%zuC", colpos);
667 bc_file_flush(&vm.fout);
671 * Insert the character 'c' at cursor current position.
673 static void bc_history_edit_insert(BcHistory *h, const char *cbuf, size_t clen)
675 bc_vec_grow(&h->buf, clen);
677 if (h->pos == BC_HIST_BUF_LEN(h)) {
679 size_t colpos = 0, len;
681 memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen);
684 h->buf.len += clen - 1;
685 bc_vec_pushByte(&h->buf, '\0');
687 len = BC_HIST_BUF_LEN(h);
689 colpos = bc_history_promptColLen(h->prompt, h->plen);
690 #endif // BC_ENABLE_PROMPT
691 colpos += bc_history_colPos(h->buf.v, len, len);
693 if (colpos < h->cols) {
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);
699 else bc_history_refresh(h);
703 size_t amt = BC_HIST_BUF_LEN(h) - h->pos;
705 memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt);
706 memcpy(h->buf.v + h->pos, cbuf, clen);
710 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
712 bc_history_refresh(h);
717 * Move cursor to the left.
719 static void bc_history_edit_left(BcHistory *h) {
721 if (h->pos <= 0) return;
723 h->pos -= bc_history_prevLen(h->buf.v, h->pos, NULL);
725 bc_history_refresh(h);
729 * Move cursor on the right.
731 static void bc_history_edit_right(BcHistory *h) {
733 if (h->pos == BC_HIST_BUF_LEN(h)) return;
735 h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL);
737 bc_history_refresh(h);
741 * Move cursor to the end of the current word.
743 static void bc_history_edit_wordEnd(BcHistory *h) {
745 size_t len = BC_HIST_BUF_LEN(h);
747 if (!len || h->pos >= len) return;
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;
752 bc_history_refresh(h);
756 * Move cursor to the start of the current word.
758 static void bc_history_edit_wordStart(BcHistory *h) {
760 size_t len = BC_HIST_BUF_LEN(h);
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;
767 bc_history_refresh(h);
771 * Move cursor to the start of the line.
773 static void bc_history_edit_home(BcHistory *h) {
779 bc_history_refresh(h);
783 * Move cursor to the end of the line.
785 static void bc_history_edit_end(BcHistory *h) {
787 if (h->pos == BC_HIST_BUF_LEN(h)) return;
789 h->pos = BC_HIST_BUF_LEN(h);
791 bc_history_refresh(h);
795 * Substitute the currently edited line with the next or previous history
796 * entry as specified by 'dir' (direction).
798 static void bc_history_edit_next(BcHistory *h, bool dir) {
800 const char *dup, *str;
802 if (h->history.len <= 1) return;
806 if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v);
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);
815 // Show the new entry.
816 h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX);
818 if (h->idx == SIZE_MAX) {
822 else if (h->idx >= h->history.len) {
823 h->idx = h->history.len - 1;
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);
831 h->pos = BC_HIST_BUF_LEN(h);
833 bc_history_refresh(h);
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.
840 static void bc_history_edit_delete(BcHistory *h) {
842 size_t chlen, len = BC_HIST_BUF_LEN(h);
844 if (!len || h->pos >= len) return;
846 chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL);
848 memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen);
851 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
853 bc_history_refresh(h);
856 static void bc_history_edit_backspace(BcHistory *h) {
858 size_t chlen, len = BC_HIST_BUF_LEN(h);
860 if (!h->pos || !len) return;
862 chlen = bc_history_prevLen(h->buf.v, h->pos, NULL);
864 memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos);
868 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
870 bc_history_refresh(h);
874 * Delete the previous word, maintaining the cursor at the start of the
877 static void bc_history_edit_deletePrevWord(BcHistory *h) {
879 size_t diff, old_pos = h->pos;
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;
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);
889 bc_history_refresh(h);
893 * Delete the next word, maintaining the cursor at the same position.
895 static void bc_history_edit_deleteNextWord(BcHistory *h) {
897 size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h);
899 while (next_end < len && h->buf.v[next_end] == ' ') ++next_end;
900 while (next_end < len && h->buf.v[next_end] != ' ') ++next_end;
902 memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end);
904 h->buf.len -= next_end - h->pos;
906 bc_history_refresh(h);
909 static void bc_history_swap(BcHistory *h) {
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);
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) {
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);
926 h->pos += -pcl + ncl;
928 bc_history_refresh(h);
933 * Handle escape sequences.
935 static void bc_history_escape(BcHistory *h) {
939 if (BC_ERR(BC_HIST_READ(seq, 1))) return;
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);
951 if (BC_ERR(BC_HIST_READ(seq + 1, 1))) bc_vm_err(BC_ERR_FATAL_IO_ERR);
958 if (c >= '0' && c <= '9') {
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);
964 if (seq[2] == '~' && c == '3') bc_history_edit_delete(h);
965 else if(seq[2] == ';') {
967 if (BC_ERR(BC_HIST_READ(seq, 2)))
968 bc_vm_err(BC_ERR_FATAL_IO_ERR);
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);
982 bc_history_edit_next(h, BC_HIST_PREV);
989 bc_history_edit_next(h, BC_HIST_NEXT);
996 bc_history_edit_right(h);
1003 bc_history_edit_left(h);
1011 bc_history_edit_home(h);
1019 bc_history_edit_end(h);
1025 bc_history_edit_deleteNextWord(h);
1032 else if (c == 'O') {
1038 bc_history_edit_next(h, BC_HIST_PREV);
1044 bc_history_edit_next(h, BC_HIST_NEXT);
1050 bc_history_edit_right(h);
1056 bc_history_edit_left(h);
1062 bc_history_edit_end(h);
1068 bc_history_edit_home(h);
1076 static void bc_history_reset(BcHistory *h) {
1078 h->oldcolpos = h->pos = h->idx = 0;
1079 h->cols = bc_history_columns();
1081 // The latest history entry is always our current buffer, that
1082 // initially is just an empty string.
1083 bc_history_add_empty(h);
1085 // Buffer starts empty.
1086 bc_vec_empty(&h->buf);
1089 static void bc_history_printCtrl(BcHistory *h, unsigned int c) {
1092 const char newline[2] = "\n";
1094 str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A);
1096 bc_vec_concat(&h->buf, str);
1098 bc_history_refresh(h);
1100 bc_vec_npop(&h->buf, sizeof(str));
1101 bc_vec_pushByte(&h->buf, '\0');
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);
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().
1114 static BcStatus bc_history_edit(BcHistory *h, const char *prompt) {
1116 bc_history_reset(h);
1118 #if BC_ENABLE_PROMPT
1119 if (BC_USE_PROMPT) {
1122 h->plen = strlen(prompt);
1123 h->pcol = bc_history_promptColLen(prompt, h->plen);
1125 bc_file_write(&vm.fout, prompt, h->plen);
1126 bc_file_flush(&vm.fout);
1128 #endif // BC_ENABLE_PROMPT
1133 // Large enough for any encoding?
1138 s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread);
1139 if (BC_ERR(s)) return s;
1143 case BC_ACTION_LINE_FEED:
1144 case BC_ACTION_ENTER:
1146 bc_vec_pop(&h->history);
1152 memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1);
1153 bc_history_edit_insert(h, cbuf, bc_history_tab_len);
1157 case BC_ACTION_CTRL_C:
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);
1168 case BC_ACTION_BACKSPACE:
1169 case BC_ACTION_CTRL_H:
1171 bc_history_edit_backspace(h);
1175 // Act as end-of-file.
1176 case BC_ACTION_CTRL_D:
1178 bc_history_printCtrl(h, c);
1179 return BC_STATUS_EOF;
1182 // Swaps current character with previous.
1183 case BC_ACTION_CTRL_T:
1189 case BC_ACTION_CTRL_B:
1191 bc_history_edit_left(h);
1195 case BC_ACTION_CTRL_F:
1197 bc_history_edit_right(h);
1201 case BC_ACTION_CTRL_P:
1203 bc_history_edit_next(h, BC_HIST_PREV);
1207 case BC_ACTION_CTRL_N:
1209 bc_history_edit_next(h, BC_HIST_NEXT);
1215 bc_history_escape(h);
1219 // Delete the whole line.
1220 case BC_ACTION_CTRL_U:
1222 bc_vec_string(&h->buf, 0, "");
1225 bc_history_refresh(h);
1230 // Delete from current to end of line.
1231 case BC_ACTION_CTRL_K:
1233 bc_vec_npop(&h->buf, h->buf.len - h->pos);
1234 bc_vec_pushByte(&h->buf, '\0');
1235 bc_history_refresh(h);
1239 // Go to the start of the line.
1240 case BC_ACTION_CTRL_A:
1242 bc_history_edit_home(h);
1246 // Go to the end of the line.
1247 case BC_ACTION_CTRL_E:
1249 bc_history_edit_end(h);
1254 case BC_ACTION_CTRL_L:
1256 bc_file_write(&vm.fout, "\x1b[H\x1b[2J", 7);
1257 bc_history_refresh(h);
1261 // Delete previous word.
1262 case BC_ACTION_CTRL_W:
1264 bc_history_edit_deletePrevWord(h);
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);
1278 return BC_STATUS_SUCCESS;
1281 static inline bool bc_history_stdinHasData(BcHistory *h) {
1283 return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 ||
1284 (ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0);
1288 * This function calls the line editing function bc_history_edit()
1289 * using the STDIN file descriptor set in raw mode.
1291 static BcStatus bc_history_raw(BcHistory *h, const char *prompt) {
1295 assert(vm.fout.len == 0);
1297 bc_history_enableRaw(h);
1299 s = bc_history_edit(h, prompt);
1301 h->stdin_has_data = bc_history_stdinHasData(h);
1302 if (!h->stdin_has_data) bc_history_disableRaw(h);
1304 bc_file_write(&vm.fout, "\n", 1);
1305 bc_file_flush(&vm.fout);
1310 BcStatus bc_history_line(BcHistory *h, BcVec *vec, const char *prompt) {
1315 s = bc_history_raw(h, prompt);
1316 assert(!s || s == BC_STATUS_EOF);
1318 bc_vec_string(vec, BC_HIST_BUF_LEN(h), h->buf.v);
1324 line = bc_vm_strdup(h->buf.v);
1328 bc_history_add(h, line);
1330 else bc_history_add_empty(h);
1332 bc_vec_concat(vec, "\n");
1337 static void bc_history_add(BcHistory *h, char *line) {
1339 if (h->history.len) {
1341 char *s = *((char**) bc_vec_item_rev(&h->history, 0));
1343 if (!strcmp(s, line)) {
1355 bc_vec_push(&h->history, &line);
1358 static void bc_history_add_empty(BcHistory *h) {
1360 const char *line = "";
1362 if (h->history.len) {
1364 char *s = *((char**) bc_vec_item_rev(&h->history, 0));
1369 bc_vec_push(&h->history, &line);
1372 static void bc_history_string_free(void *str) {
1373 char *s = *((char**) str);
1374 BC_SIG_ASSERT_LOCKED;
1378 void bc_history_init(BcHistory *h) {
1380 BC_SIG_ASSERT_LOCKED;
1382 bc_vec_init(&h->buf, sizeof(char), NULL);
1383 bc_vec_init(&h->history, sizeof(char*), bc_history_string_free);
1386 FD_SET(STDIN_FILENO, &h->rdset);
1390 sigemptyset(&h->sigmask);
1391 sigaddset(&h->sigmask, SIGINT);
1393 h->rawMode = h->stdin_has_data = false;
1394 h->badTerm = bc_history_isBadTerm();
1397 void bc_history_free(BcHistory *h) {
1398 BC_SIG_ASSERT_LOCKED;
1399 bc_history_disableRaw(h);
1401 bc_vec_free(&h->buf);
1402 bc_vec_free(&h->history);
1407 * This special mode is used by bc history in order to print scan codes
1408 * on screen for debugging / development purposes.
1411 void bc_history_printKeyCodes(BcHistory *h) {
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");
1419 bc_history_enableRaw(h);
1420 memset(quit, ' ', 4);
1427 nread = bc_history_read(&c, 1);
1428 if (nread <= 0) continue;
1430 // Shift string to left.
1431 memmove(quit, quit + 1, sizeof(quit) - 1);
1433 // Insert current char on the right.
1434 quit[sizeof(quit) - 1] = c;
1435 if (!memcmp(quit, "quit", sizeof(quit))) break;
1437 bc_vm_printf("'%c' %lu (type quit to exit)\n",
1438 isprint(c) ? c : '?', (unsigned long) c);
1440 // Go left edge manually, we are in raw mode.
1441 bc_vm_putchar('\r');
1442 bc_file_flush(&vm.fout);
1445 bc_history_disableRaw(h);
1447 #endif // BC_DEBUG_CODE
1449 #endif // BC_ENABLE_HISTORY