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