From 79b08ad9e881b172e1bed3e3454aa25e13f3139c Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sat, 14 Dec 2019 14:38:15 +0200 Subject: [PATCH 1/3] py/unicode: Add unichar_isalnum(). --- py/misc.h | 1 + py/unicode.c | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/py/misc.h b/py/misc.h index 0aa4a5d2c6c91..12bf7b38ebfb1 100644 --- a/py/misc.h +++ b/py/misc.h @@ -140,6 +140,7 @@ bool unichar_isprint(unichar c); bool unichar_isdigit(unichar c); bool unichar_isxdigit(unichar c); bool unichar_isident(unichar c); +bool unichar_isalnum(unichar c); bool unichar_isupper(unichar c); bool unichar_islower(unichar c); unichar unichar_tolower(unichar c); diff --git a/py/unicode.c b/py/unicode.c index d69b6f56f0fde..369240e23ca31 100644 --- a/py/unicode.c +++ b/py/unicode.c @@ -140,6 +140,10 @@ bool unichar_isident(unichar c) { return c < 128 && ((attr[c] & (FL_ALPHA | FL_DIGIT)) != 0 || c == '_'); } +bool unichar_isalnum(unichar c) { + return c < 128 && ((attr[c] & (FL_ALPHA | FL_DIGIT)) != 0); +} + bool unichar_isupper(unichar c) { return c < 128 && (attr[c] & FL_UPPER) != 0; } From 5352e1e1b1f56efa3f8a90cb1bc89af5c3db960a Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Tue, 17 Dec 2019 22:40:40 +0200 Subject: [PATCH 2/3] readline: Add an assert() to catch buffer overflows. During readline development, this function may receive bad `pos` values. It's easier to understand the assert() failing error than to have a "stack smashing detected" message. --- lib/mp-readline/readline.c | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/mp-readline/readline.c b/lib/mp-readline/readline.c index 9d254d8cfebe7..1500873f69ed1 100644 --- a/lib/mp-readline/readline.c +++ b/lib/mp-readline/readline.c @@ -74,6 +74,7 @@ STATIC void mp_hal_move_cursor_back(uint pos) { // snprintf needs space for the terminating null character int n = snprintf(&vt100_command[0], sizeof(vt100_command), "\x1b[%u", pos); if (n > 0) { + assert((unsigned)n < sizeof(vt100_command)); vt100_command[n] = 'D'; // replace null char mp_hal_stdout_tx_strn(vt100_command, n + 1); } From c1a0fd7693ce6d1be4496c9d38538df44aa6c336 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sat, 14 Dec 2019 23:42:03 +0200 Subject: [PATCH 3/3] readline: Add backward-word, backward-kill-word, forward-word, forward-kill-word sequences. --- lib/mp-readline/readline.c | 86 +++++++++++++++++++ lib/mp-readline/readline.h | 1 + ports/unix/mpconfigport.h | 2 + py/mpconfig.h | 15 ++++ tests/cmdline/repl_words_move.py | 31 +++++++ tests/cmdline/repl_words_move.py.exp | 47 ++++++++++ tests/feature_check/repl_words_move_check.py | 4 + .../repl_words_move_check.py.exp | 7 ++ tests/run-tests | 7 +- 9 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 tests/cmdline/repl_words_move.py create mode 100644 tests/cmdline/repl_words_move.py.exp create mode 100644 tests/feature_check/repl_words_move_check.py create mode 100644 tests/feature_check/repl_words_move_check.py.exp diff --git a/lib/mp-readline/readline.c b/lib/mp-readline/readline.c index 1500873f69ed1..f3513a0602b9e 100644 --- a/lib/mp-readline/readline.c +++ b/lib/mp-readline/readline.c @@ -99,6 +99,35 @@ typedef struct _readline_t { STATIC readline_t rl; +#if MICROPY_REPL_EMACS_WORDS_MOVE +STATIC size_t cursor_count_word(int forward) { + const char *line_buf = vstr_str(rl.line); + size_t pos = rl.cursor_pos; + bool in_word = false; + + while (1) { + // if moving backwards and we've reached 0... break + if (!forward && pos == 0) { + break; + } + // or if moving forwards and we've reached to the end of line... break + else if (forward && pos == vstr_len(rl.line)) { + break; + } + + if (unichar_isalnum(line_buf[pos + (forward - 1)])) { + in_word = true; + } else if (in_word) { + break; + } + + pos += forward ?: -1; + } + + return forward ? pos - rl.cursor_pos : rl.cursor_pos - pos; +} +#endif + int readline_process_char(int c) { size_t last_line_len = rl.line->len; int redraw_step_back = 0; @@ -149,6 +178,10 @@ int readline_process_char(int c) { redraw_step_back = rl.cursor_pos - rl.orig_line_len; redraw_from_cursor = true; #endif + #if MICROPY_REPL_EXTRA_WORDS_MOVE + } else if (c == CHAR_CTRL_W) { + goto backward_kill_word; + #endif } else if (c == '\r') { // newline mp_hal_stdout_tx_str("\r\n"); @@ -222,9 +255,40 @@ int readline_process_char(int c) { case 'O': rl.escape_seq = ESEQ_ESC_O; break; + #if MICROPY_REPL_EMACS_WORDS_MOVE + case 'b': +#if MICROPY_REPL_EXTRA_WORDS_MOVE +backward_word: +#endif + redraw_step_back = cursor_count_word(0); + rl.escape_seq = ESEQ_NONE; + break; + case 'f': +#if MICROPY_REPL_EXTRA_WORDS_MOVE +forward_word: +#endif + redraw_step_forward = cursor_count_word(1); + rl.escape_seq = ESEQ_NONE; + break; + case 'd': + vstr_cut_out_bytes(rl.line, rl.cursor_pos, cursor_count_word(1)); + redraw_from_cursor = true; + rl.escape_seq = ESEQ_NONE; + break; + case 127: +#if MICROPY_REPL_EXTRA_WORDS_MOVE +backward_kill_word: +#endif + redraw_step_back = cursor_count_word(0); + vstr_cut_out_bytes(rl.line, rl.cursor_pos - redraw_step_back, redraw_step_back); + redraw_from_cursor = true; + rl.escape_seq = ESEQ_NONE; + break; + #endif default: DEBUG_printf("(ESC %d)", c); rl.escape_seq = ESEQ_NONE; + break; } } else if (rl.escape_seq == ESEQ_ESC_BRACKET) { if ('0' <= c && c <= '9') { @@ -312,6 +376,24 @@ int readline_process_char(int c) { } else { DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c); } + #if MICROPY_REPL_EXTRA_WORDS_MOVE + } else if (c == ';' && rl.escape_seq_buf[0] == '1') { + // ';' is used to separate parameters. so first parameter was '1', + // that's used for sequences like ctrl+left, which we will try to parse. + // escape_seq state is reset back to ESEQ_ESC_BRACKET, as if we've just received + // the opening bracket, because more parameters are to come. + // we don't track the parameters themselves to keep low on logic and code size. that + // might be required in the future if more complex sequences are added. + rl.escape_seq = ESEQ_ESC_BRACKET; + // goto away from the state-machine, as rl.escape_seq will be overrided. + goto redraw; + } else if (rl.escape_seq_buf[0] == '5' && c == 'C') { + // ctrl+right + goto forward_word; + } else if (rl.escape_seq_buf[0] == '5' && c == 'D') { + // ctrl+left + goto backward_word; + #endif } else { DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c); } @@ -330,6 +412,10 @@ int readline_process_char(int c) { rl.escape_seq = ESEQ_NONE; } +#if MICROPY_REPL_EXTRA_WORDS_MOVE +redraw: +#endif + // redraw command prompt, efficiently if (redraw_step_back > 0) { mp_hal_move_cursor_back(redraw_step_back); diff --git a/lib/mp-readline/readline.h b/lib/mp-readline/readline.h index 00aa9622a8023..a19e1209ab716 100644 --- a/lib/mp-readline/readline.h +++ b/lib/mp-readline/readline.h @@ -36,6 +36,7 @@ #define CHAR_CTRL_N (14) #define CHAR_CTRL_P (16) #define CHAR_CTRL_U (21) +#define CHAR_CTRL_W (23) void readline_init0(void); int readline(vstr_t *line, const char *prompt); diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h index 40cd1f570252c..3fa6149a99172 100644 --- a/ports/unix/mpconfigport.h +++ b/ports/unix/mpconfigport.h @@ -59,6 +59,8 @@ #define MICROPY_USE_READLINE_HISTORY (1) #define MICROPY_HELPER_REPL (1) #define MICROPY_REPL_EMACS_KEYS (1) +#define MICROPY_REPL_EMACS_WORDS_MOVE (1) +#define MICROPY_REPL_EXTRA_WORDS_MOVE (1) #define MICROPY_REPL_AUTO_INDENT (1) #define MICROPY_HELPER_LEXER_UNIX (1) #define MICROPY_ENABLE_SOURCE_LINE (1) diff --git a/py/mpconfig.h b/py/mpconfig.h index e46da3e83c721..b98d05aa5538d 100644 --- a/py/mpconfig.h +++ b/py/mpconfig.h @@ -565,6 +565,21 @@ #define MICROPY_REPL_EMACS_KEYS (0) #endif +// Whether to include emacs-style word movement/kill readline behavior in REPL. +// This adds Alt+F, Alt+B, Alt+D and Alt+Backspace for forward-word, backward-word, forward-kill-word +// and backward-kill-word, respectively. +#ifndef MICROPY_REPL_EMACS_WORDS_MOVE +#define MICROPY_REPL_EMACS_WORDS_MOVE (0) +#endif + +// Whether to include extra convenience keys for word movement/kill in readline REPL. +// This adds Ctrl+Right, Ctrl+Left and Ctrl+W for forward-word, backward-word and backward-kill-word +// respectively. Ctrl+Delete is not implemented because it's a very different escape sequence. +// Depends on MICROPY_REPL_EMACS_WORDS_MOVE. +#ifndef MICROPY_REPL_EXTRA_WORDS_MOVE +#define MICROPY_REPL_EXTRA_WORDS_MOVE (0) +#endif + // Whether to implement auto-indent in REPL #ifndef MICROPY_REPL_AUTO_INDENT #define MICROPY_REPL_AUTO_INDENT (0) diff --git a/tests/cmdline/repl_words_move.py b/tests/cmdline/repl_words_move.py new file mode 100644 index 0000000000000..e1eb5295350c1 --- /dev/null +++ b/tests/cmdline/repl_words_move.py @@ -0,0 +1,31 @@ +# word movement +# backward-word, start in word +234b1 +# backward-word, don't start in word +234 b1 +# backward-word on start of line. if cursor is moved, this will result in a SyntaxError +1 2 + 3b+ +# forward-word, start in word +1+2 12+f+3 +# forward-word, don't start in word +1+ 12 3f+ +# forward-word on eol. if cursor is moved, this will result in a SyntaxError +1 + 2 3f+ + +# kill word +# backward-kill-word, start in word +100 + 45623 +# backward-kill-word, don't start in word +100 + 456231 +# forward-kill-word, start in word +100 + 256d3 +# forward-kill-word, don't start in word +1 + 256d2 + +# extra move/kill shortcuts +# ctrl-left +2341 +# ctrl-right +123 +# ctrl-w +1231 diff --git a/tests/cmdline/repl_words_move.py.exp b/tests/cmdline/repl_words_move.py.exp new file mode 100644 index 0000000000000..86f6b7788989e --- /dev/null +++ b/tests/cmdline/repl_words_move.py.exp @@ -0,0 +1,47 @@ +MicroPython \.\+ version +Use \.\+ +>>> # word movement +>>> # backward-word, start in word +>>> \.\+ +1234 +>>> # backward-word, don't start in word +>>> \.\+ +1234 +>>> # backward-word on start of line. if cursor is moved, this will result in a SyntaxError +>>> \.\+ +6 +>>> # forward-word, start in word +>>> \.\+ +18 +>>> # forward-word, don't start in word +>>> \.\+ +16 +>>> # forward-word on eol. if cursor is moved, this will result in a SyntaxError +>>> \.\+ +6 +>>> +>>> # kill word +>>> # backward-kill-word, start in word +>>> \.\+ +123 +>>> # backward-kill-word, don't start in word +>>> \.\+ +101 +>>> # forward-kill-word, start in word +>>> \.\+ +123 +>>> # forward-kill-word, don't start in word +>>> \.\+ +3 +>>> +>>> # extra move/kill shortcuts +>>> # ctrl-left +>>> \.\+ +1234 +>>> # ctrl-right +>>> \.\+ +123 +>>> # ctrl-w +>>> \.\+ +1 +>>> diff --git a/tests/feature_check/repl_words_move_check.py b/tests/feature_check/repl_words_move_check.py new file mode 100644 index 0000000000000..e74615e98cad3 --- /dev/null +++ b/tests/feature_check/repl_words_move_check.py @@ -0,0 +1,4 @@ +# just check if ctrl+w is supported, because it makes sure that +# both MICROPY_REPL_EMACS_WORDS_MOVE and MICROPY_REPL_EXTRA_WORDS_MOVE are enabled. +t = 1231 +t == 1 diff --git a/tests/feature_check/repl_words_move_check.py.exp b/tests/feature_check/repl_words_move_check.py.exp new file mode 100644 index 0000000000000..82a4e28ee4f84 --- /dev/null +++ b/tests/feature_check/repl_words_move_check.py.exp @@ -0,0 +1,7 @@ +MicroPython \.\+ version +Use \.\+ +>>> # Check for emacs keys in REPL +>>> t = \.\+ +>>> t == 2 +True +>>> diff --git a/tests/run-tests b/tests/run-tests index 77667c271153f..38aadd4662f3b 100755 --- a/tests/run-tests +++ b/tests/run-tests @@ -284,9 +284,14 @@ def run_tests(pyb, tests, args, base_path="."): # Check if emacs repl is supported, and skip such tests if it's not t = run_feature_check(pyb, args, base_path, 'repl_emacs_check.py') - if not 'True' in str(t, 'ascii'): + if 'True' not in str(t, 'ascii'): skip_tests.add('cmdline/repl_emacs_keys.py') + # Check if words movement in repl is supported, and skip such tests if it's not + t = run_feature_check(pyb, args, base_path, 'repl_words_move_check.py') + if 'True' not in str(t, 'ascii'): + skip_tests.add('cmdline/repl_words_move.py') + upy_byteorder = run_feature_check(pyb, args, base_path, 'byteorder.py') upy_float_precision = int(run_feature_check(pyb, args, base_path, 'float.py')) has_complex = run_feature_check(pyb, args, base_path, 'complex.py') == b'complex\n' pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy