lib/mp-readline: Add word-based move/delete EMACS key sequences.
This commit adds backward-word, backward-kill-word, forward-word, forward-kill-word sequences for the REPL, with bindings to Alt+F, Alt+B, Alt+D and Alt+Backspace respectively. It is disabled by default and can be enabled via MICROPY_REPL_EMACS_WORDS_MOVE. Further enabling MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE adds extra bindings for these new sequences: Ctrl+Right, Ctrl+Left and Ctrl+W. The features are enabled on unix micropython-coverage and micropython-dev.
This commit is contained in:
parent
dce590c29d
commit
853aaa06f2
|
@ -99,6 +99,35 @@ typedef struct _readline_t {
|
||||||
|
|
||||||
STATIC readline_t rl;
|
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;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
// 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 ? forward : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return forward ? pos - rl.cursor_pos : rl.cursor_pos - pos;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int readline_process_char(int c) {
|
int readline_process_char(int c) {
|
||||||
size_t last_line_len = rl.line->len;
|
size_t last_line_len = rl.line->len;
|
||||||
int redraw_step_back = 0;
|
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_step_back = rl.cursor_pos - rl.orig_line_len;
|
||||||
redraw_from_cursor = true;
|
redraw_from_cursor = true;
|
||||||
#endif
|
#endif
|
||||||
|
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
|
||||||
|
} else if (c == CHAR_CTRL_W) {
|
||||||
|
goto backward_kill_word;
|
||||||
|
#endif
|
||||||
} else if (c == '\r') {
|
} else if (c == '\r') {
|
||||||
// newline
|
// newline
|
||||||
mp_hal_stdout_tx_str("\r\n");
|
mp_hal_stdout_tx_str("\r\n");
|
||||||
|
@ -222,9 +255,40 @@ int readline_process_char(int c) {
|
||||||
case 'O':
|
case 'O':
|
||||||
rl.escape_seq = ESEQ_ESC_O;
|
rl.escape_seq = ESEQ_ESC_O;
|
||||||
break;
|
break;
|
||||||
|
#if MICROPY_REPL_EMACS_WORDS_MOVE
|
||||||
|
case 'b':
|
||||||
|
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
|
||||||
|
backward_word:
|
||||||
|
#endif
|
||||||
|
redraw_step_back = cursor_count_word(0);
|
||||||
|
rl.escape_seq = ESEQ_NONE;
|
||||||
|
break;
|
||||||
|
case 'f':
|
||||||
|
#if MICROPY_REPL_EMACS_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_EMACS_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:
|
default:
|
||||||
DEBUG_printf("(ESC %d)", c);
|
DEBUG_printf("(ESC %d)", c);
|
||||||
rl.escape_seq = ESEQ_NONE;
|
rl.escape_seq = ESEQ_NONE;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} else if (rl.escape_seq == ESEQ_ESC_BRACKET) {
|
} else if (rl.escape_seq == ESEQ_ESC_BRACKET) {
|
||||||
if ('0' <= c && c <= '9') {
|
if ('0' <= c && c <= '9') {
|
||||||
|
@ -312,6 +376,24 @@ delete_key:
|
||||||
} else {
|
} else {
|
||||||
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
|
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
|
||||||
}
|
}
|
||||||
|
#if MICROPY_REPL_EMACS_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 overridden.
|
||||||
|
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 {
|
} else {
|
||||||
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
|
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
|
||||||
}
|
}
|
||||||
|
@ -330,6 +412,10 @@ delete_key:
|
||||||
rl.escape_seq = ESEQ_NONE;
|
rl.escape_seq = ESEQ_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE
|
||||||
|
redraw:
|
||||||
|
#endif
|
||||||
|
|
||||||
// redraw command prompt, efficiently
|
// redraw command prompt, efficiently
|
||||||
if (redraw_step_back > 0) {
|
if (redraw_step_back > 0) {
|
||||||
mp_hal_move_cursor_back(redraw_step_back);
|
mp_hal_move_cursor_back(redraw_step_back);
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#define CHAR_CTRL_N (14)
|
#define CHAR_CTRL_N (14)
|
||||||
#define CHAR_CTRL_P (16)
|
#define CHAR_CTRL_P (16)
|
||||||
#define CHAR_CTRL_U (21)
|
#define CHAR_CTRL_U (21)
|
||||||
|
#define CHAR_CTRL_W (23)
|
||||||
|
|
||||||
void readline_init0(void);
|
void readline_init0(void);
|
||||||
int readline(vstr_t *line, const char *prompt);
|
int readline(vstr_t *line, const char *prompt);
|
||||||
|
|
|
@ -34,6 +34,8 @@
|
||||||
#define MICROPY_FLOAT_HIGH_QUALITY_HASH (1)
|
#define MICROPY_FLOAT_HIGH_QUALITY_HASH (1)
|
||||||
#define MICROPY_ENABLE_SCHEDULER (1)
|
#define MICROPY_ENABLE_SCHEDULER (1)
|
||||||
#define MICROPY_READER_VFS (1)
|
#define MICROPY_READER_VFS (1)
|
||||||
|
#define MICROPY_REPL_EMACS_WORDS_MOVE (1)
|
||||||
|
#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1)
|
||||||
#define MICROPY_WARNINGS_CATEGORY (1)
|
#define MICROPY_WARNINGS_CATEGORY (1)
|
||||||
#define MICROPY_MODULE_GETATTR (1)
|
#define MICROPY_MODULE_GETATTR (1)
|
||||||
#define MICROPY_PY_DELATTR_SETATTR (1)
|
#define MICROPY_PY_DELATTR_SETATTR (1)
|
||||||
|
|
|
@ -24,4 +24,7 @@
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#define MICROPY_REPL_EMACS_WORDS_MOVE (1)
|
||||||
|
#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (1)
|
||||||
|
|
||||||
#define MICROPY_PY_SYS_SETTRACE (1)
|
#define MICROPY_PY_SYS_SETTRACE (1)
|
||||||
|
|
|
@ -570,6 +570,21 @@
|
||||||
#define MICROPY_REPL_EMACS_KEYS (0)
|
#define MICROPY_REPL_EMACS_KEYS (0)
|
||||||
#endif
|
#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_EMACS_EXTRA_WORDS_MOVE
|
||||||
|
#define MICROPY_REPL_EMACS_EXTRA_WORDS_MOVE (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
// Whether to implement auto-indent in REPL
|
// Whether to implement auto-indent in REPL
|
||||||
#ifndef MICROPY_REPL_AUTO_INDENT
|
#ifndef MICROPY_REPL_AUTO_INDENT
|
||||||
#define MICROPY_REPL_AUTO_INDENT (0)
|
#define MICROPY_REPL_AUTO_INDENT (0)
|
||||||
|
|
|
@ -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
|
||||||
|
234[1;5D1
|
||||||
|
# ctrl-right
|
||||||
|
12[1;5C3
|
||||||
|
# ctrl-w
|
||||||
|
1231
|
|
@ -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
|
||||||
|
>>>
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
||||||
|
MicroPython \.\+ version
|
||||||
|
Use \.\+
|
||||||
|
>>> # Check for emacs keys in REPL
|
||||||
|
>>> t = \.\+
|
||||||
|
>>> t == 2
|
||||||
|
True
|
||||||
|
>>>
|
|
@ -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
|
# 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')
|
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')
|
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_byteorder = run_feature_check(pyb, args, base_path, 'byteorder.py')
|
||||||
upy_float_precision = run_feature_check(pyb, args, base_path, 'float.py')
|
upy_float_precision = run_feature_check(pyb, args, base_path, 'float.py')
|
||||||
if upy_float_precision == b'CRASH':
|
if upy_float_precision == b'CRASH':
|
||||||
|
|
Loading…
Reference in New Issue