shell: Add shell history feature
Extending shell with terminal-like history feature. Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
This commit is contained in:
parent
f7aad1a89d
commit
82ca811661
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <zephyr.h>
|
||||
#include <shell/shell_types.h>
|
||||
#include <shell/shell_history.h>
|
||||
#include <shell/shell_fprintf.h>
|
||||
#include <logging/log_backend.h>
|
||||
#include <logging/log_instance.h>
|
||||
|
@ -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 \
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
61
include/shell/shell_history.h
Normal file
61
include/shell/shell_history.h
Normal file
|
@ -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 <zephyr.h>
|
||||
#include <misc/util.h>
|
||||
#include <misc/dlist.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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__ */
|
|
@ -17,4 +17,9 @@ zephyr_sources_ifdef(
|
|||
shell_utils.c
|
||||
shell_ops.c
|
||||
shell_uart.c
|
||||
)
|
||||
)
|
||||
|
||||
zephyr_sources_ifdef(
|
||||
CONFIG_SHELL_HISTORY
|
||||
shell_history.c
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
126
subsys/shell/shell_history.c
Normal file
126
subsys/shell/shell_history.c
Normal file
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (c) 2018 Nordic Semiconductor ASA
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <shell/shell_history.h>
|
||||
#include <string.h>
|
||||
|
||||
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;
|
||||
}
|
Loading…
Reference in a new issue