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