blob: e0258bf4189720218915867ddb97dad24a900ec9 [file] [log] [blame]
/*
* Copyright (c) 2018 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/shell/shell_history.h>
#include <string.h>
/*
* History must store strings (commands) and allow traversing them and adding
* new string. When new item is added then first it is compared if it is not
* the same as the last one (then it is not stored). If there is no room in the
* buffer to store the new item, oldest one is removed until there is a room.
*/
struct shell_history_item {
sys_dnode_t dnode;
uint16_t len;
char data[];
};
void z_shell_history_mode_exit(struct shell_history *history)
{
history->current = NULL;
}
bool z_shell_history_get(struct shell_history *history, bool up,
uint8_t *dst, uint16_t *len)
{
struct shell_history_item *h_item; /* history item */
sys_dnode_t *l_item; /* list item */
if (up) { /* button up */
l_item = (history->current == NULL) ?
sys_dlist_peek_head(&history->list) :
sys_dlist_peek_next_no_check(&history->list, history->current);
} else { /* button down */
l_item = sys_dlist_peek_prev(&history->list, history->current);
}
history->current = l_item;
if (l_item == NULL) {
/* Reached the end of history. */
*len = 0U;
return false;
}
h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
memcpy(dst, h_item->data, h_item->len);
*len = h_item->len;
dst[*len] = '\0';
return true;
}
/* Returns true if element was removed. */
static bool remove_from_tail(struct shell_history *history)
{
sys_dnode_t *node;
if (sys_dlist_is_empty(&history->list)) {
return false;
}
node = sys_dlist_peek_tail(&history->list);
sys_dlist_remove(node);
k_heap_free(history->heap, CONTAINER_OF(node, struct shell_history_item, dnode));
return true;
}
void z_shell_history_purge(struct shell_history *history)
{
while (remove_from_tail(history)) {
}
}
void z_shell_history_put(struct shell_history *history, uint8_t *line,
size_t len)
{
sys_dnode_t *node;
struct shell_history_item *new, *h_prev_item;
uint32_t total_len = len + offsetof(struct shell_history_item, data);
z_shell_history_mode_exit(history);
if (len == 0) {
return;
}
node = sys_dlist_peek_head(&history->list);
h_prev_item = CONTAINER_OF(node, struct shell_history_item, dnode);
if (node &&
(h_prev_item->len == len) &&
(memcmp(h_prev_item->data, line, len) == 0)) {
/* Same command as before, do not store */
return;
}
for (;;) {
new = k_heap_alloc(history->heap, total_len, K_NO_WAIT);
if (new) {
/* Got memory, add new item */
break;
} else if (!remove_from_tail(history)) {
/* Nothing to remove, cannot allocate memory. */
return;
}
}
new->len = len;
memcpy(new->data, line, len);
sys_dlist_prepend(&history->list, &new->dnode);
}
void z_shell_history_init(struct shell_history *history)
{
sys_dlist_init(&history->list);
history->current = NULL;
}