NeoMutt  2024-02-01-25-ga71e95
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
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, sizeof(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, read;
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(_("Bad history file format (line %d)"), line);
233 goto cleanup;
234 }
235 /* silently ignore too high class (probably newer neomutt) */
236 if (hclass >= HC_MAX)
237 continue;
238 *p = '\0';
239 if (c_history_remove_dups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
240 {
241 regen_file = true;
242 continue;
243 }
244 n[hclass]++;
245 }
246
247 if (!regen_file)
248 {
249 for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
250 {
251 if (n[hclass] > c_save_history)
252 {
253 regen_file = true;
254 break;
255 }
256 }
257 }
258
259 if (regen_file)
260 {
261 fp_tmp = mutt_file_mkstemp();
262 if (!fp_tmp)
263 {
264 mutt_perror(_("Can't create temporary file"));
265 goto cleanup;
266 }
267 rewind(fp);
268 line = 0;
269 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
270 {
271 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
272 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
273 {
274 mutt_error(_("Bad history file format (line %d)"), line);
275 goto cleanup;
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) && (fp = mutt_file_fopen(NONULL(c_history_file), "w")))
296 {
297 rewind(fp_tmp);
298 mutt_file_copy_stream(fp_tmp, fp);
299 mutt_file_fclose(&fp);
300 }
301 mutt_file_fclose(&fp_tmp);
302 }
303 if (c_history_remove_dups)
304 for (hclass = 0; hclass < HC_MAX; hclass++)
305 mutt_hash_free(&dup_hashes[hclass]);
306}
307
313static void save_history(enum HistoryClass hclass, const char *str)
314{
315 static int n = 0;
316 char *tmp = NULL;
317
318 if (!str || (*str == '\0')) /* This shouldn't happen, but it's safer. */
319 return;
320
321 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
322 FILE *fp = mutt_file_fopen(c_history_file, "a");
323 if (!fp)
324 return;
325
326 tmp = mutt_str_dup(str);
328
329 /* Format of a history item (1 line): "<histclass>:<string>|".
330 * We add a '|' in order to avoid lines ending with '\'. */
331 fprintf(fp, "%d:", (int) hclass);
332 for (char *p = tmp; *p; p++)
333 {
334 /* Don't copy \n as a history item must fit on one line. The string
335 * shouldn't contain such a character anyway, but as this can happen
336 * in practice, we must deal with that. */
337 if (*p != '\n')
338 putc((unsigned char) *p, fp);
339 }
340 fputs("|\n", fp);
341
342 mutt_file_fclose(&fp);
343 FREE(&tmp);
344
345 if (--n < 0)
346 {
347 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
348 n = c_save_history;
350 }
351}
352
363static void remove_history_dups(enum HistoryClass hclass, const char *str)
364{
365 struct History *h = get_history(hclass);
366 if (!h)
367 return; /* disabled */
368
369 /* Remove dups from 0..last-1 compacting up. */
370 int source = 0;
371 int dest = 0;
372 while (source < h->last)
373 {
374 if (mutt_str_equal(h->hist[source], str))
375 FREE(&h->hist[source++]);
376 else
377 h->hist[dest++] = h->hist[source++];
378 }
379
380 /* Move 'last' entry up. */
381 h->hist[dest] = h->hist[source];
382 int old_last = h->last;
383 h->last = dest;
384
385 /* Fill in moved entries with NULL */
386 while (source > h->last)
387 h->hist[source--] = NULL;
388
389 /* Remove dups from last+1 .. `$history` compacting down. */
390 const short c_history = cs_subset_number(NeoMutt->sub, "history");
391 source = c_history;
392 dest = c_history;
393 while (source > old_last)
394 {
395 if (mutt_str_equal(h->hist[source], str))
396 FREE(&h->hist[source--]);
397 else
398 h->hist[dest--] = h->hist[source--];
399 }
400
401 /* Fill in moved entries with NULL */
402 while (dest > old_last)
403 h->hist[dest--] = NULL;
404}
405
413int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
414{
415 if (!search_buf || !matches)
416 return 0;
417
418 struct History *h = get_history(hclass);
419 if (!h)
420 return 0;
421
422 int match_count = 0;
423 int cur = h->last;
424 const short c_history = cs_subset_number(NeoMutt->sub, "history");
425 do
426 {
427 cur--;
428 if (cur < 0)
429 cur = c_history;
430 if (cur == h->last)
431 break;
432 if (mutt_istr_find(h->hist[cur], search_buf))
433 matches[match_count++] = h->hist[cur];
434 } while (match_count < c_history);
435
436 return match_count;
437}
438
443{
444 if (!NeoMutt)
445 return;
446
447 const short c_history = cs_subset_number(NeoMutt->sub, "history");
448 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
449 {
450 struct History *h = &Histories[hclass];
451 if (!h->hist)
452 continue;
453
454 /* The array has (`$history`+1) elements */
455 for (int i = 0; i <= c_history; i++)
456 {
457 FREE(&h->hist[i]);
458 }
459 FREE(&h->hist);
460 }
461}
462
470{
471 const short c_history = cs_subset_number(NeoMutt->sub, "history");
472 if (c_history == OldSize)
473 return;
474
475 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
476 init_history(&Histories[hclass]);
477
478 OldSize = c_history;
479}
480
487void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
488{
489 struct History *h = get_history(hclass);
490 if (!h)
491 return; /* disabled */
492
493 if (*str)
494 {
495 int prev = h->last - 1;
496 const short c_history = cs_subset_number(NeoMutt->sub, "history");
497 if (prev < 0)
498 prev = c_history;
499
500 /* don't add to prompt history:
501 * - lines beginning by a space
502 * - repeated lines */
503 if ((*str != ' ') && (!h->hist[prev] || !mutt_str_equal(h->hist[prev], str)))
504 {
505 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
506 if (c_history_remove_dups)
507 remove_history_dups(hclass, str);
508 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
509 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
510 if (save && (c_save_history != 0) && c_history_file)
511 save_history(hclass, str);
512 mutt_str_replace(&h->hist[h->last++], str);
513 if (h->last > c_history)
514 h->last = 0;
515 }
516 }
517 h->cur = h->last; /* reset to the last entry */
518}
519
527char *mutt_hist_next(enum HistoryClass hclass)
528{
529 struct History *h = get_history(hclass);
530 if (!h)
531 return ""; /* disabled */
532
533 int next = h->cur;
534 const short c_history = cs_subset_number(NeoMutt->sub, "history");
535 do
536 {
537 next++;
538 if (next > c_history)
539 next = 0;
540 if (next == h->last)
541 break;
542 } while (!h->hist[next]);
543
544 h->cur = next;
545 return NONULL(h->hist[h->cur]);
546}
547
555char *mutt_hist_prev(enum HistoryClass hclass)
556{
557 struct History *h = get_history(hclass);
558 if (!h)
559 return ""; /* disabled */
560
561 int prev = h->cur;
562 const short c_history = cs_subset_number(NeoMutt->sub, "history");
563 do
564 {
565 prev--;
566 if (prev < 0)
567 prev = c_history;
568 if (prev == h->last)
569 break;
570 } while (!h->hist[prev]);
571
572 h->cur = prev;
573 return NONULL(h->hist[h->cur]);
574}
575
584{
585 struct History *h = get_history(hclass);
586 if (!h)
587 return; /* disabled */
588
589 h->cur = h->last;
590}
591
598{
599 int line = 0, hclass, read;
600 char *linebuf = NULL, *p = NULL;
601 size_t buflen;
602
603 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
604 if (!c_history_file)
605 return;
606
607 FILE *fp = mutt_file_fopen(c_history_file, "r");
608 if (!fp)
609 return;
610
611 const char *const c_charset = cc_charset();
612 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
613 {
614 read = 0;
615 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
616 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
617 {
618 mutt_error(_("Bad history file format (line %d)"), line);
619 break;
620 }
621 /* silently ignore too high class (probably newer neomutt) */
622 if (hclass >= HC_MAX)
623 continue;
624 *p = '\0';
625 p = mutt_str_dup(linebuf + read);
626 if (p)
627 {
628 mutt_ch_convert_string(&p, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS);
629 mutt_hist_add(hclass, p, false);
630 FREE(&p);
631 }
632 }
633
634 mutt_file_fclose(&fp);
635 FREE(&linebuf);
636}
637
650{
651 struct History *h = get_history(hclass);
652 if (!h)
653 return false; /* disabled */
654
655 return h->cur == h->last;
656}
657
666void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
667{
668 struct History *h = get_history(hclass);
669 if (!h)
670 return; /* disabled */
671
672 /* Don't check if str has a value because the scratch buffer may contain
673 * an old garbage value that should be overwritten */
674 mutt_str_replace(&h->hist[h->last], str);
675}
676
683void mutt_hist_complete(char *buf, size_t buflen, enum HistoryClass hclass)
684{
685 const short c_history = cs_subset_number(NeoMutt->sub, "history");
686 char **matches = mutt_mem_calloc(c_history, sizeof(char *));
687 int match_count = mutt_hist_search(buf, hclass, matches);
688 if (match_count)
689 {
690 if (match_count == 1)
691 mutt_str_copy(buf, matches[0], buflen);
692 else
693 dlg_history(buf, buflen, matches, match_count);
694 }
695 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}
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:144
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:169
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:48
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:282
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:801
#define mutt_file_fclose(FP)
Definition: file.h:148
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:147
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:40
void dlg_history(char *buf, size_t buflen, char **matches, int match_count)
Select an item from a history list -.
Definition: dlg_history.c:147
#define mutt_error(...)
Definition: logging2.h:92
#define mutt_debug(LEVEL,...)
Definition: logging2.h:89
#define mutt_perror(...)
Definition: logging2.h:93
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:111
HistoryClass
Type to differentiate different histories.
Definition: lib.h:50
@ HC_MAX
Definition: lib.h:58
static void remove_history_dups(enum HistoryClass hclass, const char *str)
De-dupe the history.
Definition: history.c:363
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:527
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:597
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:666
static struct History Histories[HC_MAX]
Command histories, one for each HistoryClass.
Definition: history.c:100
#define HC_FIRST
Definition: history.c:83
int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
Find matches in a history list.
Definition: history.c:413
void mutt_hist_init(void)
Create a set of empty History ring buffers.
Definition: history.c:469
bool mutt_hist_at_scratch(enum HistoryClass hclass)
Is the current History position at the 'scratch' place?
Definition: history.c:649
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:313
void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
Add a string to a history.
Definition: history.c:487
void mutt_hist_reset_state(enum HistoryClass hclass)
Move the 'current' position to the end of the History.
Definition: history.c:583
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:555
void mutt_hist_complete(char *buf, size_t buflen, enum HistoryClass hclass)
Complete a string from a history list.
Definition: history.c:683
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition: history.c:126
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:442
@ LL_DEBUG5
Log at debug level 5.
Definition: logging2.h:47
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
#define FREE(x)
Definition: memory.h:45
#define MAX(a, b)
Definition: memory.h:31
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition: charset.c:830
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition: charset.h:72
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:253
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:709
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:570
size_t mutt_str_copy(char *dest, const char *src, size_t dsize)
Copy a string into a buffer (guaranteeing NUL-termination)
Definition: string.c:630
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:329
@ NT_CONFIG
Config has changed, NotifyConfig, EventConfig.
Definition: notify_type.h:43
Key value store.
#define NONULL(x)
Definition: string2.h:37
A config-change event.
Definition: subset.h:71
const char * name
Name of config item that changed.
Definition: subset.h:73
The item stored in a Hash Table.
Definition: hash.h:43
void * data
User-supplied data.
Definition: hash.h:46
A Hash Table.
Definition: hash.h:97
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:41
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:45
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