LCOV - code coverage report
Current view: top level - shared/readline - readline.c (source / functions) Hit Total Coverage
Test: unix_coverage_v1.22.0-313-g628a37e6c.info Lines: 226 258 87.6 %
Date: 2024-03-28 12:42:09 Functions: 10 11 90.9 %
Branches: 135 177 76.3 %

           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]);

Generated by: LCOV version 1.15-5-g462f71d