| 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 | |