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