/src/tmux/prompt-history.c
Line | Count | Source |
1 | | /* $OpenBSD: prompt-history.c,v 1.2 2026/07/13 19:35:22 nicm Exp $ */ |
2 | | |
3 | | /* |
4 | | * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com> |
5 | | * |
6 | | * Permission to use, copy, modify, and distribute this software for any |
7 | | * purpose with or without fee is hereby granted, provided that the above |
8 | | * copyright notice and this permission notice appear in all copies. |
9 | | * |
10 | | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
11 | | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
12 | | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
13 | | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
14 | | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER |
15 | | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING |
16 | | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
17 | | */ |
18 | | |
19 | | #include <sys/types.h> |
20 | | |
21 | | #include <errno.h> |
22 | | #include <stdlib.h> |
23 | | #include <string.h> |
24 | | |
25 | | #include "tmux.h" |
26 | | |
27 | | static char *prompt_find_history_file(void); |
28 | | static void prompt_add_typed_history(char *); |
29 | | |
30 | | /* Prompt history. */ |
31 | | static char **prompt_hlist[PROMPT_NTYPES]; |
32 | | static u_int prompt_hsize[PROMPT_NTYPES]; |
33 | | |
34 | | /* Find the history file to load/save from/to. */ |
35 | | static char * |
36 | | prompt_find_history_file(void) |
37 | 0 | { |
38 | 0 | const char *home, *history_file; |
39 | 0 | char *path; |
40 | |
|
41 | 0 | history_file = options_get_string(global_options, "history-file"); |
42 | 0 | if (*history_file == '\0') |
43 | 0 | return (NULL); |
44 | 0 | if (*history_file == '/') |
45 | 0 | return (xstrdup(history_file)); |
46 | | |
47 | 0 | if (history_file[0] != '~' || history_file[1] != '/') |
48 | 0 | return (NULL); |
49 | 0 | if ((home = find_home()) == NULL) |
50 | 0 | return (NULL); |
51 | 0 | xasprintf(&path, "%s%s", home, history_file + 1); |
52 | 0 | return (path); |
53 | 0 | } |
54 | | |
55 | | /* Add loaded history item to the appropriate list. */ |
56 | | static void |
57 | | prompt_add_typed_history(char *line) |
58 | 0 | { |
59 | 0 | char *typestr; |
60 | 0 | enum prompt_type type = PROMPT_TYPE_INVALID; |
61 | |
|
62 | 0 | typestr = strsep(&line, ":"); |
63 | 0 | if (line != NULL) |
64 | 0 | type = prompt_type(typestr); |
65 | 0 | if (type == PROMPT_TYPE_INVALID) { |
66 | | /* |
67 | | * Invalid types are not expected, but this provides backward |
68 | | * compatibility with old history files. |
69 | | */ |
70 | 0 | if (line != NULL) |
71 | 0 | *(--line) = ':'; |
72 | 0 | prompt_add_history(typestr, PROMPT_TYPE_COMMAND); |
73 | 0 | } else |
74 | 0 | prompt_add_history(line, type); |
75 | 0 | } |
76 | | |
77 | | /* Load prompt history from file. */ |
78 | | void |
79 | | prompt_load_history(void) |
80 | 0 | { |
81 | 0 | FILE *f; |
82 | 0 | char *history_file, *line = NULL; |
83 | 0 | size_t length = 0; |
84 | 0 | ssize_t got; |
85 | |
|
86 | 0 | if ((history_file = prompt_find_history_file()) == NULL) |
87 | 0 | return; |
88 | 0 | log_debug("loading history from %s", history_file); |
89 | |
|
90 | 0 | f = fopen(history_file, "r"); |
91 | 0 | if (f == NULL) { |
92 | 0 | log_debug("%s: %s", history_file, strerror(errno)); |
93 | 0 | free(history_file); |
94 | 0 | return; |
95 | 0 | } |
96 | 0 | free(history_file); |
97 | |
|
98 | 0 | while ((got = getline(&line, &length, f)) != -1) { |
99 | 0 | if (got > 0 && line[got - 1] == '\n') |
100 | 0 | line[got - 1] = '\0'; |
101 | 0 | if (got > 0) |
102 | 0 | prompt_add_typed_history(line); |
103 | 0 | } |
104 | 0 | free(line); |
105 | 0 | fclose(f); |
106 | 0 | } |
107 | | |
108 | | /* Save prompt history to file. */ |
109 | | void |
110 | | prompt_save_history(void) |
111 | 0 | { |
112 | 0 | FILE *f; |
113 | 0 | u_int i, type; |
114 | 0 | char *history_file; |
115 | |
|
116 | 0 | if ((history_file = prompt_find_history_file()) == NULL) |
117 | 0 | return; |
118 | 0 | log_debug("saving history to %s", history_file); |
119 | |
|
120 | 0 | f = fopen(history_file, "w"); |
121 | 0 | if (f == NULL) { |
122 | 0 | log_debug("%s: %s", history_file, strerror(errno)); |
123 | 0 | free(history_file); |
124 | 0 | return; |
125 | 0 | } |
126 | 0 | free(history_file); |
127 | |
|
128 | 0 | for (type = 0; type < PROMPT_NTYPES; type++) { |
129 | 0 | for (i = 0; i < prompt_hsize[type]; i++) { |
130 | 0 | fputs(prompt_type_string(type), f); |
131 | 0 | fputc(':', f); |
132 | 0 | fputs(prompt_hlist[type][i], f); |
133 | 0 | fputc('\n', f); |
134 | 0 | } |
135 | 0 | } |
136 | 0 | fclose(f); |
137 | |
|
138 | 0 | } |
139 | | |
140 | | /* Get previous line from the history. */ |
141 | | const char * |
142 | | prompt_up_history(u_int *idx, u_int type) |
143 | 0 | { |
144 | | /* |
145 | | * History runs from 0 to size - 1. Index is from 0 to size. Zero is |
146 | | * empty. |
147 | | */ |
148 | |
|
149 | 0 | if (type >= PROMPT_NTYPES) |
150 | 0 | return (NULL); |
151 | 0 | if (prompt_hsize[type] == 0 || idx[type] == prompt_hsize[type]) |
152 | 0 | return (NULL); |
153 | 0 | idx[type]++; |
154 | 0 | return (prompt_hlist[type][prompt_hsize[type] - idx[type]]); |
155 | 0 | } |
156 | | |
157 | | /* Get next line from the history. */ |
158 | | const char * |
159 | | prompt_down_history(u_int *idx, u_int type) |
160 | 0 | { |
161 | 0 | if (type >= PROMPT_NTYPES) |
162 | 0 | return (""); |
163 | 0 | if (prompt_hsize[type] == 0 || idx[type] == 0) |
164 | 0 | return (""); |
165 | 0 | idx[type]--; |
166 | 0 | if (idx[type] == 0) |
167 | 0 | return (""); |
168 | 0 | return (prompt_hlist[type][prompt_hsize[type] - idx[type]]); |
169 | 0 | } |
170 | | |
171 | | /* Add line to the history. */ |
172 | | void |
173 | | prompt_add_history(const char *line, u_int type) |
174 | 0 | { |
175 | 0 | u_int i, oldsize, newsize, freecount, hlimit, new = 1; |
176 | 0 | size_t movesize; |
177 | |
|
178 | 0 | if (type >= PROMPT_NTYPES) |
179 | 0 | return; |
180 | | |
181 | 0 | oldsize = prompt_hsize[type]; |
182 | 0 | if (oldsize > 0 && |
183 | 0 | strcmp(prompt_hlist[type][oldsize - 1], line) == 0) |
184 | 0 | new = 0; |
185 | |
|
186 | 0 | hlimit = options_get_number(global_options, "prompt-history-limit"); |
187 | 0 | if (hlimit > oldsize) { |
188 | 0 | if (new == 0) |
189 | 0 | return; |
190 | 0 | newsize = oldsize + new; |
191 | 0 | } else { |
192 | 0 | newsize = hlimit; |
193 | 0 | freecount = oldsize + new - newsize; |
194 | 0 | if (freecount > oldsize) |
195 | 0 | freecount = oldsize; |
196 | 0 | if (freecount == 0) |
197 | 0 | return; |
198 | 0 | for (i = 0; i < freecount; i++) |
199 | 0 | free(prompt_hlist[type][i]); |
200 | 0 | movesize = (oldsize - freecount) * |
201 | 0 | sizeof *prompt_hlist[type]; |
202 | 0 | if (movesize > 0) { |
203 | 0 | memmove(&prompt_hlist[type][0], |
204 | 0 | &prompt_hlist[type][freecount], movesize); |
205 | 0 | } |
206 | 0 | } |
207 | | |
208 | 0 | if (newsize == 0) { |
209 | 0 | free(prompt_hlist[type]); |
210 | 0 | prompt_hlist[type] = NULL; |
211 | 0 | } else if (newsize != oldsize) { |
212 | 0 | prompt_hlist[type] = |
213 | 0 | xreallocarray(prompt_hlist[type], newsize, |
214 | 0 | sizeof *prompt_hlist[type]); |
215 | 0 | } |
216 | |
|
217 | 0 | if (new == 1 && newsize > 0) |
218 | 0 | prompt_hlist[type][newsize - 1] = xstrdup(line); |
219 | 0 | prompt_hsize[type] = newsize; |
220 | 0 | } |
221 | | |
222 | | /* Get history size. */ |
223 | | u_int |
224 | | prompt_history_size(enum prompt_type type) |
225 | 0 | { |
226 | 0 | if (type >= PROMPT_NTYPES) |
227 | 0 | return (0); |
228 | 0 | return (prompt_hsize[type]); |
229 | 0 | } |
230 | | |
231 | | /* Get history entry. */ |
232 | | const char * |
233 | | prompt_history_get(enum prompt_type type, u_int idx) |
234 | 0 | { |
235 | 0 | if (type >= PROMPT_NTYPES) |
236 | 0 | return (NULL); |
237 | 0 | if (idx >= prompt_hsize[type]) |
238 | 0 | return (NULL); |
239 | 0 | return (prompt_hlist[type][idx]); |
240 | 0 | } |
241 | | |
242 | | /* Clear prompt history. */ |
243 | | void |
244 | | prompt_history_clear(enum prompt_type type) |
245 | 0 | { |
246 | 0 | u_int idx; |
247 | |
|
248 | 0 | if (type >= PROMPT_NTYPES) |
249 | 0 | return; |
250 | 0 | for (idx = 0; idx < prompt_hsize[type]; idx++) |
251 | 0 | free(prompt_hlist[type][idx]); |
252 | 0 | free(prompt_hlist[type]); |
253 | | prompt_hlist[type] = NULL; |
254 | 0 | prompt_hsize[type] = 0; |
255 | 0 | } |