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 "lib/mp-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 | |
47 | void readline_init0(void) { |
48 | memset(MP_STATE_PORT(readline_hist), 0, READLINE_HIST_SIZE * sizeof(const char*)); |
49 | } |
50 | |
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 |
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 | |
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 |
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 | |
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 |
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 | |
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 | |
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 | |
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 | |
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 | |