diff --git a/include/shell/shell.h b/include/shell/shell.h index 0ea58c100a0..8ef37a7297b 100644 --- a/include/shell/shell.h +++ b/include/shell/shell.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -339,6 +340,8 @@ struct shell { const struct shell_transport *iface; /*!< Transport interface.*/ struct shell_ctx *ctx; /*!< Internal context.*/ + struct shell_history *history; + const struct shell_fprintf *fprintf_ctx; LOG_INSTANCE_PTR_DECLARE(log); @@ -360,26 +363,28 @@ struct shell { * '\\n' or '\\r'. * @param[in] log_queue_size Logger processing queue size. */ -#define SHELL_DEFINE(_name, shell_prefix, transport_iface, \ - newline_ch, log_queue_size) \ - static const struct shell _name; \ - static struct shell_ctx UTIL_CAT(_name, _ctx); \ - static u8_t _name##_out_buffer[CONFIG_SHELL_PRINTF_BUFF_SIZE]; \ - SHELL_FPRINTF_DEFINE(_name## _fprintf, &_name, _name##_out_buffer, \ - CONFIG_SHELL_PRINTF_BUFF_SIZE, \ - true, shell_print_stream); \ +#define SHELL_DEFINE(_name, shell_prefix, transport_iface, \ + newline_ch, log_queue_size) \ + static const struct shell _name; \ + static struct shell_ctx UTIL_CAT(_name, _ctx); \ + static u8_t _name##_out_buffer[CONFIG_SHELL_PRINTF_BUFF_SIZE]; \ + SHELL_HISTORY_DEFINE(_name, 128, 8);/*todo*/ \ + SHELL_FPRINTF_DEFINE(_name## _fprintf, &_name, _name##_out_buffer, \ + CONFIG_SHELL_PRINTF_BUFF_SIZE, \ + true, shell_print_stream); \ LOG_INSTANCE_REGISTER(shell, _name, CONFIG_SHELL_LOG_LEVEL); \ - static struct k_thread _name##_thread; \ - static K_THREAD_STACK_DEFINE(_name##_stack, CONFIG_SHELL_STACK_SIZE); \ - static const struct shell _name = { \ - .name = shell_prefix, \ - .iface = transport_iface, \ - .ctx = &UTIL_CAT(_name, _ctx), \ - .fprintf_ctx = &_name##_fprintf, \ - LOG_INSTANCE_PTR_INIT(log, shell, _name) \ - .newline_char = newline_ch, \ - .thread = &_name##_thread, \ - .stack = _name##_stack \ + static K_THREAD_STACK_DEFINE(_name##_stack, CONFIG_SHELL_STACK_SIZE);\ + static struct k_thread _name##_thread; \ + static const struct shell _name = { \ + .name = shell_prefix, \ + .iface = transport_iface, \ + .ctx = &UTIL_CAT(_name, _ctx), \ + .history = SHELL_HISTORY_PTR(_name), \ + .fprintf_ctx = &_name##_fprintf, \ + LOG_INSTANCE_PTR_INIT(log, shell, _name) \ + .newline_char = newline_ch, \ + .thread = &_name##_thread, \ + .stack = _name##_stack \ } /* diff --git a/include/shell/shell_history.h b/include/shell/shell_history.h new file mode 100644 index 00000000000..33ebdc4bd28 --- /dev/null +++ b/include/shell/shell_history.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_HISTORY_H__ +#define SHELL_HISTORY_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +struct shell_history { + struct k_mem_slab *mem_slab; + sys_dlist_t list; + sys_dnode_t *current; +}; +#if CONFIG_SHELL_HISTORY +#define SHELL_HISTORY_DEFINE(_name, block_size, block_count) \ + \ + K_MEM_SLAB_DEFINE(_name##_history_memslab, \ + block_size, block_count, 4); \ + static struct shell_history _name##_history = { \ + .mem_slab = &_name##_history_memslab \ + } +#define SHELL_HISTORY_PTR(_name) (&_name##_history) +#else /* CONFIG_SHELL_HISTORY */ +#define SHELL_HISTORY_DEFINE(_name, block_size, block_count) /*empty*/ +#define SHELL_HISTORY_PTR(_name) NULL +#endif + + +void shell_history_init(struct shell_history *history); + +void shell_history_purge(struct shell_history *history); + +void shell_history_mode_exit(struct shell_history *history); + +/* returns true if remains in history mode.*/ +bool shell_history_get(struct shell_history *history, bool up, + u8_t *dst, size_t *len); + +void shell_history_put(struct shell_history *history, u8_t *line, size_t len); + +static inline bool shell_history_active(struct shell_history *history) +{ + return (history->current) ? true : false; +} + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_HISTORY_H__ */ diff --git a/subsys/shell/CMakeLists.txt b/subsys/shell/CMakeLists.txt index 63c4f3bbee3..c8300409a7d 100644 --- a/subsys/shell/CMakeLists.txt +++ b/subsys/shell/CMakeLists.txt @@ -17,4 +17,9 @@ zephyr_sources_ifdef( shell_utils.c shell_ops.c shell_uart.c - ) \ No newline at end of file + ) + +zephyr_sources_ifdef( + CONFIG_SHELL_HISTORY + shell_history.c +) diff --git a/subsys/shell/Kconfig b/subsys/shell/Kconfig index fbb56e41c46..df9818a68fb 100644 --- a/subsys/shell/Kconfig +++ b/subsys/shell/Kconfig @@ -118,4 +118,21 @@ config SHELL_HELP_ON_WRONG_ARGUMENT_COUNT bool "Enable printing help on wrong argument count" default y +config SHELL_HISTORY + bool "Enable history in shell" + default y + help + Enable commands history. History can be accessed using up and down + arrows + +if SHELL_HISTORY + +config SHELL_HISTORY_BUFFER + int "History buffer in bytes" + default 1024 + help + Number of bytes dedicated for storing executed commands. + +endif #SHELL_HISTORY + endif #SHELL diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index 284fd110e50..536ff28f4d8 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -265,6 +265,89 @@ static void tab_item_print(const struct shell *shell, const char *option, shell_op_cursor_horiz_move(shell, diff); } +static void history_init(const struct shell *shell) +{ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } + + shell_history_init(shell->history); +} + +static void history_purge(const struct shell *shell) +{ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } + + shell_history_purge(shell->history); +} + +static void history_mode_exit(const struct shell *shell) +{ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } + + shell_history_mode_exit(shell->history); +} + +static void history_put(const struct shell *shell, u8_t *line, size_t length) +{ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } + + shell_history_put(shell->history, line, length); +} + +static void history_handle(const struct shell *shell, bool up) +{ + bool history_mode; + size_t len; + + /*optional feature */ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } + + /* Backup command if history is entered */ + if (!shell_history_active(shell->history)) { + if (up) { + u16_t cmd_len = shell_strlen(shell->ctx->cmd_buff); + + if (cmd_len) { + strcpy(shell->ctx->temp_buff, + shell->ctx->cmd_buff); + } else { + shell->ctx->temp_buff[0] = '\0'; + } + } else { + /* Pressing 'down' not in history mode has no effect. */ + return; + } + } + + /* Start by checking if history is not empty. */ + history_mode = shell_history_get(shell->history, true, + shell->ctx->cmd_buff, &len); + + /* On exiting history mode print backed up command. */ + if (!history_mode) { + strcpy(shell->ctx->cmd_buff, shell->ctx->temp_buff); + len = shell_strlen(shell->ctx->cmd_buff); + } + + if (len) { + shell_op_cursor_home_move(shell); + clear_eos(shell); + shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); + shell->ctx->cmd_buff_pos = len; + shell->ctx->cmd_buff_len = len; + shell_op_cond_next_line(shell); + } +} + static const struct shell_static_entry *find_cmd( const struct shell_cmd_entry *cmd, size_t lvl, @@ -334,6 +417,11 @@ static bool shell_tab_prepare(const struct shell *shell, return false; } + /* If the Tab key is pressed, "history mode" must be terminated because + * tab and history handlers are sharing the same array: temp_buff. + */ + history_mode_exit(shell); + /* Copy command from its beginning to cursor position. */ memcpy(shell->ctx->temp_buff, shell->ctx->cmd_buff, shell->ctx->cmd_buff_pos); @@ -665,6 +753,7 @@ static void shell_state_collect(const struct shell *shell) case SHELL_RECEIVE_DEFAULT: if (data == shell->newline_char) { if (!shell->ctx->cmd_buff_len) { + history_mode_exit(shell); cursor_next_line_move(shell); } else { /* Command execution */ @@ -732,6 +821,14 @@ static void shell_state_collect(const struct shell *shell) } switch (data) { + case 'A': /* UP arrow */ + history_handle(shell, true); + break; + + case 'B': /* DOWN arrow */ + history_handle(shell, false); + break; + case 'C': /* RIGHT arrow */ shell_op_right_arrow(shell); break; @@ -838,6 +935,9 @@ static void shell_execute(const struct shell *shell) cmd_trim(shell); + history_put(shell, shell->ctx->cmd_buff, + shell->ctx->cmd_buff_len); + /* create argument list */ quote = shell_make_argv(&argc, &argv[0], shell->ctx->cmd_buff, CONFIG_SHELL_ARGC_MAX); @@ -949,6 +1049,8 @@ static int shell_instance_init(const struct shell *shell, const void *p_config, return err; } + history_init(shell); + memset(shell->ctx, 0, sizeof(*shell->ctx)); if (IS_ENABLED(CONFIG_SHELL_BACKSPACE_MODE_DELETE)) { @@ -1049,6 +1151,8 @@ static int shell_instance_uninit(const struct shell *shell) return err; } + history_purge(shell); + shell->ctx->state = SHELL_STATE_UNINITIALIZED; return 0; diff --git a/subsys/shell/shell_history.c b/subsys/shell/shell_history.c new file mode 100644 index 00000000000..5b36b500856 --- /dev/null +++ b/subsys/shell/shell_history.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +struct shell_history_item { + sys_dnode_t dnode; + u16_t len; + char data[1]; +}; + +void shell_history_mode_exit(struct shell_history *history) +{ + history->current = NULL; +} + +bool shell_history_get(struct shell_history *history, bool up, + u8_t *dst, size_t *len) +{ + struct shell_history_item *h_item; /* history item */ + sys_dnode_t *l_item; /* list item */ + + if (sys_dlist_is_empty(&history->list)) { + *len = 0; + return false; + } + + if (!up) { /* button down */ + if (history->current == NULL) { + /* Not in history mode. It is started by up button. */ + *len = 0; + + return false; + } + + l_item = sys_dlist_peek_prev_no_check(&history->list, + history->current); + } else { /* button up */ + l_item = (history->current == NULL) ? + sys_dlist_peek_head_not_empty(&history->list) : + sys_dlist_peek_next_no_check(&history->list, history->current); + + } + + history->current = l_item; + h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); + + if (h_item) { + memcpy(dst, h_item->data, h_item->len); + *len = h_item->len; + dst[*len] = '\0'; + return true; + } + + *len = 0; + return up; +} + +static void add_to_head(struct shell_history *history, + struct shell_history_item *item, + u8_t *src, size_t len) +{ + item->len = len; + memcpy(item->data, src, len); + item->data[len] = '\0'; + sys_dlist_prepend(&history->list, &item->dnode); +} + +static void remove_from_tail(struct shell_history *history) +{ + sys_dnode_t *l_item; /* list item */ + struct shell_history_item *h_item; + + l_item = sys_dlist_peek_tail(&history->list); + sys_dlist_remove(l_item); + + h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); + k_mem_slab_free(history->mem_slab, (void **)&l_item); +} + +void shell_history_purge(struct shell_history *history) +{ + while (!sys_dlist_is_empty(&history->list)) { + remove_from_tail(history); + } +} + +void shell_history_put(struct shell_history *history, u8_t *line, size_t len) +{ + sys_dnode_t *l_item; /* list item */ + struct shell_history_item *h_item; + + shell_history_mode_exit(history); + + if (len == 0) { + return; + } + + l_item = sys_dlist_peek_head(&history->list); + h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); + + if (h_item && + (h_item->len == len) && + (strncmp(h_item->data, line, CONFIG_SHELL_CMD_BUFF_SIZE) == 0)) { + /* Same command as before, do not store */ + return; + } + + while (k_mem_slab_alloc(history->mem_slab, (void **)&h_item, K_NO_WAIT) + != 0) { + /* if no space remove the oldest entry. */ + remove_from_tail(history); + } + + add_to_head(history, h_item, line, len); +} + +void shell_history_init(struct shell_history *history) +{ + sys_dlist_init(&history->list); + history->current = NULL; +}