From 9ae3fc65235303322ef5282d3cdd4ca99a2c37cc Mon Sep 17 00:00:00 2001 From: Damien George Date: Sun, 24 May 2015 22:36:31 +0100 Subject: [PATCH] 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. --- tests/cmdline/repl_basic.py | 2 +- unix/Makefile | 12 +++++- unix/input.c | 86 ++++++++++++++++++++++++++++++++++--- unix/main.c | 5 +-- unix/mpconfigport.h | 5 +++ unix/mpconfigport.mk | 5 ++- unix/unix_mphal.c | 36 +++++++++++++--- unix/unix_mphal.h | 3 ++ 8 files changed, 136 insertions(+), 18 deletions(-) diff --git a/tests/cmdline/repl_basic.py b/tests/cmdline/repl_basic.py index 67d18cd577..b416493dce 100644 --- a/tests/cmdline/repl_basic.py +++ b/tests/cmdline/repl_basic.py @@ -1,3 +1,3 @@ # basic REPL tests print(1) -OA + diff --git a/unix/Makefile b/unix/Makefile index 0d8e35f261..790bbafbc6 100644 --- a/unix/Makefile +++ b/unix/Makefile @@ -57,7 +57,12 @@ endif endif ifeq ($(MICROPY_USE_READLINE),1) +INC += -I../lib/mp-readline CFLAGS_MOD += -DMICROPY_USE_READLINE=1 +LIB_SRC_C_EXTRA += mp-readline/readline.c +endif +ifeq ($(MICROPY_USE_READLINE),2) +CFLAGS_MOD += -DMICROPY_USE_READLINE=2 LDFLAGS_MOD += -lreadline # the following is needed for BSD #LDFLAGS_MOD += -ltermcap @@ -98,8 +103,13 @@ SRC_C = \ coverage.c \ $(SRC_MOD) +LIB_SRC_C = $(addprefix lib/,\ + $(LIB_SRC_C_EXTRA) \ + ) -OBJ = $(PY_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ = $(PY_O) +OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) +OBJ += $(addprefix $(BUILD)/, $(LIB_SRC_C:.c=.o)) include ../py/mkrules.mk diff --git a/unix/input.c b/unix/input.c index 0a02483f70..d41487c7ef 100644 --- a/unix/input.c +++ b/unix/input.c @@ -28,26 +28,50 @@ #include #include -#include "py/nlr.h" -#include "py/obj.h" +#include "py/mpstate.h" #include "input.h" -#if MICROPY_USE_READLINE +#if MICROPY_USE_READLINE == 1 +#include MICROPY_HAL_H +#include "lib/mp-readline/readline.h" +#elif MICROPY_USE_READLINE == 2 #include #include #include -#else -#undef MICROPY_USE_READLINE_HISTORY -#define MICROPY_USE_READLINE_HISTORY (0) #endif char *prompt(char *p) { -#if MICROPY_USE_READLINE +#if MICROPY_USE_READLINE == 1 + // MicroPython supplied readline + vstr_t vstr; + vstr_init(&vstr, 16); + mp_hal_stdio_mode_raw(); + int ret = readline(&vstr, p); + mp_hal_stdio_mode_orig(); + if (ret != 0) { + vstr_clear(&vstr); + if (ret == CHAR_CTRL_D) { + // EOF + return NULL; + } else { + printf("\n"); + char *line = malloc(1); + line[0] = '\0'; + return line; + } + } + vstr_null_terminated_str(&vstr); + char *line = malloc(vstr.len + 1); + memcpy(line, vstr.buf, vstr.len + 1); + vstr_clear(&vstr); +#elif MICROPY_USE_READLINE == 2 + // GNU readline char *line = readline(p); if (line) { add_history(line); } #else + // simple read string static char buf[256]; fputs(p, stdout); char *s = fgets(buf, sizeof(buf), stdin); @@ -68,13 +92,61 @@ char *prompt(char *p) { void prompt_read_history(void) { #if MICROPY_USE_READLINE_HISTORY + #if MICROPY_USE_READLINE == 1 + readline_init0(); // will clear history pointers + char *home = getenv("HOME"); + if (home != NULL) { + vstr_t vstr; + vstr_init(&vstr, 50); + vstr_printf(&vstr, "%s/.micropython.history", home); + FILE *fp = fopen(vstr_null_terminated_str(&vstr), "r"); + if (fp != NULL) { + vstr_reset(&vstr); + for (;;) { + int c = fgetc(fp); + if (c == EOF || c == '\n') { + readline_push_history(vstr_null_terminated_str(&vstr)); + if (c == EOF) { + break; + } + vstr_reset(&vstr); + } else { + vstr_add_byte(&vstr, c); + } + } + fclose(fp); + } + vstr_clear(&vstr); + } + #elif MICROPY_USE_READLINE == 2 read_history(tilde_expand("~/.micropython.history")); + #endif #endif } void prompt_write_history(void) { #if MICROPY_USE_READLINE_HISTORY + #if MICROPY_USE_READLINE == 1 + char *home = getenv("HOME"); + if (home != NULL) { + vstr_t vstr; + vstr_init(&vstr, 50); + vstr_printf(&vstr, "%s/.micropython.history", home); + FILE *fp = fopen(vstr_null_terminated_str(&vstr), "w"); + if (fp != NULL) { + for (int i = MP_ARRAY_SIZE(MP_STATE_PORT(readline_hist)) - 1; i >= 0; i--) { + const char *line = MP_STATE_PORT(readline_hist)[i]; + if (line != NULL) { + fwrite(line, 1, strlen(line), fp); + fputc('\n', fp); + } + } + fclose(fp); + } + } + #elif MICROPY_USE_READLINE == 2 write_history(tilde_expand("~/.micropython.history")); + #endif #endif } diff --git a/unix/main.c b/unix/main.c index fb6868c88a..1cc174c269 100644 --- a/unix/main.c +++ b/unix/main.c @@ -278,8 +278,6 @@ STATIC void set_sys_argv(char *argv[], int argc, int start_arg) { #endif int main(int argc, char **argv) { - prompt_read_history(); - mp_stack_set_limit(40000 * (BYTES_PER_WORD / 4)); pre_process_options(argc, argv); @@ -445,7 +443,9 @@ int main(int argc, char **argv) { } if (ret == NOTHING_EXECUTED) { + prompt_read_history(); ret = do_repl(); + prompt_write_history(); } #if MICROPY_PY_MICROPYTHON_MEM_INFO @@ -463,7 +463,6 @@ int main(int argc, char **argv) { #endif //printf("total bytes = %d\n", m_get_total_bytes_allocated()); - prompt_write_history(); return ret & 0xff; } diff --git a/unix/mpconfigport.h b/unix/mpconfigport.h index 411817130a..2a24061c9e 100644 --- a/unix/mpconfigport.h +++ b/unix/mpconfigport.h @@ -184,10 +184,15 @@ extern const struct _mp_obj_fun_builtin_t mp_builtin_open_obj; { MP_OBJ_NEW_QSTR(MP_QSTR_input), (mp_obj_t)&mp_builtin_input_obj }, \ { MP_OBJ_NEW_QSTR(MP_QSTR_open), (mp_obj_t)&mp_builtin_open_obj }, +#define MP_STATE_PORT MP_STATE_VM + #define MICROPY_PORT_ROOT_POINTERS \ + const char *readline_hist[50]; \ mp_obj_t keyboard_interrupt_obj; \ void *mmap_region_head; \ +#define MICROPY_HAL_H "unix_mphal.h" + // We need to provide a declaration/definition of alloca() #ifdef __FreeBSD__ #include diff --git a/unix/mpconfigport.mk b/unix/mpconfigport.mk index 2bf86078ae..84b8e437fa 100644 --- a/unix/mpconfigport.mk +++ b/unix/mpconfigport.mk @@ -3,7 +3,10 @@ # Build 32-bit binaries on a 64-bit host MICROPY_FORCE_32BIT = 0 -# Linking with GNU readline causes binary to be licensed under GPL +# This variable can take the following values: +# 0 - no readline, just simple input +# 1 - use MicroPython version of readline +# 2 - use GNU readline (causes binary to be licensed under GPL) MICROPY_USE_READLINE = 1 # Subset of CPython time module diff --git a/unix/unix_mphal.c b/unix/unix_mphal.c index c70045b4e4..1f545f9e51 100644 --- a/unix/unix_mphal.c +++ b/unix/unix_mphal.c @@ -25,6 +25,7 @@ */ #include +#include #include #include "py/mpstate.h" @@ -35,13 +36,12 @@ STATIC void sighandler(int signum) { if (signum == SIGINT) { + if (MP_STATE_VM(mp_pending_exception) == MP_STATE_VM(keyboard_interrupt_obj)) { + // this is the second time we are called, so die straight away + exit(1); + } mp_obj_exception_clear_traceback(MP_STATE_VM(keyboard_interrupt_obj)); MP_STATE_VM(mp_pending_exception) = MP_STATE_VM(keyboard_interrupt_obj); - // disable our handler so next we really die - struct sigaction sa; - sa.sa_handler = SIG_DFL; - sigemptyset(&sa.sa_mask); - sigaction(SIGINT, &sa, NULL); } } #endif @@ -67,6 +67,32 @@ void mp_hal_set_interrupt_char(char c) { } } +#if MICROPY_USE_READLINE == 1 + +#include + +static struct termios orig_termios; + +void mp_hal_stdio_mode_raw(void) { + // save and set terminal settings + tcgetattr(0, &orig_termios); + static struct termios termios; + termios = orig_termios; + termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + termios.c_cflag = (termios.c_cflag & ~(CSIZE | PARENB)) | CS8; + termios.c_lflag = 0; + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 0; + tcsetattr(0, TCSAFLUSH, &termios); +} + +void mp_hal_stdio_mode_orig(void) { + // restore terminal settings + tcsetattr(0, TCSAFLUSH, &orig_termios); +} + +#endif + int mp_hal_stdin_rx_chr(void) { unsigned char c; int ret = read(0, &c, 1); diff --git a/unix/unix_mphal.h b/unix/unix_mphal.h index 3d9fee5c3c..209ce6d07b 100644 --- a/unix/unix_mphal.h +++ b/unix/unix_mphal.h @@ -30,6 +30,9 @@ void mp_hal_set_interrupt_char(char c); +void mp_hal_stdio_mode_raw(void); +void mp_hal_stdio_mode_orig(void); + int mp_hal_stdin_rx_chr(void); void mp_hal_stdout_tx_str(const char *str); void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len);