1 /*
2  * This file is part of the MicroPython project, http://micropython.org/
3  *
4  * The MIT License (MIT)
5  *
6  * Copyright (c) 2013, 2014 Damien P. George
7  *
8  * Permission is hereby granted, free of charge, to any person obtaining a copy
9  * of this software and associated documentation files (the "Software"), to deal
10  * in the Software without restriction, including without limitation the rights
11  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12  * copies of the Software, and to permit persons to whom the Software is
13  * furnished to do so, subject to the following conditions:
14  *
15  * The above copyright notice and this permission notice shall be included in
16  * all copies or substantial portions of the Software.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24  * THE SOFTWARE.
25  */
26 
27 #include <stdio.h>
28 #include <stdint.h>
29 #include <string.h>
30 
31 #include "py/mpstate.h"
32 #include "py/repl.h"
33 #include "py/mphal.h"
34 #include "shared/readline/readline.h"
35 
36 #if 0 // print debugging info
37 #define DEBUG_PRINT (1)
38 #define DEBUG_printf printf
39 #else // don't print debugging info
40 #define DEBUG_printf(...) (void)0
41 #endif
42 
43 #define READLINE_HIST_SIZE (MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist)))
44 
45 enum { ESEQ_NONE, ESEQ_ESC, ESEQ_ESC_BRACKET, ESEQ_ESC_BRACKET_DIGIT, ESEQ_ESC_O };
46 
readline_init0(void)47 void readline_init0(void) {
48     memset(MP_STATE_PORT(readline_hist), 0, READLINE_HIST_SIZE * sizeof(const char*));
49 }
50 
str_dup_maybe(const char * str)51 STATIC char *str_dup_maybe(const char *str) {
52     uint32_t len = strlen(str);
53     char *s2 = m_new_maybe(char, len + 1);
54     if (s2 == NULL) {
55         return NULL;
56     }
57     memcpy(s2, str, len + 1);
58     return s2;
59 }
60 
61 // By default assume terminal which implements VT100 commands...
62 #ifndef MICROPY_HAL_HAS_VT100
63 #define MICROPY_HAL_HAS_VT100 (1)
64 #endif
65 
66 // ...and provide the implementation using them
67 #if MICROPY_HAL_HAS_VT100
mp_hal_move_cursor_back(uint pos)68 STATIC void mp_hal_move_cursor_back(uint pos) {
69     if (pos <= 4) {
70         // fast path for most common case of 1 step back
71         mp_hal_stdout_tx_strn("\b\b\b\b", pos);
72     } else {
73         char vt100_command[6];
74         // snprintf needs space for the terminating null character
75         int n = snprintf(&vt100_command[0], sizeof(vt100_command), "\x1b[%u", pos);
76         if (n > 0) {
77             assert((unsigned)n < sizeof(vt100_command));
78             vt100_command[n] = 'D'; // replace null char
79             mp_hal_stdout_tx_strn(vt100_command, n + 1);
80         }
81     }
82 }
83 
mp_hal_erase_line_from_cursor(uint n_chars_to_erase)84 STATIC void mp_hal_erase_line_from_cursor(uint n_chars_to_erase) {
85     (void)n_chars_to_erase;
86     mp_hal_stdout_tx_strn("\x1b[K", 3);
87 }
88 #endif
89 
90 typedef struct _readline_t {
91     vstr_t *line;
92     size_t orig_line_len;
93     int escape_seq;
94     int hist_cur;
95     size_t cursor_pos;
96     char escape_seq_buf[1];
97     const char *prompt;
98 } readline_t;
99 
100 STATIC readline_t rl;
101 
102 #if MICROPY_REPL_EMACS_WORDS_MOVE
cursor_count_word(int forward)103 STATIC size_t cursor_count_word(int forward) {
104     const char *line_buf = vstr_str(rl.line);
105     size_t pos = rl.cursor_pos;
106     bool in_word = false;
107 
108     for (;;) {
109         // if moving backwards and we've reached 0... break
110         if (!forward && pos == 0) {
111             break;
112         }
113         // or if moving forwards and we've reached to the end of line... break
114         else if (forward && pos == vstr_len(rl.line)) {
115             break;
116         }
117 
118         if (unichar_isalnum(line_buf[pos + (forward - 1)])) {
119             in_word = true;
120         } else if (in_word) {
121             break;
122         }
123 
124         pos += forward ? forward : -1;
125     }
126 
127     return forward ? pos - rl.cursor_pos : rl.cursor_pos - pos;
128 }
129 #endif
130 
readline_process_char(int c)131 int readline_process_char(int c) {
132     size_t last_line_len = rl.line->len;
133     int redraw_step_back = 0;
134     bool redraw_from_cursor = false;
135     int redraw_step_forward = 0;
136     if (rl.escape_seq == ESEQ_NONE) {
137         if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_E && vstr_len(rl.line) == rl.orig_line_len) {
138             // control character with empty line
139             return c;
140         } else if (c == CHAR_CTRL_A) {
141             // CTRL-A with non-empty line is go-to-start-of-line
142             goto home_key;
143         #if MICROPY_REPL_EMACS_KEYS
144         } else if (c == CHAR_CTRL_B) {
145             // CTRL-B with non-empty line is go-back-one-char
146             goto left_arrow_key;
147         #endif
148         } else if (c == CHAR_CTRL_C) {
149             // CTRL-C with non-empty line is cancel
150             return c;
151         #if MICROPY_REPL_EMACS_KEYS
152         } else if (c == CHAR_CTRL_D) {
153             // CTRL-D with non-empty line is delete-at-cursor
154             goto delete_key;
155         #endif
156         } else if (c == CHAR_CTRL_E) {
157             // CTRL-E is go-to-end-of-line
158             goto end_key;
159         #if MICROPY_REPL_EMACS_KEYS
160         } else if (c == CHAR_CTRL_F) {
161             // CTRL-F with non-empty line is go-forward-one-char
162             goto right_arrow_key;
163         } else if (c == CHAR_CTRL_K) {
164             // CTRL-K is kill from cursor to end-of-line, inclusive
165             vstr_cut_tail_bytes(rl.line, last_line_len - rl.cursor_pos);
166             // set redraw parameters
167             redraw_from_cursor = true;
168         } else if (c == CHAR_CTRL_N) {
169             // CTRL-N is go to next line in history
170             goto down_arrow_key;
171         } else if (c == CHAR_CTRL_P) {
172             // CTRL-P is go to previous line in history
173             goto up_arrow_key;
174         } else if (c == CHAR_CTRL_U) {
175             // CTRL-U is kill from beginning-of-line up to cursor
176             vstr_cut_out_bytes(rl.line, rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
177             // set redraw parameters
178             redraw_step_back = rl.cursor_pos - rl.orig_line_len;
179             redraw_from_cursor = true;
180         #endif
181         #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
182         } else if (c == CHAR_CTRL_W) {
183             goto backward_kill_word;
184         #endif
185         } else if (c == '\r') {
186             // newline
187             mp_hal_stdout_tx_str("\r\n");
188             readline_push_history(vstr_null_terminated_str(rl.line) + rl.orig_line_len);
189             return 0;
190         } else if (c == 27) {
191             // escape sequence
192             rl.escape_seq = ESEQ_ESC;
193         } else if (c == 8 || c == 127) {
194             // backspace/delete
195             if (rl.cursor_pos > rl.orig_line_len) {
196                 // work out how many chars to backspace
197                 #if MICROPY_REPL_AUTO_INDENT
198                 int nspace = 0;
199                 for (size_t i = rl.orig_line_len; i < rl.cursor_pos; i++) {
200                     if (rl.line->buf[i] != ' ') {
201                         nspace = 0;
202                         break;
203                     }
204                     nspace += 1;
205                 }
206                 if (nspace < 4) {
207                     nspace = 1;
208                 } else {
209                     nspace = 4;
210                 }
211                 #else
212                 int nspace = 1;
213                 #endif
214 
215                 // do the backspace
216                 vstr_cut_out_bytes(rl.line, rl.cursor_pos - nspace, nspace);
217                 // set redraw parameters
218                 redraw_step_back = nspace;
219                 redraw_from_cursor = true;
220             }
221         #if MICROPY_HELPER_REPL
222         } else if (c == 9) {
223             // tab magic
224             const char *compl_str;
225             size_t compl_len = mp_repl_autocomplete(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len, &mp_plat_print, &compl_str);
226             if (compl_len == 0) {
227                 // no match
228             } else if (compl_len == (size_t)(-1)) {
229                 // many matches
230                 mp_hal_stdout_tx_str(rl.prompt);
231                 mp_hal_stdout_tx_strn(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
232                 redraw_from_cursor = true;
233             } else {
234                 // one match
235                 for (size_t i = 0; i < compl_len; ++i) {
236                     vstr_ins_byte(rl.line, rl.cursor_pos + i, *compl_str++);
237                 }
238                 // set redraw parameters
239                 redraw_from_cursor = true;
240                 redraw_step_forward = compl_len;
241             }
242         #endif
243         } else if (32 <= c && c <= 126) {
244             // printable character
245             vstr_ins_char(rl.line, rl.cursor_pos, c);
246             // set redraw parameters
247             redraw_from_cursor = true;
248             redraw_step_forward = 1;
249         }
250     } else if (rl.escape_seq == ESEQ_ESC) {
251         switch (c) {
252             case '[':
253                 rl.escape_seq = ESEQ_ESC_BRACKET;
254                 break;
255             case 'O':
256                 rl.escape_seq = ESEQ_ESC_O;
257                 break;
258             #if MICROPY_REPL_EMACS_WORDS_MOVE
259             case 'b':
260 #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
261 backward_word:
262 #endif
263                 redraw_step_back = cursor_count_word(0);
264                 rl.escape_seq = ESEQ_NONE;
265                 break;
266             case 'f':
267 #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
268 forward_word:
269 #endif
270                 redraw_step_forward = cursor_count_word(1);
271                 rl.escape_seq = ESEQ_NONE;
272                 break;
273             case 'd':
274                 vstr_cut_out_bytes(rl.line, rl.cursor_pos, cursor_count_word(1));
275                 redraw_from_cursor = true;
276                 rl.escape_seq = ESEQ_NONE;
277                 break;
278             case 127:
279 #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
280 backward_kill_word:
281 #endif
282                 redraw_step_back = cursor_count_word(0);
283                 vstr_cut_out_bytes(rl.line, rl.cursor_pos - redraw_step_back, redraw_step_back);
284                 redraw_from_cursor = true;
285                 rl.escape_seq = ESEQ_NONE;
286                 break;
287             #endif
288             default:
289                 DEBUG_printf("(ESC %d)", c);
290                 rl.escape_seq = ESEQ_NONE;
291                 break;
292         }
293     } else if (rl.escape_seq == ESEQ_ESC_BRACKET) {
294         if ('0' <= c && c <= '9') {
295             rl.escape_seq = ESEQ_ESC_BRACKET_DIGIT;
296             rl.escape_seq_buf[0] = c;
297         } else {
298             rl.escape_seq = ESEQ_NONE;
299             if (c == 'A') {
300 #if MICROPY_REPL_EMACS_KEYS
301 up_arrow_key:
302 #endif
303                 // up arrow
304                 if (rl.hist_cur + 1 < (int)READLINE_HIST_SIZE && MP_STATE_PORT(readline_hist)[rl.hist_cur + 1] != NULL) {
305                     // increase hist num
306                     rl.hist_cur += 1;
307                     // set line to history
308                     rl.line->len = rl.orig_line_len;
309                     vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
310                     // set redraw parameters
311                     redraw_step_back = rl.cursor_pos - rl.orig_line_len;
312                     redraw_from_cursor = true;
313                     redraw_step_forward = rl.line->len - rl.orig_line_len;
314                 }
315             } else if (c == 'B') {
316 #if MICROPY_REPL_EMACS_KEYS
317 down_arrow_key:
318 #endif
319                 // down arrow
320                 if (rl.hist_cur >= 0) {
321                     // decrease hist num
322                     rl.hist_cur -= 1;
323                     // set line to history
324                     vstr_cut_tail_bytes(rl.line, rl.line->len - rl.orig_line_len);
325                     if (rl.hist_cur >= 0) {
326                         vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
327                     }
328                     // set redraw parameters
329                     redraw_step_back = rl.cursor_pos - rl.orig_line_len;
330                     redraw_from_cursor = true;
331                     redraw_step_forward = rl.line->len - rl.orig_line_len;
332                 }
333             } else if (c == 'C') {
334 #if MICROPY_REPL_EMACS_KEYS
335 right_arrow_key:
336 #endif
337                 // right arrow
338                 if (rl.cursor_pos < rl.line->len) {
339                     redraw_step_forward = 1;
340                 }
341             } else if (c == 'D') {
342 #if MICROPY_REPL_EMACS_KEYS
343 left_arrow_key:
344 #endif
345                 // left arrow
346                 if (rl.cursor_pos > rl.orig_line_len) {
347                     redraw_step_back = 1;
348                 }
349             } else if (c == 'H') {
350                 // home
351                 goto home_key;
352             } else if (c == 'F') {
353                 // end
354                 goto end_key;
355             } else {
356                 DEBUG_printf("(ESC [ %d)", c);
357             }
358         }
359     } else if (rl.escape_seq == ESEQ_ESC_BRACKET_DIGIT) {
360         if (c == '~') {
361             if (rl.escape_seq_buf[0] == '1' || rl.escape_seq_buf[0] == '7') {
362 home_key:
363                 redraw_step_back = rl.cursor_pos - rl.orig_line_len;
364             } else if (rl.escape_seq_buf[0] == '4' || rl.escape_seq_buf[0] == '8') {
365 end_key:
366                 redraw_step_forward = rl.line->len - rl.cursor_pos;
367             } else if (rl.escape_seq_buf[0] == '3') {
368                 // delete
369 #if MICROPY_REPL_EMACS_KEYS
370 delete_key:
371 #endif
372                 if (rl.cursor_pos < rl.line->len) {
373                     vstr_cut_out_bytes(rl.line, rl.cursor_pos, 1);
374                     redraw_from_cursor = true;
375                 }
376             } else {
377                 DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
378             }
379         #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
380         } else if (c == ';' && rl.escape_seq_buf[0] == '1') {
381             // ';' is used to separate parameters. so first parameter was '1',
382             // that's used for sequences like ctrl+left, which we will try to parse.
383             // escape_seq state is reset back to ESEQ_ESC_BRACKET, as if we've just received
384             // the opening bracket, because more parameters are to come.
385             // we don't track the parameters themselves to keep low on logic and code size. that
386             // might be required in the future if more complex sequences are added.
387             rl.escape_seq = ESEQ_ESC_BRACKET;
388             // goto away from the state-machine, as rl.escape_seq will be overridden.
389             goto redraw;
390         } else if (rl.escape_seq_buf[0] == '5' && c == 'C') {
391             // ctrl+right
392             goto forward_word;
393         } else if (rl.escape_seq_buf[0] == '5' && c == 'D') {
394             // ctrl+left
395             goto backward_word;
396         #endif
397         } else {
398             DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
399         }
400         rl.escape_seq = ESEQ_NONE;
401     } else if (rl.escape_seq == ESEQ_ESC_O) {
402         switch (c) {
403             case 'H':
404                 goto home_key;
405             case 'F':
406                 goto end_key;
407             default:
408                 DEBUG_printf("(ESC O %d)", c);
409                 rl.escape_seq = ESEQ_NONE;
410         }
411     } else {
412         rl.escape_seq = ESEQ_NONE;
413     }
414 
415 #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
416 redraw:
417 #endif
418 
419     // redraw command prompt, efficiently
420     if (redraw_step_back > 0) {
421         mp_hal_move_cursor_back(redraw_step_back);
422         rl.cursor_pos -= redraw_step_back;
423     }
424     if (redraw_from_cursor) {
425         if (rl.line->len < last_line_len) {
426             // erase old chars
427             mp_hal_erase_line_from_cursor(last_line_len - rl.cursor_pos);
428         }
429         // draw new chars
430         mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, rl.line->len - rl.cursor_pos);
431         // move cursor forward if needed (already moved forward by length of line, so move it back)
432         mp_hal_move_cursor_back(rl.line->len - (rl.cursor_pos + redraw_step_forward));
433         rl.cursor_pos += redraw_step_forward;
434     } else if (redraw_step_forward > 0) {
435         // draw over old chars to move cursor forwards
436         mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, redraw_step_forward);
437         rl.cursor_pos += redraw_step_forward;
438     }
439 
440     return -1;
441 }
442 
443 #if MICROPY_REPL_AUTO_INDENT
readline_auto_indent(void)444 STATIC void readline_auto_indent(void) {
445     vstr_t *line = rl.line;
446     if (line->len > 1 && line->buf[line->len - 1] == '\n') {
447         int i;
448         for (i = line->len - 1; i > 0; i--) {
449             if (line->buf[i - 1] == '\n') {
450                 break;
451             }
452         }
453         size_t j;
454         for (j = i; j < line->len; j++) {
455             if (line->buf[j] != ' ') {
456                 break;
457             }
458         }
459         // i=start of line; j=first non-space
460         if (i > 0 && j + 1 == line->len) {
461             // previous line is not first line and is all spaces
462             for (size_t k = i - 1; k > 0; --k) {
463                 if (line->buf[k - 1] == '\n') {
464                     // don't auto-indent if last 2 lines are all spaces
465                     return;
466                 } else if (line->buf[k - 1] != ' ') {
467                     // 2nd previous line is not all spaces
468                     break;
469                 }
470             }
471         }
472         int n = (j - i) / 4;
473         if (line->buf[line->len - 2] == ':') {
474             n += 1;
475         }
476         while (n-- > 0) {
477             vstr_add_strn(line, "    ", 4);
478             mp_hal_stdout_tx_strn("    ", 4);
479             rl.cursor_pos += 4;
480         }
481     }
482 }
483 #endif
484 
readline_note_newline(const char * prompt)485 void readline_note_newline(const char *prompt) {
486     rl.orig_line_len = rl.line->len;
487     rl.cursor_pos = rl.orig_line_len;
488     rl.prompt = prompt;
489     mp_hal_stdout_tx_str(prompt);
490     #if MICROPY_REPL_AUTO_INDENT
491     readline_auto_indent();
492     #endif
493 }
494 
readline_init(vstr_t * line,const char * prompt)495 void readline_init(vstr_t *line, const char *prompt) {
496     rl.line = line;
497     rl.orig_line_len = line->len;
498     rl.escape_seq = ESEQ_NONE;
499     rl.escape_seq_buf[0] = 0;
500     rl.hist_cur = -1;
501     rl.cursor_pos = rl.orig_line_len;
502     rl.prompt = prompt;
503     mp_hal_stdout_tx_str(prompt);
504     #if MICROPY_REPL_AUTO_INDENT
505     readline_auto_indent();
506     #endif
507 }
508 
readline(vstr_t * line,const char * prompt)509 int readline(vstr_t *line, const char *prompt) {
510     readline_init(line, prompt);
511     for (;;) {
512         int c = mp_hal_stdin_rx_chr();
513         int r = readline_process_char(c);
514         if (r >= 0) {
515             return r;
516         }
517     }
518 }
519 
readline_push_history(const char * line)520 void readline_push_history(const char *line) {
521     if (line[0] != '\0'
522         && (MP_STATE_PORT(readline_hist)[0] == NULL
523             || strcmp(MP_STATE_PORT(readline_hist)[0], line) != 0)) {
524         // a line which is not empty and different from the last one
525         // so update the history
526         char *most_recent_hist = str_dup_maybe(line);
527         if (most_recent_hist != NULL) {
528             for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) {
529                 MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1];
530             }
531             MP_STATE_PORT(readline_hist)[0] = most_recent_hist;
532         }
533     }
534 }
535