From 98f33581a95bcbd689fa20ff6a34048eff44c6f2 Mon Sep 17 00:00:00 2001 From: Shoichi Sakane Date: Sun, 8 Nov 2015 14:47:10 +0900 Subject: [PATCH] better-CLI supports a tiny history feature for the native environment. It assumes that the terminal is xterm or something like this, and supposes values of the TCap codes because it does not use ncurses to avoid from increasing code size. See $(RIOT)/Makefile.include for detail. --- Makefile.include | 19 ++++ sys/shell/shell.c | 253 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) diff --git a/Makefile.include b/Makefile.include index 1144efbd5ac6..47c4f5cc2f99 100644 --- a/Makefile.include +++ b/Makefile.include @@ -140,6 +140,25 @@ endif $(AD)$(OBJCOPY) $(OFLAGS) $(ELFFILE) $(HEXFILE) endif +## +# USE_BETTER_CLI tells which CLI is used. +# It's supposed to be defined in each Makefile in the application build directory, +# or, to be specified in the building time (e.g. % USE_BETTER_CLI=1 make native). +# If it is set to 0 (i.e. USE_BETTER_CLI=0), or is not defined, original CLI is applied. +# If it is set to 1, better-CLI is enabled. +# better-CLI supports a tiny history feature for the native environment. +# It assumes that the terminal is xterm or something like this, and supposes values +# of the TCap codes because it does not use ncurses to avoid from increasing code size. +# SHELL_HISTORY_SIZE tells how many history size the shell supports. The default value is 3. +# It can be specified in the building time. (e.g. % SHELL_HISTORY_SIZE=10 make native). +# +ifdef USE_BETTER_CLI +CFLAGS += -DUSE_BETTER_CLI=$(USE_BETTER_CLI) +ifdef SHELL_HISTORY_SIZE +CFLAGS += -DSHELL_HISTORY_SIZE=$(SHELL_HISTORY_SIZE) +endif +endif + ..build-message: @echo "Building application $(APPLICATION) for $(BOARD) w/ MCU $(MCU)." diff --git a/sys/shell/shell.c b/sys/shell/shell.c index 217053a01d1e..cab973ed3f80 100644 --- a/sys/shell/shell.c +++ b/sys/shell/shell.c @@ -30,9 +30,23 @@ #include #include #include +#if USE_BETTER_CLI == 1 +#include +#include +#endif #include "shell.h" #include "shell_commands.h" +/* + * See $(RIOT)/Makefile.include for USE_BETTER_CLI + */ +#if USE_BETTER_CLI == 1 +#ifndef SHELL_HISTORY_SIZE +#define SHELL_HISTORY_SIZE 3 +#endif +extern ssize_t _native_read(int fd, void *buf, size_t count); +#endif + static shell_command_handler_t find_handler(const shell_command_t *command_list, char *command) { const shell_command_t *command_lists[] = { @@ -207,16 +221,212 @@ static void handle_input_line(shell_t *shell, char *line) } } +#if USE_BETTER_CLI == 1 +static void del_char(shell_t *shell) +{ + /* white-tape the character */ + shell->put_char('\b'); + shell->put_char(' '); + shell->put_char('\b'); +} + +static void kill_readline(shell_t *shell, char *buf, char **line_buf_ptr) +{ + while (*line_buf_ptr > buf) { + (*line_buf_ptr)--; + del_char(shell); + } +} + +static int set_stdin_nonblock(void) +{ + struct termios ts; + + if (tcgetattr(STDIN_FILENO, &ts) < 0) + return -1; + + ts.c_lflag &= ~(ICANON | ECHO); + ts.c_cc[VMIN] = 1; + + tcsetattr(STDIN_FILENO, TCSANOW, &ts); + + return 0; +} + +static int nonblock_getchar(void) +{ + unsigned char cmdbuf; + + if (_native_read(STDIN_FILENO, (void *)&cmdbuf, 1) <= 0) + return -1; + + return (int)cmdbuf; +} + +/* + * tiny history feature + */ +struct hist_ctx { + char **hist; + int hist_current_index; /* the place for the next input */ + int hist_search_index; /* point to the index during searching */ + int hist_max_index; + shell_t *shell; +}; +static struct hist_ctx *hist; + +static void hist_init(shell_t *shell) +{ + int i = 0; + + if ((hist = malloc(sizeof(*hist))) == NULL) + return; + if ((hist->hist = malloc(sizeof(char *) * SHELL_HISTORY_SIZE)) == NULL) { +err: + free(hist); + hist = NULL; + return; + } + for (i = 0; i < SHELL_HISTORY_SIZE; i++) { + if ((hist->hist[i] = malloc(shell->shell_buffer_size)) == NULL) { + while (i--) { + if (hist->hist[i] != NULL) + free(hist->hist[i]); + } + goto err; + } + hist->hist[i][0] = '\0'; + } + hist->hist_current_index = 0; + hist->hist_search_index = -1; + hist->hist_max_index = -1; + hist->shell = shell; + return; +} + +static int +hist_prev_index(void) +{ + if (hist->hist_current_index == 0) + return hist->hist_max_index; + return hist->hist_current_index - 1; +} + +/* + * don't need to check the length of the command line is less than the buffer size. + * it has been done in the readline() loop. + */ +static void hist_put(char *line_buf) +{ + /* + * check whether the command is exactly identical to the previous command. + * if so, it doesn't put the history. + */ + if (hist->hist_max_index != -1 && + strcmp(hist->hist[hist_prev_index()], line_buf) == 0) { + return; + } + + snprintf(hist->hist[hist->hist_current_index], hist->shell->shell_buffer_size, + "%s", line_buf); + + if (hist->hist_current_index + 1 < SHELL_HISTORY_SIZE) + hist->hist_current_index++; + else + hist->hist_current_index = 0; + + if (hist->hist_max_index + 1 < SHELL_HISTORY_SIZE) + hist->hist_max_index++; + + hist->hist_search_index = -1; +} + +static void hist_get(int index, char *line_buf, char **line_buf_ptr) +{ + int len, i; + + kill_readline(hist->shell, line_buf, line_buf_ptr); + + /* + * no need to check whether the line_buf size is enough to copy the command, + * because it is checked when a command was copied into the history. + */ + len = strlen(hist->hist[index]); + for (i = 0; i < len; i++) { + hist->shell->put_char(hist->hist[index][i]); + line_buf[i] = hist->hist[index][i]; + } + + *line_buf_ptr = line_buf + len; +} + +static void hist_prev(char *line_buf, char **line_buf_ptr) +{ + /* no history so far */ + if (hist->hist_max_index == -1) + return; + + if (hist->hist_search_index == -1) { + /* just enter searching the history */ + hist->hist_search_index = hist_prev_index(); + } else { + /* in searching */ + if (hist->hist_search_index == hist->hist_current_index) + return; /* reached the head of the history */ + else if (hist->hist_search_index == 0) { + if (hist->hist_max_index < SHELL_HISTORY_SIZE - 1) + return; + hist->hist_search_index = hist->hist_max_index; + } else + hist->hist_search_index--; + } + + hist_get(hist->hist_search_index, line_buf, line_buf_ptr); +} + +static void hist_next(char *line_buf, char **line_buf_ptr) +{ + /* no history so far */ + if (hist->hist_search_index == -1) + return; + + /* end of searching the history */ + if (hist->hist_search_index + 1 == hist->hist_current_index || + (hist->hist_current_index == 0 && hist->hist_search_index == hist->hist_max_index)) { + kill_readline(hist->shell, line_buf, line_buf_ptr); + hist->hist_search_index = -1; + return; + } + + if (hist->hist_search_index + 1 < SHELL_HISTORY_SIZE && + hist->hist_search_index + 1 != hist->hist_current_index) + hist->hist_search_index++; + else if (hist->hist_current_index != 0) + hist->hist_search_index = 0; + else + return; + + hist_get(hist->hist_search_index, line_buf, line_buf_ptr); +} +#endif /* USE_BETTER_CLI == 1 */ + static int readline(shell_t *shell, char *buf, size_t size) { char *line_buf_ptr = buf; +#if USE_BETTER_CLI == 1 + int escaped = 0; +#endif while (1) { if ((line_buf_ptr - buf) >= ((int) size) - 1) { return -1; } +#if USE_BETTER_CLI == 1 + int c = nonblock_getchar(); +#else int c = shell->readchar(); +#endif if (c < 0) { return 1; } @@ -231,10 +441,14 @@ static int readline(shell_t *shell, char *buf, size_t size) } *line_buf_ptr = '\0'; +#if USE_BETTER_CLI == 1 + hist_put(buf); +#endif shell->put_char('\r'); shell->put_char('\n'); return 0; } +#if !defined(USE_BETTER_CLI) || USE_BETTER_CLI == 0 /* QEMU uses 0x7f (DEL) as backspace, while 0x08 (BS) is for most terminals */ else if (c == 0x08 || c == 0x7f) { if (line_buf_ptr == buf) { @@ -248,6 +462,40 @@ static int readline(shell_t *shell, char *buf, size_t size) shell->put_char(' '); shell->put_char('\b'); } +#else + /* QEMU uses 0x7f (DEL) as backspace, while 0x08 (BS) is for most terminals */ + else if (c == 0x08 || c == 0x7f || c == 0x3f) { + if (line_buf_ptr == buf) { + /* The line is empty. */ + continue; + } + + *--line_buf_ptr = '\0'; + /* white-tape the character */ + del_char(shell); + } + else if (c == 0x15) { + /* kill the command line */ + /* ^U */ + kill_readline(shell, buf, &line_buf_ptr); + } + else if (c == 0x10 || (escaped == 2 && c == 0x41)) { + /* ^P or ^[[A */ + hist_prev(buf, &line_buf_ptr); + escaped = 0; + } + else if (c == 0xe || (escaped == 2 && c == 0x42)) { + /* ^N or ^[[B */ + hist_next(buf, &line_buf_ptr); + escaped = 0; + } + else if (c == 0x1b) { + escaped = 1; + } + else if (escaped == 1 && c == 0x5b) { + escaped = 2; + } +#endif else { *line_buf_ptr++ = c; shell->put_char(c); @@ -266,6 +514,11 @@ void shell_run(shell_t *shell) { char line_buf[shell->shell_buffer_size]; +#if USE_BETTER_CLI == 1 + set_stdin_nonblock(); + hist_init(shell); +#endif + print_prompt(shell); while (1) {