NeoMutt  2024-04-25-76-g20fe7b
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 <errno.h>
75#include <stdbool.h>
76#include <stdint.h>
77#include <stdio.h>
78#include <string.h>
79#include <unistd.h>
80#include "mutt/lib.h"
81#include "config/lib.h"
82#include "core/lib.h"
83#include "lib.h"
84
85#define HC_FIRST HC_EXT_COMMAND
86
92struct History
93{
94 char **hist;
95 short cur;
96 short last;
97};
98
99/* global vars used for the string-history routines */
100
102static struct History Histories[HC_MAX];
105static int OldSize = 0;
106
112static struct History *get_history(enum HistoryClass hclass)
113{
114 const short c_history = cs_subset_number(NeoMutt->sub, "history");
115 if ((hclass >= HC_MAX) || (c_history == 0))
116 return NULL;
117
118 struct History *hist = &Histories[hclass];
119 return hist->hist ? hist : NULL;
120}
121
128static void init_history(struct History *h)
129{
130 if (OldSize != 0)
131 {
132 if (h->hist)
133 {
134 for (int i = 0; i <= OldSize; i++)
135 FREE(&h->hist[i]);
136 FREE(&h->hist);
137 }
138 }
139
140 const short c_history = cs_subset_number(NeoMutt->sub, "history");
141 if (c_history != 0)
142 h->hist = mutt_mem_calloc(c_history + 1, sizeof(char *));
143
144 h->cur = 0;
145 h->last = 0;
146}
147
158static int dup_hash_dec(struct HashTable *dup_hash, char *str)
159{
160 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
161 if (!he)
162 return -1;
163
164 uintptr_t count = (uintptr_t) he->data;
165 if (count <= 1)
166 {
167 mutt_hash_delete(dup_hash, str, NULL);
168 return 0;
169 }
170
171 count--;
172 he->data = (void *) count;
173 return count;
174}
175
184static int dup_hash_inc(struct HashTable *dup_hash, char *str)
185{
186 uintptr_t count;
187
188 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
189 if (!he)
190 {
191 count = 1;
192 mutt_hash_insert(dup_hash, str, (void *) count);
193 return count;
194 }
195
196 count = (uintptr_t) he->data;
197 count++;
198 he->data = (void *) count;
199 return count;
200}
201
205static void shrink_histfile(void)
206{
207 FILE *fp_tmp = NULL;
208 int n[HC_MAX] = { 0 };
209 int line, hclass, read;
210 char *linebuf = NULL, *p = NULL;
211 size_t buflen;
212 bool regen_file = false;
213 struct HashTable *dup_hashes[HC_MAX] = { 0 };
214
215 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
216 FILE *fp = mutt_file_fopen(c_history_file, "r");
217 if (!fp)
218 return;
219
220 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
221 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
222 if (c_history_remove_dups)
223 {
224 for (hclass = 0; hclass < HC_MAX; hclass++)
225 dup_hashes[hclass] = mutt_hash_new(MAX(10, c_save_history * 2), MUTT_HASH_STRDUP_KEYS);
226 }
227
228 line = 0;
229 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
230 {
231 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
232 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
233 {
234 mutt_error(_("%s:%d: Bad history file format"), c_history_file, line);
235 regen_file = true;
236 continue;
237 }
238 /* silently ignore too high class (probably newer neomutt) */
239 if (hclass >= HC_MAX)
240 continue;
241 *p = '\0';
242 if (c_history_remove_dups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
243 {
244 regen_file = true;
245 continue;
246 }
247 n[hclass]++;
248 }
249
250 if (!regen_file)
251 {
252 for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
253 {
254 if (n[hclass] > c_save_history)
255 {
256 regen_file = true;
257 break;
258 }
259 }
260 }
261
262 if (regen_file)
263 {
264 fp_tmp = mutt_file_mkstemp();
265 if (!fp_tmp)
266 {
267 mutt_perror(_("Can't create temporary file"));
268 goto cleanup;
269 }
270 rewind(fp);
271 line = 0;
272 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
273 {
274 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
275 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
276 {
277 continue;
278 }
279 if (hclass >= HC_MAX)
280 continue;
281 *p = '\0';
282 if (c_history_remove_dups && (dup_hash_dec(dup_hashes[hclass], linebuf + read) > 0))
283 {
284 continue;
285 }
286 *p = '|';
287 if (n[hclass]-- <= c_save_history)
288 fprintf(fp_tmp, "%s\n", linebuf);
289 }
290 }
291
292cleanup:
293 mutt_file_fclose(&fp);
294 FREE(&linebuf);
295 if (fp_tmp)
296 {
297 if (fflush(fp_tmp) == 0)
298 {
299 if (truncate(c_history_file, 0) < 0)
300 mutt_debug(LL_DEBUG1, "truncate: %s\n", strerror(errno));
301 fp = mutt_file_fopen(c_history_file, "w");
302 if (fp)
303 {
304 rewind(fp_tmp);
305 mutt_file_copy_stream(fp_tmp, fp);
306 mutt_file_fclose(&fp);
307 }
308 }
309 mutt_file_fclose(&fp_tmp);
310 }
311 if (c_history_remove_dups)
312 for (hclass = 0; hclass < HC_MAX; hclass++)
313 mutt_hash_free(&dup_hashes[hclass]);
314}
315
321static void save_history(enum HistoryClass hclass, const char *str)
322{
323 static int n = 0;
324
325 if (!str || (*str == '\0')) // This shouldn't happen, but it's safer
326 return;
327
328 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
329 FILE *fp = mutt_file_fopen(c_history_file, "a");
330 if (!fp)
331 return;
332
333 char *tmp = mutt_str_dup(str);
335
336 // If tmp contains '\n' terminate it there.
337 char *nl = strchr(tmp, '\n');
338 if (nl)
339 *nl = '\0';
340
341 /* Format of a history item (1 line): "<histclass>:<string>|".
342 * We add a '|' in order to avoid lines ending with '\'. */
343 fprintf(fp, "%d:%s|\n", (int) hclass, tmp);
344
345 mutt_file_fclose(&fp);
346 FREE(&tmp);
347
348 if (--n < 0)
349 {
350 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
351 n = c_save_history;
353 }
354}
355
366static void remove_history_dups(enum HistoryClass hclass, const char *str)
367{
368 struct History *h = get_history(hclass);
369 if (!h)
370 return; /* disabled */
371
372 /* Remove dups from 0..last-1 compacting up. */
373 int source = 0;
374 int dest = 0;
375 while (source < h->last)
376 {
377 if (mutt_str_equal(h->hist[source], str))
378 FREE(&h->hist[source++]);
379 else
380 h->hist[dest++] = h->hist[source++];
381 }
382
383 /* Move 'last' entry up. */
384 h->hist[dest] = h->hist[source];
385 int old_last = h->last;
386 h->last = dest;
387
388 /* Fill in moved entries with NULL */
389 while (source > h->last)
390 h->hist[source--] = NULL;
391
392 /* Remove dups from last+1 .. `$history` compacting down. */
393 const short c_history = cs_subset_number(NeoMutt->sub, "history");
394 source = c_history;
395 dest = c_history;
396 while (source > old_last)
397 {
398 if (mutt_str_equal(h->hist[source], str))
399 FREE(&h->hist[source--]);
400 else
401 h->hist[dest--] = h->hist[source--];
402 }
403
404 /* Fill in moved entries with NULL */
405 while (dest > old_last)
406 h->hist[dest--] = NULL;
407}
408
416int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
417{
418 if (!search_buf || !matches)
419 return 0;
420
421 struct History *h = get_history(hclass);
422 if (!h)
423 return 0;
424
425 int match_count = 0;
426 int cur = h->last;
427 const short c_history = cs_subset_number(NeoMutt->sub, "history");
428 do
429 {
430 cur--;
431 if (cur < 0)
432 cur = c_history;
433 if (cur == h->last)
434 break;
435 if (mutt_istr_find(h->hist[cur], search_buf))
436 matches[match_count++] = h->hist[cur];
437 } while (match_count < c_history);
438
439 return match_count;
440}
441
446{
447 if (!NeoMutt)
448 return;
449
450 const short c_history = cs_subset_number(NeoMutt->sub, "history");
451 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
452 {
453 struct History *h = &Histories[hclass];
454 if (!h->hist)
455 continue;
456
457 /* The array has (`$history`+1) elements */
458 for (int i = 0; i <= c_history; i++)
459 {
460 FREE(&h->hist[i]);
461 }
462 FREE(&h->hist);
463 }
464}
465
473{
474 const short c_history = cs_subset_number(NeoMutt->sub, "history");
475 if (c_history == OldSize)
476 return;
477
478 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
479 init_history(&Histories[hclass]);
480
481 OldSize = c_history;
482}
483
490void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
491{
492 struct History *h = get_history(hclass);
493 if (!h)
494 return; /* disabled */
495
496 if (*str)
497 {
498 int prev = h->last - 1;
499 const short c_history = cs_subset_number(NeoMutt->sub, "history");
500 if (prev < 0)
501 prev = c_history;
502
503 /* don't add to prompt history:
504 * - lines beginning by a space
505 * - repeated lines */
506 if ((*str != ' ') && (!h->hist[prev] || !mutt_str_equal(h->hist[prev], str)))
507 {
508 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
509 if (c_history_remove_dups)
510 remove_history_dups(hclass, str);
511 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
512 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
513 if (save && (c_save_history != 0) && c_history_file)
514 save_history(hclass, str);
515 mutt_str_replace(&h->hist[h->last++], str);
516 if (h->last > c_history)
517 h->last = 0;
518 }
519 }
520 h->cur = h->last; /* reset to the last entry */
521}
522
530char *mutt_hist_next(enum HistoryClass hclass)
531{
532 struct History *h = get_history(hclass);
533 if (!h)
534 return ""; /* disabled */
535
536 int next = h->cur;
537 const short c_history = cs_subset_number(NeoMutt->sub, "history");
538 do
539 {
540 next++;
541 if (next > c_history)
542 next = 0;
543 if (next == h->last)
544 break;
545 } while (!h->hist[next]);
546
547 h->cur = next;
548 return NONULL(h->hist[h->cur]);
549}
550
558char *mutt_hist_prev(enum HistoryClass hclass)
559{
560 struct History *h = get_history(hclass);
561 if (!h)
562 return ""; /* disabled */
563
564 int prev = h->cur;
565 const short c_history = cs_subset_number(NeoMutt->sub, "history");
566 do
567 {
568 prev--;
569 if (prev < 0)
570 prev = c_history;
571 if (prev == h->last)
572 break;
573 } while (!h->hist[prev]);
574
575 h->cur = prev;
576 return NONULL(h->hist[h->cur]);
577}
578
587{
588 struct History *h = get_history(hclass);
589 if (!h)
590 return; /* disabled */
591
592 h->cur = h->last;
593}
594
601{
602 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
603 if (!c_history_file)
604 return;
605
606 FILE *fp = mutt_file_fopen(c_history_file, "r");
607 if (!fp)
608 return;
609
610 int line = 0, hclass, read;
611 char *linebuf = NULL, *p = NULL;
612 size_t buflen;
613
614 const char *const c_charset = cc_charset();
615 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
616 {
617 read = 0;
618 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
619 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
620 {
621 mutt_error(_("%s:%d: Bad history file format"), c_history_file, line);
622 continue;
623 }
624 /* silently ignore too high class (probably newer neomutt) */
625 if (hclass >= HC_MAX)
626 continue;
627 *p = '\0';
628 p = mutt_str_dup(linebuf + read);
629 if (p)
630 {
631 mutt_ch_convert_string(&p, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS);
632 mutt_hist_add(hclass, p, false);
633 FREE(&p);
634 }
635 }
636
637 mutt_file_fclose(&fp);
638 FREE(&linebuf);
639}
640
653{
654 struct History *h = get_history(hclass);
655 if (!h)
656 return false; /* disabled */
657
658 return h->cur == h->last;
659}
660
669void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
670{
671 struct History *h = get_history(hclass);
672 if (!h)
673 return; /* disabled */
674
675 /* Don't check if str has a value because the scratch buffer may contain
676 * an old garbage value that should be overwritten */
677 mutt_str_replace(&h->hist[h->last], str);
678}
679
686void mutt_hist_complete(char *buf, size_t buflen, enum HistoryClass hclass)
687{
688 const short c_history = cs_subset_number(NeoMutt->sub, "history");
689 char **matches = mutt_mem_calloc(c_history, sizeof(char *));
690 int match_count = mutt_hist_search(buf, hclass, matches);
691 if (match_count)
692 {
693 if (match_count == 1)
694 mutt_str_copy(buf, matches[0], buflen);
695 else
696 dlg_history(buf, buflen, matches, match_count);
697 }
698 FREE(&matches);
699}
700
705{
706 if (nc->event_type != NT_CONFIG)
707 return 0;
708 if (!nc->event_data)
709 return -1;
710
711 struct EventConfig *ev_c = nc->event_data;
712
713 if (!mutt_str_equal(ev_c->name, "history"))
714 return 0;
715
717 mutt_debug(LL_DEBUG5, "history done\n");
718 return 0;
719}
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:287
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:808
#define mutt_file_fclose(FP)
Definition: file.h:149
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:148
#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:139
#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:704
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:366
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:530
static int OldSize
The previous number of history entries to save.
Definition: history.c:105
void mutt_hist_read_file(void)
Read the History from a file.
Definition: history.c:600
static int dup_hash_inc(struct HashTable *dup_hash, char *str)
Increase the refcount of a history string.
Definition: history.c:184
void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
Save a temporary string to the History.
Definition: history.c:669
static struct History Histories[HC_MAX]
Command histories, one for each HistoryClass.
Definition: history.c:102
#define HC_FIRST
Definition: history.c:85
int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
Find matches in a history list.
Definition: history.c:416
void mutt_hist_init(void)
Create a set of empty History ring buffers.
Definition: history.c:472
bool mutt_hist_at_scratch(enum HistoryClass hclass)
Is the current History position at the 'scratch' place?
Definition: history.c:652
static struct History * get_history(enum HistoryClass hclass)
Get a particular history.
Definition: history.c:112
static void save_history(enum HistoryClass hclass, const char *str)
Save one history string to a file.
Definition: history.c:321
void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
Add a string to a history.
Definition: history.c:490
void mutt_hist_reset_state(enum HistoryClass hclass)
Move the 'current' position to the end of the History.
Definition: history.c:586
static int dup_hash_dec(struct HashTable *dup_hash, char *str)
Decrease the refcount of a history string.
Definition: history.c:158
char * mutt_hist_prev(enum HistoryClass hclass)
Get the previous string in a History.
Definition: history.c:558
void mutt_hist_complete(char *buf, size_t buflen, enum HistoryClass hclass)
Complete a string from a history list.
Definition: history.c:686
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition: history.c:128
static void shrink_histfile(void)
Read, de-dupe and write the history file.
Definition: history.c:205
void mutt_hist_cleanup(void)
Free all the history lists.
Definition: history.c:445
@ LL_DEBUG5
Log at debug level 5.
Definition: logging2.h:47
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:51
#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:831
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition: charset.h:73
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:660
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:521
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:581
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:280
@ 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:93
short cur
Current history item.
Definition: history.c:95
short last
Last history item.
Definition: history.c:96
char ** hist
Array of history items.
Definition: history.c:94
Container for Accounts, Notifications.
Definition: neomutt.h:42
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:46
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