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) {