NeoMutt  2024-12-12-14-g7b49f7
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#include "functions.h"
85
86#define HC_FIRST HC_EXT_COMMAND
87
93struct History
94{
95 char **hist;
96 short cur;
97 short last;
98};
99
100/* global vars used for the string-history routines */
101
103static struct History Histories[HC_MAX];
106static int OldSize = 0;
107
113static struct History *get_history(enum HistoryClass hclass)
114{
115 const short c_history = cs_subset_number(NeoMutt->sub, "history");
116 if ((hclass >= HC_MAX) || (c_history == 0))
117 return NULL;
118
119 struct History *hist = &Histories[hclass];
120 return hist->hist ? hist : NULL;
121}
122
129static void init_history(struct History *h)
130{
131 if (OldSize != 0)
132 {
133 if (h->hist)
134 {
135 for (int i = 0; i <= OldSize; i++)
136 FREE(&h->hist[i]);
137 FREE(&h->hist);
138 }
139 }
140
141 const short c_history = cs_subset_number(NeoMutt->sub, "history");
142 if (c_history != 0)
143 h->hist = MUTT_MEM_CALLOC(c_history + 1, char *);
144
145 h->cur = 0;
146 h->last = 0;
147}
148
159static int dup_hash_dec(struct HashTable *dup_hash, char *str)
160{
161 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
162 if (!he)
163 return -1;
164
165 uintptr_t count = (uintptr_t) he->data;
166 if (count <= 1)
167 {
168 mutt_hash_delete(dup_hash, str, NULL);
169 return 0;
170 }
171
172 count--;
173 he->data = (void *) count;
174 return count;
175}
176
185static int dup_hash_inc(struct HashTable *dup_hash, char *str)
186{
187 uintptr_t count;
188
189 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
190 if (!he)
191 {
192 count = 1;
193 mutt_hash_insert(dup_hash, str, (void *) count);
194 return count;
195 }
196
197 count = (uintptr_t) he->data;
198 count++;
199 he->data = (void *) count;
200 return count;
201}
202
206static void shrink_histfile(void)
207{
208 FILE *fp_tmp = NULL;
209 int n[HC_MAX] = { 0 };
210 int line, hclass = 0, read = 0;
211 char *linebuf = NULL, *p = NULL;
212 size_t buflen;
213 bool regen_file = false;
214 struct HashTable *dup_hashes[HC_MAX] = { 0 };
215
216 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
217 FILE *fp = mutt_file_fopen(c_history_file, "r");
218 if (!fp)
219 return;
220
221 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
222 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
223 if (c_history_remove_dups)
224 {
225 for (hclass = 0; hclass < HC_MAX; hclass++)
226 dup_hashes[hclass] = mutt_hash_new(MAX(10, c_save_history * 2), MUTT_HASH_STRDUP_KEYS);
227 }
228
229 line = 0;
230 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
231 {
232 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
233 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
234 {
235 mutt_error(_("%s:%d: Bad history file format"), c_history_file, line);
236 regen_file = true;
237 continue;
238 }
239 /* silently ignore too high class (probably newer neomutt) */
240 if (hclass >= HC_MAX)
241 continue;
242 *p = '\0';
243 if (c_history_remove_dups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
244 {
245 regen_file = true;
246 continue;
247 }
248 n[hclass]++;
249 }
250
251 if (!regen_file)
252 {
253 for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
254 {
255 if (n[hclass] > c_save_history)
256 {
257 regen_file = true;
258 break;
259 }
260 }
261 }
262
263 if (regen_file)
264 {
265 fp_tmp = mutt_file_mkstemp();
266 if (!fp_tmp)
267 {
268 mutt_perror(_("Can't create temporary file"));
269 goto cleanup;
270 }
271 rewind(fp);
272 line = 0;
273 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
274 {
275 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
276 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
277 {
278 continue;
279 }
280 if (hclass >= HC_MAX)
281 continue;
282 *p = '\0';
283 if (c_history_remove_dups && (dup_hash_dec(dup_hashes[hclass], linebuf + read) > 0))
284 {
285 continue;
286 }
287 *p = '|';
288 if (n[hclass]-- <= c_save_history)
289 fprintf(fp_tmp, "%s\n", linebuf);
290 }
291 }
292
293cleanup:
294 mutt_file_fclose(&fp);
295 FREE(&linebuf);
296 if (fp_tmp)
297 {
298 if (fflush(fp_tmp) == 0)
299 {
300 if (truncate(c_history_file, 0) < 0)
301 mutt_debug(LL_DEBUG1, "truncate: %s\n", strerror(errno));
302 fp = mutt_file_fopen(c_history_file, "w");
303 if (fp)
304 {
305 rewind(fp_tmp);
306 mutt_file_copy_stream(fp_tmp, fp);
307 mutt_file_fclose(&fp);
308 }
309 }
310 mutt_file_fclose(&fp_tmp);
311 }
312 if (c_history_remove_dups)
313 for (hclass = 0; hclass < HC_MAX; hclass++)
314 mutt_hash_free(&dup_hashes[hclass]);
315}
316
322static void save_history(enum HistoryClass hclass, const char *str)
323{
324 static int n = 0;
325
326 if (!str || (*str == '\0')) // This shouldn't happen, but it's safer
327 return;
328
329 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
330 FILE *fp = mutt_file_fopen(c_history_file, "a");
331 if (!fp)
332 return;
333
334 char *tmp = mutt_str_dup(str);
336
337 // If tmp contains '\n' terminate it there.
338 char *nl = strchr(tmp, '\n');
339 if (nl)
340 *nl = '\0';
341
342 /* Format of a history item (1 line): "<histclass>:<string>|".
343 * We add a '|' in order to avoid lines ending with '\'. */
344 fprintf(fp, "%d:%s|\n", (int) hclass, tmp);
345
346 mutt_file_fclose(&fp);
347 FREE(&tmp);
348
349 if (--n < 0)
350 {
351 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
352 n = c_save_history;
354 }
355}
356
367static void remove_history_dups(enum HistoryClass hclass, const char *str)
368{
369 struct History *h = get_history(hclass);
370 if (!h)
371 return; /* disabled */
372
373 /* Remove dups from 0..last-1 compacting up. */
374 int source = 0;
375 int dest = 0;
376 while (source < h->last)
377 {
378 if (mutt_str_equal(h->hist[source], str))
379 FREE(&h->hist[source++]);
380 else
381 h->hist[dest++] = h->hist[source++];
382 }
383
384 /* Move 'last' entry up. */
385 h->hist[dest] = h->hist[source];
386 int old_last = h->last;
387 h->last = dest;
388
389 /* Fill in moved entries with NULL */
390 while (source > h->last)
391 h->hist[source--] = NULL;
392
393 /* Remove dups from last+1 .. `$history` compacting down. */
394 const short c_history = cs_subset_number(NeoMutt->sub, "history");
395 source = c_history;
396 dest = c_history;
397 while (source > old_last)
398 {
399 if (mutt_str_equal(h->hist[source], str))
400 FREE(&h->hist[source--]);
401 else
402 h->hist[dest--] = h->hist[source--];
403 }
404
405 /* Fill in moved entries with NULL */
406 while (dest > old_last)
407 h->hist[dest--] = NULL;
408}
409
417int mutt_hist_search(const char *find, enum HistoryClass hclass, struct HistoryArray *matches)
418{
419 if (!find || !matches)
420 return 0;
421
422 struct History *h = get_history(hclass);
423 if (!h)
424 return 0;
425
426 int cur = h->last;
427 const short c_history = cs_subset_number(NeoMutt->sub, "history");
428
429 do
430 {
431 cur--;
432 if (cur < 0)
433 cur = c_history;
434
435 if (cur == h->last)
436 break;
437
438 if (mutt_istr_find(h->hist[cur], find))
439 ARRAY_ADD(matches, h->hist[cur]);
440
441 } while (ARRAY_SIZE(matches) < c_history);
442
443 return ARRAY_SIZE(matches);
444}
445
450{
451 if (!NeoMutt)
452 return;
453
454 const short c_history = cs_subset_number(NeoMutt->sub, "history");
455 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
456 {
457 struct History *h = &Histories[hclass];
458 if (!h->hist)
459 continue;
460
461 /* The array has (`$history`+1) elements */
462 for (int i = 0; i <= c_history; i++)
463 {
464 FREE(&h->hist[i]);
465 }
466 FREE(&h->hist);
467 }
468}
469
477{
478 const short c_history = cs_subset_number(NeoMutt->sub, "history");
479 if (c_history == OldSize)
480 return;
481
482 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
483 init_history(&Histories[hclass]);
484
485 OldSize = c_history;
486}
487
494void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
495{
496 struct History *h = get_history(hclass);
497 if (!h)
498 return; /* disabled */
499
500 if (*str)
501 {
502 int prev = h->last - 1;
503 const short c_history = cs_subset_number(NeoMutt->sub, "history");
504 if (prev < 0)
505 prev = c_history;
506
507 /* don't add to prompt history:
508 * - lines beginning by a space
509 * - repeated lines */
510 if ((*str != ' ') && (!h->hist[prev] || !mutt_str_equal(h->hist[prev], str)))
511 {
512 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
513 if (c_history_remove_dups)
514 remove_history_dups(hclass, str);
515 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
516 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
517 if (save && (c_save_history != 0) && c_history_file)
518 save_history(hclass, str);
519 mutt_str_replace(&h->hist[h->last++], str);
520 if (h->last > c_history)
521 h->last = 0;
522 }
523 }
524 h->cur = h->last; /* reset to the last entry */
525}
526
534char *mutt_hist_next(enum HistoryClass hclass)
535{
536 struct History *h = get_history(hclass);
537 if (!h)
538 return ""; /* disabled */
539
540 int next = h->cur;
541 const short c_history = cs_subset_number(NeoMutt->sub, "history");
542 do
543 {
544 next++;
545 if (next > c_history)
546 next = 0;
547 if (next == h->last)
548 break;
549 } while (!h->hist[next]);
550
551 h->cur = next;
552 return NONULL(h->hist[h->cur]);
553}
554
562char *mutt_hist_prev(enum HistoryClass hclass)
563{
564 struct History *h = get_history(hclass);
565 if (!h)
566 return ""; /* disabled */
567
568 int prev = h->cur;
569 const short c_history = cs_subset_number(NeoMutt->sub, "history");
570 do
571 {
572 prev--;
573 if (prev < 0)
574 prev = c_history;
575 if (prev == h->last)
576 break;
577 } while (!h->hist[prev]);
578
579 h->cur = prev;
580 return NONULL(h->hist[h->cur]);
581}
582
591{
592 struct History *h = get_history(hclass);
593 if (!h)
594 return; /* disabled */
595
596 h->cur = h->last;
597}
598
605{
606 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
607 if (!c_history_file)
608 return;
609
610 FILE *fp = mutt_file_fopen(c_history_file, "r");
611 if (!fp)
612 return;
613
614 int line = 0, hclass = 0, read = 0;
615 char *linebuf = NULL, *p = NULL;
616 size_t buflen;
617
618 const char *const c_charset = cc_charset();
619 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
620 {
621 read = 0;
622 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
623 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
624 {
625 mutt_error(_("%s:%d: Bad history file format"), c_history_file, line);
626 continue;
627 }
628 /* silently ignore too high class (probably newer neomutt) */
629 if (hclass >= HC_MAX)
630 continue;
631 *p = '\0';
632 p = mutt_str_dup(linebuf + read);
633 if (p)
634 {
635 mutt_ch_convert_string(&p, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS);
636 mutt_hist_add(hclass, p, false);
637 FREE(&p);
638 }
639 }
640
641 mutt_file_fclose(&fp);
642 FREE(&linebuf);
643}
644
657{
658 struct History *h = get_history(hclass);
659 if (!h)
660 return false; /* disabled */
661
662 return h->cur == h->last;
663}
664
673void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
674{
675 struct History *h = get_history(hclass);
676 if (!h)
677 return; /* disabled */
678
679 /* Don't check if str has a value because the scratch buffer may contain
680 * an old garbage value that should be overwritten */
681 mutt_str_replace(&h->hist[h->last], str);
682}
683
689void mutt_hist_complete(struct Buffer *buf, enum HistoryClass hclass)
690{
691 struct HistoryArray matches = ARRAY_HEAD_INITIALIZER;
692
693 int match_count = mutt_hist_search(buf_string(buf), hclass, &matches);
694 if (match_count != 0)
695 {
696 if (match_count == 1)
697 {
698 const char **pstr = ARRAY_GET(&matches, 0);
699 buf_strcpy(buf, *pstr);
700 }
701 else
702 {
703 dlg_history(buf, &matches);
704 }
705 }
706
707 ARRAY_FREE(&matches);
708}
709
714{
715 if (nc->event_type != NT_CONFIG)
716 return 0;
717 if (!nc->event_data)
718 return -1;
719
720 struct EventConfig *ev_c = nc->event_data;
721
722 if (!mutt_str_equal(ev_c->name, "history"))
723 return 0;
724
726 mutt_debug(LL_DEBUG5, "history done\n");
727 return 0;
728}
#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: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:138
#define mutt_file_fopen(PATH, MODE)
Definition: file.h:137
#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: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:713
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: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:367
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:534
static int OldSize
The previous number of history entries to save.
Definition: history.c:106
void mutt_hist_read_file(void)
Read the History from a file.
Definition: history.c:604
static int dup_hash_inc(struct HashTable *dup_hash, char *str)
Increase the refcount of a history string.
Definition: history.c:185
void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
Save a temporary string to the History.
Definition: history.c:673
static struct History Histories[HC_MAX]
Command histories, one for each HistoryClass.
Definition: history.c:103
void mutt_hist_complete(struct Buffer *buf, enum HistoryClass hclass)
Complete a string from a history list.
Definition: history.c:689
#define HC_FIRST
Definition: history.c:86
void mutt_hist_init(void)
Create a set of empty History ring buffers.
Definition: history.c:476
bool mutt_hist_at_scratch(enum HistoryClass hclass)
Is the current History position at the 'scratch' place?
Definition: history.c:656
static struct History * get_history(enum HistoryClass hclass)
Get a particular history.
Definition: history.c:113
static void save_history(enum HistoryClass hclass, const char *str)
Save one history string to a file.
Definition: history.c:322
void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
Add a string to a history.
Definition: history.c:494
void mutt_hist_reset_state(enum HistoryClass hclass)
Move the 'current' position to the end of the History.
Definition: history.c:590
static int dup_hash_dec(struct HashTable *dup_hash, char *str)
Decrease the refcount of a history string.
Definition: history.c:159
char * mutt_hist_prev(enum HistoryClass hclass)
Get the previous string in a History.
Definition: history.c:562
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition: history.c:129
static void shrink_histfile(void)
Read, de-dupe and write the history file.
Definition: history.c:206
int mutt_hist_search(const char *find, enum HistoryClass hclass, struct HistoryArray *matches)
Find matches in a history list.
Definition: history.c:417
void mutt_hist_cleanup(void)
Free all the history lists.
Definition: history.c:449
@ LL_DEBUG5
Log at debug level 5.
Definition: logging2.h:47
@ LL_DEBUG1
Log at debug level 1.
Definition: logging2.h:43
#define FREE(x)
Definition: memory.h:55
#define MUTT_MEM_CALLOC(n, type)
Definition: memory.h:40
#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: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: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
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
Sidebar functions.
Key value store.
#define NONULL(x)
Definition: string2.h:37
String manipulation buffer.
Definition: buffer.h:36
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:94
short cur
Current history item.
Definition: history.c:96
short last
Last history item.
Definition: history.c:97
char ** hist
Array of history items.
Definition: history.c:95
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