Branch data Line data Source code
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 : : // flags for readline_t.auto_indent_state
44 : : #define AUTO_INDENT_ENABLED (0x01)
45 : : #define AUTO_INDENT_JUST_ADDED (0x02)
46 : :
47 : : enum { ESEQ_NONE, ESEQ_ESC, ESEQ_ESC_BRACKET, ESEQ_ESC_BRACKET_DIGIT, ESEQ_ESC_O };
48 : :
49 : : #ifdef _MSC_VER
50 : : // work around MSVC compiler bug: https://stackoverflow.com/q/62259834/1976323
51 : : #pragma warning(disable : 4090)
52 : : #endif
53 : :
54 : 28 : void readline_init0(void) {
55 : 28 : memset(MP_STATE_PORT(readline_hist), 0, MICROPY_READLINE_HISTORY_SIZE * sizeof(const char*));
56 : 28 : }
57 : :
58 : 1641 : static char *str_dup_maybe(const char *str) {
59 : 1641 : uint32_t len = strlen(str);
60 : 1641 : char *s2 = m_new_maybe(char, len + 1);
61 [ + - ]: 1641 : if (s2 == NULL) {
62 : : return NULL;
63 : : }
64 : 1641 : memcpy(s2, str, len + 1);
65 : 1641 : return s2;
66 : : }
67 : :
68 : : // By default assume terminal which implements VT100 commands...
69 : : #ifndef MICROPY_HAL_HAS_VT100
70 : : #define MICROPY_HAL_HAS_VT100 (1)
71 : : #endif
72 : :
73 : : // ...and provide the implementation using them
74 : : #if MICROPY_HAL_HAS_VT100
75 : 4179 : static void mp_hal_move_cursor_back(uint pos) {
76 [ + + ]: 4179 : if (pos <= 4) {
77 : : // fast path for most common case of 1 step back
78 : 4167 : mp_hal_stdout_tx_strn("\b\b\b\b", pos);
79 : : } else {
80 : 12 : char vt100_command[6];
81 : : // snprintf needs space for the terminating null character
82 : 12 : int n = snprintf(&vt100_command[0], sizeof(vt100_command), "\x1b[%u", pos);
83 [ + - ]: 12 : if (n > 0) {
84 [ - + ]: 12 : assert((unsigned)n < sizeof(vt100_command));
85 : 12 : vt100_command[n] = 'D'; // replace null char
86 : 12 : mp_hal_stdout_tx_strn(vt100_command, n + 1);
87 : : }
88 : : }
89 : 4179 : }
90 : :
91 : 69 : static void mp_hal_erase_line_from_cursor(uint n_chars_to_erase) {
92 : 69 : (void)n_chars_to_erase;
93 : 69 : mp_hal_stdout_tx_strn("\x1b[K", 3);
94 : 69 : }
95 : : #endif
96 : :
97 : : typedef struct _readline_t {
98 : : vstr_t *line;
99 : : size_t orig_line_len;
100 : : int escape_seq;
101 : : int hist_cur;
102 : : size_t cursor_pos;
103 : : char escape_seq_buf[1];
104 : : #if MICROPY_REPL_AUTO_INDENT
105 : : uint8_t auto_indent_state;
106 : : #endif
107 : : const char *prompt;
108 : : } readline_t;
109 : :
110 : : static readline_t rl;
111 : :
112 : : #if MICROPY_REPL_EMACS_WORDS_MOVE
113 : 31 : static size_t cursor_count_word(int forward) {
114 : 31 : const char *line_buf = vstr_str(rl.line);
115 : 31 : size_t pos = rl.cursor_pos;
116 : 31 : bool in_word = false;
117 : :
118 : 221 : for (;;) {
119 : : // if moving backwards and we've reached 0... break
120 [ + + ]: 126 : if (!forward && pos == 0) {
121 : : break;
122 : : }
123 : : // or if moving forwards and we've reached to the end of line... break
124 [ + + + + ]: 116 : else if (forward && pos == vstr_len(rl.line)) {
125 : : break;
126 : : }
127 : :
128 [ + + ]: 106 : if (unichar_isalnum(line_buf[pos + (forward - 1)])) {
129 : : in_word = true;
130 [ + + ]: 29 : } else if (in_word) {
131 : : break;
132 : : }
133 : :
134 [ + + ]: 95 : pos += forward ? forward : -1;
135 : : }
136 : :
137 [ + + ]: 31 : return forward ? pos - rl.cursor_pos : rl.cursor_pos - pos;
138 : : }
139 : : #endif
140 : :
141 : 4471 : int readline_process_char(int c) {
142 : 4471 : size_t last_line_len = rl.line->len;
143 : 4471 : int redraw_step_back = 0;
144 : 4471 : bool redraw_from_cursor = false;
145 : 4471 : int redraw_step_forward = 0;
146 [ + + + + : 4471 : if (rl.escape_seq == ESEQ_NONE) {
- - ]
147 [ + + + + ]: 4427 : if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_E && vstr_len(rl.line) == rl.orig_line_len) {
148 : : // control character with empty line
149 : : return c;
150 [ + + ]: 54 : } else if (c == CHAR_CTRL_A) {
151 : : // CTRL-A with non-empty line is go-to-start-of-line
152 : 6 : goto home_key;
153 : : #if MICROPY_REPL_EMACS_KEYS
154 [ + + + + : 4393 : } else if (c == CHAR_CTRL_B) {
+ + + - +
+ + + +
- ]
155 : : // CTRL-B with non-empty line is go-back-one-char
156 : 44 : goto left_arrow_key;
157 : : #endif
158 : : } else if (c == CHAR_CTRL_C) {
159 : : // CTRL-C with non-empty line is cancel
160 : : return c;
161 : : #if MICROPY_REPL_EMACS_KEYS
162 : : } else if (c == CHAR_CTRL_D) {
163 : : // CTRL-D with non-empty line is delete-at-cursor
164 : 2 : goto delete_key;
165 : : #endif
166 : : } else if (c == CHAR_CTRL_E) {
167 : : // CTRL-E is go-to-end-of-line
168 : 2 : goto end_key;
169 : : #if MICROPY_REPL_EMACS_KEYS
170 : : } else if (c == CHAR_CTRL_F) {
171 : : // CTRL-F with non-empty line is go-forward-one-char
172 : 8 : goto right_arrow_key;
173 : : } else if (c == CHAR_CTRL_K) {
174 : : // CTRL-K is kill from cursor to end-of-line, inclusive
175 : 2 : vstr_cut_tail_bytes(rl.line, last_line_len - rl.cursor_pos);
176 : : // set redraw parameters
177 : 2 : redraw_from_cursor = true;
178 : : } else if (c == CHAR_CTRL_N) {
179 : : // CTRL-N is go to next line in history
180 : 2 : goto down_arrow_key;
181 : : } else if (c == CHAR_CTRL_P) {
182 : : // CTRL-P is go to previous line in history
183 : 12 : goto up_arrow_key;
184 : : } else if (c == CHAR_CTRL_U) {
185 : : // CTRL-U is kill from beginning-of-line up to cursor
186 : 0 : vstr_cut_out_bytes(rl.line, rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
187 : : // set redraw parameters
188 : 0 : redraw_step_back = rl.cursor_pos - rl.orig_line_len;
189 : 0 : redraw_from_cursor = true;
190 : : #endif
191 : : #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
192 : : } else if (c == CHAR_CTRL_W) {
193 : 7 : goto backward_kill_word;
194 : : #endif
195 : : } else if (c == '\r') {
196 : : // newline
197 : 263 : mp_hal_stdout_tx_str("\r\n");
198 : 263 : readline_push_history(vstr_null_terminated_str(rl.line) + rl.orig_line_len);
199 : 263 : return 0;
200 : : } else if (c == 27) {
201 : : // escape sequence
202 : 26 : rl.escape_seq = ESEQ_ESC;
203 : : } else if (c == 8 || c == 127) {
204 : : // backspace/delete
205 [ + - ]: 50 : if (rl.cursor_pos > rl.orig_line_len) {
206 : : // work out how many chars to backspace
207 : : #if MICROPY_REPL_AUTO_INDENT
208 : : int nspace = 0;
209 [ + + ]: 58 : for (size_t i = rl.orig_line_len; i < rl.cursor_pos; i++) {
210 [ + + ]: 56 : if (rl.line->buf[i] != ' ') {
211 : : nspace = 0;
212 : : break;
213 : : }
214 : 8 : nspace += 1;
215 : : }
216 [ + + ]: 50 : if (nspace < 4) {
217 : : nspace = 1;
218 : : } else {
219 : 2 : nspace = 4;
220 : : }
221 : : #else
222 : : int nspace = 1;
223 : : #endif
224 : :
225 : : // do the backspace
226 : 50 : vstr_cut_out_bytes(rl.line, rl.cursor_pos - nspace, nspace);
227 : : // set redraw parameters
228 : 50 : redraw_step_back = nspace;
229 : 50 : redraw_from_cursor = true;
230 : : }
231 : : #if MICROPY_REPL_AUTO_INDENT
232 [ + + + + ]: 3975 : } else if ((rl.auto_indent_state & AUTO_INDENT_JUST_ADDED) && (c == 9 || c == ' ')) {
233 : : // tab/space after auto-indent: disable auto-indent
234 : : // - if it's a tab then leave existing indent
235 : : // - if it's a space then remove 3 spaces from existing indent
236 : 4 : rl.auto_indent_state = 0;
237 [ + + ]: 4 : if (c == ' ') {
238 : 2 : redraw_step_back = 3;
239 : 2 : vstr_cut_tail_bytes(rl.line, 3);
240 : : }
241 : : #endif
242 : : #if MICROPY_HELPER_REPL
243 [ + + ]: 3967 : } else if (c == 9) {
244 : : // tab magic
245 : 14 : const char *compl_str;
246 : 14 : size_t compl_len;
247 [ + - + + ]: 14 : if (vstr_len(rl.line) != 0 && unichar_isspace(vstr_str(rl.line)[rl.cursor_pos - 1])) {
248 : : // expand tab to 4 spaces if it follows whitespace:
249 : : // - includes the case of additional indenting
250 : : // - includes the case of indenting the start of a line that's not the first line,
251 : : // because a newline will be the previous character
252 : : // - doesn't include the case when at the start of the first line, because we still
253 : : // want to use auto-complete there
254 : 2 : compl_str = " ";
255 : 2 : compl_len = 4;
256 : : } else {
257 : : // try to auto-complete a word
258 : 12 : const char *cur_line_buf = vstr_str(rl.line) + rl.orig_line_len;
259 : 12 : size_t cur_line_len = rl.cursor_pos - rl.orig_line_len;
260 : 12 : compl_len = mp_repl_autocomplete(cur_line_buf, cur_line_len, &mp_plat_print, &compl_str);
261 : : }
262 [ + + ]: 14 : if (compl_len == 0) {
263 : : // no match
264 [ - + ]: 8 : } else if (compl_len == (size_t)(-1)) {
265 : : // many matches
266 : 0 : mp_hal_stdout_tx_str(rl.prompt);
267 : 0 : mp_hal_stdout_tx_strn(rl.line->buf + rl.orig_line_len, rl.cursor_pos - rl.orig_line_len);
268 : 0 : redraw_from_cursor = true;
269 : : } else {
270 : : // one match
271 [ + + ]: 30 : for (size_t i = 0; i < compl_len; ++i) {
272 : 22 : vstr_ins_byte(rl.line, rl.cursor_pos + i, *compl_str++);
273 : : }
274 : : // set redraw parameters
275 : 8 : redraw_from_cursor = true;
276 : 8 : redraw_step_forward = compl_len;
277 : : }
278 : : #endif
279 [ + - ]: 3957 : } else if (32 <= c && c <= 126) {
280 : : // printable character
281 : 3957 : vstr_ins_char(rl.line, rl.cursor_pos, c);
282 : : // set redraw parameters
283 : 3957 : redraw_from_cursor = true;
284 : 3957 : redraw_step_forward = 1;
285 : : }
286 : : } else if (rl.escape_seq == ESEQ_ESC) {
287 [ + - + + : 26 : switch (c) {
+ + - ]
288 : 6 : case '[':
289 : 6 : rl.escape_seq = ESEQ_ESC_BRACKET;
290 : 6 : break;
291 : 0 : case 'O':
292 : 0 : rl.escape_seq = ESEQ_ESC_O;
293 : 0 : break;
294 : : #if MICROPY_REPL_EMACS_WORDS_MOVE
295 : : case 'b':
296 : : #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
297 : 8 : backward_word:
298 : : #endif
299 : 8 : redraw_step_back = cursor_count_word(0);
300 : 8 : rl.escape_seq = ESEQ_NONE;
301 : 8 : break;
302 : : case 'f':
303 : : #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
304 : 8 : forward_word:
305 : : #endif
306 : 8 : redraw_step_forward = cursor_count_word(1);
307 : 8 : rl.escape_seq = ESEQ_NONE;
308 : 8 : break;
309 : 4 : case 'd':
310 : 4 : vstr_cut_out_bytes(rl.line, rl.cursor_pos, cursor_count_word(1));
311 : 4 : redraw_from_cursor = true;
312 : 4 : rl.escape_seq = ESEQ_NONE;
313 : 4 : break;
314 : : case 127:
315 : : #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
316 : 11 : backward_kill_word:
317 : : #endif
318 : 11 : redraw_step_back = cursor_count_word(0);
319 : 11 : vstr_cut_out_bytes(rl.line, rl.cursor_pos - redraw_step_back, redraw_step_back);
320 : 11 : redraw_from_cursor = true;
321 : 11 : rl.escape_seq = ESEQ_NONE;
322 : 11 : break;
323 : : #endif
324 : 0 : default:
325 : 0 : DEBUG_printf("(ESC %d)", c);
326 : 0 : rl.escape_seq = ESEQ_NONE;
327 : 0 : break;
328 : : }
329 : : } else if (rl.escape_seq == ESEQ_ESC_BRACKET) {
330 [ + + ]: 10 : if ('0' <= c && c <= '9') {
331 : 8 : rl.escape_seq = ESEQ_ESC_BRACKET_DIGIT;
332 : 8 : rl.escape_seq_buf[0] = c;
333 : : } else {
334 : 2 : rl.escape_seq = ESEQ_NONE;
335 [ + - - - : 2 : if (c == 'A') {
- - - ]
336 : : #if MICROPY_REPL_EMACS_KEYS
337 : 14 : up_arrow_key:
338 : : #endif
339 : : // up arrow
340 [ + - + - ]: 14 : if (rl.hist_cur + 1 < MICROPY_READLINE_HISTORY_SIZE && MP_STATE_PORT(readline_hist)[rl.hist_cur + 1] != NULL) {
341 : : // increase hist num
342 : 14 : rl.hist_cur += 1;
343 : : // set line to history
344 : 14 : rl.line->len = rl.orig_line_len;
345 : 14 : vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
346 : : // set redraw parameters
347 : 14 : redraw_step_back = rl.cursor_pos - rl.orig_line_len;
348 : 14 : redraw_from_cursor = true;
349 : 14 : redraw_step_forward = rl.line->len - rl.orig_line_len;
350 : : }
351 : : } else if (c == 'B') {
352 : : #if MICROPY_REPL_EMACS_KEYS
353 : 2 : down_arrow_key:
354 : : #endif
355 : : // down arrow
356 [ + - ]: 2 : if (rl.hist_cur >= 0) {
357 : : // decrease hist num
358 : 2 : rl.hist_cur -= 1;
359 : : // set line to history
360 : 2 : vstr_cut_tail_bytes(rl.line, rl.line->len - rl.orig_line_len);
361 [ + - ]: 2 : if (rl.hist_cur >= 0) {
362 : 2 : vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
363 : : }
364 : : // set redraw parameters
365 : 2 : redraw_step_back = rl.cursor_pos - rl.orig_line_len;
366 : 2 : redraw_from_cursor = true;
367 : 2 : redraw_step_forward = rl.line->len - rl.orig_line_len;
368 : : }
369 : : } else if (c == 'C') {
370 : : #if MICROPY_REPL_EMACS_KEYS
371 : 8 : right_arrow_key:
372 : : #endif
373 : : // right arrow
374 [ + - ]: 8 : if (rl.cursor_pos < rl.line->len) {
375 : 8 : redraw_step_forward = 1;
376 : : }
377 : : } else if (c == 'D') {
378 : : #if MICROPY_REPL_EMACS_KEYS
379 : 44 : left_arrow_key:
380 : : #endif
381 : : // left arrow
382 [ + - ]: 44 : if (rl.cursor_pos > rl.orig_line_len) {
383 : 44 : redraw_step_back = 1;
384 : : }
385 : : } else if (c == 'H') {
386 : : // home
387 : 0 : goto home_key;
388 : : } else if (c == 'F') {
389 : : // end
390 : 0 : goto end_key;
391 : : } else {
392 : : DEBUG_printf("(ESC [ %d)", c);
393 : : }
394 : : }
395 : : } else if (rl.escape_seq == ESEQ_ESC_BRACKET_DIGIT) {
396 [ - + ]: 8 : if (c == '~') {
397 [ # # # # ]: 0 : if (rl.escape_seq_buf[0] == '1' || rl.escape_seq_buf[0] == '7') {
398 : 6 : home_key:
399 : 6 : redraw_step_back = rl.cursor_pos - rl.orig_line_len;
400 : : } else if (rl.escape_seq_buf[0] == '4' || rl.escape_seq_buf[0] == '8') {
401 : 2 : end_key:
402 : 2 : redraw_step_forward = rl.line->len - rl.cursor_pos;
403 : : } else if (rl.escape_seq_buf[0] == '3') {
404 : : // delete
405 : : #if MICROPY_REPL_EMACS_KEYS
406 : 2 : delete_key:
407 : : #endif
408 [ + - ]: 2 : if (rl.cursor_pos < rl.line->len) {
409 : 2 : vstr_cut_out_bytes(rl.line, rl.cursor_pos, 1);
410 : 2 : redraw_from_cursor = true;
411 : : }
412 : : } else {
413 : : DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
414 : : }
415 : : #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
416 [ + + + - ]: 8 : } else if (c == ';' && rl.escape_seq_buf[0] == '1') {
417 : : // ';' is used to separate parameters. so first parameter was '1',
418 : : // that's used for sequences like ctrl+left, which we will try to parse.
419 : : // escape_seq state is reset back to ESEQ_ESC_BRACKET, as if we've just received
420 : : // the opening bracket, because more parameters are to come.
421 : : // we don't track the parameters themselves to keep low on logic and code size. that
422 : : // might be required in the future if more complex sequences are added.
423 : 4 : rl.escape_seq = ESEQ_ESC_BRACKET;
424 : : // goto away from the state-machine, as rl.escape_seq will be overridden.
425 : 4 : goto redraw;
426 [ + - + + ]: 4 : } else if (rl.escape_seq_buf[0] == '5' && c == 'C') {
427 : : // ctrl+right
428 : 2 : goto forward_word;
429 [ + - + - ]: 2 : } else if (rl.escape_seq_buf[0] == '5' && c == 'D') {
430 : : // ctrl+left
431 : 2 : goto backward_word;
432 : : #endif
433 : : } else {
434 : 10 : DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
435 : : }
436 : 10 : rl.escape_seq = ESEQ_NONE;
437 : : } else if (rl.escape_seq == ESEQ_ESC_O) {
438 [ # # # ]: 0 : switch (c) {
439 : 0 : case 'H':
440 : 0 : goto home_key;
441 : 0 : case 'F':
442 : 0 : goto end_key;
443 : 0 : default:
444 : 0 : DEBUG_printf("(ESC O %d)", c);
445 : 0 : rl.escape_seq = ESEQ_NONE;
446 : : }
447 : : } else {
448 : 0 : rl.escape_seq = ESEQ_NONE;
449 : : }
450 : :
451 : : #if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
452 : 4180 : redraw:
453 : : #endif
454 : :
455 : : // redraw command prompt, efficiently
456 [ + + ]: 4180 : if (redraw_step_back > 0) {
457 : 129 : mp_hal_move_cursor_back(redraw_step_back);
458 : 129 : rl.cursor_pos -= redraw_step_back;
459 : : }
460 [ + + ]: 4180 : if (redraw_from_cursor) {
461 [ + + ]: 4050 : if (rl.line->len < last_line_len) {
462 : : // erase old chars
463 : 69 : mp_hal_erase_line_from_cursor(last_line_len - rl.cursor_pos);
464 : : }
465 : : // draw new chars
466 : 4050 : mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, rl.line->len - rl.cursor_pos);
467 : : // move cursor forward if needed (already moved forward by length of line, so move it back)
468 : 4050 : mp_hal_move_cursor_back(rl.line->len - (rl.cursor_pos + redraw_step_forward));
469 : 4050 : rl.cursor_pos += redraw_step_forward;
470 [ + + ]: 130 : } else if (redraw_step_forward > 0) {
471 : : // draw over old chars to move cursor forwards
472 : 16 : mp_hal_stdout_tx_strn(rl.line->buf + rl.cursor_pos, redraw_step_forward);
473 : 16 : rl.cursor_pos += redraw_step_forward;
474 : : }
475 : :
476 : : #if MICROPY_REPL_AUTO_INDENT
477 : 4180 : rl.auto_indent_state &= ~AUTO_INDENT_JUST_ADDED;
478 : : #endif
479 : :
480 : 4180 : return -1;
481 : : }
482 : :
483 : : #if MICROPY_REPL_AUTO_INDENT
484 : 291 : static void readline_auto_indent(void) {
485 [ + + ]: 291 : if (!(rl.auto_indent_state & AUTO_INDENT_ENABLED)) {
486 : : return;
487 : : }
488 : 279 : vstr_t *line = rl.line;
489 [ + + + - ]: 279 : if (line->len > 1 && line->buf[line->len - 1] == '\n') {
490 : 30 : int i;
491 [ + + ]: 232 : for (i = line->len - 1; i > 0; i--) {
492 [ + + ]: 210 : if (line->buf[i - 1] == '\n') {
493 : : break;
494 : : }
495 : : }
496 : 30 : size_t j;
497 [ + - ]: 62 : for (j = i; j < line->len; j++) {
498 [ + + ]: 62 : if (line->buf[j] != ' ') {
499 : : break;
500 : : }
501 : : }
502 : : // i=start of line; j=first non-space
503 [ + + + + ]: 30 : if (i > 0 && j + 1 == line->len) {
504 : : // previous line is not first line and is all spaces
505 [ + - ]: 12 : for (size_t k = i - 1; k > 0; --k) {
506 [ + + ]: 12 : if (line->buf[k - 1] == '\n') {
507 : : // don't auto-indent if last 2 lines are all spaces
508 : : return;
509 [ + + ]: 10 : } else if (line->buf[k - 1] != ' ') {
510 : : // 2nd previous line is not all spaces
511 : : break;
512 : : }
513 : : }
514 : : }
515 : 28 : int n = (j - i) / 4;
516 [ + + ]: 28 : if (line->buf[line->len - 2] == ':') {
517 : 8 : n += 1;
518 : : }
519 [ + + ]: 42 : while (n-- > 0) {
520 : 14 : vstr_add_strn(line, " ", 4);
521 : 14 : mp_hal_stdout_tx_strn(" ", 4);
522 : 14 : rl.cursor_pos += 4;
523 : 14 : rl.auto_indent_state |= AUTO_INDENT_JUST_ADDED;
524 : : }
525 : : }
526 : : }
527 : : #endif
528 : :
529 : 0 : void readline_note_newline(const char *prompt) {
530 : 0 : rl.orig_line_len = rl.line->len;
531 : 0 : rl.cursor_pos = rl.orig_line_len;
532 : 0 : rl.prompt = prompt;
533 : 0 : mp_hal_stdout_tx_str(prompt);
534 : : #if MICROPY_REPL_AUTO_INDENT
535 : 0 : readline_auto_indent();
536 : : #endif
537 : 0 : }
538 : :
539 : 291 : void readline_init(vstr_t *line, const char *prompt) {
540 : 291 : rl.line = line;
541 : 291 : rl.orig_line_len = line->len;
542 : 291 : rl.escape_seq = ESEQ_NONE;
543 : 291 : rl.escape_seq_buf[0] = 0;
544 : 291 : rl.hist_cur = -1;
545 : 291 : rl.cursor_pos = rl.orig_line_len;
546 : 291 : rl.prompt = prompt;
547 : 291 : mp_hal_stdout_tx_str(prompt);
548 : : #if MICROPY_REPL_AUTO_INDENT
549 [ + + ]: 291 : if (vstr_len(line) == 0) {
550 : : // start with auto-indent enabled
551 : 249 : rl.auto_indent_state = AUTO_INDENT_ENABLED;
552 : : }
553 : 291 : readline_auto_indent();
554 : : #endif
555 : 291 : }
556 : :
557 : 291 : int readline(vstr_t *line, const char *prompt) {
558 : 291 : readline_init(line, prompt);
559 : 4471 : for (;;) {
560 : 4471 : int c = mp_hal_stdin_rx_chr();
561 : 4471 : int r = readline_process_char(c);
562 [ + + ]: 4471 : if (r >= 0) {
563 : 291 : return r;
564 : : }
565 : : }
566 : : }
567 : :
568 : 1691 : void readline_push_history(const char *line) {
569 [ + + ]: 1691 : if (line[0] != '\0'
570 [ + + ]: 1645 : && (MP_STATE_PORT(readline_hist)[0] == NULL
571 [ + + ]: 1617 : || strcmp(MP_STATE_PORT(readline_hist)[0], line) != 0)) {
572 : : // a line which is not empty and different from the last one
573 : : // so update the history
574 : 1641 : char *most_recent_hist = str_dup_maybe(line);
575 [ + - ]: 1641 : if (most_recent_hist != NULL) {
576 [ + + ]: 82050 : for (int i = MICROPY_READLINE_HISTORY_SIZE - 1; i > 0; i--) {
577 : 80409 : MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1];
578 : : }
579 : 1641 : MP_STATE_PORT(readline_hist)[0] = most_recent_hist;
580 : : }
581 : : }
582 : 1691 : }
583 : :
584 : : MP_REGISTER_ROOT_POINTER(const char *readline_hist[MICROPY_READLINE_HISTORY_SIZE]);
|