Line | Branch | Exec | Source |
---|---|---|---|
1 | #include <errno.h> | ||
2 | #include <stdlib.h> | ||
3 | #include <string.h> | ||
4 | #include "history.h" | ||
5 | #include "error.h" | ||
6 | #include "util/arith.h" | ||
7 | #include "util/debug.h" | ||
8 | #include "util/readfile.h" | ||
9 | #include "util/str-util.h" | ||
10 | #include "util/xmalloc.h" | ||
11 | #include "util/xstdio.h" | ||
12 | |||
13 | 12023 | void history_append(History *history, const char *text) | |
14 | { | ||
15 | 12023 | BUG_ON(history->max_entries < 2); | |
16 |
2/2✓ Branch 0 taken 12021 times.
✓ Branch 1 taken 2 times.
|
12023 | if (text[0] == '\0') { |
17 | return; | ||
18 | } | ||
19 | |||
20 | 12021 | HashMap *map = &history->entries; | |
21 | 12021 | HistoryEntry *e = hashmap_get(map, text); | |
22 | |||
23 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 12016 times.
|
12021 | if (e) { |
24 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | if (e == history->last) { |
25 | // Existing entry already at end of list; nothing more to do | ||
26 | return; | ||
27 | } | ||
28 | // Remove existing entry from list | ||
29 | 4 | e->next->prev = e->prev; | |
30 |
2/2✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
|
4 | if (unlikely(e == history->first)) { |
31 | 2 | history->first = e->next; | |
32 | } else { | ||
33 | 2 | e->prev->next = e->next; | |
34 | } | ||
35 | } else { | ||
36 |
2/2✓ Branch 0 taken 11491 times.
✓ Branch 1 taken 525 times.
|
12016 | if (map->count == history->max_entries) { |
37 | // History is full; recycle oldest entry | ||
38 | 11491 | HistoryEntry *old_first = history->first; | |
39 | 11491 | HistoryEntry *new_first = old_first->next; | |
40 | 11491 | new_first->prev = NULL; | |
41 | 11491 | history->first = new_first; | |
42 | 11491 | e = hashmap_remove(map, old_first->text); | |
43 | 11491 | BUG_ON(e != old_first); | |
44 | } else { | ||
45 | 525 | e = xnew(HistoryEntry, 1); | |
46 | } | ||
47 | 12016 | e->text = xstrdup(text); | |
48 | 12016 | hashmap_insert(map, e->text, e); | |
49 | } | ||
50 | |||
51 | // Insert entry at end of list | ||
52 | 12020 | HistoryEntry *old_last = history->last; | |
53 | 12020 | e->next = NULL; | |
54 | 12020 | e->prev = old_last; | |
55 | 12020 | history->last = e; | |
56 |
2/2✓ Branch 0 taken 12015 times.
✓ Branch 1 taken 5 times.
|
12020 | if (likely(old_last)) { |
57 | 12015 | old_last->next = e; | |
58 | } else { | ||
59 | 5 | history->first = e; | |
60 | } | ||
61 | } | ||
62 | |||
63 | 4 | bool history_search_forward ( | |
64 | const History *history, | ||
65 | const HistoryEntry **pos, | ||
66 | const char *text | ||
67 | ) { | ||
68 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | const HistoryEntry *start = *pos ? (*pos)->prev : history->last; |
69 | 4 | const bool always_match = (text[0] == '\0'); | |
70 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | for (const HistoryEntry *e = start; e; e = e->prev) { |
71 |
4/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
4 | if (always_match || str_has_prefix(e->text, text)) { |
72 | 3 | *pos = e; | |
73 | 3 | return true; | |
74 | } | ||
75 | } | ||
76 | return false; | ||
77 | } | ||
78 | |||
79 | 4 | bool history_search_backward ( | |
80 | const History *history, | ||
81 | const HistoryEntry **pos, | ||
82 | const char *text | ||
83 | ) { | ||
84 |
1/2✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
|
4 | const HistoryEntry *start = *pos ? (*pos)->next : history->first; |
85 | 4 | const bool always_match = (text[0] == '\0'); | |
86 |
2/2✓ Branch 0 taken 4 times.
✓ Branch 1 taken 1 times.
|
5 | for (const HistoryEntry *e = start; e; e = e->next) { |
87 |
4/4✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 1 times.
✓ Branch 3 taken 1 times.
|
4 | if (always_match || str_has_prefix(e->text, text)) { |
88 | 3 | *pos = e; | |
89 | 3 | return true; | |
90 | } | ||
91 | } | ||
92 | return false; | ||
93 | } | ||
94 | |||
95 | 11 | void history_load(History *history, char *filename, size_t size_limit) | |
96 | { | ||
97 | 11 | BUG_ON(history->filename); | |
98 | 11 | BUG_ON(history->max_entries < 2); | |
99 | 11 | hashmap_init(&history->entries, history->max_entries); | |
100 | 11 | history->filename = filename; | |
101 | |||
102 | 11 | char *buf; | |
103 | 11 | const ssize_t ssize = read_file(filename, &buf, size_limit); | |
104 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 times.
|
11 | if (ssize < 0) { |
105 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
|
10 | if (errno != ENOENT) { |
106 | ✗ | error_msg("Error reading %s: %s", filename, strerror(errno)); | |
107 | } | ||
108 | 10 | return; | |
109 | } | ||
110 | |||
111 |
2/2✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
|
6 | for (size_t pos = 0, size = ssize; pos < size; ) { |
112 | 5 | history_append(history, buf_next_line(buf, &pos, size)); | |
113 | } | ||
114 | |||
115 | 1 | free(buf); | |
116 | } | ||
117 | |||
118 | 1 | void history_save(const History *history) | |
119 | { | ||
120 | 1 | const char *filename = history->filename; | |
121 |
1/2✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
|
1 | if (!filename) { |
122 | return; | ||
123 | } | ||
124 | |||
125 | 1 | FILE *f = xfopen(filename, "w", O_CLOEXEC, 0666); | |
126 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1 | if (!f) { |
127 | ✗ | error_msg("Error creating %s: %s", filename, strerror(errno)); | |
128 | ✗ | return; | |
129 | } | ||
130 | |||
131 |
2/2✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
|
4 | for (const HistoryEntry *e = history->first; e; e = e->next) { |
132 | 3 | xfputs(e->text, f); | |
133 | 3 | xfputc('\n', f); | |
134 | } | ||
135 | |||
136 | 1 | fclose(f); | |
137 | } | ||
138 | |||
139 | 20 | void history_free(History *history) | |
140 | { | ||
141 | 20 | hashmap_free(&history->entries, free); | |
142 | 20 | free(history->filename); | |
143 | 20 | history->filename = NULL; | |
144 | 20 | history->first = NULL; | |
145 | 20 | history->last = NULL; | |
146 | 20 | } | |
147 | |||
148 | ✗ | String history_dump(const History *history) | |
149 | { | ||
150 | ✗ | const size_t nr_entries = history->entries.count; | |
151 | ✗ | const size_t size = round_size_to_next_multiple(16 * nr_entries, 4096); | |
152 | ✗ | String buf = string_new(size); | |
153 | ✗ | size_t n = 0; | |
154 | ✗ | for (const HistoryEntry *e = history->first; e; e = e->next, n++) { | |
155 | ✗ | string_append_cstring(&buf, e->text); | |
156 | ✗ | string_append_byte(&buf, '\n'); | |
157 | } | ||
158 | ✗ | BUG_ON(n != nr_entries); | |
159 | ✗ | return buf; | |
160 | } | ||
161 |