Skip to content

Commit c1a0fd7

Browse files
committed
readline: Add backward-word, backward-kill-word, forward-word, forward-kill-word sequences.
1 parent 5352e1e commit c1a0fd7

File tree

9 files changed

+199
-1
lines changed

9 files changed

+199
-1
lines changed

lib/mp-readline/readline.c

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,35 @@ typedef struct _readline_t {
9999

100100
STATIC readline_t rl;
101101

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+
while (1) {
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 ?: -1;
125+
}
126+
127+
return forward ? pos - rl.cursor_pos : rl.cursor_pos - pos;
128+
}
129+
#endif
130+
102131
int readline_process_char(int c) {
103132
size_t last_line_len = rl.line->len;
104133
int redraw_step_back = 0;
@@ -149,6 +178,10 @@ int readline_process_char(int c) {
149178
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
150179
redraw_from_cursor = true;
151180
#endif
181+
#if MICROPY_REPL_EXTRA_WORDS_MOVE
182+
} else if (c == CHAR_CTRL_W) {
183+
goto backward_kill_word;
184+
#endif
152185
} else if (c == '\r') {
153186
// newline
154187
mp_hal_stdout_tx_str("\r\n");
@@ -222,9 +255,40 @@ int readline_process_char(int c) {
222255
case 'O':
223256
rl.escape_seq = ESEQ_ESC_O;
224257
break;
258+
#if MICROPY_REPL_EMACS_WORDS_MOVE
259+
case 'b':
260+
#if MICROPY_REPL_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_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_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
225288
default:
226289
DEBUG_printf("(ESC %d)", c);
227290
rl.escape_seq = ESEQ_NONE;
291+
break;
228292
}
229293
} else if (rl.escape_seq == ESEQ_ESC_BRACKET) {
230294
if ('0' <= c && c <= '9') {
@@ -312,6 +376,24 @@ int readline_process_char(int c) {
312376
} else {
313377
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
314378
}
379+
#if MICROPY_REPL_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 overrided.
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
315397
} else {
316398
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
317399
}
@@ -330,6 +412,10 @@ int readline_process_char(int c) {
330412
rl.escape_seq = ESEQ_NONE;
331413
}
332414

415+
#if MICROPY_REPL_EXTRA_WORDS_MOVE
416+
redraw:
417+
#endif
418+
333419
// redraw command prompt, efficiently
334420
if (redraw_step_back > 0) {
335421
mp_hal_move_cursor_back(redraw_step_back);

lib/mp-readline/readline.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#define CHAR_CTRL_N (14)
3737
#define CHAR_CTRL_P (16)
3838
#define CHAR_CTRL_U (21)
39+
#define CHAR_CTRL_W (23)
3940

4041
void readline_init0(void);
4142
int readline(vstr_t *line, const char *prompt);

ports/unix/mpconfigport.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
#define MICROPY_USE_READLINE_HISTORY (1)
6060
#define MICROPY_HELPER_REPL (1)
6161
#define MICROPY_REPL_EMACS_KEYS (1)
62+
#define MICROPY_REPL_EMACS_WORDS_MOVE (1)
63+
#define MICROPY_REPL_EXTRA_WORDS_MOVE (1)
6264
#define MICROPY_REPL_AUTO_INDENT (1)
6365
#define MICROPY_HELPER_LEXER_UNIX (1)
6466
#define MICROPY_ENABLE_SOURCE_LINE (1)

py/mpconfig.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,21 @@
565565
#define MICROPY_REPL_EMACS_KEYS (0)
566566
#endif
567567

568+
// Whether to include emacs-style word movement/kill readline behavior in REPL.
569+
// This adds Alt+F, Alt+B, Alt+D and Alt+Backspace for forward-word, backward-word, forward-kill-word
570+
// and backward-kill-word, respectively.
571+
#ifndef MICROPY_REPL_EMACS_WORDS_MOVE
572+
#define MICROPY_REPL_EMACS_WORDS_MOVE (0)
573+
#endif
574+
575+
// Whether to include extra convenience keys for word movement/kill in readline REPL.
576+
// This adds Ctrl+Right, Ctrl+Left and Ctrl+W for forward-word, backward-word and backward-kill-word
577+
// respectively. Ctrl+Delete is not implemented because it's a very different escape sequence.
578+
// Depends on MICROPY_REPL_EMACS_WORDS_MOVE.
579+
#ifndef MICROPY_REPL_EXTRA_WORDS_MOVE
580+
#define MICROPY_REPL_EXTRA_WORDS_MOVE (0)
581+
#endif
582+
568583
// Whether to implement auto-indent in REPL
569584
#ifndef MICROPY_REPL_AUTO_INDENT
570585
#define MICROPY_REPL_AUTO_INDENT (0)

tests/cmdline/repl_words_move.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# word movement
2+
# backward-word, start in word
3+
234b1
4+
# backward-word, don't start in word
5+
234 b1
6+
# backward-word on start of line. if cursor is moved, this will result in a SyntaxError
7+
1 2 + 3b+
8+
# forward-word, start in word
9+
1+2 12+f+3
10+
# forward-word, don't start in word
11+
1+ 12 3f+
12+
# forward-word on eol. if cursor is moved, this will result in a SyntaxError
13+
1 + 2 3f+
14+
15+
# kill word
16+
# backward-kill-word, start in word
17+
100 + 45623
18+
# backward-kill-word, don't start in word
19+
100 + 456231
20+
# forward-kill-word, start in word
21+
100 + 256d3
22+
# forward-kill-word, don't start in word
23+
1 + 256d2
24+
25+
# extra move/kill shortcuts
26+
# ctrl-left
27+
2341
28+
# ctrl-right
29+
123
30+
# ctrl-w
31+
1231

tests/cmdline/repl_words_move.py.exp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
MicroPython \.\+ version
2+
Use \.\+
3+
>>> # word movement
4+
>>> # backward-word, start in word
5+
>>> \.\+
6+
1234
7+
>>> # backward-word, don't start in word
8+
>>> \.\+
9+
1234
10+
>>> # backward-word on start of line. if cursor is moved, this will result in a SyntaxError
11+
>>> \.\+
12+
6
13+
>>> # forward-word, start in word
14+
>>> \.\+
15+
18
16+
>>> # forward-word, don't start in word
17+
>>> \.\+
18+
16
19+
>>> # forward-word on eol. if cursor is moved, this will result in a SyntaxError
20+
>>> \.\+
21+
6
22+
>>>
23+
>>> # kill word
24+
>>> # backward-kill-word, start in word
25+
>>> \.\+
26+
123
27+
>>> # backward-kill-word, don't start in word
28+
>>> \.\+
29+
101
30+
>>> # forward-kill-word, start in word
31+
>>> \.\+
32+
123
33+
>>> # forward-kill-word, don't start in word
34+
>>> \.\+
35+
3
36+
>>>
37+
>>> # extra move/kill shortcuts
38+
>>> # ctrl-left
39+
>>> \.\+
40+
1234
41+
>>> # ctrl-right
42+
>>> \.\+
43+
123
44+
>>> # ctrl-w
45+
>>> \.\+
46+
1
47+
>>>
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# just check if ctrl+w is supported, because it makes sure that
2+
# both MICROPY_REPL_EMACS_WORDS_MOVE and MICROPY_REPL_EXTRA_WORDS_MOVE are enabled.
3+
t = 1231
4+
t == 1
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
MicroPython \.\+ version
2+
Use \.\+
3+
>>> # Check for emacs keys in REPL
4+
>>> t = \.\+
5+
>>> t == 2
6+
True
7+
>>>

tests/run-tests

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,9 +284,14 @@ def run_tests(pyb, tests, args, base_path="."):
284284

285285
# Check if emacs repl is supported, and skip such tests if it's not
286286
t = run_feature_check(pyb, args, base_path, 'repl_emacs_check.py')
287-
if not 'True' in str(t, 'ascii'):
287+
if 'True' not in str(t, 'ascii'):
288288
skip_tests.add('cmdline/repl_emacs_keys.py')
289289

290+
# Check if words movement in repl is supported, and skip such tests if it's not
291+
t = run_feature_check(pyb, args, base_path, 'repl_words_move_check.py')
292+
if 'True' not in str(t, 'ascii'):
293+
skip_tests.add('cmdline/repl_words_move.py')
294+
290295
upy_byteorder = run_feature_check(pyb, args, base_path, 'byteorder.py')
291296
upy_float_precision = int(run_feature_check(pyb, args, base_path, 'float.py'))
292297
has_complex = run_feature_check(pyb, args, base_path, 'complex.py') == b'complex\n'

0 commit comments

Comments
 (0)
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