NeoMutt  2025-09-05-43-g177ed6
Teaching an old dog new tricks
DOXYGEN
Loading...
Searching...
No Matches
history.c
Go to the documentation of this file.
1
24
72
73#include "config.h"
74#include <stdbool.h>
75#include <stdint.h>
76#include <stdio.h>
77#include <string.h>
78#include "mutt/lib.h"
79#include "config/lib.h"
80#include "core/lib.h"
81#include "lib.h"
82
83#define HC_FIRST HC_EXT_COMMAND
84
90struct History
91{
92 char **hist;
93 short cur;
94 short last;
95};
96
97/* global vars used for the string-history routines */
98
100static struct History Histories[HC_MAX];
103static int OldSize = 0;
104
110static struct History *get_history(enum HistoryClass hclass)
111{
112 const short c_history = cs_subset_number(NeoMutt->sub, "history");
113 if ((hclass >= HC_MAX) || (c_history == 0))
114 return NULL;
115
116 struct History *hist = &Histories[hclass];
117 return hist->hist ? hist : NULL;
118}
119
126static void init_history(struct History *h)
127{
128 if (OldSize != 0)
129 {
130 if (h->hist)
131 {
132 for (int i = 0; i <= OldSize; i++)
133 FREE(&h->hist[i]);
134 FREE(&h->hist);
135 }
136 }
137
138 const short c_history = cs_subset_number(NeoMutt->sub, "history");
139 if (c_history != 0)
140 h->hist = MUTT_MEM_CALLOC(c_history + 1, char *);
141
142 h->cur = 0;
143 h->last = 0;
144}
145
156static int dup_hash_dec(struct HashTable *dup_hash, char *str)
157{
158 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
159 if (!he)
160 return -1;
161
162 uintptr_t count = (uintptr_t) he->data;
163 if (count <= 1)
164 {
165 mutt_hash_delete(dup_hash, str, NULL);
166 return 0;
167 }
168
169 count--;
170 he->data = (void *) count;
171 return count;
172}
173
182static int dup_hash_inc(struct HashTable *dup_hash, char *str)
183{
184 uintptr_t count;
185
186 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
187 if (!he)
188 {
189 count = 1;
190 mutt_hash_insert(dup_hash, str, (void *) count);
191 return count;
192 }
193
194 count = (uintptr_t) he->data;
195 count++;
196 he->data = (void *) count;
197 return count;
198}
199
203static void shrink_histfile(void)
204{
205 FILE *fp_tmp = NULL;
206 int n[HC_MAX] = { 0 };
207 int line, hclass = 0, read = 0;
208 char *linebuf = NULL, *p = NULL;
209 size_t buflen;
210 bool regen_file = false;
211 struct HashTable *dup_hashes[HC_MAX] = { 0 };
212
213 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
214 FILE *fp = mutt_file_fopen(c_history_file, "r");
215 if (!fp)
216 return;
217
218 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
219 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
220 if (c_history_remove_dups)
221 {
222 for (hclass = 0; hclass < HC_MAX; hclass++)
223 dup_hashes[hclass] = mutt_hash_new(MAX(10, c_save_history * 2), MUTT_HASH_STRDUP_KEYS);
224 }
225
226 line = 0;
227 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
228 {
229 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
230 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
231 {
232 mutt_error(_("%s:%d: Bad history file format"), c_history_file, line);
233 regen_file = true;
234 continue;
235 }
236 /* silently ignore too high class (probably newer neomutt) */
237 if (hclass >= HC_MAX)
238 continue;
239 *p = '\0';
240 if (c_history_remove_dups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
241 {
242 regen_file = true;
243 continue;
244 }
245 n[hclass]++;
246 }
247
248 if (!regen_file)
249 {
250 for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
251 {
252 if (n[hclass] > c_save_history)
253 {
254 regen_file = true;
255 break;
256 }
257 }
258 }
259
260 if (regen_file)
261 {
262 fp_tmp = mutt_file_mkstemp();
263 if (!fp_tmp)
264 {
265 mutt_perror(_("Can't create temporary file"));
266 goto cleanup;
267 }
268 rewind(fp);
269 line = 0;
270 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
271 {
272 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
273 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
274 {
275 continue;
276 }
277 if (hclass >= HC_MAX)
278 continue;
279 *p = '\0';
280 if (c_history_remove_dups && (dup_hash_dec(dup_hashes[hclass], linebuf + read) > 0))
281 {
282 continue;
283 }
284 *p = '|';
285 if (n[hclass]-- <= c_save_history)
286 fprintf(fp_tmp, "%s\n", linebuf);
287 }
288 }
289
290cleanup:
291 mutt_file_fclose(&fp);
292 FREE(&linebuf);
293 if (fp_tmp)
294 {
295 if (fflush(fp_tmp) == 0)
296 {
297 fp = mutt_file_fopen(c_history_file, "w");
298 if (fp)
299 {
300 rewind(fp_tmp);
301 mutt_file_copy_stream(fp_tmp, fp);
302 mutt_file_fclose(&fp);
303 }
304 }
305 mutt_file_fclose(&fp_tmp);
306 }
307 if (c_history_remove_dups)
308 for (hclass = 0; hclass < HC_MAX; hclass++)
309 mutt_hash_free(&dup_hashes[hclass]);
310}
311
317static void save_history(enum HistoryClass hclass, const char *str)
318{
319 if (!str || (*str == '\0')) // This shouldn't happen, but it's safer
320 return;
321
322 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
323 FILE *fp = mutt_file_fopen(c_history_file, "a");
324 if (!fp)
325 return;
326
327 char *tmp = mutt_str_dup(str);
329
330 // If tmp contains '\n' terminate it there.
331 char *nl = strchr(tmp, '\n');
332 if (nl)
333 *nl = '\0';
334
335 /* Format of a history item (1 line): "<histclass>:<string>|".
336 * We add a '|' in order to avoid lines ending with '\'. */
337 fprintf(fp, "%d:%s|\n", (int) hclass, tmp);
338
339 mutt_file_fclose(&fp);
340 FREE(&tmp);
341
343}
344
355static void remove_history_dups(enum HistoryClass hclass, const char *str)
356{
357 struct History *h = get_history(hclass);
358 if (!h)
359 return; /* disabled */
360
361 /* Remove dups from 0..last-1 compacting up. */
362 int source = 0;
363 int dest = 0;
364 while (source < h->last)
365 {
366 if (mutt_str_equal(h->hist[source], str))
367 FREE(&h->hist[source++]);
368 else
369 h->hist[dest++] = h->hist[source++];
370 }
371
372 /* Move 'last' entry up. */
373 h->hist[dest] = h->hist[source];
374 int old_last = h->last;
375 h->last = dest;
376
377 /* Fill in moved entries with NULL */
378 while (source > h->last)
379 h->hist[source--] = NULL;
380
381 /* Remove dups from last+1 .. `$history` compacting down. */
382 const short c_history = cs_subset_number(NeoMutt->sub, "history");
383 source = c_history;
384 dest = c_history;
385 while (source > old_last)
386 {
387 if (mutt_str_equal(h->hist[source], str))
388 FREE(&h->hist[source--]);
389 else
390 h->hist[dest--] = h->hist[source--];
391 }
392
393 /* Fill in moved entries with NULL */
394 while (dest > old_last)
395 h->hist[dest--] = NULL;
396}
397
405int mutt_hist_search(const char *find, enum HistoryClass hclass, struct StringArray *matches)
406{
407 if (!find || !matches)
408 return 0;
409
410 struct History *h = get_history(hclass);
411 if (!h)
412 return 0;
413
414 int cur = h->last;
415 const short c_history = cs_subset_number(NeoMutt->sub, "history");
416
417 do
418 {
419 cur--;
420 if (cur < 0)
421 cur = c_history;
422
423 if (cur == h->last)
424 break;
425
426 if (mutt_istr_find(h->hist[cur], find))
427 ARRAY_ADD(matches, h->hist[cur]);
428
429 } while (ARRAY_SIZE(matches) < c_history);
430
431 return ARRAY_SIZE(matches);
432}
433
438{
439 if (!NeoMutt)
440 return;
441
442 const short c_history = cs_subset_number(NeoMutt->sub, "history");
443 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
444 {
445 struct History *h = &Histories[hclass];
446 if (!h->hist)
447 continue;
448
449 /* The array has (`$history`+1) elements */
450 for (int i = 0; i <= c_history; i++)
451 {
452 FREE(&h->hist[i]);
453 }
454 FREE(&h->hist);
455 }
456}
457
465{
466 const short c_history = cs_subset_number(NeoMutt->sub, "history");
467 if (c_history == OldSize)
468 return;
469
470 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
471 init_history(&Histories[hclass]);
472
473 OldSize = c_history;
474}
475
482void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
483{
484 struct History *h = get_history(hclass);
485 if (!h)
486 return; /* disabled */
487
488 if (*str)
489 {
490 int prev = h->last - 1;
491 const short c_history = cs_subset_number(NeoMutt->sub, "history");
492 if (prev < 0)
493 prev = c_history;
494
495 /* don't add to prompt history:
496 * - lines beginning by a space
497 * - repeated lines */
498 if ((*str != ' ') && (!h->hist[prev] || !mutt_str_equal(h->hist[prev], str)))
499 {
500 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
501 if (c_history_remove_dups)
502 remove_history_dups(hclass, str);
503 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
504 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
505 if (save && (c_save_history != 0) && c_history_file)
506 save_history(hclass, str);
507 mutt_str_replace(&h->hist[h->last++], str);
508 if (h->last > c_history)
509 h->last = 0;
510 }
511 }
512 h->cur = h->last; /* reset to the last entry */
513}
514
522char *mutt_hist_next(enum HistoryClass hclass)
523{
524 struct History *h = get_history(hclass);
525 if (!h)
526 return ""; /* disabled */
527
528 int next = h->cur;
529 const short c_history = cs_subset_number(NeoMutt->sub, "history");
530 do
531 {
532 next++;
533 if (next > c_history)
534 next = 0;
535 if (next == h->last)
536 break;
537 } while (!h->hist[next]);
538
539 h->cur = next;
540 return NONULL(h->hist[h->cur]);
541}
542
550char *mutt_hist_prev(enum HistoryClass hclass)
551{
552 struct History *h = get_history(hclass);
553 if (!h)
554 return ""; /* disabled */
555
556 int prev = h->cur;
557 const short c_history = cs_subset_number(NeoMutt->sub, "history");
558 do
559 {
560 prev--;
561 if (prev < 0)
562 prev = c_history;
563 if (prev == h->last)
564 break;
565 } while (!h->hist[prev]);
566
567 h->cur = prev;
568 return NONULL(h->hist[h->cur]);
569}
570
579{
580 struct History *h = get_history(hclass);
581 if (!h)
582 return; /* disabled */
583
584 h->cur = h->last;
585}
586
593{
594 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
595 if (!c_history_file)
596 return;
597
598 FILE *fp = mutt_file_fopen(c_history_file, "r");
599 if (!fp)
600 return;
601
602 int line = 0, hclass = 0, read = 0;
603 char *linebuf = NULL, *p = NULL;
604 size_t buflen;
605
606 const char *const c_charset = cc_charset();
607 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
608 {
609 read = 0;
610 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
611 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
612 {
613 mutt_error(_("%s:%d: Bad history file format"), c_history_file, line);
614 continue;
615 }
616 /* silently ignore too high class (probably newer neomutt) */
617 if (hclass >= HC_MAX)
618 continue;
619 *p = '\0';
620 p = mutt_str_dup(linebuf + read);
621 if (p)
622 {
623 mutt_ch_convert_string(&p, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS);
624 mutt_hist_add(hclass, p, false);
625 FREE(&p);
626 }
627 }
628
629 mutt_file_fclose(&fp);
630 FREE(&linebuf);
631}
632
645{
646 struct History *h = get_history(hclass);
647 if (!h)
648 return false; /* disabled */
649
650 return h->cur == h->last;
651}
652
661void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
662{
663 struct History *h = get_history(hclass);
664 if (!h)
665 return; /* disabled */
666
667 /* Don't check if str has a value because the scratch buffer may contain
668 * an old garbage value that should be overwritten */
669 mutt_str_replace(&h->hist[h->last], str);
670}
671
677void mutt_hist_complete(struct Buffer *buf, enum HistoryClass hclass)
678{
679 struct StringArray matches = ARRAY_HEAD_INITIALIZER;
680
681 int match_count = mutt_hist_search(buf_string(buf), hclass, &matches);
682 if (match_count != 0)
683 {
684 if (match_count == 1)
685 {
686 const char **pstr = ARRAY_GET(&matches, 0);
687 buf_strcpy(buf, *pstr);
688 }
689 else
690 {
691 dlg_history(buf, &matches);
692 }
693 }
694
695 ARRAY_FREE(&matches);
696}
697
702{
703 if (nc->event_type != NT_CONFIG)
704 return 0;
705 if (!nc->event_data)
706 return -1;
707
708 struct EventConfig *ev_c = nc->event_data;
709
710 if (!mutt_str_equal(ev_c->name, "history"))
711 return 0;
712
714 mutt_debug(LL_DEBUG5, "history done\n");
715 return 0;
716}
#define ARRAY_ADD(head, elem)
Add an element at the end of the array.
Definition array.h:156
#define ARRAY_SIZE(head)
The number of elements stored.
Definition array.h:87
#define ARRAY_FREE(head)
Release all memory.
Definition array.h:204
#define ARRAY_GET(head, idx)
Return the element at index.
Definition array.h:109
#define ARRAY_HEAD_INITIALIZER
Static initializer for arrays.
Definition array.h:58
size_t buf_strcpy(struct Buffer *buf, const char *s)
Copy a string into a Buffer.
Definition buffer.c:395
static const char * buf_string(const struct Buffer *buf)
Convert a buffer to a const char * "string".
Definition buffer.h:96
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition helpers.c:143
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition helpers.c:168
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition helpers.c:47
Convenience wrapper for the config headers.
const char * cc_charset(void)
Get the cached value of $charset.
Convenience wrapper for the core headers.
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition file.c:225
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, ReadLineFlags flags)
Read a line from a file.
Definition file.c:685
#define mutt_file_fclose(FP)
Definition file.h:139
#define mutt_file_fopen(PATH, MODE)
Definition file.h:138
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition file.h:40
void dlg_history(struct Buffer *buf, struct StringArray *matches)
Select an item from a history list -.
#define mutt_error(...)
Definition logging2.h:93
#define mutt_debug(LEVEL,...)
Definition logging2.h:90
#define mutt_perror(...)
Definition logging2.h:94
int main_hist_observer(struct NotifyCallback *nc)
Notification that a Config Variable has change - Implements observer_t -.
Definition history.c:701
struct HashElem * mutt_hash_insert(struct HashTable *table, const char *strkey, void *data)
Add a new element to the Hash Table (with string keys)
Definition hash.c:335
void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
Remove an element from a Hash Table.
Definition hash.c:427
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition hash.c:259
struct HashElem * mutt_hash_find_elem(const struct HashTable *table, const char *strkey)
Find the HashElem in a Hash Table element using a key.
Definition hash.c:377
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition hash.c:457
#define MUTT_HASH_STRDUP_KEYS
make a copy of the keys
Definition hash.h:113
Read/write command history from/to a file.
HistoryClass
Type to differentiate different histories.
Definition lib.h:53
@ HC_MAX
Definition lib.h:61
static void remove_history_dups(enum HistoryClass hclass, const char *str)
De-dupe the history.
Definition history.c:355
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition history.c:522
static int OldSize
The previous number of history entries to save.
Definition history.c:103
void mutt_hist_read_file(void)
Read the History from a file.
Definition history.c:592
static int dup_hash_inc(struct HashTable *dup_hash, char *str)
Increase the refcount of a history string.
Definition history.c:182
void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
Save a temporary string to the History.
Definition history.c:661
static struct History Histories[HC_MAX]
Command histories, one for each HistoryClass.
Definition history.c:100
void mutt_hist_complete(struct Buffer *buf, enum HistoryClass hclass)
Complete a string from a history list.
Definition history.c:677
#define HC_FIRST
Definition history.c:83
void mutt_hist_init(void)
Create a set of empty History ring buffers.
Definition history.c:464
bool mutt_hist_at_scratch(enum HistoryClass hclass)
Is the current History position at the 'scratch' place?
Definition history.c:644
static struct History * get_history(enum HistoryClass hclass)
Get a particular history.
Definition history.c:110
static void save_history(enum HistoryClass hclass, const char *str)
Save one history string to a file.
Definition history.c:317
void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
Add a string to a history.
Definition history.c:482
void mutt_hist_reset_state(enum HistoryClass hclass)
Move the 'current' position to the end of the History.
Definition history.c:578
static int dup_hash_dec(struct HashTable *dup_hash, char *str)
Decrease the refcount of a history string.
Definition history.c:156
char * mutt_hist_prev(enum HistoryClass hclass)
Get the previous string in a History.
Definition history.c:550
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition history.c:126
int mutt_hist_search(const char *find, enum HistoryClass hclass, struct StringArray *matches)
Find matches in a history list.
Definition history.c:405
static void shrink_histfile(void)
Read, de-dupe and write the history file.
Definition history.c:203
void mutt_hist_cleanup(void)
Free all the history lists.
Definition history.c:437
@ LL_DEBUG5
Log at debug level 5.
Definition logging2.h:48
#define FREE(x)
Definition memory.h:62
#define MUTT_MEM_CALLOC(n, type)
Definition memory.h:47
#define MAX(a, b)
Definition memory.h:36
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition charset.c:831
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition charset.h:64
Convenience wrapper for the library headers.
#define _(a)
Definition message.h:28
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition string.c:255
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition string.c:660
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition string.c:523
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition string.c:282
@ NT_CONFIG
Config has changed, NotifyConfig, EventConfig.
Definition notify_type.h:43
#define NONULL(x)
Definition string2.h:43
String manipulation buffer.
Definition buffer.h:36
A config-change event.
Definition subset.h:70
const char * name
Name of config item that changed.
Definition subset.h:72
The item stored in a Hash Table.
Definition hash.h:44
void * data
User-supplied data.
Definition hash.h:47
A Hash Table.
Definition hash.h:99
Saved list of user-entered commands/searches.
Definition history.c:91
short cur
Current history item.
Definition history.c:93
short last
Last history item.
Definition history.c:94
char ** hist
Array of history items.
Definition history.c:92
Container for Accounts, Notifications.
Definition neomutt.h:43
struct ConfigSubset * sub
Inherited config items.
Definition neomutt.h:47
Data passed to a notification function.
Definition observer.h:34
void * event_data
Data from notify_send()
Definition observer.h:38
enum NotifyType event_type
Send: Event type, e.g. NT_ACCOUNT.
Definition observer.h:36
#define mutt_file_mkstemp()
Definition tmp.h:36