| /* |
| * ***************************************************************************** |
| * |
| * SPDX-License-Identifier: BSD-2-Clause |
| * |
| * Copyright (c) 2018-2021 Gavin D. Howard and contributors. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * * Redistributions of source code must retain the above copyright notice, this |
| * list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| * |
| * ***************************************************************************** |
| * |
| * Adapted from the following: |
| * |
| * linenoise.c -- guerrilla line editing library against the idea that a |
| * line editing lib needs to be 20,000 lines of C code. |
| * |
| * You can find the original source code at: |
| * http://github.com/antirez/linenoise |
| * |
| * You can find the fork that this code is based on at: |
| * https://github.com/rain-1/linenoise-mob |
| * |
| * ------------------------------------------------------------------------ |
| * |
| * This code is also under the following license: |
| * |
| * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com> |
| * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com> |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| * |
| * ------------------------------------------------------------------------ |
| * |
| * Does a number of crazy assumptions that happen to be true in 99.9999% of |
| * the 2010 UNIX computers around. |
| * |
| * References: |
| * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html |
| * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html |
| * |
| * Todo list: |
| * - Filter bogus Ctrl+<char> combinations. |
| * - Win32 support |
| * |
| * Bloat: |
| * - History search like Ctrl+r in readline? |
| * |
| * List of escape sequences used by this program, we do everything just |
| * with three sequences. In order to be so cheap we may have some |
| * flickering effect with some slow terminal, but the lesser sequences |
| * the more compatible. |
| * |
| * EL (Erase Line) |
| * Sequence: ESC [ n K |
| * Effect: if n is 0 or missing, clear from cursor to end of line |
| * Effect: if n is 1, clear from beginning of line to cursor |
| * Effect: if n is 2, clear entire line |
| * |
| * CUF (CUrsor Forward) |
| * Sequence: ESC [ n C |
| * Effect: moves cursor forward n chars |
| * |
| * CUB (CUrsor Backward) |
| * Sequence: ESC [ n D |
| * Effect: moves cursor backward n chars |
| * |
| * The following is used to get the terminal width if getting |
| * the width with the TIOCGWINSZ ioctl fails |
| * |
| * DSR (Device Status Report) |
| * Sequence: ESC [ 6 n |
| * Effect: reports the current cusor position as ESC [ n ; m R |
| * where n is the row and m is the column |
| * |
| * When multi line mode is enabled, we also use two additional escape |
| * sequences. However multi line editing is disabled by default. |
| * |
| * CUU (CUrsor Up) |
| * Sequence: ESC [ n A |
| * Effect: moves cursor up of n chars. |
| * |
| * CUD (CUrsor Down) |
| * Sequence: ESC [ n B |
| * Effect: moves cursor down of n chars. |
| * |
| * When bc_history_clearScreen() is called, two additional escape sequences |
| * are used in order to clear the screen and position the cursor at home |
| * position. |
| * |
| * CUP (CUrsor Position) |
| * Sequence: ESC [ H |
| * Effect: moves the cursor to upper left corner |
| * |
| * ED (Erase Display) |
| * Sequence: ESC [ 2 J |
| * Effect: clear the whole screen |
| * |
| * ***************************************************************************** |
| * |
| * Code for line history. |
| * |
| */ |
| |
| #if BC_ENABLE_HISTORY |
| |
| #include <assert.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <ctype.h> |
| |
| #include <signal.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #ifndef _WIN32 |
| #include <strings.h> |
| #include <termios.h> |
| #include <unistd.h> |
| #include <sys/ioctl.h> |
| #include <sys/select.h> |
| #endif // _WIN32 |
| |
| #include <status.h> |
| #include <vector.h> |
| #include <history.h> |
| #include <read.h> |
| #include <file.h> |
| #include <vm.h> |
| |
| #if BC_DEBUG_CODE |
| |
| /// A file for outputting to when debugging. |
| BcFile bc_history_debug_fp; |
| |
| /// A buffer for the above file. |
| char *bc_history_debug_buf; |
| |
| #endif // BC_DEBUG_CODE |
| |
| /** |
| * Checks if the code is a wide character. |
| * @param cp The codepoint to check. |
| * @return True if @a cp is a wide character, false otherwise. |
| */ |
| static bool bc_history_wchar(uint32_t cp) { |
| |
| size_t i; |
| |
| for (i = 0; i < bc_history_wchars_len; ++i) { |
| |
| // Ranges are listed in ascending order. Therefore, once the |
| // whole range is higher than the codepoint we're testing, the |
| // codepoint won't be found in any remaining range => bail early. |
| if (bc_history_wchars[i][0] > cp) return false; |
| |
| // Test this range. |
| if (bc_history_wchars[i][0] <= cp && cp <= bc_history_wchars[i][1]) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Checks if the code is a combining character. |
| * @param cp The codepoint to check. |
| * @return True if @a cp is a combining character, false otherwise. |
| */ |
| static bool bc_history_comboChar(uint32_t cp) { |
| |
| size_t i; |
| |
| for (i = 0; i < bc_history_combo_chars_len; ++i) { |
| |
| // Combining chars are listed in ascending order, so once we pass |
| // the codepoint of interest, we know it's not a combining char. |
| if (bc_history_combo_chars[i] > cp) return false; |
| if (bc_history_combo_chars[i] == cp) return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Gets the length of previous UTF8 character. |
| * @param buf The buffer of characters. |
| * @param pos The index into the buffer. |
| */ |
| static size_t bc_history_prevCharLen(const char *buf, size_t pos) { |
| size_t end = pos; |
| for (pos -= 1; pos < end && (buf[pos] & 0xC0) == 0x80; --pos); |
| return end - (pos >= end ? 0 : pos); |
| } |
| |
| /** |
| * Converts UTF-8 to a Unicode code point. |
| * @param s The string. |
| * @param len The length of the string. |
| * @param cp An out parameter for the codepoint. |
| * @return The number of bytes eaten by the codepoint. |
| */ |
| static size_t bc_history_codePoint(const char *s, size_t len, uint32_t *cp) { |
| |
| if (len) { |
| |
| uchar byte = (uchar) s[0]; |
| |
| // This is literally the UTF-8 decoding algorithm. Look that up if you |
| // don't understand this. |
| |
| if ((byte & 0x80) == 0) { |
| *cp = byte; |
| return 1; |
| } |
| else if ((byte & 0xE0) == 0xC0) { |
| |
| if (len >= 2) { |
| *cp = (((uint32_t) (s[0] & 0x1F)) << 6) | |
| ((uint32_t) (s[1] & 0x3F)); |
| return 2; |
| } |
| } |
| else if ((byte & 0xF0) == 0xE0) { |
| |
| if (len >= 3) { |
| *cp = (((uint32_t) (s[0] & 0x0F)) << 12) | |
| (((uint32_t) (s[1] & 0x3F)) << 6) | |
| ((uint32_t) (s[2] & 0x3F)); |
| return 3; |
| } |
| } |
| else if ((byte & 0xF8) == 0xF0) { |
| |
| if (len >= 4) { |
| *cp = (((uint32_t) (s[0] & 0x07)) << 18) | |
| (((uint32_t) (s[1] & 0x3F)) << 12) | |
| (((uint32_t) (s[2] & 0x3F)) << 6) | |
| ((uint32_t) (s[3] & 0x3F)); |
| return 4; |
| } |
| } |
| else { |
| *cp = 0xFFFD; |
| return 1; |
| } |
| } |
| |
| *cp = 0; |
| |
| return 1; |
| } |
| |
| /** |
| * Gets the length of next grapheme. |
| * @param buf The buffer. |
| * @param buf_len The length of the buffer. |
| * @param pos The index into the buffer. |
| * @param col_len An out parameter for the length of the grapheme on screen. |
| * @return The number of bytes in the grapheme. |
| */ |
| static size_t bc_history_nextLen(const char *buf, size_t buf_len, |
| size_t pos, size_t *col_len) |
| { |
| uint32_t cp; |
| size_t beg = pos; |
| size_t len = bc_history_codePoint(buf + pos, buf_len - pos, &cp); |
| |
| if (bc_history_comboChar(cp)) { |
| |
| BC_UNREACHABLE |
| |
| if (col_len != NULL) *col_len = 0; |
| |
| return 0; |
| } |
| |
| // Store the width of the character on screen. |
| if (col_len != NULL) *col_len = bc_history_wchar(cp) ? 2 : 1; |
| |
| pos += len; |
| |
| // Find the first non-combining character. |
| while (pos < buf_len) { |
| |
| len = bc_history_codePoint(buf + pos, buf_len - pos, &cp); |
| |
| if (!bc_history_comboChar(cp)) return pos - beg; |
| |
| pos += len; |
| } |
| |
| return pos - beg; |
| } |
| |
| /** |
| * Gets the length of previous grapheme. |
| * @param buf The buffer. |
| * @param pos The index into the buffer. |
| * @return The number of bytes in the grapheme. |
| */ |
| static size_t bc_history_prevLen(const char *buf, size_t pos) { |
| |
| size_t end = pos; |
| |
| // Find the first non-combining character. |
| while (pos > 0) { |
| |
| uint32_t cp; |
| size_t len = bc_history_prevCharLen(buf, pos); |
| |
| pos -= len; |
| bc_history_codePoint(buf + pos, len, &cp); |
| |
| // The original linenoise-mob had an extra parameter col_len, like |
| // bc_history_nextLen(), which, if not NULL, was set in this if |
| // statement. However, we always passed NULL, so just skip that. |
| if (!bc_history_comboChar(cp)) return end - pos; |
| } |
| |
| BC_UNREACHABLE |
| |
| return 0; |
| } |
| |
| /** |
| * Reads @a n characters from stdin. |
| * @param buf The buffer to read into. The caller is responsible for making |
| * sure this is big enough for @a n. |
| * @param n The number of characters to read. |
| * @return The number of characters read or less than 0 on error. |
| */ |
| static ssize_t bc_history_read(char *buf, size_t n) { |
| |
| ssize_t ret; |
| |
| BC_SIG_LOCK; |
| |
| #ifndef _WIN32 |
| |
| do { |
| // We don't care about being interrupted. |
| ret = read(STDIN_FILENO, buf, n); |
| } while (ret == EINTR); |
| |
| #else // _WIN32 |
| |
| bool good; |
| DWORD read; |
| HANDLE hn = GetStdHandle(STD_INPUT_HANDLE); |
| |
| good = ReadConsole(hn, buf, (DWORD) n, &read, NULL); |
| |
| ret = (read != n) ? -1 : 1; |
| |
| #endif // _WIN32 |
| |
| BC_SIG_UNLOCK; |
| |
| return ret; |
| } |
| |
| /** |
| * Reads a Unicode code point into a buffer. |
| * @param buf The buffer to read into. |
| * @param buf_len The length of the buffer. |
| * @param cp An out parameter for the codepoint. |
| * @param nread An out parameter for the number of bytes read. |
| * @return BC_STATUS_EOF or BC_STATUS_SUCCESS. |
| */ |
| static BcStatus bc_history_readCode(char *buf, size_t buf_len, |
| uint32_t *cp, size_t *nread) |
| { |
| ssize_t n; |
| |
| assert(buf_len >= 1); |
| |
| // Read a byte. |
| n = bc_history_read(buf, 1); |
| if (BC_ERR(n <= 0)) goto err; |
| |
| // Get the byte. |
| uchar byte = ((uchar*) buf)[0]; |
| |
| // Once again, this is the UTF-8 decoding algorithm, but it has reads |
| // instead of actual decoding. |
| if ((byte & 0x80) != 0) { |
| |
| if ((byte & 0xE0) == 0xC0) { |
| |
| assert(buf_len >= 2); |
| |
| n = bc_history_read(buf + 1, 1); |
| |
| if (BC_ERR(n <= 0)) goto err; |
| } |
| else if ((byte & 0xF0) == 0xE0) { |
| |
| assert(buf_len >= 3); |
| |
| n = bc_history_read(buf + 1, 2); |
| |
| if (BC_ERR(n <= 0)) goto err; |
| } |
| else if ((byte & 0xF8) == 0xF0) { |
| |
| assert(buf_len >= 3); |
| |
| n = bc_history_read(buf + 1, 3); |
| |
| if (BC_ERR(n <= 0)) goto err; |
| } |
| else { |
| n = -1; |
| goto err; |
| } |
| } |
| |
| // Convert to the codepoint. |
| *nread = bc_history_codePoint(buf, buf_len, cp); |
| |
| return BC_STATUS_SUCCESS; |
| |
| err: |
| // If we get here, we either had a fatal error of EOF. |
| if (BC_ERR(n < 0)) bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); |
| else *nread = (size_t) n; |
| return BC_STATUS_EOF; |
| } |
| |
| /** |
| * Gets the column length from beginning of buffer to current byte position. |
| * @param buf The buffer. |
| * @param buf_len The length of the buffer. |
| * @param pos The index into the buffer. |
| * @return The number of columns between the beginning of @a buffer to |
| * @a pos. |
| */ |
| static size_t bc_history_colPos(const char *buf, size_t buf_len, size_t pos) { |
| |
| size_t ret = 0, off = 0; |
| |
| // While we haven't reached the offset, get the length of the next grapheme. |
| while (off < pos && off < buf_len) { |
| |
| size_t col_len, len; |
| |
| len = bc_history_nextLen(buf, buf_len, off, &col_len); |
| |
| off += len; |
| ret += col_len; |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Returns true if the terminal name is in the list of terminals we know are |
| * not able to understand basic escape sequences. |
| * @return True if the terminal is a bad terminal. |
| */ |
| static inline bool bc_history_isBadTerm(void) { |
| |
| size_t i; |
| bool ret = false; |
| char *term = bc_vm_getenv("TERM"); |
| |
| if (term == NULL) return false; |
| |
| for (i = 0; !ret && bc_history_bad_terms[i]; ++i) |
| ret = (!strcasecmp(term, bc_history_bad_terms[i])); |
| |
| bc_vm_getenvFree(term); |
| |
| return ret; |
| } |
| |
| /** |
| * Enables raw mode (1960's black magic). |
| * @param h The history data. |
| */ |
| static void bc_history_enableRaw(BcHistory *h) { |
| |
| // I don't do anything for Windows because in Windows, you set their |
| // equivalent of raw mode and leave it, so I do it in bc_history_init(). |
| |
| #ifndef _WIN32 |
| struct termios raw; |
| int err; |
| |
| assert(BC_TTYIN); |
| |
| if (h->rawMode) return; |
| |
| BC_SIG_LOCK; |
| |
| if (BC_ERR(tcgetattr(STDIN_FILENO, &h->orig_termios) == -1)) |
| bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); |
| |
| BC_SIG_UNLOCK; |
| |
| // Modify the original mode. |
| raw = h->orig_termios; |
| |
| // Input modes: no break, no CR to NL, no parity check, no strip char, |
| // no start/stop output control. |
| raw.c_iflag &= (unsigned int) (~(BRKINT | ICRNL | INPCK | ISTRIP | IXON)); |
| |
| // Control modes: set 8 bit chars. |
| raw.c_cflag |= (CS8); |
| |
| // Local modes - choing off, canonical off, no extended functions, |
| // no signal chars (^Z,^C). |
| raw.c_lflag &= (unsigned int) (~(ECHO | ICANON | IEXTEN | ISIG)); |
| |
| // Control chars - set return condition: min number of bytes and timer. |
| // We want read to give every single byte, w/o timeout (1 byte, no timer). |
| raw.c_cc[VMIN] = 1; |
| raw.c_cc[VTIME] = 0; |
| |
| BC_SIG_LOCK; |
| |
| // Put terminal in raw mode after flushing. |
| do { |
| err = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); |
| } while (BC_ERR(err < 0) && errno == EINTR); |
| |
| BC_SIG_UNLOCK; |
| |
| if (BC_ERR(err < 0)) bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); |
| #endif // _WIN32 |
| |
| h->rawMode = true; |
| } |
| |
| /** |
| * Disables raw mode. |
| * @param h The history data. |
| */ |
| static void bc_history_disableRaw(BcHistory *h) { |
| |
| sig_atomic_t lock; |
| |
| if (!h->rawMode) return; |
| |
| BC_SIG_TRYLOCK(lock); |
| |
| #ifndef _WIN32 |
| if (BC_ERR(tcsetattr(STDIN_FILENO, TCSAFLUSH, &h->orig_termios) != -1)) |
| h->rawMode = false; |
| #endif // _WIN32 |
| |
| BC_SIG_TRYUNLOCK(lock); |
| } |
| |
| /** |
| * Uses the ESC [6n escape sequence to query the horizontal cursor position |
| * and return it. On error -1 is returned, on success the position of the |
| * cursor. |
| * @return The horizontal cursor position. |
| */ |
| static size_t bc_history_cursorPos(void) { |
| |
| char buf[BC_HIST_SEQ_SIZE]; |
| char *ptr, *ptr2; |
| size_t cols, rows, i; |
| |
| // Report cursor location. |
| bc_file_write(&vm.fout, bc_flush_none, "\x1b[6n", 4); |
| bc_file_flush(&vm.fout, bc_flush_none); |
| |
| // Read the response: ESC [ rows ; cols R. |
| for (i = 0; i < sizeof(buf) - 1; ++i) { |
| if (bc_history_read(buf + i, 1) != 1 || buf[i] == 'R') break; |
| } |
| |
| buf[i] = '\0'; |
| |
| // This is basically an error; we didn't get what we were expecting. |
| if (BC_ERR(buf[0] != BC_ACTION_ESC || buf[1] != '[')) return SIZE_MAX; |
| |
| // Parse the rows. |
| ptr = buf + 2; |
| rows = strtoul(ptr, &ptr2, 10); |
| |
| // Here we also didn't get what we were expecting. |
| if (BC_ERR(!rows || ptr2[0] != ';')) return SIZE_MAX; |
| |
| // Parse the columns. |
| ptr = ptr2 + 1; |
| cols = strtoul(ptr, NULL, 10); |
| |
| if (BC_ERR(!cols)) return SIZE_MAX; |
| |
| return cols <= UINT16_MAX ? cols : 0; |
| } |
| |
| /** |
| * Tries to get the number of columns in the current terminal, or assume 80 |
| * if it fails. |
| * @return The number of columns in the terminal. |
| */ |
| static size_t bc_history_columns(void) { |
| |
| #ifndef _WIN32 |
| |
| struct winsize ws; |
| int ret; |
| |
| BC_SIG_LOCK; |
| |
| ret = ioctl(vm.fout.fd, TIOCGWINSZ, &ws); |
| |
| BC_SIG_UNLOCK; |
| |
| if (BC_ERR(ret == -1 || !ws.ws_col)) { |
| |
| // Calling ioctl() failed. Try to query the terminal itself. |
| size_t start, cols; |
| |
| // Get the initial position so we can restore it later. |
| start = bc_history_cursorPos(); |
| if (BC_ERR(start == SIZE_MAX)) return BC_HIST_DEF_COLS; |
| |
| // Go to right margin and get position. |
| bc_file_write(&vm.fout, bc_flush_none, "\x1b[999C", 6); |
| bc_file_flush(&vm.fout, bc_flush_none); |
| cols = bc_history_cursorPos(); |
| if (BC_ERR(cols == SIZE_MAX)) return BC_HIST_DEF_COLS; |
| |
| // Restore position. |
| if (cols > start) { |
| bc_file_printf(&vm.fout, "\x1b[%zuD", cols - start); |
| bc_file_flush(&vm.fout, bc_flush_none); |
| } |
| |
| return cols; |
| } |
| |
| return ws.ws_col; |
| |
| #else // _WIN32 |
| |
| CONSOLE_SCREEN_BUFFER_INFO csbi; |
| |
| if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) |
| return 80; |
| |
| return ((size_t) (csbi.srWindow.Right)) - csbi.srWindow.Left + 1; |
| |
| #endif // _WIN32 |
| } |
| |
| /** |
| * Gets the column length of prompt text. This is probably unnecessary because |
| * the prompts that I use are ASCII, but I kept it just in case. |
| * @param prompt The prompt. |
| * @param plen The length of the prompt. |
| * @return The column length of the prompt. |
| */ |
| static size_t bc_history_promptColLen(const char *prompt, size_t plen) { |
| |
| char buf[BC_HIST_MAX_LINE + 1]; |
| size_t buf_len = 0, off = 0; |
| |
| // The original linenoise-mob checked for ANSI escapes here on the prompt. I |
| // know the prompts do not have ANSI escapes. I deleted the code. |
| while (off < plen) buf[buf_len++] = prompt[off++]; |
| |
| return bc_history_colPos(buf, buf_len, buf_len); |
| } |
| |
| /** |
| * Rewrites the currently edited line accordingly to the buffer content, |
| * cursor position, and number of columns of the terminal. |
| * @param h The history data. |
| */ |
| static void bc_history_refresh(BcHistory *h) { |
| |
| char* buf = h->buf.v; |
| size_t colpos, len = BC_HIST_BUF_LEN(h), pos = h->pos, extras_len = 0; |
| |
| bc_file_flush(&vm.fout, bc_flush_none); |
| |
| // Get to the prompt column position from the left. |
| while(h->pcol + bc_history_colPos(buf, len, pos) >= h->cols) { |
| |
| size_t chlen = bc_history_nextLen(buf, len, 0, NULL); |
| |
| buf += chlen; |
| len -= chlen; |
| pos -= chlen; |
| } |
| |
| // Get to the prompt column position from the right. |
| while (h->pcol + bc_history_colPos(buf, len, len) > h->cols) |
| len -= bc_history_prevLen(buf, len); |
| |
| // Cursor to left edge. |
| bc_file_write(&vm.fout, bc_flush_none, "\r", 1); |
| |
| // Take the extra stuff into account. This is where history makes sure to |
| // preserve stuff that was printed without a newline. |
| if (h->extras.len > 1) { |
| |
| extras_len = h->extras.len - 1; |
| |
| bc_vec_grow(&h->buf, extras_len); |
| |
| len += extras_len; |
| pos += extras_len; |
| |
| bc_file_write(&vm.fout, bc_flush_none, h->extras.v, extras_len); |
| } |
| |
| // Write the prompt, if desired. |
| if (BC_PROMPT) bc_file_write(&vm.fout, bc_flush_none, h->prompt, h->plen); |
| |
| bc_file_write(&vm.fout, bc_flush_none, h->buf.v, len - extras_len); |
| |
| // Erase to right. |
| bc_file_write(&vm.fout, bc_flush_none, "\x1b[0K", 4); |
| |
| // We need to be sure to grow this. |
| if (pos >= h->buf.len - extras_len) |
| bc_vec_grow(&h->buf, pos + extras_len); |
| |
| // Move cursor to original position. |
| colpos = bc_history_colPos(h->buf.v, len - extras_len, pos) + h->pcol; |
| |
| // Set the cursor position again. |
| if (colpos) bc_file_printf(&vm.fout, "\r\x1b[%zuC", colpos); |
| |
| bc_file_flush(&vm.fout, bc_flush_none); |
| } |
| |
| /** |
| * Inserts the character(s) 'c' at cursor current position. |
| * @param h The history data. |
| * @param cbuf The character buffer to copy from. |
| * @param clen The number of characters to copy. |
| */ |
| static void bc_history_edit_insert(BcHistory *h, const char *cbuf, size_t clen) |
| { |
| bc_vec_grow(&h->buf, clen); |
| |
| // If we are at the end of the line... |
| if (h->pos == BC_HIST_BUF_LEN(h)) { |
| |
| size_t colpos = 0, len; |
| |
| // Copy into the buffer. |
| memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen); |
| |
| // Adjust the buffer. |
| h->pos += clen; |
| h->buf.len += clen - 1; |
| bc_vec_pushByte(&h->buf, '\0'); |
| |
| // Set the length and column position. |
| len = BC_HIST_BUF_LEN(h) + h->extras.len - 1; |
| colpos = bc_history_promptColLen(h->prompt, h->plen); |
| colpos += bc_history_colPos(h->buf.v, len, len); |
| |
| // Do we have the trivial case? |
| if (colpos < h->cols) { |
| |
| // Avoid a full update of the line in the trivial case. |
| bc_file_write(&vm.fout, bc_flush_none, cbuf, clen); |
| bc_file_flush(&vm.fout, bc_flush_none); |
| } |
| else bc_history_refresh(h); |
| } |
| else { |
| |
| // Amount that we need to move. |
| size_t amt = BC_HIST_BUF_LEN(h) - h->pos; |
| |
| // Move the stuff. |
| memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt); |
| memcpy(h->buf.v + h->pos, cbuf, clen); |
| |
| // Adjust the buffer. |
| h->pos += clen; |
| h->buf.len += clen; |
| h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; |
| |
| bc_history_refresh(h); |
| } |
| } |
| |
| /** |
| * Moves the cursor to the left. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_left(BcHistory *h) { |
| |
| // Stop at the left end. |
| if (h->pos <= 0) return; |
| |
| h->pos -= bc_history_prevLen(h->buf.v, h->pos); |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Moves the cursor to the right. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_right(BcHistory *h) { |
| |
| // Stop at the right end. |
| if (h->pos == BC_HIST_BUF_LEN(h)) return; |
| |
| h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Moves the cursor to the end of the current word. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_wordEnd(BcHistory *h) { |
| |
| size_t len = BC_HIST_BUF_LEN(h); |
| |
| // Don't overflow. |
| if (!len || h->pos >= len) return; |
| |
| // Find the word, then find the end of it. |
| while (h->pos < len && isspace(h->buf.v[h->pos])) h->pos += 1; |
| while (h->pos < len && !isspace(h->buf.v[h->pos])) h->pos += 1; |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Moves the cursor to the start of the current word. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_wordStart(BcHistory *h) { |
| |
| size_t len = BC_HIST_BUF_LEN(h); |
| |
| // Stop with no data. |
| if (!len) return; |
| |
| // Find the word, the find the beginning of the word. |
| while (h->pos > 0 && isspace(h->buf.v[h->pos - 1])) h->pos -= 1; |
| while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1])) h->pos -= 1; |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Moves the cursor to the start of the line. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_home(BcHistory *h) { |
| |
| // Stop at the beginning. |
| if (!h->pos) return; |
| |
| h->pos = 0; |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Moves the cursor to the end of the line. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_end(BcHistory *h) { |
| |
| // Stop at the end of the line. |
| if (h->pos == BC_HIST_BUF_LEN(h)) return; |
| |
| h->pos = BC_HIST_BUF_LEN(h); |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Substitutes the currently edited line with the next or previous history |
| * entry as specified by 'dir' (direction). |
| * @param h The history data. |
| * @param dir The direction to substitute; true means previous, false next. |
| */ |
| static void bc_history_edit_next(BcHistory *h, bool dir) { |
| |
| const char *dup, *str; |
| |
| // Stop if there is no history. |
| if (h->history.len <= 1) return; |
| |
| BC_SIG_LOCK; |
| |
| // Duplicate the buffer. |
| if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v); |
| else dup = ""; |
| |
| // Update the current history entry before overwriting it with the next one. |
| bc_vec_replaceAt(&h->history, h->history.len - 1 - h->idx, &dup); |
| |
| BC_SIG_UNLOCK; |
| |
| // Show the new entry. |
| h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX); |
| |
| // Se the index appropriately at the ends. |
| if (h->idx == SIZE_MAX) { |
| h->idx = 0; |
| return; |
| } |
| else if (h->idx >= h->history.len) { |
| h->idx = h->history.len - 1; |
| return; |
| } |
| |
| // Get the string. |
| str = *((char**) bc_vec_item(&h->history, h->history.len - 1 - h->idx)); |
| bc_vec_string(&h->buf, strlen(str), str); |
| |
| assert(h->buf.len > 0); |
| |
| // Set the position at the end. |
| h->pos = BC_HIST_BUF_LEN(h); |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Deletes the character at the right of the cursor without altering the cursor |
| * position. Basically, this is what happens with the "Delete" keyboard key. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_delete(BcHistory *h) { |
| |
| size_t chlen, len = BC_HIST_BUF_LEN(h); |
| |
| // If there is no character, skip. |
| if (!len || h->pos >= len) return; |
| |
| // Get the length of the character. |
| chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL); |
| |
| // Move characters after it into its place. |
| memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen); |
| |
| // Make the buffer valid again. |
| h->buf.len -= chlen; |
| h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Deletes the character to the left of the cursor and moves the cursor back one |
| * space. Basically, this is what happens with the "Backspace" keyboard key. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_backspace(BcHistory *h) { |
| |
| size_t chlen, len = BC_HIST_BUF_LEN(h); |
| |
| // If there are no characters, skip. |
| if (!h->pos || !len) return; |
| |
| // Get the length of the previous character. |
| chlen = bc_history_prevLen(h->buf.v, h->pos); |
| |
| // Move everything back one. |
| memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos); |
| |
| // Make the buffer valid again. |
| h->pos -= chlen; |
| h->buf.len -= chlen; |
| h->buf.v[BC_HIST_BUF_LEN(h)] = '\0'; |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Deletes the previous word, maintaining the cursor at the start of the |
| * current word. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_deletePrevWord(BcHistory *h) { |
| |
| size_t diff, old_pos = h->pos; |
| |
| // If at the beginning of the line, skip. |
| if (!old_pos) return; |
| |
| // Find the word, then the beginning of the word. |
| while (h->pos > 0 && isspace(h->buf.v[h->pos - 1])) --h->pos; |
| while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1])) --h->pos; |
| |
| // Get the difference in position. |
| diff = old_pos - h->pos; |
| |
| // Move the data back. |
| memmove(h->buf.v + h->pos, h->buf.v + old_pos, |
| BC_HIST_BUF_LEN(h) - old_pos + 1); |
| |
| // Make the buffer valid again. |
| h->buf.len -= diff; |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Deletes the next word, maintaining the cursor at the same position. |
| * @param h The history data. |
| */ |
| static void bc_history_edit_deleteNextWord(BcHistory *h) { |
| |
| size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h); |
| |
| // If at the end of the line, skip. |
| if (next_end == len) return; |
| |
| // Find the word, then the end of the word. |
| while (next_end < len && isspace(h->buf.v[next_end])) ++next_end; |
| while (next_end < len && !isspace(h->buf.v[next_end])) ++next_end; |
| |
| // Move the stuff into position. |
| memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end); |
| |
| // Make the buffer valid again. |
| h->buf.len -= next_end - h->pos; |
| |
| bc_history_refresh(h); |
| } |
| |
| /** |
| * Swaps two characters, the one under the cursor and the one to the left. |
| * @param h The history data. |
| */ |
| static void bc_history_swap(BcHistory *h) { |
| |
| size_t pcl, ncl; |
| char auxb[5]; |
| |
| // Get the length of the previous and next characters. |
| pcl = bc_history_prevLen(h->buf.v, h->pos); |
| ncl = bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL); |
| |
| // To perform a swap we need: |
| // * Nonzero char length to the left. |
| // * To not be at the end of the line. |
| if (pcl && h->pos != BC_HIST_BUF_LEN(h) && pcl < 5 && ncl < 5) { |
| |
| // Swap. |
| memcpy(auxb, h->buf.v + h->pos - pcl, pcl); |
| memcpy(h->buf.v + h->pos - pcl, h->buf.v + h->pos, ncl); |
| memcpy(h->buf.v + h->pos - pcl + ncl, auxb, pcl); |
| |
| // Reset the position. |
| h->pos += ((~pcl) + 1) + ncl; |
| |
| bc_history_refresh(h); |
| } |
| } |
| |
| /** |
| * Raises the specified signal. This is a convenience function. |
| * @param h The history data. |
| * @param sig The signal to raise. |
| */ |
| static void bc_history_raise(BcHistory *h, int sig) { |
| |
| // We really don't want to be in raw mode when longjmp()'s are flying. |
| bc_history_disableRaw(h); |
| raise(sig); |
| } |
| |
| /** |
| * Handles escape sequences. This function will make sense if you know VT100 |
| * escape codes; otherwise, it will be confusing. |
| * @param h The history data. |
| */ |
| static void bc_history_escape(BcHistory *h) { |
| |
| char c, seq[3]; |
| |
| // Read a character into seq. |
| if (BC_ERR(BC_HIST_READ(seq, 1))) return; |
| |
| c = seq[0]; |
| |
| // ESC ? sequences. |
| if (c != '[' && c != 'O') { |
| if (c == 'f') bc_history_edit_wordEnd(h); |
| else if (c == 'b') bc_history_edit_wordStart(h); |
| else if (c == 'd') bc_history_edit_deleteNextWord(h); |
| } |
| else { |
| |
| // Read a character into seq. |
| if (BC_ERR(BC_HIST_READ(seq + 1, 1))) |
| bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); |
| |
| // ESC [ sequences. |
| if (c == '[') { |
| |
| c = seq[1]; |
| |
| if (c >= '0' && c <= '9') { |
| |
| // Extended escape, read additional byte. |
| if (BC_ERR(BC_HIST_READ(seq + 2, 1))) |
| bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); |
| |
| if (seq[2] == '~' && c == '3') bc_history_edit_delete(h); |
| else if(seq[2] == ';') { |
| |
| // Read two characters into seq. |
| if (BC_ERR(BC_HIST_READ(seq, 2))) |
| bc_vm_fatalError(BC_ERR_FATAL_IO_ERR); |
| |
| if (seq[0] != '5') return; |
| else if (seq[1] == 'C') bc_history_edit_wordEnd(h); |
| else if (seq[1] == 'D') bc_history_edit_wordStart(h); |
| } |
| } |
| else { |
| |
| switch(c) { |
| |
| // Up. |
| case 'A': |
| { |
| bc_history_edit_next(h, BC_HIST_PREV); |
| break; |
| } |
| |
| // Down. |
| case 'B': |
| { |
| bc_history_edit_next(h, BC_HIST_NEXT); |
| break; |
| } |
| |
| // Right. |
| case 'C': |
| { |
| bc_history_edit_right(h); |
| break; |
| } |
| |
| // Left. |
| case 'D': |
| { |
| bc_history_edit_left(h); |
| break; |
| } |
| |
| // Home. |
| case 'H': |
| case '1': |
| { |
| bc_history_edit_home(h); |
| break; |
| } |
| |
| // End. |
| case 'F': |
| case '4': |
| { |
| bc_history_edit_end(h); |
| break; |
| } |
| |
| case 'd': |
| { |
| bc_history_edit_deleteNextWord(h); |
| break; |
| } |
| } |
| } |
| } |
| // ESC O sequences. |
| else { |
| |
| switch (seq[1]) { |
| |
| case 'A': |
| { |
| bc_history_edit_next(h, BC_HIST_PREV); |
| break; |
| } |
| |
| case 'B': |
| { |
| bc_history_edit_next(h, BC_HIST_NEXT); |
| break; |
| } |
| |
| case 'C': |
| { |
| bc_history_edit_right(h); |
| break; |
| } |
| |
| case 'D': |
| { |
| bc_history_edit_left(h); |
| break; |
| } |
| |
| case 'F': |
| { |
| bc_history_edit_end(h); |
| break; |
| } |
| |
| case 'H': |
| { |
| bc_history_edit_home(h); |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Adds a line to the history. |
| * @param h The history data. |
| * @param line The line to add. |
| */ |
| static void bc_history_add(BcHistory *h, char *line) { |
| |
| // If there is something already there... |
| if (h->history.len) { |
| |
| // Get the previous. |
| char *s = *((char**) bc_vec_item_rev(&h->history, 0)); |
| |
| // Check for, and discard, duplicates. |
| if (!strcmp(s, line)) { |
| |
| BC_SIG_LOCK; |
| |
| free(line); |
| |
| BC_SIG_UNLOCK; |
| |
| return; |
| } |
| } |
| |
| bc_vec_push(&h->history, &line); |
| } |
| |
| /** |
| * Adds an empty line to the history. This is separate from bc_history_add() |
| * because we don't want it allocating. |
| * @param h The history data. |
| */ |
| static void bc_history_add_empty(BcHistory *h) { |
| |
| const char *line = ""; |
| |
| // If there is something already there... |
| if (h->history.len) { |
| |
| // Get the previous. |
| char *s = *((char**) bc_vec_item_rev(&h->history, 0)); |
| |
| // Check for, and discard, duplicates. |
| if (!s[0]) return; |
| } |
| |
| bc_vec_push(&h->history, &line); |
| } |
| |
| /** |
| * Resets the history state to nothing. |
| * @param h The history data. |
| */ |
| static void bc_history_reset(BcHistory *h) { |
| |
| h->oldcolpos = h->pos = h->idx = 0; |
| h->cols = bc_history_columns(); |
| |
| // The latest history entry is always our current buffer, that |
| // initially is just an empty string. |
| bc_history_add_empty(h); |
| |
| // Buffer starts empty. |
| bc_vec_empty(&h->buf); |
| } |
| |
| /** |
| * Prints a control character. |
| * @param h The history data. |
| * @param c The control character to print. |
| */ |
| static void bc_history_printCtrl(BcHistory *h, unsigned int c) { |
| |
| char str[3] = "^A"; |
| const char newline[2] = "\n"; |
| |
| // Set the correct character. |
| str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A); |
| |
| // Concatenate the string. |
| bc_vec_concat(&h->buf, str); |
| |
| bc_history_refresh(h); |
| |
| // Pop the string. |
| bc_vec_npop(&h->buf, sizeof(str)); |
| bc_vec_pushByte(&h->buf, '\0'); |
| |
| #ifndef _WIN32 |
| if (c != BC_ACTION_CTRL_C && c != BC_ACTION_CTRL_D) |
| #endif // _WIN32 |
| { |
| // We sometimes want to print a newline; for the times we don't; it's |
| // because newlines are taken care of elsewhere. |
| bc_file_write(&vm.fout, bc_flush_none, newline, sizeof(newline) - 1); |
| bc_history_refresh(h); |
| } |
| } |
| |
| /** |
| * Edits a line of history. This function is the core of the line editing |
| * capability of bc history. It expects 'fd' to be already in "raw mode" so that |
| * every key pressed will be returned ASAP to read(). |
| * @param h The history data. |
| * @param prompt The prompt. |
| * @return BC_STATUS_SUCCESS or BC_STATUS_EOF. |
| */ |
| static BcStatus bc_history_edit(BcHistory *h, const char *prompt) { |
| |
| bc_history_reset(h); |
| |
| // Don't write the saved output the first time. This is because it has |
| // already been written to output. In other words, don't uncomment the |
| // line below or add anything like it. |
| // bc_file_write(&vm.fout, bc_flush_none, h->extras.v, h->extras.len - 1); |
| |
| // Write the prompt if desired. |
| if (BC_PROMPT) { |
| |
| h->prompt = prompt; |
| h->plen = strlen(prompt); |
| h->pcol = bc_history_promptColLen(prompt, h->plen); |
| |
| bc_file_write(&vm.fout, bc_flush_none, prompt, h->plen); |
| bc_file_flush(&vm.fout, bc_flush_none); |
| } |
| |
| // This is the input loop. |
| for (;;) { |
| |
| BcStatus s; |
| char cbuf[32]; |
| unsigned int c = 0; |
| size_t nread = 0; |
| |
| // Read a code. |
| s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread); |
| if (BC_ERR(s)) return s; |
| |
| switch (c) { |
| |
| case BC_ACTION_LINE_FEED: |
| case BC_ACTION_ENTER: |
| { |
| // Return the line. |
| bc_vec_pop(&h->history); |
| return s; |
| } |
| |
| case BC_ACTION_TAB: |
| { |
| // My tab handling is dumb; it just prints 8 spaces every time. |
| memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1); |
| bc_history_edit_insert(h, cbuf, bc_history_tab_len); |
| break; |
| } |
| |
| #ifndef _WIN32 |
| case BC_ACTION_CTRL_C: |
| { |
| bc_history_printCtrl(h, c); |
| |
| // Quit if the user wants it. |
| if (!BC_SIGINT) { |
| vm.status = BC_STATUS_QUIT; |
| BC_JMP; |
| } |
| |
| // Print the ready message. |
| bc_file_write(&vm.fout, bc_flush_none, vm.sigmsg, vm.siglen); |
| bc_file_write(&vm.fout, bc_flush_none, bc_program_ready_msg, |
| bc_program_ready_msg_len); |
| bc_history_reset(h); |
| bc_history_refresh(h); |
| |
| break; |
| } |
| #endif // _WIN32 |
| |
| case BC_ACTION_BACKSPACE: |
| case BC_ACTION_CTRL_H: |
| { |
| bc_history_edit_backspace(h); |
| break; |
| } |
| |
| #ifndef _WIN32 |
| // Act as end-of-file. |
| case BC_ACTION_CTRL_D: |
| { |
| bc_history_printCtrl(h, c); |
| return BC_STATUS_EOF; |
| } |
| #endif // _WIN32 |
| |
| // Swaps current character with previous. |
| case BC_ACTION_CTRL_T: |
| { |
| bc_history_swap(h); |
| break; |
| } |
| |
| case BC_ACTION_CTRL_B: |
| { |
| bc_history_edit_left(h); |
| break; |
| } |
| |
| case BC_ACTION_CTRL_F: |
| { |
| bc_history_edit_right(h); |
| break; |
| } |
| |
| case BC_ACTION_CTRL_P: |
| { |
| bc_history_edit_next(h, BC_HIST_PREV); |
| break; |
| } |
| |
| case BC_ACTION_CTRL_N: |
| { |
| bc_history_edit_next(h, BC_HIST_NEXT); |
| break; |
| } |
| |
| case BC_ACTION_ESC: |
| { |
| bc_history_escape(h); |
| break; |
| } |
| |
| // Delete the whole line. |
| case BC_ACTION_CTRL_U: |
| { |
| bc_vec_string(&h->buf, 0, ""); |
| h->pos = 0; |
| |
| bc_history_refresh(h); |
| |
| break; |
| } |
| |
| // Delete from current to end of line. |
| case BC_ACTION_CTRL_K: |
| { |
| bc_vec_npop(&h->buf, h->buf.len - h->pos); |
| bc_vec_pushByte(&h->buf, '\0'); |
| bc_history_refresh(h); |
| break; |
| } |
| |
| // Go to the start of the line. |
| case BC_ACTION_CTRL_A: |
| { |
| bc_history_edit_home(h); |
| break; |
| } |
| |
| // Go to the end of the line. |
| case BC_ACTION_CTRL_E: |
| { |
| bc_history_edit_end(h); |
| break; |
| } |
| |
| // Clear screen. |
| case BC_ACTION_CTRL_L: |
| { |
| bc_file_write(&vm.fout, bc_flush_none, "\x1b[H\x1b[2J", 7); |
| bc_history_refresh(h); |
| break; |
| } |
| |
| // Delete previous word. |
| case BC_ACTION_CTRL_W: |
| { |
| bc_history_edit_deletePrevWord(h); |
| break; |
| } |
| |
| default: |
| { |
| // If we have a control character, print it and raise signals as |
| // needed. |
| if ((c >= BC_ACTION_CTRL_A && c <= BC_ACTION_CTRL_Z) || |
| c == BC_ACTION_CTRL_BSLASH) |
| { |
| bc_history_printCtrl(h, c); |
| #ifndef _WIN32 |
| if (c == BC_ACTION_CTRL_Z) bc_history_raise(h, SIGTSTP); |
| if (c == BC_ACTION_CTRL_S) bc_history_raise(h, SIGSTOP); |
| if (c == BC_ACTION_CTRL_BSLASH) |
| bc_history_raise(h, SIGQUIT); |
| #else // _WIN32 |
| vm.status = BC_STATUS_QUIT; |
| BC_JMP; |
| #endif // _WIN32 |
| } |
| // Otherwise, just insert. |
| else bc_history_edit_insert(h, cbuf, nread); |
| break; |
| } |
| } |
| } |
| |
| return BC_STATUS_SUCCESS; |
| } |
| |
| /** |
| * Returns true if stdin has more data. This is for multi-line pasting, and it |
| * does not work on Windows. |
| * @param h The history data. |
| */ |
| static inline bool bc_history_stdinHasData(BcHistory *h) { |
| #ifndef _WIN32 |
| int n; |
| return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 || |
| (ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0); |
| #else // _WIN32 |
| return false; |
| #endif // _WIN32 |
| } |
| |
| BcStatus bc_history_line(BcHistory *h, BcVec *vec, const char *prompt) { |
| |
| BcStatus s; |
| char* line; |
| |
| assert(vm.fout.len == 0); |
| |
| bc_history_enableRaw(h); |
| |
| do { |
| |
| // Do the edit. |
| s = bc_history_edit(h, prompt); |
| |
| // Print a newline and flush. |
| bc_file_write(&vm.fout, bc_flush_none, "\n", 1); |
| bc_file_flush(&vm.fout, bc_flush_none); |
| |
| // If we actually have data... |
| if (h->buf.v[0]) { |
| |
| BC_SIG_LOCK; |
| |
| // Duplicate it. |
| line = bc_vm_strdup(h->buf.v); |
| |
| BC_SIG_UNLOCK; |
| |
| // Store it. |
| bc_history_add(h, line); |
| } |
| // Add an empty string. |
| else bc_history_add_empty(h); |
| |
| // Concatenate the line to the return vector. |
| bc_vec_concat(vec, h->buf.v); |
| bc_vec_concat(vec, "\n"); |
| |
| } while (!s && bc_history_stdinHasData(h)); |
| |
| assert(!s || s == BC_STATUS_EOF); |
| |
| bc_history_disableRaw(h); |
| |
| return s; |
| } |
| |
| void bc_history_string_free(void *str) { |
| char *s = *((char**) str); |
| BC_SIG_ASSERT_LOCKED; |
| if (s[0]) free(s); |
| } |
| |
| void bc_history_init(BcHistory *h) { |
| |
| BC_SIG_ASSERT_LOCKED; |
| |
| bc_vec_init(&h->buf, sizeof(char), BC_DTOR_NONE); |
| bc_vec_init(&h->history, sizeof(char*), BC_DTOR_HISTORY_STRING); |
| bc_vec_init(&h->extras, sizeof(char), BC_DTOR_NONE); |
| |
| #ifndef _WIN32 |
| FD_ZERO(&h->rdset); |
| FD_SET(STDIN_FILENO, &h->rdset); |
| h->ts.tv_sec = 0; |
| h->ts.tv_nsec = 0; |
| |
| sigemptyset(&h->sigmask); |
| sigaddset(&h->sigmask, SIGINT); |
| #endif // _WIN32 |
| |
| h->rawMode = false; |
| h->badTerm = bc_history_isBadTerm(); |
| |
| #ifdef _WIN32 |
| if (!h->badTerm) { |
| SetConsoleCP(CP_UTF8); |
| SetConsoleOutputCP(CP_UTF8); |
| GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &h->orig_console_mode); |
| SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), |
| ENABLE_VIRTUAL_TERMINAL_INPUT); |
| } |
| #endif // _WIN32 |
| } |
| |
| void bc_history_free(BcHistory *h) { |
| BC_SIG_ASSERT_LOCKED; |
| #ifndef _WIN32 |
| bc_history_disableRaw(h); |
| #else // _WIN32 |
| SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), h->orig_console_mode); |
| #endif // _WIN32 |
| #ifndef NDEBUG |
| bc_vec_free(&h->buf); |
| bc_vec_free(&h->history); |
| bc_vec_free(&h->extras); |
| #endif // NDEBUG |
| } |
| |
| #if BC_DEBUG_CODE |
| |
| /** |
| * Prints scan codes. This special mode is used by bc history in order to print |
| * scan codes on screen for debugging / development purposes. |
| * @param h The history data. |
| */ |
| void bc_history_printKeyCodes(BcHistory *h) { |
| |
| char quit[4]; |
| |
| bc_vm_printf("Linenoise key codes debugging mode.\n" |
| "Press keys to see scan codes. " |
| "Type 'quit' at any time to exit.\n"); |
| |
| bc_history_enableRaw(h); |
| memset(quit, ' ', 4); |
| |
| while(true) { |
| |
| char c; |
| ssize_t nread; |
| |
| nread = bc_history_read(&c, 1); |
| if (nread <= 0) continue; |
| |
| // Shift string to left. |
| memmove(quit, quit + 1, sizeof(quit) - 1); |
| |
| // Insert current char on the right. |
| quit[sizeof(quit) - 1] = c; |
| if (!memcmp(quit, "quit", sizeof(quit))) break; |
| |
| bc_vm_printf("'%c' %lu (type quit to exit)\n", |
| isprint(c) ? c : '?', (unsigned long) c); |
| |
| // Go left edge manually, we are in raw mode. |
| bc_vm_putchar('\r', bc_flush_none); |
| bc_file_flush(&vm.fout, bc_flush_none); |
| } |
| |
| bc_history_disableRaw(h); |
| } |
| #endif // BC_DEBUG_CODE |
| |
| #endif // BC_ENABLE_HISTORY |