Skip to content

Commit 9ae3fc6

Browse files
committed
unix: Add option to use uPy readline, and enable by default.
This gets uPy readline working with unix port, with tab completion and history. GNU readline is still supported, configure using MICROPY_USE_READLINE variable.
1 parent 4a10214 commit 9ae3fc6

File tree

8 files changed

+136
-18
lines changed

8 files changed

+136
-18
lines changed

tests/cmdline/repl_basic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# basic REPL tests
22
print(1)
3-
OA
3+
[A

unix/Makefile

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ endif
5757
endif
5858

5959
ifeq ($(MICROPY_USE_READLINE),1)
60+
INC += -I../lib/mp-readline
6061
CFLAGS_MOD += -DMICROPY_USE_READLINE=1
62+
LIB_SRC_C_EXTRA += mp-readline/readline.c
63+
endif
64+
ifeq ($(MICROPY_USE_READLINE),2)
65+
CFLAGS_MOD += -DMICROPY_USE_READLINE=2
6166
LDFLAGS_MOD += -lreadline
6267
# the following is needed for BSD
6368
#LDFLAGS_MOD += -ltermcap
@@ -98,8 +103,13 @@ SRC_C = \
98103
coverage.c \
99104
$(SRC_MOD)
100105

106+
LIB_SRC_C = $(addprefix lib/,\
107+
$(LIB_SRC_C_EXTRA) \
108+
)
101109

102-
OBJ = $(PY_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
110+
OBJ = $(PY_O)
111+
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
112+
OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o))
103113

104114
include ../py/mkrules.mk
105115

unix/input.c

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,50 @@
2828
#include <stdlib.h>
2929
#include <string.h>
3030

31-
#include "py/nlr.h"
32-
#include "py/obj.h"
31+
#include "py/mpstate.h"
3332
#include "input.h"
3433

35-
#if MICROPY_USE_READLINE
34+
#if MICROPY_USE_READLINE == 1
35+
#include MICROPY_HAL_H
36+
#include "lib/mp-readline/readline.h"
37+
#elif MICROPY_USE_READLINE == 2
3638
#include <readline/readline.h>
3739
#include <readline/history.h>
3840
#include <readline/tilde.h>
39-
#else
40-
#undef MICROPY_USE_READLINE_HISTORY
41-
#define MICROPY_USE_READLINE_HISTORY (0)
4241
#endif
4342

4443
char *prompt(char *p) {
45-
#if MICROPY_USE_READLINE
44+
#if MICROPY_USE_READLINE == 1
45+
// MicroPython supplied readline
46+
vstr_t vstr;
47+
vstr_init(&vstr, 16);
48+
mp_hal_stdio_mode_raw();
49+
int ret = readline(&vstr, p);
50+
mp_hal_stdio_mode_orig();
51+
if (ret != 0) {
52+
vstr_clear(&vstr);
53+
if (ret == CHAR_CTRL_D) {
54+
// EOF
55+
return NULL;
56+
} else {
57+
printf("\n");
58+
char *line = malloc(1);
59+
line[0] = '\0';
60+
return line;
61+
}
62+
}
63+
vstr_null_terminated_str(&vstr);
64+
char *line = malloc(vstr.len + 1);
65+
memcpy(line, vstr.buf, vstr.len + 1);
66+
vstr_clear(&vstr);
67+
#elif MICROPY_USE_READLINE == 2
68+
// GNU readline
4669
char *line = readline(p);
4770
if (line) {
4871
add_history(line);
4972
}
5073
#else
74+
// simple read string
5175
static char buf[256];
5276
fputs(p, stdout);
5377
char *s = fgets(buf, sizeof(buf), stdin);
@@ -68,13 +92,61 @@ char *prompt(char *p) {
6892

6993
void prompt_read_history(void) {
7094
#if MICROPY_USE_READLINE_HISTORY
95+
#if MICROPY_USE_READLINE == 1
96+
readline_init0(); // will clear history pointers
97+
char *home = getenv("HOME");
98+
if (home != NULL) {
99+
vstr_t vstr;
100+
vstr_init(&vstr, 50);
101+
vstr_printf(&vstr, "%s/.micropython.history", home);
102+
FILE *fp = fopen(vstr_null_terminated_str(&vstr), "r");
103+
if (fp != NULL) {
104+
vstr_reset(&vstr);
105+
for (;;) {
106+
int c = fgetc(fp);
107+
if (c == EOF || c == '\n') {
108+
readline_push_history(vstr_null_terminated_str(&vstr));
109+
if (c == EOF) {
110+
break;
111+
}
112+
vstr_reset(&vstr);
113+
} else {
114+
vstr_add_byte(&vstr, c);
115+
}
116+
}
117+
fclose(fp);
118+
}
119+
vstr_clear(&vstr);
120+
}
121+
#elif MICROPY_USE_READLINE == 2
71122
read_history(tilde_expand("~/.micropython.history"));
123+
#endif
72124
#endif
73125
}
74126

75127
void prompt_write_history(void) {
76128
#if MICROPY_USE_READLINE_HISTORY
129+
#if MICROPY_USE_READLINE == 1
130+
char *home = getenv("HOME");
131+
if (home != NULL) {
132+
vstr_t vstr;
133+
vstr_init(&vstr, 50);
134+
vstr_printf(&vstr, "%s/.micropython.history", home);
135+
FILE *fp = fopen(vstr_null_terminated_str(&vstr), "w");
136+
if (fp != NULL) {
137+
for (int i = MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist)) - 1; i >= 0; i--) {
138+
const char *line = MP_STATE_PORT(readline_hist)[i];
139+
if (line != NULL) {
140+
fwrite(line, 1, strlen(line), fp);
141+
fputc('\n', fp);
142+
}
143+
}
144+
fclose(fp);
145+
}
146+
}
147+
#elif MICROPY_USE_READLINE == 2
77148
write_history(tilde_expand("~/.micropython.history"));
149+
#endif
78150
#endif
79151
}
80152

