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