|  | /* | 
|  | * 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; | 
|  | } |