1 /*
2  * Copyright (c) 2018 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <zephyr/shell/shell_history.h>
8 #include <string.h>
9 
10 /*
11  * History must store strings (commands) and allow traversing them and adding
12  * new string. When new item is added then first it is compared if it is not
13  * the same as the last one (then it is not stored). If there is no room in the
14  * buffer to store the new item, oldest one is removed until there is a room.
15  */
16 
17 struct shell_history_item {
18 	sys_dnode_t dnode;
19 	uint16_t len;
20 	char data[];
21 };
22 
z_shell_history_mode_exit(struct shell_history * history)23 void z_shell_history_mode_exit(struct shell_history *history)
24 {
25 	history->current = NULL;
26 }
27 
z_shell_history_get(struct shell_history * history,bool up,uint8_t * dst,uint16_t * len)28 bool z_shell_history_get(struct shell_history *history, bool up,
29 			 uint8_t *dst, uint16_t *len)
30 {
31 	struct shell_history_item *h_item; /* history item */
32 	sys_dnode_t *l_item; /* list item */
33 
34 	if (up) { /* button up */
35 		l_item = (history->current == NULL) ?
36 		sys_dlist_peek_head(&history->list) :
37 		sys_dlist_peek_next_no_check(&history->list, history->current);
38 	} else { /* button down */
39 		l_item = sys_dlist_peek_prev(&history->list, history->current);
40 	}
41 
42 	history->current = l_item;
43 	if (l_item == NULL) {
44 		/* Reached the end of history. */
45 		*len = 0U;
46 		return false;
47 	}
48 
49 	h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode);
50 	memcpy(dst, h_item->data, h_item->len);
51 	*len = h_item->len;
52 	dst[*len] = '\0';
53 	return true;
54 }
55 
56 /* Returns true if element was removed. */
remove_from_tail(struct shell_history * history)57 static bool remove_from_tail(struct shell_history *history)
58 {
59 	sys_dnode_t *node;
60 
61 	if (sys_dlist_is_empty(&history->list)) {
62 		return false;
63 	}
64 
65 	node = sys_dlist_peek_tail(&history->list);
66 	sys_dlist_remove(node);
67 	k_heap_free(history->heap, CONTAINER_OF(node, struct shell_history_item, dnode));
68 	return true;
69 }
70 
z_shell_history_put(struct shell_history * history,uint8_t * line,size_t len)71 void z_shell_history_put(struct shell_history *history, uint8_t *line,
72 			 size_t len)
73 {
74 	sys_dnode_t *node;
75 	struct shell_history_item *new, *h_prev_item;
76 	uint32_t total_len = len + offsetof(struct shell_history_item, data);
77 
78 	z_shell_history_mode_exit(history);
79 	if (len == 0) {
80 		return;
81 	}
82 
83 	node = sys_dlist_peek_head(&history->list);
84 	h_prev_item = CONTAINER_OF(node, struct shell_history_item, dnode);
85 
86 	if (node &&
87 	   (h_prev_item->len == len) &&
88 	   (memcmp(h_prev_item->data, line, len) == 0)) {
89 		/* Same command as before, do not store */
90 		return;
91 	}
92 
93 	for (;;) {
94 		new = k_heap_alloc(history->heap, total_len, K_NO_WAIT);
95 		if (new) {
96 			/* Got memory, add new item */
97 			break;
98 		} else if (!remove_from_tail(history)) {
99 			/* Nothing to remove, cannot allocate memory. */
100 			return;
101 		}
102 	}
103 
104 	new->len = len;
105 	memcpy(new->data, line, len);
106 	sys_dlist_prepend(&history->list, &new->dnode);
107 }
108 
z_shell_history_purge(struct shell_history * history)109 void z_shell_history_purge(struct shell_history *history)
110 {
111 	while (remove_from_tail(history)) {
112 	}
113 	history->current = NULL;
114 }
115