/*1* *****************************************************************************2*3* SPDX-License-Identifier: BSD-2-Clause4*5* Copyright (c) 2018-2025 Gavin D. Howard and contributors.6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions are met:9*10* * Redistributions of source code must retain the above copyright notice, this11* list of conditions and the following disclaimer.12*13* * Redistributions in binary form must reproduce the above copyright notice,14* this list of conditions and the following disclaimer in the documentation15* and/or other materials provided with the distribution.16*17* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"18* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE19* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE20* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE21* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR22* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF23* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS24* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN25* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)26* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE27* POSSIBILITY OF SUCH DAMAGE.28*29* *****************************************************************************30*31* Adapted from the following:32*33* linenoise.c -- guerrilla line editing library against the idea that a34* line editing lib needs to be 20,000 lines of C code.35*36* You can find the original source code at:37* http://github.com/antirez/linenoise38*39* You can find the fork that this code is based on at:40* https://github.com/rain-1/linenoise-mob41*42* ------------------------------------------------------------------------43*44* This code is also under the following license:45*46* Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>47* Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>48*49* Redistribution and use in source and binary forms, with or without50* modification, are permitted provided that the following conditions are51* met:52*53* * Redistributions of source code must retain the above copyright54* notice, this list of conditions and the following disclaimer.55*56* * Redistributions in binary form must reproduce the above copyright57* notice, this list of conditions and the following disclaimer in the58* documentation and/or other materials provided with the distribution.59*60* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS61* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT62* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR63* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT64* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,65* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT66* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,67* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY68* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT69* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE70* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.71*72* ------------------------------------------------------------------------73*74* Does a number of crazy assumptions that happen to be true in 99.9999% of75* the 2010 UNIX computers around.76*77* References:78* - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html79* - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html80*81* Todo list:82* - Filter bogus Ctrl+<char> combinations.83* - Win32 support84*85* Bloat:86* - History search like Ctrl+r in readline?87*88* List of escape sequences used by this program, we do everything just89* with three sequences. In order to be so cheap we may have some90* flickering effect with some slow terminal, but the lesser sequences91* the more compatible.92*93* EL (Erase Line)94* Sequence: ESC [ n K95* Effect: if n is 0 or missing, clear from cursor to end of line96* Effect: if n is 1, clear from beginning of line to cursor97* Effect: if n is 2, clear entire line98*99* CUF (CUrsor Forward)100* Sequence: ESC [ n C101* Effect: moves cursor forward n chars102*103* CUB (CUrsor Backward)104* Sequence: ESC [ n D105* Effect: moves cursor backward n chars106*107* The following is used to get the terminal width if getting108* the width with the TIOCGWINSZ ioctl fails109*110* DSR (Device Status Report)111* Sequence: ESC [ 6 n112* Effect: reports the current cusor position as ESC [ n ; m R113* where n is the row and m is the column114*115* When multi line mode is enabled, we also use two additional escape116* sequences. However multi line editing is disabled by default.117*118* CUU (CUrsor Up)119* Sequence: ESC [ n A120* Effect: moves cursor up of n chars.121*122* CUD (CUrsor Down)123* Sequence: ESC [ n B124* Effect: moves cursor down of n chars.125*126* When bc_history_clearScreen() is called, two additional escape sequences127* are used in order to clear the screen and position the cursor at home128* position.129*130* CUP (CUrsor Position)131* Sequence: ESC [ H132* Effect: moves the cursor to upper left corner133*134* ED (Erase Display)135* Sequence: ESC [ 2 J136* Effect: clear the whole screen137*138* *****************************************************************************139*140* Code for line history.141*142*/143144#if BC_ENABLE_HISTORY145146#if BC_ENABLE_EDITLINE147148#include <string.h>149#include <errno.h>150#include <setjmp.h>151152#include <history.h>153#include <vm.h>154155sigjmp_buf bc_history_jmpbuf;156volatile sig_atomic_t bc_history_inlinelib;157158static char* bc_history_prompt;159static char bc_history_no_prompt[] = "";160static HistEvent bc_history_event;161static bool bc_history_use_prompt;162163static char*164bc_history_promptFunc(EditLine* el)165{166BC_UNUSED(el);167return BC_PROMPT && bc_history_use_prompt ? bc_history_prompt :168bc_history_no_prompt;169}170171void172bc_history_init(BcHistory* h)173{174BcVec v;175char* home;176177home = getenv("HOME");178179// This will hold the true path to the editrc.180bc_vec_init(&v, 1, BC_DTOR_NONE);181182// Initialize the path to the editrc. This is done manually because the183// libedit I used to test was failing with a NULL argument for the path,184// which was supposed to automatically do $HOME/.editrc. But it was failing,185// so I set it manually.186if (home == NULL)187{188bc_vec_string(&v, bc_history_editrc_len - 1, bc_history_editrc + 1);189}190else191{192bc_vec_string(&v, strlen(home), home);193bc_vec_concat(&v, bc_history_editrc);194}195196h->hist = history_init();197if (BC_ERR(h->hist == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);198199h->el = el_init(vm->name, stdin, stdout, stderr);200if (BC_ERR(h->el == NULL)) bc_vm_fatalError(BC_ERR_FATAL_ALLOC_ERR);201el_set(h->el, EL_SIGNAL, 1);202203// I want history and a prompt.204history(h->hist, &bc_history_event, H_SETSIZE, 100);205history(h->hist, &bc_history_event, H_SETUNIQUE, 1);206el_set(h->el, EL_EDITOR, "emacs");207el_set(h->el, EL_HIST, history, h->hist);208el_set(h->el, EL_PROMPT, bc_history_promptFunc);209210// I also want to get the user's .editrc.211el_source(h->el, v.v);212213bc_vec_free(&v);214215h->badTerm = false;216bc_history_prompt = NULL;217}218219void220bc_history_free(BcHistory* h)221{222if (BC_PROMPT && bc_history_prompt != NULL) free(bc_history_prompt);223el_end(h->el);224history_end(h->hist);225}226227BcStatus228bc_history_line(BcHistory* h, BcVec* vec, const char* prompt)229{230BcStatus s = BC_STATUS_SUCCESS;231const char* line;232int len;233234BC_SIG_LOCK;235236// If the jump happens here, then a SIGINT occurred.237if (sigsetjmp(bc_history_jmpbuf, 0))238{239bc_vec_string(vec, 1, "\n");240goto end;241}242243// This is so the signal handler can handle line libraries properly.244bc_history_inlinelib = 1;245246if (BC_PROMPT)247{248// Make sure to set the prompt.249if (bc_history_prompt != NULL)250{251if (strcmp(bc_history_prompt, prompt))252{253free(bc_history_prompt);254bc_history_prompt = bc_vm_strdup(prompt);255}256}257else bc_history_prompt = bc_vm_strdup(prompt);258}259260bc_history_use_prompt = true;261262line = NULL;263len = -1;264errno = EINTR;265266// Get the line.267while (line == NULL && len == -1 && errno == EINTR)268{269line = el_gets(h->el, &len);270bc_history_use_prompt = false;271}272273// If there is no line...274if (BC_ERR(line == NULL))275{276// If this is true, there was an error. Otherwise, it's just EOF.277if (len == -1)278{279if (errno == ENOMEM) bc_err(BC_ERR_FATAL_ALLOC_ERR);280bc_err(BC_ERR_FATAL_IO_ERR);281}282else283{284bc_file_printf(&vm->fout, "\n");285s = BC_STATUS_EOF;286}287}288// If there is a line...289else290{291bc_vec_string(vec, strlen(line), line);292293if (strcmp(line, "") && strcmp(line, "\n"))294{295history(h->hist, &bc_history_event, H_ENTER, line);296}297298s = BC_STATUS_SUCCESS;299}300301end:302303bc_history_inlinelib = 0;304305BC_SIG_UNLOCK;306307return s;308}309310#else // BC_ENABLE_EDITLINE311312#if BC_ENABLE_READLINE313314#include <assert.h>315#include <setjmp.h>316#include <string.h>317318#include <history.h>319#include <vm.h>320321sigjmp_buf bc_history_jmpbuf;322volatile sig_atomic_t bc_history_inlinelib;323324void325bc_history_init(BcHistory* h)326{327h->line = NULL;328h->badTerm = false;329330// I want no tab completion.331rl_bind_key('\t', rl_insert);332}333334void335bc_history_free(BcHistory* h)336{337if (h->line != NULL) free(h->line);338}339340BcStatus341bc_history_line(BcHistory* h, BcVec* vec, const char* prompt)342{343BcStatus s = BC_STATUS_SUCCESS;344size_t len;345346BC_SIG_LOCK;347348// If the jump happens here, then a SIGINT occurred.349if (sigsetjmp(bc_history_jmpbuf, 0))350{351bc_vec_string(vec, 1, "\n");352goto end;353}354355// This is so the signal handler can handle line libraries properly.356bc_history_inlinelib = 1;357358// Get rid of the last line.359if (h->line != NULL)360{361free(h->line);362h->line = NULL;363}364365// Get the line.366h->line = readline(BC_PROMPT ? prompt : "");367368// If there was a line, add it to the history. Otherwise, just return an369// empty line. Oh, and NULL actually means EOF.370if (h->line != NULL && h->line[0])371{372add_history(h->line);373374len = strlen(h->line);375376bc_vec_expand(vec, len + 2);377378bc_vec_string(vec, len, h->line);379bc_vec_concat(vec, "\n");380}381else if (h->line == NULL)382{383bc_file_printf(&vm->fout, "%s\n", "^D");384s = BC_STATUS_EOF;385}386else bc_vec_string(vec, 1, "\n");387388end:389390bc_history_inlinelib = 0;391392BC_SIG_UNLOCK;393394return s;395}396397#else // BC_ENABLE_READLINE398399#include <assert.h>400#include <stdlib.h>401#include <errno.h>402#include <string.h>403#include <ctype.h>404405#include <signal.h>406#include <sys/stat.h>407#include <sys/types.h>408409#ifndef _WIN32410#include <strings.h>411#include <termios.h>412#include <unistd.h>413#include <sys/ioctl.h>414#include <sys/select.h>415#endif // _WIN32416417#include <status.h>418#include <vector.h>419#include <history.h>420#include <read.h>421#include <file.h>422#include <vm.h>423424#if BC_DEBUG_CODE425426/// A file for outputting to when debugging.427BcFile bc_history_debug_fp;428429/// A buffer for the above file.430char* bc_history_debug_buf;431432#endif // BC_DEBUG_CODE433434/**435* Checks if the code is a wide character.436* @param cp The codepoint to check.437* @return True if @a cp is a wide character, false otherwise.438*/439static bool440bc_history_wchar(uint32_t cp)441{442size_t i;443444for (i = 0; i < bc_history_wchars_len; ++i)445{446// Ranges are listed in ascending order. Therefore, once the447// whole range is higher than the codepoint we're testing, the448// codepoint won't be found in any remaining range => bail early.449if (bc_history_wchars[i][0] > cp) return false;450451// Test this range.452if (bc_history_wchars[i][0] <= cp && cp <= bc_history_wchars[i][1])453{454return true;455}456}457458return false;459}460461/**462* Checks if the code is a combining character.463* @param cp The codepoint to check.464* @return True if @a cp is a combining character, false otherwise.465*/466static bool467bc_history_comboChar(uint32_t cp)468{469size_t i;470471for (i = 0; i < bc_history_combo_chars_len; ++i)472{473// Combining chars are listed in ascending order, so once we pass474// the codepoint of interest, we know it's not a combining char.475if (bc_history_combo_chars[i] > cp) return false;476if (bc_history_combo_chars[i] == cp) return true;477}478479return false;480}481482/**483* Gets the length of previous UTF8 character.484* @param buf The buffer of characters.485* @param pos The index into the buffer.486*/487static size_t488bc_history_prevCharLen(const char* buf, size_t pos)489{490size_t end = pos;491for (pos -= 1; pos < end && (buf[pos] & 0xC0) == 0x80; --pos)492{493continue;494}495return end - (pos >= end ? 0 : pos);496}497498/**499* Converts UTF-8 to a Unicode code point.500* @param s The string.501* @param len The length of the string.502* @param cp An out parameter for the codepoint.503* @return The number of bytes eaten by the codepoint.504*/505static size_t506bc_history_codePoint(const char* s, size_t len, uint32_t* cp)507{508if (len)509{510uchar byte = (uchar) s[0];511512// This is literally the UTF-8 decoding algorithm. Look that up if you513// don't understand this.514515if ((byte & 0x80) == 0)516{517*cp = byte;518return 1;519}520else if ((byte & 0xE0) == 0xC0)521{522if (len >= 2)523{524*cp = (((uint32_t) (s[0] & 0x1F)) << 6) |525((uint32_t) (s[1] & 0x3F));526return 2;527}528}529else if ((byte & 0xF0) == 0xE0)530{531if (len >= 3)532{533*cp = (((uint32_t) (s[0] & 0x0F)) << 12) |534(((uint32_t) (s[1] & 0x3F)) << 6) |535((uint32_t) (s[2] & 0x3F));536return 3;537}538}539else if ((byte & 0xF8) == 0xF0)540{541if (len >= 4)542{543*cp = (((uint32_t) (s[0] & 0x07)) << 18) |544(((uint32_t) (s[1] & 0x3F)) << 12) |545(((uint32_t) (s[2] & 0x3F)) << 6) |546((uint32_t) (s[3] & 0x3F));547return 4;548}549}550else551{552*cp = 0xFFFD;553return 1;554}555}556557*cp = 0;558559return 1;560}561562/**563* Gets the length of next grapheme.564* @param buf The buffer.565* @param buf_len The length of the buffer.566* @param pos The index into the buffer.567* @param col_len An out parameter for the length of the grapheme on screen.568* @return The number of bytes in the grapheme.569*/570static size_t571bc_history_nextLen(const char* buf, size_t buf_len, size_t pos, size_t* col_len)572{573uint32_t cp;574size_t beg = pos;575size_t len = bc_history_codePoint(buf + pos, buf_len - pos, &cp);576577if (bc_history_comboChar(cp))578{579BC_UNREACHABLE580581#if !BC_CLANG582if (col_len != NULL) *col_len = 0;583584return 0;585#endif // !BC_CLANG586}587588// Store the width of the character on screen.589if (col_len != NULL) *col_len = bc_history_wchar(cp) ? 2 : 1;590591pos += len;592593// Find the first non-combining character.594while (pos < buf_len)595{596len = bc_history_codePoint(buf + pos, buf_len - pos, &cp);597598if (!bc_history_comboChar(cp)) return pos - beg;599600pos += len;601}602603return pos - beg;604}605606/**607* Gets the length of previous grapheme.608* @param buf The buffer.609* @param pos The index into the buffer.610* @return The number of bytes in the grapheme.611*/612static size_t613bc_history_prevLen(const char* buf, size_t pos)614{615size_t end = pos;616617// Find the first non-combining character.618while (pos > 0)619{620uint32_t cp;621size_t len = bc_history_prevCharLen(buf, pos);622623pos -= len;624bc_history_codePoint(buf + pos, len, &cp);625626// The original linenoise-mob had an extra parameter col_len, like627// bc_history_nextLen(), which, if not NULL, was set in this if628// statement. However, we always passed NULL, so just skip that.629if (!bc_history_comboChar(cp)) return end - pos;630}631632BC_UNREACHABLE633634#if !BC_CLANG635return 0;636#endif // BC_CLANG637}638639/**640* Reads @a n characters from stdin.641* @param buf The buffer to read into. The caller is responsible for making642* sure this is big enough for @a n.643* @param n The number of characters to read.644* @return The number of characters read or less than 0 on error.645*/646static ssize_t647bc_history_read(char* buf, size_t n)648{649ssize_t ret;650651BC_SIG_ASSERT_LOCKED;652653#ifndef _WIN32654655do656{657// We don't care about being interrupted.658ret = read(STDIN_FILENO, buf, n);659}660while (ret == EINTR);661662#else // _WIN32663664bool good;665DWORD read;666HANDLE hn = GetStdHandle(STD_INPUT_HANDLE);667668good = ReadConsole(hn, buf, (DWORD) n, &read, NULL);669670ret = (read != n || !good) ? -1 : 1;671672#endif // _WIN32673674return ret;675}676677/**678* Reads a Unicode code point into a buffer.679* @param buf The buffer to read into.680* @param buf_len The length of the buffer.681* @param cp An out parameter for the codepoint.682* @param nread An out parameter for the number of bytes read.683* @return BC_STATUS_EOF or BC_STATUS_SUCCESS.684*/685static BcStatus686bc_history_readCode(char* buf, size_t buf_len, uint32_t* cp, size_t* nread)687{688ssize_t n;689uchar byte;690691assert(buf_len >= 1);692693BC_SIG_LOCK;694695// Read a byte.696n = bc_history_read(buf, 1);697698BC_SIG_UNLOCK;699700if (BC_ERR(n <= 0)) goto err;701702// Get the byte.703byte = ((uchar*) buf)[0];704705// Once again, this is the UTF-8 decoding algorithm, but it has reads706// instead of actual decoding.707if ((byte & 0x80) != 0)708{709if ((byte & 0xE0) == 0xC0)710{711assert(buf_len >= 2);712713BC_SIG_LOCK;714715n = bc_history_read(buf + 1, 1);716717BC_SIG_UNLOCK;718719if (BC_ERR(n <= 0)) goto err;720}721else if ((byte & 0xF0) == 0xE0)722{723assert(buf_len >= 3);724725BC_SIG_LOCK;726727n = bc_history_read(buf + 1, 2);728729BC_SIG_UNLOCK;730731if (BC_ERR(n <= 0)) goto err;732}733else if ((byte & 0xF8) == 0xF0)734{735assert(buf_len >= 3);736737BC_SIG_LOCK;738739n = bc_history_read(buf + 1, 3);740741BC_SIG_UNLOCK;742743if (BC_ERR(n <= 0)) goto err;744}745else746{747n = -1;748goto err;749}750}751752// Convert to the codepoint.753*nread = bc_history_codePoint(buf, buf_len, cp);754755return BC_STATUS_SUCCESS;756757err:758// If we get here, we either had a fatal error of EOF.759if (BC_ERR(n < 0)) bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);760else *nread = (size_t) n;761return BC_STATUS_EOF;762}763764/**765* Gets the column length from beginning of buffer to current byte position.766* @param buf The buffer.767* @param buf_len The length of the buffer.768* @param pos The index into the buffer.769* @return The number of columns between the beginning of @a buffer to770* @a pos.771*/772static size_t773bc_history_colPos(const char* buf, size_t buf_len, size_t pos)774{775size_t ret = 0, off = 0;776777// While we haven't reached the offset, get the length of the next grapheme.778while (off < pos && off < buf_len)779{780size_t col_len, len;781782len = bc_history_nextLen(buf, buf_len, off, &col_len);783784off += len;785ret += col_len;786}787788return ret;789}790791/**792* Returns true if the terminal name is in the list of terminals we know are793* not able to understand basic escape sequences.794* @return True if the terminal is a bad terminal.795*/796static inline bool797bc_history_isBadTerm(void)798{799size_t i;800bool ret = false;801char* term = bc_vm_getenv("TERM");802803if (term == NULL) return false;804805for (i = 0; !ret && bc_history_bad_terms[i]; ++i)806{807ret = (!strcasecmp(term, bc_history_bad_terms[i]));808}809810bc_vm_getenvFree(term);811812return ret;813}814815/**816* Enables raw mode (1960's black magic).817* @param h The history data.818*/819static void820bc_history_enableRaw(BcHistory* h)821{822// I don't do anything for Windows because in Windows, you set their823// equivalent of raw mode and leave it, so I do it in bc_history_init().824825#ifndef _WIN32826struct termios raw;827int err;828829assert(BC_TTYIN);830831if (h->rawMode) return;832833BC_SIG_LOCK;834835if (BC_ERR(tcgetattr(STDIN_FILENO, &h->orig_termios) == -1))836{837bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);838}839840BC_SIG_UNLOCK;841842// Modify the original mode.843raw = h->orig_termios;844845// Input modes: no break, no CR to NL, no parity check, no strip char,846// no start/stop output control.847raw.c_iflag &= (unsigned int) (~(BRKINT | ICRNL | INPCK | ISTRIP | IXON));848849// Control modes: set 8 bit chars.850raw.c_cflag |= (CS8);851852// Local modes - choing off, canonical off, no extended functions,853// no signal chars (^Z,^C).854raw.c_lflag &= (unsigned int) (~(ECHO | ICANON | IEXTEN | ISIG));855856// Control chars - set return condition: min number of bytes and timer.857// We want read to give every single byte, w/o timeout (1 byte, no timer).858raw.c_cc[VMIN] = 1;859raw.c_cc[VTIME] = 0;860861BC_SIG_LOCK;862863// Put terminal in raw mode after flushing.864do865{866err = tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw);867}868while (BC_ERR(err < 0) && errno == EINTR);869870BC_SIG_UNLOCK;871872if (BC_ERR(err < 0)) bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);873#endif // _WIN32874875h->rawMode = true;876}877878/**879* Disables raw mode.880* @param h The history data.881*/882static void883bc_history_disableRaw(BcHistory* h)884{885sig_atomic_t lock;886887if (!h->rawMode) return;888889BC_SIG_TRYLOCK(lock);890891#ifndef _WIN32892if (BC_ERR(tcsetattr(STDIN_FILENO, TCSAFLUSH, &h->orig_termios) != -1))893{894h->rawMode = false;895}896#endif // _WIN32897898BC_SIG_TRYUNLOCK(lock);899}900901/**902* Uses the ESC [6n escape sequence to query the horizontal cursor position903* and return it. On error -1 is returned, on success the position of the904* cursor.905* @return The horizontal cursor position.906*/907static size_t908bc_history_cursorPos(void)909{910char buf[BC_HIST_SEQ_SIZE];911char* ptr;912char* ptr2;913size_t cols, rows, i;914915BC_SIG_ASSERT_LOCKED;916917// Report cursor location.918bc_file_write(&vm->fout, bc_flush_none, "\x1b[6n", 4);919bc_file_flush(&vm->fout, bc_flush_none);920921// Read the response: ESC [ rows ; cols R.922for (i = 0; i < sizeof(buf) - 1; ++i)923{924if (bc_history_read(buf + i, 1) != 1 || buf[i] == 'R') break;925}926927buf[i] = '\0';928929// This is basically an error; we didn't get what we were expecting.930if (BC_ERR(buf[0] != BC_ACTION_ESC || buf[1] != '[')) return SIZE_MAX;931932// Parse the rows.933ptr = buf + 2;934rows = strtoul(ptr, &ptr2, 10);935936// Here we also didn't get what we were expecting.937if (BC_ERR(!rows || ptr2[0] != ';')) return SIZE_MAX;938939// Parse the columns.940ptr = ptr2 + 1;941cols = strtoul(ptr, NULL, 10);942943if (BC_ERR(!cols)) return SIZE_MAX;944945return cols <= UINT16_MAX ? cols : 0;946}947948/**949* Tries to get the number of columns in the current terminal, or assume 80950* if it fails.951* @return The number of columns in the terminal.952*/953static size_t954bc_history_columns(void)955{956957#ifndef _WIN32958959struct winsize ws;960int ret;961962ret = ioctl(vm->fout.fd, TIOCGWINSZ, &ws);963964if (BC_ERR(ret == -1 || !ws.ws_col))965{966// Calling ioctl() failed. Try to query the terminal itself.967size_t start, cols;968969// Get the initial position so we can restore it later.970start = bc_history_cursorPos();971if (BC_ERR(start == SIZE_MAX)) return BC_HIST_DEF_COLS;972973// Go to right margin and get position.974bc_file_write(&vm->fout, bc_flush_none, "\x1b[999C", 6);975bc_file_flush(&vm->fout, bc_flush_none);976cols = bc_history_cursorPos();977if (BC_ERR(cols == SIZE_MAX)) return BC_HIST_DEF_COLS;978979// Restore position.980if (cols > start)981{982bc_file_printf(&vm->fout, "\x1b[%zuD", cols - start);983bc_file_flush(&vm->fout, bc_flush_none);984}985986return cols;987}988989return ws.ws_col;990991#else // _WIN32992993CONSOLE_SCREEN_BUFFER_INFO csbi;994995if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))996{997return 80;998}9991000return ((size_t) (csbi.srWindow.Right)) - csbi.srWindow.Left + 1;10011002#endif // _WIN321003}10041005/**1006* Gets the column length of prompt text. This is probably unnecessary because1007* the prompts that I use are ASCII, but I kept it just in case.1008* @param prompt The prompt.1009* @param plen The length of the prompt.1010* @return The column length of the prompt.1011*/1012static size_t1013bc_history_promptColLen(const char* prompt, size_t plen)1014{1015char buf[BC_HIST_MAX_LINE + 1];1016size_t buf_len = 0, off = 0;10171018// The original linenoise-mob checked for ANSI escapes here on the prompt. I1019// know the prompts do not have ANSI escapes. I deleted the code.1020while (off < plen)1021{1022buf[buf_len++] = prompt[off++];1023}10241025return bc_history_colPos(buf, buf_len, buf_len);1026}10271028/**1029* Rewrites the currently edited line accordingly to the buffer content,1030* cursor position, and number of columns of the terminal.1031* @param h The history data.1032*/1033static void1034bc_history_refresh(BcHistory* h)1035{1036char* buf = h->buf.v;1037size_t colpos, len = BC_HIST_BUF_LEN(h), pos = h->pos, extras_len = 0;10381039BC_SIG_ASSERT_LOCKED;10401041bc_file_flush(&vm->fout, bc_flush_none);10421043// Get to the prompt column position from the left.1044while (h->pcol + bc_history_colPos(buf, len, pos) >= h->cols)1045{1046size_t chlen = bc_history_nextLen(buf, len, 0, NULL);10471048buf += chlen;1049len -= chlen;1050pos -= chlen;1051}10521053// Get to the prompt column position from the right.1054while (h->pcol + bc_history_colPos(buf, len, len) > h->cols)1055{1056len -= bc_history_prevLen(buf, len);1057}10581059// Cursor to left edge.1060bc_file_write(&vm->fout, bc_flush_none, "\r", 1);10611062// Take the extra stuff into account. This is where history makes sure to1063// preserve stuff that was printed without a newline.1064if (h->extras.len > 1)1065{1066extras_len = h->extras.len - 1;10671068bc_vec_grow(&h->buf, extras_len);10691070len += extras_len;1071pos += extras_len;10721073bc_file_write(&vm->fout, bc_flush_none, h->extras.v, extras_len);1074}10751076// Write the prompt, if desired.1077if (BC_PROMPT) bc_file_write(&vm->fout, bc_flush_none, h->prompt, h->plen);10781079bc_file_write(&vm->fout, bc_flush_none, h->buf.v, len - extras_len);10801081// Erase to right.1082bc_file_write(&vm->fout, bc_flush_none, "\x1b[0K", 4);10831084// We need to be sure to grow this.1085if (pos >= h->buf.len - extras_len) bc_vec_grow(&h->buf, pos + extras_len);10861087// Move cursor to original position. Do NOT move the putchar of '\r' to the1088// printf with colpos. That causes a bug where the cursor will go to the end1089// of the line when there is no prompt.1090bc_file_putchar(&vm->fout, bc_flush_none, '\r');1091colpos = bc_history_colPos(h->buf.v, len - extras_len, pos) + h->pcol;10921093// Set the cursor position again.1094if (colpos) bc_file_printf(&vm->fout, "\x1b[%zuC", colpos);10951096bc_file_flush(&vm->fout, bc_flush_none);1097}10981099/**1100* Inserts the character(s) 'c' at cursor current position.1101* @param h The history data.1102* @param cbuf The character buffer to copy from.1103* @param clen The number of characters to copy.1104*/1105static void1106bc_history_edit_insert(BcHistory* h, const char* cbuf, size_t clen)1107{1108BC_SIG_ASSERT_LOCKED;11091110bc_vec_grow(&h->buf, clen);11111112// If we are at the end of the line...1113if (h->pos == BC_HIST_BUF_LEN(h))1114{1115size_t colpos = 0, len;11161117// Copy into the buffer.1118memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen);11191120// Adjust the buffer.1121h->pos += clen;1122h->buf.len += clen - 1;1123bc_vec_pushByte(&h->buf, '\0');11241125// Set the length and column position.1126len = BC_HIST_BUF_LEN(h) + h->extras.len - 1;1127colpos = bc_history_promptColLen(h->prompt, h->plen);1128colpos += bc_history_colPos(h->buf.v, len, len);11291130// Do we have the trivial case?1131if (colpos < h->cols)1132{1133// Avoid a full update of the line in the trivial case.1134bc_file_write(&vm->fout, bc_flush_none, cbuf, clen);1135bc_file_flush(&vm->fout, bc_flush_none);1136}1137else bc_history_refresh(h);1138}1139else1140{1141// Amount that we need to move.1142size_t amt = BC_HIST_BUF_LEN(h) - h->pos;11431144// Move the stuff.1145memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt);1146memcpy(h->buf.v + h->pos, cbuf, clen);11471148// Adjust the buffer.1149h->pos += clen;1150h->buf.len += clen;1151h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';11521153bc_history_refresh(h);1154}1155}11561157/**1158* Moves the cursor to the left.1159* @param h The history data.1160*/1161static void1162bc_history_edit_left(BcHistory* h)1163{1164BC_SIG_ASSERT_LOCKED;11651166// Stop at the left end.1167if (h->pos <= 0) return;11681169h->pos -= bc_history_prevLen(h->buf.v, h->pos);11701171bc_history_refresh(h);1172}11731174/**1175* Moves the cursor to the right.1176* @param h The history data.1177*/1178static void1179bc_history_edit_right(BcHistory* h)1180{1181BC_SIG_ASSERT_LOCKED;11821183// Stop at the right end.1184if (h->pos == BC_HIST_BUF_LEN(h)) return;11851186h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL);11871188bc_history_refresh(h);1189}11901191/**1192* Moves the cursor to the end of the current word.1193* @param h The history data.1194*/1195static void1196bc_history_edit_wordEnd(BcHistory* h)1197{1198size_t len = BC_HIST_BUF_LEN(h);11991200BC_SIG_ASSERT_LOCKED;12011202// Don't overflow.1203if (!len || h->pos >= len) return;12041205// Find the word, then find the end of it.1206while (h->pos < len && isspace(h->buf.v[h->pos]))1207{1208h->pos += 1;1209}1210while (h->pos < len && !isspace(h->buf.v[h->pos]))1211{1212h->pos += 1;1213}12141215bc_history_refresh(h);1216}12171218/**1219* Moves the cursor to the start of the current word.1220* @param h The history data.1221*/1222static void1223bc_history_edit_wordStart(BcHistory* h)1224{1225size_t len = BC_HIST_BUF_LEN(h);12261227BC_SIG_ASSERT_LOCKED;12281229// Stop with no data.1230if (!len) return;12311232// Find the word, the find the beginning of the word.1233while (h->pos > 0 && isspace(h->buf.v[h->pos - 1]))1234{1235h->pos -= 1;1236}1237while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1]))1238{1239h->pos -= 1;1240}12411242bc_history_refresh(h);1243}12441245/**1246* Moves the cursor to the start of the line.1247* @param h The history data.1248*/1249static void1250bc_history_edit_home(BcHistory* h)1251{1252BC_SIG_ASSERT_LOCKED;12531254// Stop at the beginning.1255if (!h->pos) return;12561257h->pos = 0;12581259bc_history_refresh(h);1260}12611262/**1263* Moves the cursor to the end of the line.1264* @param h The history data.1265*/1266static void1267bc_history_edit_end(BcHistory* h)1268{1269BC_SIG_ASSERT_LOCKED;12701271// Stop at the end of the line.1272if (h->pos == BC_HIST_BUF_LEN(h)) return;12731274h->pos = BC_HIST_BUF_LEN(h);12751276bc_history_refresh(h);1277}12781279/**1280* Substitutes the currently edited line with the next or previous history1281* entry as specified by 'dir' (direction).1282* @param h The history data.1283* @param dir The direction to substitute; true means previous, false next.1284*/1285static void1286bc_history_edit_next(BcHistory* h, bool dir)1287{1288const char* dup;1289const char* str;12901291BC_SIG_ASSERT_LOCKED;12921293// Stop if there is no history.1294if (h->history.len <= 1) return;12951296// Duplicate the buffer.1297if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v);1298else dup = "";12991300// Update the current history entry before overwriting it with the next one.1301bc_vec_replaceAt(&h->history, h->history.len - 1 - h->idx, &dup);13021303// Show the new entry.1304h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX);13051306// Se the index appropriately at the ends.1307if (h->idx == SIZE_MAX)1308{1309h->idx = 0;1310return;1311}1312else if (h->idx >= h->history.len)1313{1314h->idx = h->history.len - 1;1315return;1316}13171318// Get the string.1319str = *((char**) bc_vec_item(&h->history, h->history.len - 1 - h->idx));1320bc_vec_string(&h->buf, strlen(str), str);13211322assert(h->buf.len > 0);13231324// Set the position at the end.1325h->pos = BC_HIST_BUF_LEN(h);13261327bc_history_refresh(h);1328}13291330/**1331* Deletes the character at the right of the cursor without altering the cursor1332* position. Basically, this is what happens with the "Delete" keyboard key.1333* @param h The history data.1334*/1335static void1336bc_history_edit_delete(BcHistory* h)1337{1338size_t chlen, len = BC_HIST_BUF_LEN(h);13391340BC_SIG_ASSERT_LOCKED;13411342// If there is no character, skip.1343if (!len || h->pos >= len) return;13441345// Get the length of the character.1346chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL);13471348// Move characters after it into its place.1349memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen);13501351// Make the buffer valid again.1352h->buf.len -= chlen;1353h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';13541355bc_history_refresh(h);1356}13571358/**1359* Deletes the character to the left of the cursor and moves the cursor back one1360* space. Basically, this is what happens with the "Backspace" keyboard key.1361* @param h The history data.1362*/1363static void1364bc_history_edit_backspace(BcHistory* h)1365{1366size_t chlen, len = BC_HIST_BUF_LEN(h);13671368BC_SIG_ASSERT_LOCKED;13691370// If there are no characters, skip.1371if (!h->pos || !len) return;13721373// Get the length of the previous character.1374chlen = bc_history_prevLen(h->buf.v, h->pos);13751376// Move everything back one.1377memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos);13781379// Make the buffer valid again.1380h->pos -= chlen;1381h->buf.len -= chlen;1382h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';13831384bc_history_refresh(h);1385}13861387/**1388* Deletes the previous word, maintaining the cursor at the start of the1389* current word.1390* @param h The history data.1391*/1392static void1393bc_history_edit_deletePrevWord(BcHistory* h)1394{1395size_t diff, old_pos = h->pos;13961397BC_SIG_ASSERT_LOCKED;13981399// If at the beginning of the line, skip.1400if (!old_pos) return;14011402// Find the word, then the beginning of the word.1403while (h->pos > 0 && isspace(h->buf.v[h->pos - 1]))1404{1405h->pos -= 1;1406}1407while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1]))1408{1409h->pos -= 1;1410}14111412// Get the difference in position.1413diff = old_pos - h->pos;14141415// Move the data back.1416memmove(h->buf.v + h->pos, h->buf.v + old_pos,1417BC_HIST_BUF_LEN(h) - old_pos + 1);14181419// Make the buffer valid again.1420h->buf.len -= diff;14211422bc_history_refresh(h);1423}14241425/**1426* Deletes the next word, maintaining the cursor at the same position.1427* @param h The history data.1428*/1429static void1430bc_history_edit_deleteNextWord(BcHistory* h)1431{1432size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h);14331434BC_SIG_ASSERT_LOCKED;14351436// If at the end of the line, skip.1437if (next_end == len) return;14381439// Find the word, then the end of the word.1440while (next_end < len && isspace(h->buf.v[next_end]))1441{1442next_end += 1;1443}1444while (next_end < len && !isspace(h->buf.v[next_end]))1445{1446next_end += 1;1447}14481449// Move the stuff into position.1450memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end);14511452// Make the buffer valid again.1453h->buf.len -= next_end - h->pos;14541455bc_history_refresh(h);1456}14571458/**1459* Swaps two characters, the one under the cursor and the one to the left.1460* @param h The history data.1461*/1462static void1463bc_history_swap(BcHistory* h)1464{1465size_t pcl, ncl;1466char auxb[5];14671468BC_SIG_ASSERT_LOCKED;14691470// If there are no characters, skip.1471if (!h->pos) return;14721473// Get the length of the previous and next characters.1474pcl = bc_history_prevLen(h->buf.v, h->pos);1475ncl = bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL);14761477// To perform a swap we need:1478// * Nonzero char length to the left.1479// * To not be at the end of the line.1480if (pcl && h->pos != BC_HIST_BUF_LEN(h) && pcl < 5 && ncl < 5)1481{1482// Swap.1483memcpy(auxb, h->buf.v + h->pos - pcl, pcl);1484memcpy(h->buf.v + h->pos - pcl, h->buf.v + h->pos, ncl);1485memcpy(h->buf.v + h->pos - pcl + ncl, auxb, pcl);14861487// Reset the position.1488h->pos += ((~pcl) + 1) + ncl;14891490bc_history_refresh(h);1491}1492}14931494/**1495* Raises the specified signal. This is a convenience function.1496* @param h The history data.1497* @param sig The signal to raise.1498*/1499static void1500bc_history_raise(BcHistory* h, int sig)1501{1502// We really don't want to be in raw mode when longjmp()'s are flying.1503bc_history_disableRaw(h);1504raise(sig);1505}15061507/**1508* Handles escape sequences. This function will make sense if you know VT1001509* escape codes; otherwise, it will be confusing.1510* @param h The history data.1511*/1512static void1513bc_history_escape(BcHistory* h)1514{1515char c, seq[3];15161517BC_SIG_ASSERT_LOCKED;15181519// Read a character into seq.1520if (BC_ERR(BC_HIST_READ(seq, 1))) return;15211522c = seq[0];15231524// ESC ? sequences.1525if (c != '[' && c != 'O')1526{1527if (c == 'f') bc_history_edit_wordEnd(h);1528else if (c == 'b') bc_history_edit_wordStart(h);1529else if (c == 'd') bc_history_edit_deleteNextWord(h);1530}1531else1532{1533// Read a character into seq.1534if (BC_ERR(BC_HIST_READ(seq + 1, 1)))1535{1536bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);1537}15381539// ESC [ sequences.1540if (c == '[')1541{1542c = seq[1];15431544if (c >= '0' && c <= '9')1545{1546// Extended escape, read additional byte.1547if (BC_ERR(BC_HIST_READ(seq + 2, 1)))1548{1549bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);1550}15511552if (seq[2] == '~')1553{1554switch (c)1555{1556case '1':1557{1558bc_history_edit_home(h);1559break;1560}15611562case '3':1563{1564bc_history_edit_delete(h);1565break;1566}15671568case '4':1569{1570bc_history_edit_end(h);1571break;1572}15731574default:1575{1576break;1577}1578}1579}1580else if (seq[2] == ';')1581{1582// Read two characters into seq.1583if (BC_ERR(BC_HIST_READ(seq, 2)))1584{1585bc_vm_fatalError(BC_ERR_FATAL_IO_ERR);1586}15871588if (seq[0] != '5') return;1589else if (seq[1] == 'C') bc_history_edit_wordEnd(h);1590else if (seq[1] == 'D') bc_history_edit_wordStart(h);1591}1592}1593else1594{1595switch (c)1596{1597// Up.1598case 'A':1599{1600bc_history_edit_next(h, BC_HIST_PREV);1601break;1602}16031604// Down.1605case 'B':1606{1607bc_history_edit_next(h, BC_HIST_NEXT);1608break;1609}16101611// Right.1612case 'C':1613{1614bc_history_edit_right(h);1615break;1616}16171618// Left.1619case 'D':1620{1621bc_history_edit_left(h);1622break;1623}16241625// Home.1626case 'H':1627case '1':1628{1629bc_history_edit_home(h);1630break;1631}16321633// End.1634case 'F':1635case '4':1636{1637bc_history_edit_end(h);1638break;1639}16401641case 'd':1642{1643bc_history_edit_deleteNextWord(h);1644break;1645}1646}1647}1648}1649// ESC O sequences.1650else1651{1652switch (seq[1])1653{1654case 'A':1655{1656bc_history_edit_next(h, BC_HIST_PREV);1657break;1658}16591660case 'B':1661{1662bc_history_edit_next(h, BC_HIST_NEXT);1663break;1664}16651666case 'C':1667{1668bc_history_edit_right(h);1669break;1670}16711672case 'D':1673{1674bc_history_edit_left(h);1675break;1676}16771678case 'F':1679{1680bc_history_edit_end(h);1681break;1682}16831684case 'H':1685{1686bc_history_edit_home(h);1687break;1688}1689}1690}1691}1692}16931694/**1695* Adds a line to the history.1696* @param h The history data.1697* @param line The line to add.1698*/1699static void1700bc_history_add(BcHistory* h, char* line)1701{1702BC_SIG_ASSERT_LOCKED;17031704// If there is something already there...1705if (h->history.len)1706{1707// Get the previous.1708char* s = *((char**) bc_vec_item_rev(&h->history, 0));17091710// Check for, and discard, duplicates.1711if (!strcmp(s, line))1712{1713free(line);1714return;1715}1716}17171718bc_vec_push(&h->history, &line);1719}17201721/**1722* Adds an empty line to the history. This is separate from bc_history_add()1723* because we don't want it allocating.1724* @param h The history data.1725*/1726static void1727bc_history_add_empty(BcHistory* h)1728{1729const char* line = "";17301731BC_SIG_ASSERT_LOCKED;17321733// If there is something already there...1734if (h->history.len)1735{1736// Get the previous.1737char* s = *((char**) bc_vec_item_rev(&h->history, 0));17381739// Check for, and discard, duplicates.1740if (!s[0]) return;1741}17421743bc_vec_push(&h->history, &line);1744}17451746/**1747* Resets the history state to nothing.1748* @param h The history data.1749*/1750static void1751bc_history_reset(BcHistory* h)1752{1753BC_SIG_ASSERT_LOCKED;17541755h->oldcolpos = h->pos = h->idx = 0;1756h->cols = bc_history_columns();17571758// The latest history entry is always our current buffer, that1759// initially is just an empty string.1760bc_history_add_empty(h);17611762// Buffer starts empty.1763bc_vec_empty(&h->buf);1764}17651766/**1767* Prints a control character.1768* @param h The history data.1769* @param c The control character to print.1770*/1771static void1772bc_history_printCtrl(BcHistory* h, unsigned int c)1773{1774char str[3] = { '^', 'A', '\0' };1775const char newline[2] = { '\n', '\0' };17761777BC_SIG_ASSERT_LOCKED;17781779// Set the correct character.1780str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A);17811782// Concatenate the string.1783bc_vec_concat(&h->buf, str);17841785h->pos = BC_HIST_BUF_LEN(h);1786bc_history_refresh(h);17871788// Pop the string.1789bc_vec_npop(&h->buf, sizeof(str));1790bc_vec_pushByte(&h->buf, '\0');1791h->pos = 0;17921793if (c != BC_ACTION_CTRL_C && c != BC_ACTION_CTRL_D)1794{1795// We sometimes want to print a newline; for the times we don't; it's1796// because newlines are taken care of elsewhere.1797bc_file_write(&vm->fout, bc_flush_none, newline, sizeof(newline) - 1);1798bc_history_refresh(h);1799}1800}18011802/**1803* Edits a line of history. This function is the core of the line editing1804* capability of bc history. It expects 'fd' to be already in "raw mode" so that1805* every key pressed will be returned ASAP to read().1806* @param h The history data.1807* @param prompt The prompt.1808* @return BC_STATUS_SUCCESS or BC_STATUS_EOF.1809*/1810static BcStatus1811bc_history_edit(BcHistory* h, const char* prompt)1812{1813BC_SIG_LOCK;18141815bc_history_reset(h);18161817// Don't write the saved output the first time. This is because it has1818// already been written to output. In other words, don't uncomment the1819// line below or add anything like it.1820// bc_file_write(&vm->fout, bc_flush_none, h->extras.v, h->extras.len - 1);18211822// Write the prompt if desired.1823if (BC_PROMPT)1824{1825h->prompt = prompt;1826h->plen = strlen(prompt);1827h->pcol = bc_history_promptColLen(prompt, h->plen);18281829bc_file_write(&vm->fout, bc_flush_none, prompt, h->plen);1830bc_file_flush(&vm->fout, bc_flush_none);1831}18321833// This is the input loop.1834for (;;)1835{1836BcStatus s;1837char cbuf[32];1838unsigned int c = 0;1839size_t nread = 0;18401841BC_SIG_UNLOCK;18421843// Read a code.1844s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread);1845if (BC_ERR(s)) return s;18461847BC_SIG_LOCK;18481849switch (c)1850{1851case BC_ACTION_LINE_FEED:1852case BC_ACTION_ENTER:1853{1854// Return the line.1855bc_vec_pop(&h->history);1856BC_SIG_UNLOCK;1857return s;1858}18591860case BC_ACTION_TAB:1861{1862// My tab handling is dumb; it just prints 8 spaces every time.1863memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1);1864bc_history_edit_insert(h, cbuf, bc_history_tab_len);1865break;1866}18671868case BC_ACTION_CTRL_C:1869{1870bc_history_printCtrl(h, c);18711872// Quit if the user wants it.1873if (!BC_SIGINT)1874{1875vm->status = BC_STATUS_QUIT;1876BC_SIG_UNLOCK;1877BC_JMP;1878}18791880// Print the ready message.1881bc_file_write(&vm->fout, bc_flush_none, vm->sigmsg, vm->siglen);1882bc_file_write(&vm->fout, bc_flush_none, bc_program_ready_msg,1883bc_program_ready_msg_len);1884bc_history_reset(h);1885bc_history_refresh(h);18861887break;1888}18891890case BC_ACTION_BACKSPACE:1891case BC_ACTION_CTRL_H:1892{1893bc_history_edit_backspace(h);1894break;1895}18961897// Act as end-of-file or delete-forward-char.1898case BC_ACTION_CTRL_D:1899{1900// Act as EOF if there's no chacters, otherwise emulate Emacs1901// delete next character to match historical gnu bc behavior.1902if (BC_HIST_BUF_LEN(h) == 0)1903{1904bc_history_printCtrl(h, c);1905BC_SIG_UNLOCK;1906return BC_STATUS_EOF;1907}19081909bc_history_edit_delete(h);19101911break;1912}19131914// Swaps current character with previous.1915case BC_ACTION_CTRL_T:1916{1917bc_history_swap(h);1918break;1919}19201921case BC_ACTION_CTRL_B:1922{1923bc_history_edit_left(h);1924break;1925}19261927case BC_ACTION_CTRL_F:1928{1929bc_history_edit_right(h);1930break;1931}19321933case BC_ACTION_CTRL_P:1934{1935bc_history_edit_next(h, BC_HIST_PREV);1936break;1937}19381939case BC_ACTION_CTRL_N:1940{1941bc_history_edit_next(h, BC_HIST_NEXT);1942break;1943}19441945case BC_ACTION_ESC:1946{1947bc_history_escape(h);1948break;1949}19501951// Delete the whole line.1952case BC_ACTION_CTRL_U:1953{1954bc_vec_string(&h->buf, 0, "");1955h->pos = 0;19561957bc_history_refresh(h);19581959break;1960}19611962// Delete from current to end of line.1963case BC_ACTION_CTRL_K:1964{1965bc_vec_npop(&h->buf, h->buf.len - h->pos);1966bc_vec_pushByte(&h->buf, '\0');1967bc_history_refresh(h);1968break;1969}19701971// Go to the start of the line.1972case BC_ACTION_CTRL_A:1973{1974bc_history_edit_home(h);1975break;1976}19771978// Go to the end of the line.1979case BC_ACTION_CTRL_E:1980{1981bc_history_edit_end(h);1982break;1983}19841985// Clear screen.1986case BC_ACTION_CTRL_L:1987{1988bc_file_write(&vm->fout, bc_flush_none, "\x1b[H\x1b[2J", 7);1989bc_history_refresh(h);1990break;1991}19921993// Delete previous word.1994case BC_ACTION_CTRL_W:1995{1996bc_history_edit_deletePrevWord(h);1997break;1998}19992000default:2001{2002// If we have a control character, print it and raise signals as2003// needed.2004if ((c >= BC_ACTION_CTRL_A && c <= BC_ACTION_CTRL_Z) ||2005c == BC_ACTION_CTRL_BSLASH)2006{2007bc_history_printCtrl(h, c);2008#ifndef _WIN322009if (c == BC_ACTION_CTRL_Z) bc_history_raise(h, SIGTSTP);2010if (c == BC_ACTION_CTRL_S) bc_history_raise(h, SIGSTOP);2011if (c == BC_ACTION_CTRL_BSLASH)2012{2013bc_history_raise(h, SIGQUIT);2014}2015#else // _WIN322016vm->status = BC_STATUS_QUIT;2017BC_SIG_UNLOCK;2018BC_JMP;2019#endif // _WIN322020}2021// Otherwise, just insert.2022else bc_history_edit_insert(h, cbuf, nread);2023break;2024}2025}2026}20272028BC_SIG_UNLOCK;20292030return BC_STATUS_SUCCESS;2031}20322033/**2034* Returns true if stdin has more data. This is for multi-line pasting, and it2035* does not work on Windows.2036* @param h The history data.2037*/2038static inline bool2039bc_history_stdinHasData(BcHistory* h)2040{2041#ifndef _WIN322042int n;2043return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 ||2044(ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0);2045#else // _WIN322046return false;2047#endif // _WIN322048}20492050BcStatus2051bc_history_line(BcHistory* h, BcVec* vec, const char* prompt)2052{2053BcStatus s;2054char* line;20552056assert(vm->fout.len == 0);20572058bc_history_enableRaw(h);20592060do2061{2062// Do the edit.2063s = bc_history_edit(h, prompt);20642065// Print a newline and flush.2066bc_file_write(&vm->fout, bc_flush_none, "\n", 1);2067bc_file_flush(&vm->fout, bc_flush_none);20682069BC_SIG_LOCK;20702071// If we actually have data...2072if (h->buf.v[0])2073{2074// Duplicate it.2075line = bc_vm_strdup(h->buf.v);20762077// Store it.2078bc_history_add(h, line);2079}2080// Add an empty string.2081else bc_history_add_empty(h);20822083BC_SIG_UNLOCK;20842085// Concatenate the line to the return vector.2086bc_vec_concat(vec, h->buf.v);2087bc_vec_concat(vec, "\n");2088}2089while (!s && bc_history_stdinHasData(h));20902091assert(!s || s == BC_STATUS_EOF);20922093bc_history_disableRaw(h);20942095return s;2096}20972098void2099bc_history_string_free(void* str)2100{2101char* s = *((char**) str);2102BC_SIG_ASSERT_LOCKED;2103if (s[0]) free(s);2104}21052106void2107bc_history_init(BcHistory* h)2108{21092110#ifdef _WIN322111HANDLE out, in;2112#endif // _WIN3221132114BC_SIG_ASSERT_LOCKED;21152116h->rawMode = false;2117h->badTerm = bc_history_isBadTerm();21182119// Just don't initialize with a bad terminal.2120if (h->badTerm) return;21212122#ifdef _WIN3221232124h->orig_in = 0;2125h->orig_out = 0;21262127in = GetStdHandle(STD_INPUT_HANDLE);2128out = GetStdHandle(STD_OUTPUT_HANDLE);21292130// Set the code pages.2131SetConsoleCP(CP_UTF8);2132SetConsoleOutputCP(CP_UTF8);21332134// Get the original modes.2135if (!GetConsoleMode(in, &h->orig_in) || !GetConsoleMode(out, &h->orig_out))2136{2137// Just mark it as a bad terminal on error.2138h->badTerm = true;2139return;2140}2141else2142{2143// Set the new modes.2144DWORD reqOut = h->orig_out | ENABLE_VIRTUAL_TERMINAL_PROCESSING;2145DWORD reqIn = h->orig_in | ENABLE_VIRTUAL_TERMINAL_INPUT;21462147// The input handle requires turning *off* some modes. That's why2148// history didn't work before; I didn't read the documentation2149// closely enough to see that most modes were automaticall enabled,2150// and they need to be turned off.2151reqOut |= DISABLE_NEWLINE_AUTO_RETURN | ENABLE_PROCESSED_OUTPUT;2152reqIn &= ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);2153reqIn &= ~(ENABLE_PROCESSED_INPUT);21542155// Set the modes; if there was an error, assume a bad terminal and2156// quit.2157if (!SetConsoleMode(in, reqIn) || !SetConsoleMode(out, reqOut))2158{2159h->badTerm = true;2160return;2161}2162}2163#endif // _WIN3221642165bc_vec_init(&h->buf, sizeof(char), BC_DTOR_NONE);2166bc_vec_init(&h->history, sizeof(char*), BC_DTOR_HISTORY_STRING);2167bc_vec_init(&h->extras, sizeof(char), BC_DTOR_NONE);21682169#ifndef _WIN322170FD_ZERO(&h->rdset);2171FD_SET(STDIN_FILENO, &h->rdset);2172h->ts.tv_sec = 0;2173h->ts.tv_nsec = 0;21742175sigemptyset(&h->sigmask);2176sigaddset(&h->sigmask, SIGINT);2177#endif // _WIN322178}21792180void2181bc_history_free(BcHistory* h)2182{2183BC_SIG_ASSERT_LOCKED;2184#ifndef _WIN322185bc_history_disableRaw(h);2186#else // _WIN322187SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), h->orig_in);2188SetConsoleMode(GetStdHandle(STD_OUTPUT_HANDLE), h->orig_out);2189#endif // _WIN322190#if BC_DEBUG2191bc_vec_free(&h->buf);2192bc_vec_free(&h->history);2193bc_vec_free(&h->extras);2194#endif // BC_DEBUG2195}21962197#if BC_DEBUG_CODE21982199/**2200* Prints scan codes. This special mode is used by bc history in order to print2201* scan codes on screen for debugging / development purposes.2202* @param h The history data.2203*/2204void2205bc_history_printKeyCodes(BcHistory* h)2206{2207char quit[4];22082209bc_vm_printf("Linenoise key codes debugging mode.\n"2210"Press keys to see scan codes. "2211"Type 'quit' at any time to exit.\n");22122213bc_history_enableRaw(h);2214memset(quit, ' ', 4);22152216while (true)2217{2218char c;2219ssize_t nread;22202221nread = bc_history_read(&c, 1);2222if (nread <= 0) continue;22232224// Shift string to left.2225memmove(quit, quit + 1, sizeof(quit) - 1);22262227// Insert current char on the right.2228quit[sizeof(quit) - 1] = c;2229if (!memcmp(quit, "quit", sizeof(quit))) break;22302231bc_vm_printf("'%c' %lu (type quit to exit)\n", isprint(c) ? c : '?',2232(unsigned long) c);22332234// Go left edge manually, we are in raw mode.2235bc_vm_putchar('\r', bc_flush_none);2236bc_file_flush(&vm->fout, bc_flush_none);2237}22382239bc_history_disableRaw(h);2240}2241#endif // BC_DEBUG_CODE22422243#endif // BC_ENABLE_HISTORY22442245#endif // BC_ENABLE_READLINE22462247#endif // BC_ENABLE_EDITLINE224822492250