diff --git a/include/system/readline.h b/include/system/readline.h index 8f40c92c124..2deb2a97615 100644 --- a/include/system/readline.h +++ b/include/system/readline.h @@ -42,6 +42,11 @@ # undef CONFIG_READLINE_TABCOMPLETION #endif +/* readline_fd_ex() options */ + +#define READLINE_CTRL_D_EOF (1 << 0) +#define READLINE_RETURN_ON_EINTR (1 << 1) + /* Make sure that the are valid values for all tab-completion settings */ #ifdef CONFIG_READLINE_TABCOMPLETION @@ -176,6 +181,19 @@ FAR const struct extmatch_vtable_s * ssize_t readline_fd(FAR char *buf, int buflen, int infd, int outfd); +/**************************************************************************** + * Name: readline_fd_ex + * + * readline_fd_ex() extends readline_fd() with caller-selected options. + * READLINE_CTRL_D_EOF makes Ctrl-D return EOF when the line is empty. + * READLINE_RETURN_ON_EINTR makes an interrupted input read return -EINTR + * instead of being retried. + * + ****************************************************************************/ + +ssize_t readline_fd_ex(FAR char *buf, int buflen, int infd, int outfd, + unsigned int options); + /**************************************************************************** * Name: readline_stream * diff --git a/interpreters/python/python_wrapper.c b/interpreters/python/python_wrapper.c index be7454514df..5bd80929e8d 100644 --- a/interpreters/python/python_wrapper.c +++ b/interpreters/python/python_wrapper.c @@ -45,6 +45,10 @@ #include +#ifdef CONFIG_SYSTEM_READLINE +# include +#endif + #include "romfs_cpython_modules.h" #include "Python.h" @@ -80,6 +84,17 @@ #define MKMOUNT_DEVNAME(m) "/dev/ram" STR_RAMDEVNO(m) #define MOUNT_DEVNAME MKMOUNT_DEVNAME(CONFIG_CPYTHON_ROMFS_RAMDEVNO) +#ifdef CONFIG_SYSTEM_READLINE +# if CONFIG_LINE_MAX > 2 +# define PYTHON_READLINE_BUFSIZE CONFIG_LINE_MAX +# else +# define PYTHON_READLINE_BUFSIZE 2 +# endif + +# define PYTHON_READLINE_OPTIONS \ + (READLINE_CTRL_D_EOF | READLINE_RETURN_ON_EINTR) +#endif + /**************************************************************************** * Private Types ****************************************************************************/ @@ -92,6 +107,103 @@ * Private Functions ****************************************************************************/ +#ifdef CONFIG_SYSTEM_READLINE +/**************************************************************************** + * Name: python_readline + * + * Description: + * Read an interactive line with the NuttX line editor. CPython calls + * this hook only for a TTY attached to the main interpreter, leaving its + * standard fgets-based reader in place for redirected input and + * subinterpreters. + * + ****************************************************************************/ + +static char *python_readline(FILE *sys_stdin, FILE *sys_stdout, + const char *prompt) +{ +#if defined(CONFIG_READLINE_TABCOMPLETION) || defined(CONFIG_READLINE_EDIT) + FAR const char *oldprompt; +#endif + FAR char *line; + FAR char *tmp; + size_t linesize; + size_t used; + ssize_t nread; + + fflush(sys_stdout); + + if (prompt != NULL) + { + fputs(prompt, stderr); + } + + fflush(stderr); + + linesize = PYTHON_READLINE_BUFSIZE; + line = PyMem_RawMalloc(linesize); + if (line == NULL) + { + return NULL; + } + + used = 0; + +#if defined(CONFIG_READLINE_TABCOMPLETION) || defined(CONFIG_READLINE_EDIT) + oldprompt = readline_prompt(prompt); +#endif + + for (; ; ) + { + nread = readline_fd_ex(line + used, PYTHON_READLINE_BUFSIZE, + fileno(sys_stdin), fileno(sys_stdout), + PYTHON_READLINE_OPTIONS); + if (nread == -EINTR) + { + PyMem_RawFree(line); + line = NULL; + break; + } + + if (nread == EOF) + { + line[used] = '\0'; + break; + } + + used += nread; + if (used > 0 && line[used - 1] == '\n') + { + break; + } + + if (used > SIZE_MAX - PYTHON_READLINE_BUFSIZE) + { + PyMem_RawFree(line); + line = NULL; + break; + } + + linesize = used + PYTHON_READLINE_BUFSIZE; + tmp = PyMem_RawRealloc(line, linesize); + if (tmp == NULL) + { + PyMem_RawFree(line); + line = NULL; + break; + } + + line = tmp; + } + +#if defined(CONFIG_READLINE_TABCOMPLETION) || defined(CONFIG_READLINE_EDIT) + readline_prompt(oldprompt); +#endif + + return line; +} +#endif + /**************************************************************************** * Name: check_and_mount_romfs * @@ -194,6 +306,10 @@ int main(int argc, FAR char *argv[]) _pyruntime_early_init(); +#ifdef CONFIG_SYSTEM_READLINE + PyOS_ReadlineFunctionPointer = python_readline; +#endif + setenv("PYTHONHOME", "/usr/local", 1); setenv("PYTHON_BASIC_REPL", "1", 1); diff --git a/system/readline/readline.h b/system/readline/readline.h index b4b5927f839..c255a658061 100644 --- a/system/readline/readline.h +++ b/system/readline/readline.h @@ -84,6 +84,6 @@ struct rl_common_s ****************************************************************************/ ssize_t readline_common(FAR struct rl_common_s *vtbl, - FAR char *buf, int buflen); + FAR char *buf, int buflen, unsigned int options); #endif /* __APPS_SYSTEM_READLINE_READLINE_H */ diff --git a/system/readline/readline_common.c b/system/readline/readline_common.c index 872fcf7683d..742759ebb62 100644 --- a/system/readline/readline_common.c +++ b/system/readline/readline_common.c @@ -49,12 +49,13 @@ # define RL_CMDHIST_LINELEN CONFIG_READLINE_CMD_HISTORY_LINELEN #endif +#define CTRL_D 4 /* ^D - EOF or delete at cursor */ + #ifdef CONFIG_READLINE_EDIT_EMACS -/* Emacs-style control key codes */ +/* Additional Emacs-style control key codes */ # define CTRL_A 1 /* ^A - Home */ # define CTRL_B 2 /* ^B - Left */ -# define CTRL_D 4 /* ^D - Delete at cursor */ # define CTRL_E 5 /* ^E - End */ # define CTRL_F 6 /* ^F - Right */ # define CTRL_K 11 /* ^K - Kill to end of line */ @@ -849,7 +850,7 @@ FAR const struct extmatch_vtable_s * ****************************************************************************/ ssize_t readline_common(FAR struct rl_common_s *vtbl, FAR char *buf, - int buflen) + int buflen, unsigned int options) { int escape; int nch; @@ -1440,6 +1441,14 @@ ssize_t readline_common(FAR struct rl_common_s *vtbl, FAR char *buf, return submit_line(buf, nch); } + /* Some callers use the conventional empty-line Ctrl-D as EOF. */ + + else if (ch == CTRL_D && (options & READLINE_CTRL_D_EOF) != 0 && + nch == 0) + { + return EOF; + } + /* Emacs-style control keys */ #ifdef CONFIG_READLINE_EDIT_EMACS diff --git a/system/readline/readline_fd.c b/system/readline/readline_fd.c index 39b7c7898eb..2126f3ccfee 100644 --- a/system/readline/readline_fd.c +++ b/system/readline/readline_fd.c @@ -42,9 +42,11 @@ struct readline_s { struct rl_common_s vtbl; - int infd; + unsigned int options; + int infd; + int errcode; #ifdef CONFIG_READLINE_ECHO - int outfd; + int outfd; #endif }; @@ -92,7 +94,13 @@ static int readline_getc(FAR struct rl_common_s *vtbl) */ int errcode = errno; - if (errcode != EINTR) + if (errcode == EINTR && + (priv->options & READLINE_RETURN_ON_EINTR) != 0) + { + priv->errcode = errcode; + return EOF; + } + else if (errcode != EINTR) { /* Return EOF on any errors that we cannot handle */ @@ -218,6 +226,16 @@ static void readline_write(FAR struct rl_common_s *vtbl, ****************************************************************************/ ssize_t readline_fd(FAR char *buf, int buflen, int infd, int outfd) +{ + return readline_fd_ex(buf, buflen, infd, outfd, 0); +} + +/**************************************************************************** + * Name: readline_fd_ex + ****************************************************************************/ + +ssize_t readline_fd_ex(FAR char *buf, int buflen, int infd, int outfd, + unsigned int options) { UNUSED(outfd); @@ -240,6 +258,8 @@ ssize_t readline_fd(FAR char *buf, int buflen, int infd, int outfd) vtbl.vtbl.rl_getc = readline_getc; vtbl.infd = infd; + vtbl.options = options; + vtbl.errcode = 0; #ifdef CONFIG_READLINE_ECHO vtbl.vtbl.rl_putc = readline_putc; @@ -249,12 +269,17 @@ ssize_t readline_fd(FAR char *buf, int buflen, int infd, int outfd) /* The let the common readline logic do the work */ - ret = readline_common(&vtbl.vtbl, buf, buflen); + ret = readline_common(&vtbl.vtbl, buf, buflen, options); if (isatty(infd) && (cfg.c_lflag & ICANON)) { tcsetattr(infd, TCSANOW, &cfg); } + if (vtbl.errcode != 0) + { + ret = -vtbl.errcode; + } + return ret; }