unix/main.c

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,6 @@ STATIC void set_sys_argv(char *argv[], int argc, int start_arg) {
278278
#endif
279279

280280
int main(int argc, char **argv) {
281-
prompt_read_history();
282-
283281
mp_stack_set_limit(40000 * (BYTES_PER_WORD / 4));
284282

285283
pre_process_options(argc, argv);
@@ -445,7 +443,9 @@ int main(int argc, char **argv) {
445443
}
446444

447445
if (ret == NOTHING_EXECUTED) {
446+
prompt_read_history();
448447
ret = do_repl();
448+
prompt_write_history();
449449
}
450450

451451
#if MICROPY_PY_MICROPYTHON_MEM_INFO
@@ -463,7 +463,6 @@ int main(int argc, char **argv) {
463463
#endif
464464

465465
//printf("total bytes = %d\n", m_get_total_bytes_allocated());
466-
prompt_write_history();
467466
return ret & 0xff;
468467
}
469468

unix/mpconfigport.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,15 @@ extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj;
184184
{ MP_OBJ_NEW_QSTR(MP_QSTR_input), (mp_obj_t)&mp_builtin_input_obj }, \
185185
{ MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&mp_builtin_open_obj },
186186

187+
#define MP_STATE_PORT MP_STATE_VM
188+
187189
#define MICROPY_PORT_ROOT_POINTERS \
190+
const char *readline_hist[50]; \
188191
mp_obj_t keyboard_interrupt_obj; \
189192
void *mmap_region_head; \
190193

194+
#define MICROPY_HAL_H "unix_mphal.h"
195+
191196
// We need to provide a declaration/definition of alloca()
192197
#ifdef __FreeBSD__
193198
#include <stdlib.h>

unix/mpconfigport.mk

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
# Build 32-bit binaries on a 64-bit host
44
MICROPY_FORCE_32BIT = 0
55

6-
# Linking with GNU readline causes binary to be licensed under GPL
6+
# This variable can take the following values:
7+
# 0 - no readline, just simple input
8+
# 1 - use MicroPython version of readline
9+
# 2 - use GNU readline (causes binary to be licensed under GPL)
710
MICROPY_USE_READLINE = 1
811

912
# Subset of CPython time module

unix/unix_mphal.c

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626

2727
#include <unistd.h>
28+
#include <stdlib.h>
2829
#include <string.h>
2930

3031
#include "py/mpstate.h"
@@ -35,13 +36,12 @@
3536

3637
STATIC void sighandler(int signum) {
3738
if (signum == SIGINT) {
39+
if (MP_STATE_VM(mp_pending_exception) == MP_STATE_VM(keyboard_interrupt_obj)) {
40+
// this is the second time we are called, so die straight away
41+
exit(1);
42+
}
3843
mp_obj_exception_clear_traceback(MP_STATE_VM(keyboard_interrupt_obj));
3944
MP_STATE_VM(mp_pending_exception) = MP_STATE_VM(keyboard_interrupt_obj);
40-
// disable our handler so next we really die
41-
struct sigaction sa;
42-
sa.sa_handler = SIG_DFL;
43-
sigemptyset(&sa.sa_mask);
44-
sigaction(SIGINT, &sa, NULL);
4545
}
4646
}
4747
#endif
@@ -67,6 +67,32 @@ void mp_hal_set_interrupt_char(char c) {
6767
}
6868
}
6969

70+
#if MICROPY_USE_READLINE == 1
71+
72+
#include <termios.h>
73+
74+
static struct termios orig_termios;
75+
76+
void mp_hal_stdio_mode_raw(void) {
77+
// save and set terminal settings
78+
tcgetattr(0, &orig_termios);
79+
static struct termios termios;
80+
termios = orig_termios;
81+
termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
82+
termios.c_cflag = (termios.c_cflag & ~(CSIZE | PARENB)) | CS8;
83+
termios.c_lflag = 0;
84+
termios.c_cc[VMIN] = 1;
85+
termios.c_cc[VTIME] = 0;
86+
tcsetattr(0, TCSAFLUSH, &termios);
87+
}
88+
89+
void mp_hal_stdio_mode_orig(void) {
90+
// restore terminal settings
91+
tcsetattr(0, TCSAFLUSH, &orig_termios);
92+
}
93+
94+
#endif
95+
7096
int mp_hal_stdin_rx_chr(void) {
7197
unsigned char c;
7298
int ret = read(0, &c, 1);

unix/unix_mphal.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030

3131
void mp_hal_set_interrupt_char(char c);
3232

33+
void mp_hal_stdio_mode_raw(void);
34+
void mp_hal_stdio_mode_orig(void);
35+
3336
int mp_hal_stdin_rx_chr(void);
3437
void mp_hal_stdout_tx_str(const char *str);
3538
void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len);

0 commit comments

Comments
 (0)