Skip to content

Commit 3f9f9ca

Browse files
committed
lib/mp-readline: Refactor to support coroutine/event-driven usage.
readline_process_char() can be fed character by character, for example, received from external event loop. This will allow to integrate MicroPython into cooperative multitasking systems.
1 parent 708574b commit 3f9f9ca

2 files changed

Lines changed: 199 additions & 165 deletions

File tree

lib/mp-readline/readline.c

Lines changed: 195 additions & 165 deletions
Original file line numberDiff line numberDiff line change
@@ -57,190 +57,220 @@ STATIC char *str_dup_maybe(const char *str) {
5757
return s2;
5858
}
5959

60-
int readline(vstr_t *line, const char *prompt) {
61-
stdout_tx_str(prompt);
62-
int orig_line_len = line->len;
63-
int escape_seq = ESEQ_NONE;
64-
char escape_seq_buf[1] = {0};
65-
int hist_cur = -1;
66-
int cursor_pos = orig_line_len;
67-
for (;;) {
68-
int c = stdin_rx_chr();
69-
int last_line_len = line->len;
70-
int redraw_step_back = 0;
71-
bool redraw_from_cursor = false;
72-
int redraw_step_forward = 0;
73-
if (escape_seq == ESEQ_NONE) {
74-
if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_D && vstr_len(line) == orig_line_len) {
75-
// control character with empty line
76-
return c;
77-
} else if (c == CHAR_CTRL_A) {
78-
// CTRL-A with non-empty line is go-to-start-of-line
79-
goto home_key;
80-
} else if (c == CHAR_CTRL_C) {
81-
// CTRL-C with non-empty line is cancel
82-
return c;
83-
} else if (c == CHAR_CTRL_E) {
84-
// CTRL-E is go-to-end-of-line
85-
goto end_key;
86-
} else if (c == '\r') {
87-
// newline
88-
stdout_tx_str("\r\n");
89-
if (line->len > orig_line_len && (MP_STATE_PORT(readline_hist)[0] == NULL || strcmp(MP_STATE_PORT(readline_hist)[0], line->buf + orig_line_len) != 0)) {
90-
// a line which is not empty and different from the last one
91-
// so update the history
92-
char *most_recent_hist = str_dup_maybe(line->buf + orig_line_len);
93-
if (most_recent_hist != NULL) {
94-
for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) {
95-
MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1];
96-
}
97-
MP_STATE_PORT(readline_hist)[0] = most_recent_hist;
60+
typedef struct _readline_t {
61+
vstr_t *line;
62+
int orig_line_len;
63+
int escape_seq;
64+
int hist_cur;
65+
int cursor_pos;
66+
char escape_seq_buf[1];
67+
} readline_t;
68+
69+
readline_t rl;
70+
71+
int readline_process_char(int c) {
72+
int last_line_len = rl.line->len;
73+
int redraw_step_back = 0;
74+
bool redraw_from_cursor = false;
75+
int redraw_step_forward = 0;
76+
if (rl.escape_seq == ESEQ_NONE) {
77+
if (CHAR_CTRL_A <= c && c <= CHAR_CTRL_D && vstr_len(rl.line) == rl.orig_line_len) {
78+
// control character with empty line
79+
return c;
80+
} else if (c == CHAR_CTRL_A) {
81+
// CTRL-A with non-empty line is go-to-start-of-line
82+
goto home_key;
83+
} else if (c == CHAR_CTRL_C) {
84+
// CTRL-C with non-empty line is cancel
85+
return c;
86+
} else if (c == CHAR_CTRL_E) {
87+
// CTRL-E is go-to-end-of-line
88+
goto end_key;
89+
} else if (c == '\r') {
90+
// newline
91+
stdout_tx_str("\r\n");
92+
if (rl.line->len > rl.orig_line_len && (MP_STATE_PORT(readline_hist)[0] == NULL || strcmp(MP_STATE_PORT(readline_hist)[0], rl.line->buf + rl.orig_line_len) != 0)) {
93+
// a line which is not empty and different from the last one
94+
// so update the history
95+
char *most_recent_hist = str_dup_maybe(rl.line->buf + rl.orig_line_len);
96+
if (most_recent_hist != NULL) {
97+
for (int i = READLINE_HIST_SIZE - 1; i > 0; i--) {
98+
MP_STATE_PORT(readline_hist)[i] = MP_STATE_PORT(readline_hist)[i - 1];
9899
}
100+
MP_STATE_PORT(readline_hist)[0] = most_recent_hist;
99101
}
100-
return 0;
101-
} else if (c == 27) {
102-
// escape sequence
103-
escape_seq = ESEQ_ESC;
104-
} else if (c == 8 || c == 127) {
105-
// backspace/delete
106-
if (cursor_pos > orig_line_len) {
107-
vstr_cut_out_bytes(line, cursor_pos - 1, 1);
108-
// set redraw parameters
109-
redraw_step_back = 1;
110-
redraw_from_cursor = true;
111-
}
112-
} else if (32 <= c && c <= 126) {
113-
// printable character
114-
vstr_ins_char(line, cursor_pos, c);
102+
}
103+
return 0;
104+
} else if (c == 27) {
105+
// escape sequence
106+
rl.escape_seq = ESEQ_ESC;
107+
} else if (c == 8 || c == 127) {
108+
// backspace/delete
109+
if (rl.cursor_pos > rl.orig_line_len) {
110+
vstr_cut_out_bytes(rl.line, rl.cursor_pos - 1, 1);
115111
// set redraw parameters
112+
redraw_step_back = 1;
116113
redraw_from_cursor = true;
117-
redraw_step_forward = 1;
118-
}
119-
} else if (escape_seq == ESEQ_ESC) {
120-
switch (c) {
121-
case '[':
122-
escape_seq = ESEQ_ESC_BRACKET;
123-
break;
124-
case 'O':
125-
escape_seq = ESEQ_ESC_O;
126-
break;
127-
default:
128-
DEBUG_printf("(ESC %d)", c);
129-
escape_seq = ESEQ_NONE;
130114
}
131-
} else if (escape_seq == ESEQ_ESC_BRACKET) {
132-
if ('0' <= c && c <= '9') {
133-
escape_seq = ESEQ_ESC_BRACKET_DIGIT;
134-
escape_seq_buf[0] = c;
135-
} else {
136-
escape_seq = ESEQ_NONE;
137-
if (c == 'A') {
138-
// up arrow
139-
if (hist_cur + 1 < READLINE_HIST_SIZE && MP_STATE_PORT(readline_hist)[hist_cur + 1] != NULL) {
140-
// increase hist num
141-
hist_cur += 1;
142-
// set line to history
143-
line->len = orig_line_len;
144-
vstr_add_str(line, MP_STATE_PORT(readline_hist)[hist_cur]);
145-
// set redraw parameters
146-
redraw_step_back = cursor_pos - orig_line_len;
147-
redraw_from_cursor = true;
148-
redraw_step_forward = line->len - orig_line_len;
149-
}
150-
} else if (c == 'B') {
151-
// down arrow
152-
if (hist_cur >= 0) {
153-
// decrease hist num
154-
hist_cur -= 1;
155-
// set line to history
156-
vstr_cut_tail_bytes(line, line->len - orig_line_len);
157-
if (hist_cur >= 0) {
158-
vstr_add_str(line, MP_STATE_PORT(readline_hist)[hist_cur]);
159-
}
160-
// set redraw parameters
161-
redraw_step_back = cursor_pos - orig_line_len;
162-
redraw_from_cursor = true;
163-
redraw_step_forward = line->len - orig_line_len;
164-
}
165-
} else if (c == 'C') {
166-
// right arrow
167-
if (cursor_pos < line->len) {
168-
redraw_step_forward = 1;
169-
}
170-
} else if (c == 'D') {
171-
// left arrow
172-
if (cursor_pos > orig_line_len) {
173-
redraw_step_back = 1;
115+
} else if (32 <= c && c <= 126) {
116+
// printable character
117+
vstr_ins_char(rl.line, rl.cursor_pos, c);
118+
// set redraw parameters
119+
redraw_from_cursor = true;
120+
redraw_step_forward = 1;
121+
}
122+
} else if (rl.escape_seq == ESEQ_ESC) {
123+
switch (c) {
124+
case '[':
125+
rl.escape_seq = ESEQ_ESC_BRACKET;
126+
break;
127+
case 'O':
128+
rl.escape_seq = ESEQ_ESC_O;
129+
break;
130+
default:
131+
DEBUG_printf("(ESC %d)", c);
132+
rl.escape_seq = ESEQ_NONE;
133+
}
134+
} else if (rl.escape_seq == ESEQ_ESC_BRACKET) {
135+
if ('0' <= c && c <= '9') {
136+
rl.escape_seq = ESEQ_ESC_BRACKET_DIGIT;
137+
rl.escape_seq_buf[0] = c;
138+
} else {
139+
rl.escape_seq = ESEQ_NONE;
140+
if (c == 'A') {
141+
// up arrow
142+
if (rl.hist_cur + 1 < READLINE_HIST_SIZE && MP_STATE_PORT(readline_hist)[rl.hist_cur + 1] != NULL) {
143+
// increase hist num
144+
rl.hist_cur += 1;
145+
// set line to history
146+
rl.line->len = rl.orig_line_len;
147+
vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
148+
// set redraw parameters
149+
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
150+
redraw_from_cursor = true;
151+
redraw_step_forward = rl.line->len - rl.orig_line_len;
152+
}
153+
} else if (c == 'B') {
154+
// down arrow
155+
if (rl.hist_cur >= 0) {
156+
// decrease hist num
157+
rl.hist_cur -= 1;
158+
// set line to history
159+
vstr_cut_tail_bytes(rl.line, rl.line->len - rl.orig_line_len);
160+
if (rl.hist_cur >= 0) {
161+
vstr_add_str(rl.line, MP_STATE_PORT(readline_hist)[rl.hist_cur]);
174162
}
175-
} else if (c == 'H') {
176-
// home
177-
goto home_key;
178-
} else if (c == 'F') {
179-
// end
180-
goto end_key;
181-
} else {
182-
DEBUG_printf("(ESC [ %d)", c);
163+
// set redraw parameters
164+
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
165+
redraw_from_cursor = true;
166+
redraw_step_forward = rl.line->len - rl.orig_line_len;
167+
}
168+
} else if (c == 'C') {
169+
// right arrow
170+
if (rl.cursor_pos < rl.line->len) {
171+
redraw_step_forward = 1;
172+
}
173+
} else if (c == 'D') {
174+
// left arrow
175+
if (rl.cursor_pos > rl.orig_line_len) {
176+
redraw_step_back = 1;
183177
}
178+
} else if (c == 'H') {
179+
// home
180+
goto home_key;
181+
} else if (c == 'F') {
182+
// end
183+
goto end_key;
184+
} else {
185+
DEBUG_printf("(ESC [ %d)", c);
184186
}
185-
} else if (escape_seq == ESEQ_ESC_BRACKET_DIGIT) {
186-
if (c == '~') {
187-
if (escape_seq_buf[0] == '1' || escape_seq_buf[0] == '7') {
187+
}
188+
} else if (rl.escape_seq == ESEQ_ESC_BRACKET_DIGIT) {
189+
if (c == '~') {
190+
if (rl.escape_seq_buf[0] == '1' || rl.escape_seq_buf[0] == '7') {
188191
home_key:
189-
redraw_step_back = cursor_pos - orig_line_len;
190-
} else if (escape_seq_buf[0] == '4' || escape_seq_buf[0] == '8') {
192+
redraw_step_back = rl.cursor_pos - rl.orig_line_len;
193+
} else if (rl.escape_seq_buf[0] == '4' || rl.escape_seq_buf[0] == '8') {
191194
end_key:
192-
redraw_step_forward = line->len - cursor_pos;
193-
} else {
194-
DEBUG_printf("(ESC [ %c %d)", escape_seq_buf[0], c);
195-
}
195+
redraw_step_forward = rl.line->len - rl.cursor_pos;
196196
} else {
197-
DEBUG_printf("(ESC [ %c %d)", escape_seq_buf[0], c);
198-
}
199-
escape_seq = ESEQ_NONE;
200-
} else if (escape_seq == ESEQ_ESC_O) {
201-
switch (c) {
202-
case 'H':
203-
goto home_key;
204-
case 'F':
205-
goto end_key;
206-
default:
207-
DEBUG_printf("(ESC O %d)", c);
208-
escape_seq = ESEQ_NONE;
197+
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
209198
}
210199
} else {
211-
escape_seq = ESEQ_NONE;
200+
DEBUG_printf("(ESC [ %c %d)", rl.escape_seq_buf[0], c);
212201
}
202+
rl.escape_seq = ESEQ_NONE;
203+
} else if (rl.escape_seq == ESEQ_ESC_O) {
204+
switch (c) {
205+
case 'H':
206+
goto home_key;
207+
case 'F':
208+
goto end_key;
209+
default:
210+
DEBUG_printf("(ESC O %d)", c);
211+
rl.escape_seq = ESEQ_NONE;
212+
}
213+
} else {
214+
rl.escape_seq = ESEQ_NONE;
215+
}
213216

214-
// redraw command prompt, efficiently
215-
// TODO we can probably use some more sophisticated VT100 commands here
216-
if (redraw_step_back > 0) {
217-
for (int i = 0; i < redraw_step_back; i++) {
218-
stdout_tx_str("\b");
219-
}
220-
cursor_pos -= redraw_step_back;
217+
// redraw command prompt, efficiently
218+
// TODO we can probably use some more sophisticated VT100 commands here
219+
if (redraw_step_back > 0) {
220+
for (int i = 0; i < redraw_step_back; i++) {
221+
stdout_tx_str("\b");
221222
}
222-
if (redraw_from_cursor) {
223-
if (line->len < last_line_len) {
224-
// erase old chars
225-
for (int i = cursor_pos; i < last_line_len; i++) {
226-
stdout_tx_str(" ");
227-
}
228-
// step back
229-
for (int i = cursor_pos; i < last_line_len; i++) {
230-
stdout_tx_str("\b");
231-
}
223+
rl.cursor_pos -= redraw_step_back;
224+
}
225+
if (redraw_from_cursor) {
226+
if (rl.line->len < last_line_len) {
227+
// erase old chars
228+
for (int i = rl.cursor_pos; i < last_line_len; i++) {
229+
stdout_tx_str(" ");
232230
}
233-
// draw new chars
234-
stdout_tx_strn(line->buf + cursor_pos, line->len - cursor_pos);
235-
// move cursor forward if needed (already moved forward by length of line, so move it back)
236-
for (int i = cursor_pos + redraw_step_forward; i < line->len; i++) {
231+
// step back
232+
for (int i = rl.cursor_pos; i < last_line_len; i++) {
237233
stdout_tx_str("\b");
238234
}
239-
cursor_pos += redraw_step_forward;
240-
} else if (redraw_step_forward > 0) {
241-
// draw over old chars to move cursor forwards
242-
stdout_tx_strn(line->buf + cursor_pos, redraw_step_forward);
243-
cursor_pos += redraw_step_forward;
235+
}
236+
// draw new chars
237+
stdout_tx_strn(rl.line->buf + rl.cursor_pos, rl.line->len - rl.cursor_pos);
238+
// move cursor forward if needed (already moved forward by length of line, so move it back)
239+
for (int i = rl.cursor_pos + redraw_step_forward; i < rl.line->len; i++) {
240+
stdout_tx_str("\b");
241+
}
242+
rl.cursor_pos += redraw_step_forward;
243+
} else if (redraw_step_forward > 0) {
244+
// draw over old chars to move cursor forwards
245+
stdout_tx_strn(rl.line->buf + rl.cursor_pos, redraw_step_forward);
246+
rl.cursor_pos += redraw_step_forward;
247+
}
248+
249+
return -1;
250+
}
251+
252+
void readline_note_newline() {
253+
rl.orig_line_len = rl.line->len;
254+
rl.cursor_pos = rl.orig_line_len;
255+
}
256+
257+
void readline_init(vstr_t *line) {
258+
rl.line = line;
259+
rl.orig_line_len = line->len;
260+
rl.escape_seq = ESEQ_NONE;
261+
rl.escape_seq_buf[0] = 0;
262+
rl.hist_cur = -1;
263+
rl.cursor_pos = rl.orig_line_len;
264+
}
265+
266+
int readline(vstr_t *line, const char *prompt) {
267+
stdout_tx_str(prompt);
268+
readline_init(line);
269+
for (;;) {
270+
int c = stdin_rx_chr();
271+
int r = readline_process_char(c);
272+
if (r >= 0) {
273+
return r;
244274
}
245275
}
246276
}

lib/mp-readline/readline.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@
3232

3333
void readline_init0(void);
3434
int readline(vstr_t *line, const char *prompt);
35+
36+
void readline_init(vstr_t *line);
37+
void readline_note_newline();
38+
int readline_process_char(int c);

0 commit comments

Comments
 (0)