]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/bc/src/history.c
Update to version 3.2.0
[FreeBSD/FreeBSD.git] / contrib / bc / src / history.c
1 /*
2  * *****************************************************************************
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Copyright (c) 2018-2020 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  */
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  */
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  */
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  */
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  */
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  */
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
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  */
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_err(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  */
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  */
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  */
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_err(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_err(BC_ERR_FATAL_IO_ERR);
477
478         h->rawMode = true;
479 }
480
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  */
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, "\x1b[6n", 4);
509         bc_file_flush(&vm.fout);
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  */
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, "\x1b[999C", 6);
560                 bc_file_flush(&vm.fout);
561                 cols = bc_history_cursorPos();
562                 if (BC_ERR(cols == SIZE_MAX)) return BC_HIST_DEF_COLS;
563
564                 // Restore position.
565                 if (cols > start) {
566                         bc_file_printf(&vm.fout, "\x1b[%zuD", cols - start);
567                         bc_file_flush(&vm.fout);
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  */
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  */
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  */
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);
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, "\r", 1);
651
652         // Write the prompt, if desired.
653 #if BC_ENABLE_PROMPT
654         if (BC_USE_PROMPT) bc_file_write(&vm.fout, h->prompt, h->plen);
655 #endif // BC_ENABLE_PROMPT
656
657         bc_file_write(&vm.fout, buf, BC_HIST_BUF_LEN(h));
658
659         // Erase to right.
660         bc_file_write(&vm.fout, "\x1b[0K", 4);
661
662         // Move cursor to original position.
663         colpos = bc_history_colPos(buf, len, pos) + h->pcol;
664
665         if (colpos) bc_file_printf(&vm.fout, "\r\x1b[%zuC", colpos);
666
667         bc_file_flush(&vm.fout);
668 }
669
670 /**
671  * Insert the character 'c' at cursor current position.
672  */
673 static void bc_history_edit_insert(BcHistory *h, const char *cbuf, size_t clen)
674 {
675         bc_vec_grow(&h->buf, clen);
676
677         if (h->pos == BC_HIST_BUF_LEN(h)) {
678
679                 size_t colpos = 0, len;
680
681                 memcpy(bc_vec_item(&h->buf, h->pos), cbuf, clen);
682
683                 h->pos += clen;
684                 h->buf.len += clen - 1;
685                 bc_vec_pushByte(&h->buf, '\0');
686
687                 len = BC_HIST_BUF_LEN(h);
688 #if BC_ENABLE_PROMPT
689                 colpos = bc_history_promptColLen(h->prompt, h->plen);
690 #endif // BC_ENABLE_PROMPT
691                 colpos += bc_history_colPos(h->buf.v, len, len);
692
693                 if (colpos < h->cols) {
694
695                         // Avoid a full update of the line in the trivial case.
696                         bc_file_write(&vm.fout, cbuf, clen);
697                         bc_file_flush(&vm.fout);
698                 }
699                 else bc_history_refresh(h);
700         }
701         else {
702
703                 size_t amt = BC_HIST_BUF_LEN(h) - h->pos;
704
705                 memmove(h->buf.v + h->pos + clen, h->buf.v + h->pos, amt);
706                 memcpy(h->buf.v + h->pos, cbuf, clen);
707
708                 h->pos += clen;
709                 h->buf.len += clen;
710                 h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
711
712                 bc_history_refresh(h);
713         }
714 }
715
716 /**
717  * Move cursor to the left.
718  */
719 static void bc_history_edit_left(BcHistory *h) {
720
721         if (h->pos <= 0) return;
722
723         h->pos -= bc_history_prevLen(h->buf.v, h->pos, NULL);
724
725         bc_history_refresh(h);
726 }
727
728 /**
729  * Move cursor on the right.
730 */
731 static void bc_history_edit_right(BcHistory *h) {
732
733         if (h->pos == BC_HIST_BUF_LEN(h)) return;
734
735         h->pos += bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL);
736
737         bc_history_refresh(h);
738 }
739
740 /**
741  * Move cursor to the end of the current word.
742  */
743 static void bc_history_edit_wordEnd(BcHistory *h) {
744
745         size_t len = BC_HIST_BUF_LEN(h);
746
747         if (!len || h->pos >= len) return;
748
749         while (h->pos < len && isspace(h->buf.v[h->pos])) h->pos += 1;
750         while (h->pos < len && !isspace(h->buf.v[h->pos])) h->pos += 1;
751
752         bc_history_refresh(h);
753 }
754
755 /**
756  * Move cursor to the start of the current word.
757  */
758 static void bc_history_edit_wordStart(BcHistory *h) {
759
760         size_t len = BC_HIST_BUF_LEN(h);
761
762         if (!len) return;
763
764         while (h->pos > 0 && isspace(h->buf.v[h->pos - 1])) h->pos -= 1;
765         while (h->pos > 0 && !isspace(h->buf.v[h->pos - 1])) h->pos -= 1;
766
767         bc_history_refresh(h);
768 }
769
770 /**
771  * Move cursor to the start of the line.
772  */
773 static void bc_history_edit_home(BcHistory *h) {
774
775         if (!h->pos) return;
776
777         h->pos = 0;
778
779         bc_history_refresh(h);
780 }
781
782 /**
783  * Move cursor to the end of the line.
784  */
785 static void bc_history_edit_end(BcHistory *h) {
786
787         if (h->pos == BC_HIST_BUF_LEN(h)) return;
788
789         h->pos = BC_HIST_BUF_LEN(h);
790
791         bc_history_refresh(h);
792 }
793
794 /**
795  * Substitute the currently edited line with the next or previous history
796  * entry as specified by 'dir' (direction).
797  */
798 static void bc_history_edit_next(BcHistory *h, bool dir) {
799
800         const char *dup, *str;
801
802         if (h->history.len <= 1) return;
803
804         BC_SIG_LOCK;
805
806         if (h->buf.v[0]) dup = bc_vm_strdup(h->buf.v);
807         else dup = "";
808
809         // Update the current history entry before
810         // overwriting it with the next one.
811         bc_vec_replaceAt(&h->history, h->history.len - 1 - h->idx, &dup);
812
813         BC_SIG_UNLOCK;
814
815         // Show the new entry.
816         h->idx += (dir == BC_HIST_PREV ? 1 : SIZE_MAX);
817
818         if (h->idx == SIZE_MAX) {
819                 h->idx = 0;
820                 return;
821         }
822         else if (h->idx >= h->history.len) {
823                 h->idx = h->history.len - 1;
824                 return;
825         }
826
827         str = *((char**) bc_vec_item(&h->history, h->history.len - 1 - h->idx));
828         bc_vec_string(&h->buf, strlen(str), str);
829         assert(h->buf.len > 0);
830
831         h->pos = BC_HIST_BUF_LEN(h);
832
833         bc_history_refresh(h);
834 }
835
836 /**
837  * Delete the character at the right of the cursor without altering the cursor
838  * position. Basically this is what happens with the "Delete" keyboard key.
839  */
840 static void bc_history_edit_delete(BcHistory *h) {
841
842         size_t chlen, len = BC_HIST_BUF_LEN(h);
843
844         if (!len || h->pos >= len) return;
845
846         chlen = bc_history_nextLen(h->buf.v, len, h->pos, NULL);
847
848         memmove(h->buf.v + h->pos, h->buf.v + h->pos + chlen, len - h->pos - chlen);
849
850         h->buf.len -= chlen;
851         h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
852
853         bc_history_refresh(h);
854 }
855
856 static void bc_history_edit_backspace(BcHistory *h) {
857
858         size_t chlen, len = BC_HIST_BUF_LEN(h);
859
860         if (!h->pos || !len) return;
861
862         chlen = bc_history_prevLen(h->buf.v, h->pos, NULL);
863
864         memmove(h->buf.v + h->pos - chlen, h->buf.v + h->pos, len - h->pos);
865
866         h->pos -= chlen;
867         h->buf.len -= chlen;
868         h->buf.v[BC_HIST_BUF_LEN(h)] = '\0';
869
870         bc_history_refresh(h);
871 }
872
873 /**
874  * Delete the previous word, maintaining the cursor at the start of the
875  * current word.
876  */
877 static void bc_history_edit_deletePrevWord(BcHistory *h) {
878
879         size_t diff, old_pos = h->pos;
880
881         while (h->pos > 0 && h->buf.v[h->pos - 1] == ' ') --h->pos;
882         while (h->pos > 0 && h->buf.v[h->pos - 1] != ' ') --h->pos;
883
884         diff = old_pos - h->pos;
885         memmove(h->buf.v + h->pos, h->buf.v + old_pos,
886                 BC_HIST_BUF_LEN(h) - old_pos + 1);
887         h->buf.len -= diff;
888
889         bc_history_refresh(h);
890 }
891
892 /**
893  * Delete the next word, maintaining the cursor at the same position.
894  */
895 static void bc_history_edit_deleteNextWord(BcHistory *h) {
896
897         size_t next_end = h->pos, len = BC_HIST_BUF_LEN(h);
898
899         while (next_end < len && h->buf.v[next_end] == ' ') ++next_end;
900         while (next_end < len && h->buf.v[next_end] != ' ') ++next_end;
901
902         memmove(h->buf.v + h->pos, h->buf.v + next_end, len - next_end);
903
904         h->buf.len -= next_end - h->pos;
905
906         bc_history_refresh(h);
907 }
908
909 static void bc_history_swap(BcHistory *h) {
910
911         size_t pcl, ncl;
912         char auxb[5];
913
914         pcl = bc_history_prevLen(h->buf.v, h->pos, NULL);
915         ncl = bc_history_nextLen(h->buf.v, BC_HIST_BUF_LEN(h), h->pos, NULL);
916
917         // To perform a swap we need:
918         // * nonzero char length to the left
919         // * not at the end of the line
920         if (pcl && h->pos != BC_HIST_BUF_LEN(h) && pcl < 5 && ncl < 5) {
921
922                 memcpy(auxb, h->buf.v + h->pos - pcl, pcl);
923                 memcpy(h->buf.v + h->pos - pcl, h->buf.v + h->pos, ncl);
924                 memcpy(h->buf.v + h->pos - pcl + ncl, auxb, pcl);
925
926                 h->pos += -pcl + ncl;
927
928                 bc_history_refresh(h);
929         }
930 }
931
932 /**
933  * Handle escape sequences.
934  */
935 static void bc_history_escape(BcHistory *h) {
936
937         char c, seq[3];
938
939         if (BC_ERR(BC_HIST_READ(seq, 1))) return;
940
941         c = seq[0];
942
943         // ESC ? sequences.
944         if (c != '[' && c != 'O') {
945                 if (c == 'f') bc_history_edit_wordEnd(h);
946                 else if (c == 'b') bc_history_edit_wordStart(h);
947                 else if (c == 'd') bc_history_edit_deleteNextWord(h);
948         }
949         else {
950
951                 if (BC_ERR(BC_HIST_READ(seq + 1, 1))) bc_vm_err(BC_ERR_FATAL_IO_ERR);
952
953                 // ESC [ sequences.
954                 if (c == '[') {
955
956                         c = seq[1];
957
958                         if (c >= '0' && c <= '9') {
959
960                                 // Extended escape, read additional byte.
961                                 if (BC_ERR(BC_HIST_READ(seq + 2, 1)))
962                                         bc_vm_err(BC_ERR_FATAL_IO_ERR);
963
964                                 if (seq[2] == '~' && c == '3') bc_history_edit_delete(h);
965                                 else if(seq[2] == ';') {
966
967                                         if (BC_ERR(BC_HIST_READ(seq, 2)))
968                                                 bc_vm_err(BC_ERR_FATAL_IO_ERR);
969
970                                         if (seq[0] != '5') return;
971                                         else if (seq[1] == 'C') bc_history_edit_wordEnd(h);
972                                         else if (seq[1] == 'D') bc_history_edit_wordStart(h);
973                                 }
974                         }
975                         else {
976
977                                 switch(c) {
978
979                                         // Up.
980                                         case 'A':
981                                         {
982                                                 bc_history_edit_next(h, BC_HIST_PREV);
983                                                 break;
984                                         }
985
986                                         // Down.
987                                         case 'B':
988                                         {
989                                                 bc_history_edit_next(h, BC_HIST_NEXT);
990                                                 break;
991                                         }
992
993                                         // Right.
994                                         case 'C':
995                                         {
996                                                 bc_history_edit_right(h);
997                                                 break;
998                                         }
999
1000                                         // Left.
1001                                         case 'D':
1002                                         {
1003                                                 bc_history_edit_left(h);
1004                                                 break;
1005                                         }
1006
1007                                         // Home.
1008                                         case 'H':
1009                                         case '1':
1010                                         {
1011                                                 bc_history_edit_home(h);
1012                                                 break;
1013                                         }
1014
1015                                         // End.
1016                                         case 'F':
1017                                         case '4':
1018                                         {
1019                                                 bc_history_edit_end(h);
1020                                                 break;
1021                                         }
1022
1023                                         case 'd':
1024                                         {
1025                                                 bc_history_edit_deleteNextWord(h);
1026                                                 break;
1027                                         }
1028                                 }
1029                         }
1030                 }
1031                 // ESC O sequences.
1032                 else if (c == 'O') {
1033
1034                         switch (seq[1]) {
1035
1036                                 case 'A':
1037                                 {
1038                                         bc_history_edit_next(h, BC_HIST_PREV);
1039                                         break;
1040                                 }
1041
1042                                 case 'B':
1043                                 {
1044                                         bc_history_edit_next(h, BC_HIST_NEXT);
1045                                         break;
1046                                 }
1047
1048                                 case 'C':
1049                                 {
1050                                         bc_history_edit_right(h);
1051                                         break;
1052                                 }
1053
1054                                 case 'D':
1055                                 {
1056                                         bc_history_edit_left(h);
1057                                         break;
1058                                 }
1059
1060                                 case 'F':
1061                                 {
1062                                         bc_history_edit_end(h);
1063                                         break;
1064                                 }
1065
1066                                 case 'H':
1067                                 {
1068                                         bc_history_edit_home(h);
1069                                         break;
1070                                 }
1071                         }
1072                 }
1073         }
1074 }
1075
1076 static void bc_history_reset(BcHistory *h) {
1077
1078         h->oldcolpos = h->pos = h->idx = 0;
1079         h->cols = bc_history_columns();
1080
1081         // The latest history entry is always our current buffer, that
1082         // initially is just an empty string.
1083         bc_history_add_empty(h);
1084
1085         // Buffer starts empty.
1086         bc_vec_empty(&h->buf);
1087 }
1088
1089 static void bc_history_printCtrl(BcHistory *h, unsigned int c) {
1090
1091         char str[3] = "^A";
1092         const char newline[2] = "\n";
1093
1094         str[1] = (char) (c + 'A' - BC_ACTION_CTRL_A);
1095
1096         bc_vec_concat(&h->buf, str);
1097
1098         bc_history_refresh(h);
1099
1100         bc_vec_npop(&h->buf, sizeof(str));
1101         bc_vec_pushByte(&h->buf, '\0');
1102
1103         if (c != BC_ACTION_CTRL_C && c != BC_ACTION_CTRL_D) {
1104                 bc_file_write(&vm.fout, newline, sizeof(newline) - 1);
1105                 bc_history_refresh(h);
1106         }
1107 }
1108
1109 /**
1110  * This function is the core of the line editing capability of bc history.
1111  * It expects 'fd' to be already in "raw mode" so that every key pressed
1112  * will be returned ASAP to read().
1113  */
1114 static BcStatus bc_history_edit(BcHistory *h, const char *prompt) {
1115
1116         bc_history_reset(h);
1117
1118 #if BC_ENABLE_PROMPT
1119         if (BC_USE_PROMPT) {
1120
1121                 h->prompt = prompt;
1122                 h->plen = strlen(prompt);
1123                 h->pcol = bc_history_promptColLen(prompt, h->plen);
1124
1125                 bc_file_write(&vm.fout, prompt, h->plen);
1126                 bc_file_flush(&vm.fout);
1127         }
1128 #endif // BC_ENABLE_PROMPT
1129
1130         for (;;) {
1131
1132                 BcStatus s;
1133                 // Large enough for any encoding?
1134                 char cbuf[32];
1135                 unsigned int c = 0;
1136                 size_t nread = 0;
1137
1138                 s = bc_history_readCode(cbuf, sizeof(cbuf), &c, &nread);
1139                 if (BC_ERR(s)) return s;
1140
1141                 switch (c) {
1142
1143                         case BC_ACTION_LINE_FEED:
1144                         case BC_ACTION_ENTER:
1145                         {
1146                                 bc_vec_pop(&h->history);
1147                                 return s;
1148                         }
1149
1150                         case BC_ACTION_TAB:
1151                         {
1152                                 memcpy(cbuf, bc_history_tab, bc_history_tab_len + 1);
1153                                 bc_history_edit_insert(h, cbuf, bc_history_tab_len);
1154                                 break;
1155                         }
1156
1157                         case BC_ACTION_CTRL_C:
1158                         {
1159                                 bc_history_printCtrl(h, c);
1160                                 bc_file_write(&vm.fout, vm.sigmsg, vm.siglen);
1161                                 bc_file_write(&vm.fout, bc_program_ready_msg,
1162                                               bc_program_ready_msg_len);
1163                                 bc_history_reset(h);
1164                                 bc_history_refresh(h);
1165                                 break;
1166                         }
1167
1168                         case BC_ACTION_BACKSPACE:
1169                         case BC_ACTION_CTRL_H:
1170                         {
1171                                 bc_history_edit_backspace(h);
1172                                 break;
1173                         }
1174
1175                         // Act as end-of-file.
1176                         case BC_ACTION_CTRL_D:
1177                         {
1178                                 bc_history_printCtrl(h, c);
1179                                 return BC_STATUS_EOF;
1180                         }
1181
1182                         // Swaps current character with previous.
1183                         case BC_ACTION_CTRL_T:
1184                         {
1185                                 bc_history_swap(h);
1186                                 break;
1187                         }
1188
1189                         case BC_ACTION_CTRL_B:
1190                         {
1191                                 bc_history_edit_left(h);
1192                                 break;
1193                         }
1194
1195                         case BC_ACTION_CTRL_F:
1196                         {
1197                                 bc_history_edit_right(h);
1198                                 break;
1199                         }
1200
1201                         case BC_ACTION_CTRL_P:
1202                         {
1203                                 bc_history_edit_next(h, BC_HIST_PREV);
1204                                 break;
1205                         }
1206
1207                         case BC_ACTION_CTRL_N:
1208                         {
1209                                 bc_history_edit_next(h, BC_HIST_NEXT);
1210                                 break;
1211                         }
1212
1213                         case BC_ACTION_ESC:
1214                         {
1215                                 bc_history_escape(h);
1216                                 break;
1217                         }
1218
1219                         // Delete the whole line.
1220                         case BC_ACTION_CTRL_U:
1221                         {
1222                                 bc_vec_string(&h->buf, 0, "");
1223                                 h->pos = 0;
1224
1225                                 bc_history_refresh(h);
1226
1227                                 break;
1228                         }
1229
1230                         // Delete from current to end of line.
1231                         case BC_ACTION_CTRL_K:
1232                         {
1233                                 bc_vec_npop(&h->buf, h->buf.len - h->pos);
1234                                 bc_vec_pushByte(&h->buf, '\0');
1235                                 bc_history_refresh(h);
1236                                 break;
1237                         }
1238
1239                         // Go to the start of the line.
1240                         case BC_ACTION_CTRL_A:
1241                         {
1242                                 bc_history_edit_home(h);
1243                                 break;
1244                         }
1245
1246                         // Go to the end of the line.
1247                         case BC_ACTION_CTRL_E:
1248                         {
1249                                 bc_history_edit_end(h);
1250                                 break;
1251                         }
1252
1253                         // Clear screen.
1254                         case BC_ACTION_CTRL_L:
1255                         {
1256                                 bc_file_write(&vm.fout, "\x1b[H\x1b[2J", 7);
1257                                 bc_history_refresh(h);
1258                                 break;
1259                         }
1260
1261                         // Delete previous word.
1262                         case BC_ACTION_CTRL_W:
1263                         {
1264                                 bc_history_edit_deletePrevWord(h);
1265                                 break;
1266                         }
1267
1268                         default:
1269                         {
1270                                 if (c >= BC_ACTION_CTRL_A && c <= BC_ACTION_CTRL_Z)
1271                                         bc_history_printCtrl(h, c);
1272                                 else bc_history_edit_insert(h, cbuf, nread);
1273                                 break;
1274                         }
1275                 }
1276         }
1277
1278         return BC_STATUS_SUCCESS;
1279 }
1280
1281 static inline bool bc_history_stdinHasData(BcHistory *h) {
1282         int n;
1283         return pselect(1, &h->rdset, NULL, NULL, &h->ts, &h->sigmask) > 0 ||
1284                (ioctl(STDIN_FILENO, FIONREAD, &n) >= 0 && n > 0);
1285 }
1286
1287 /**
1288  * This function calls the line editing function bc_history_edit()
1289  * using the STDIN file descriptor set in raw mode.
1290  */
1291 static BcStatus bc_history_raw(BcHistory *h, const char *prompt) {
1292
1293         BcStatus s;
1294
1295         assert(vm.fout.len == 0);
1296
1297         bc_history_enableRaw(h);
1298
1299         s = bc_history_edit(h, prompt);
1300
1301         h->stdin_has_data = bc_history_stdinHasData(h);
1302         if (!h->stdin_has_data) bc_history_disableRaw(h);
1303
1304         bc_file_write(&vm.fout, "\n", 1);
1305         bc_file_flush(&vm.fout);
1306
1307         return s;
1308 }
1309
1310 BcStatus bc_history_line(BcHistory *h, BcVec *vec, const char *prompt) {
1311
1312         BcStatus s;
1313         char* line;
1314
1315         s = bc_history_raw(h, prompt);
1316         assert(!s || s == BC_STATUS_EOF);
1317
1318         bc_vec_string(vec, BC_HIST_BUF_LEN(h), h->buf.v);
1319
1320         if (h->buf.v[0]) {
1321
1322                 BC_SIG_LOCK;
1323
1324                 line = bc_vm_strdup(h->buf.v);
1325
1326                 BC_SIG_UNLOCK;
1327
1328                 bc_history_add(h, line);
1329         }
1330         else bc_history_add_empty(h);
1331
1332         bc_vec_concat(vec, "\n");
1333
1334         return s;
1335 }
1336
1337 static void bc_history_add(BcHistory *h, char *line) {
1338
1339         if (h->history.len) {
1340
1341                 char *s = *((char**) bc_vec_item_rev(&h->history, 0));
1342
1343                 if (!strcmp(s, line)) {
1344
1345                         BC_SIG_LOCK;
1346
1347                         free(line);
1348
1349                         BC_SIG_UNLOCK;
1350
1351                         return;
1352                 }
1353         }
1354
1355         bc_vec_push(&h->history, &line);
1356 }
1357
1358 static void bc_history_add_empty(BcHistory *h) {
1359
1360         const char *line = "";
1361
1362         if (h->history.len) {
1363
1364                 char *s = *((char**) bc_vec_item_rev(&h->history, 0));
1365
1366                 if (!s[0]) return;
1367         }
1368
1369         bc_vec_push(&h->history, &line);
1370 }
1371
1372 static void bc_history_string_free(void *str) {
1373         char *s = *((char**) str);
1374         BC_SIG_ASSERT_LOCKED;
1375         if (s[0]) free(s);
1376 }
1377
1378 void bc_history_init(BcHistory *h) {
1379
1380         BC_SIG_ASSERT_LOCKED;
1381
1382         bc_vec_init(&h->buf, sizeof(char), NULL);
1383         bc_vec_init(&h->history, sizeof(char*), bc_history_string_free);
1384
1385         FD_ZERO(&h->rdset);
1386         FD_SET(STDIN_FILENO, &h->rdset);
1387         h->ts.tv_sec = 0;
1388         h->ts.tv_nsec = 0;
1389
1390         sigemptyset(&h->sigmask);
1391         sigaddset(&h->sigmask, SIGINT);
1392
1393         h->rawMode = h->stdin_has_data = false;
1394         h->badTerm = bc_history_isBadTerm();
1395 }
1396
1397 void bc_history_free(BcHistory *h) {
1398         BC_SIG_ASSERT_LOCKED;
1399         bc_history_disableRaw(h);
1400 #ifndef NDEBUG
1401         bc_vec_free(&h->buf);
1402         bc_vec_free(&h->history);
1403 #endif // NDEBUG
1404 }
1405
1406 /**
1407  * This special mode is used by bc history in order to print scan codes
1408  * on screen for debugging / development purposes.
1409  */
1410 #if BC_DEBUG_CODE
1411 void bc_history_printKeyCodes(BcHistory *h) {
1412
1413         char quit[4];
1414
1415         bc_vm_printf("Linenoise key codes debugging mode.\n"
1416                      "Press keys to see scan codes. "
1417                      "Type 'quit' at any time to exit.\n");
1418
1419         bc_history_enableRaw(h);
1420         memset(quit, ' ', 4);
1421
1422         while(true) {
1423
1424                 char c;
1425                 ssize_t nread;
1426
1427                 nread = bc_history_read(&c, 1);
1428                 if (nread <= 0) continue;
1429
1430                 // Shift string to left.
1431                 memmove(quit, quit + 1, sizeof(quit) - 1);
1432
1433                 // Insert current char on the right.
1434                 quit[sizeof(quit) - 1] = c;
1435                 if (!memcmp(quit, "quit", sizeof(quit))) break;
1436
1437                 bc_vm_printf("'%c' %lu (type quit to exit)\n",
1438                              isprint(c) ? c : '?', (unsigned long) c);
1439
1440                 // Go left edge manually, we are in raw mode.
1441                 bc_vm_putchar('\r');
1442                 bc_file_flush(&vm.fout);
1443         }
1444
1445         bc_history_disableRaw(h);
1446 }
1447 #endif // BC_DEBUG_CODE
1448
1449 #endif // BC_ENABLE_HISTORY