NeoMutt  2022-04-29-247-gc6aae8
Teaching an old dog new tricks
DOXYGEN
history.c
Go to the documentation of this file.
1
71#include "config.h"
72#include <stdbool.h>
73#include <stdint.h>
74#include <stdio.h>
75#include <string.h>
76#include "mutt/lib.h"
77#include "config/lib.h"
78#include "core/lib.h"
79#include "lib.h"
80
81#define HC_FIRST HC_CMD
82
88struct History
89{
90 char **hist;
91 short cur;
92 short last;
93};
94
95/* global vars used for the string-history routines */
96
97static struct History Histories[HC_MAX];
98static int OldSize = 0;
99
105static struct History *get_history(enum HistoryClass hclass)
106{
107 const short c_history = cs_subset_number(NeoMutt->sub, "history");
108 if ((hclass >= HC_MAX) || (c_history == 0))
109 return NULL;
110
111 struct History *hist = &Histories[hclass];
112 return hist->hist ? hist : NULL;
113}
114
121static void init_history(struct History *h)
122{
123 if (OldSize != 0)
124 {
125 if (h->hist)
126 {
127 for (int i = 0; i <= OldSize; i++)
128 FREE(&h->hist[i]);
129 FREE(&h->hist);
130 }
131 }
132
133 const short c_history = cs_subset_number(NeoMutt->sub, "history");
134 if (c_history != 0)
135 h->hist = mutt_mem_calloc(c_history + 1, sizeof(char *));
136
137 h->cur = 0;
138 h->last = 0;
139}
140
151static int dup_hash_dec(struct HashTable *dup_hash, char *str)
152{
153 struct HashElem *elem = mutt_hash_find_elem(dup_hash, str);
154 if (!elem)
155 return -1;
156
157 uintptr_t count = (uintptr_t) elem->data;
158 if (count <= 1)
159 {
160 mutt_hash_delete(dup_hash, str, NULL);
161 return 0;
162 }
163
164 count--;
165 elem->data = (void *) count;
166 return count;
167}
168
177static int dup_hash_inc(struct HashTable *dup_hash, char *str)
178{
179 uintptr_t count;
180
181 struct HashElem *elem = mutt_hash_find_elem(dup_hash, str);
182 if (!elem)
183 {
184 count = 1;
185 mutt_hash_insert(dup_hash, str, (void *) count);
186 return count;
187 }
188
189 count = (uintptr_t) elem->data;
190 count++;
191 elem->data = (void *) count;
192 return count;
193}
194
198static void shrink_histfile(void)
199{
200 FILE *fp_tmp = NULL;
201 int n[HC_MAX] = { 0 };
202 int line, hclass, read;
203 char *linebuf = NULL, *p = NULL;
204 size_t buflen;
205 bool regen_file = false;
206 struct HashTable *dup_hashes[HC_MAX] = { 0 };
207
208 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
209 FILE *fp = mutt_file_fopen(c_history_file, "r");
210 if (!fp)
211 return;
212
213 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
214 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
215 if (c_history_remove_dups)
216 {
217 for (hclass = 0; hclass < HC_MAX; hclass++)
218 dup_hashes[hclass] = mutt_hash_new(MAX(10, c_save_history * 2), MUTT_HASH_STRDUP_KEYS);
219 }
220
221 line = 0;
222 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
223 {
224 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
225 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
226 {
227 mutt_error(_("Bad history file format (line %d)"), line);
228 goto cleanup;
229 }
230 /* silently ignore too high class (probably newer neomutt) */
231 if (hclass >= HC_MAX)
232 continue;
233 *p = '\0';
234 if (c_history_remove_dups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
235 {
236 regen_file = true;
237 continue;
238 }
239 n[hclass]++;
240 }
241
242 if (!regen_file)
243 {
244 for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
245 {
246 if (n[hclass] > c_save_history)
247 {
248 regen_file = true;
249 break;
250 }
251 }
252 }
253
254 if (regen_file)
255 {
256 fp_tmp = mutt_file_mkstemp();
257 if (!fp_tmp)
258 {
259 mutt_perror(_("Can't create temporary file"));
260 goto cleanup;
261 }
262 rewind(fp);
263 line = 0;
264 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
265 {
266 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
267 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
268 {
269 mutt_error(_("Bad history file format (line %d)"), line);
270 goto cleanup;
271 }
272 if (hclass >= HC_MAX)
273 continue;
274 *p = '\0';
275 if (c_history_remove_dups && (dup_hash_dec(dup_hashes[hclass], linebuf + read) > 0))
276 {
277 continue;
278 }
279 *p = '|';
280 if (n[hclass]-- <= c_save_history)
281 fprintf(fp_tmp, "%s\n", linebuf);
282 }
283 }
284
285cleanup:
286 mutt_file_fclose(&fp);
287 FREE(&linebuf);
288 if (fp_tmp)
289 {
290 if ((fflush(fp_tmp) == 0) && (fp = fopen(NONULL(c_history_file), "w")))
291 {
292 rewind(fp_tmp);
293 mutt_file_copy_stream(fp_tmp, fp);
294 mutt_file_fclose(&fp);
295 }
296 mutt_file_fclose(&fp_tmp);
297 }
298 if (c_history_remove_dups)
299 for (hclass = 0; hclass < HC_MAX; hclass++)
300 mutt_hash_free(&dup_hashes[hclass]);
301}
302
308static void save_history(enum HistoryClass hclass, const char *str)
309{
310 static int n = 0;
311 char *tmp = NULL;
312
313 if (!str || (*str == '\0')) /* This shouldn't happen, but it's safer. */
314 return;
315
316 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
317 FILE *fp = mutt_file_fopen(c_history_file, "a");
318 if (!fp)
319 return;
320
321 tmp = mutt_str_dup(str);
322 const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
323 mutt_ch_convert_string(&tmp, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS);
324
325 /* Format of a history item (1 line): "<histclass>:<string>|".
326 * We add a '|' in order to avoid lines ending with '\'. */
327 fprintf(fp, "%d:", (int) hclass);
328 for (char *p = tmp; *p; p++)
329 {
330 /* Don't copy \n as a history item must fit on one line. The string
331 * shouldn't contain such a character anyway, but as this can happen
332 * in practice, we must deal with that. */
333 if (*p != '\n')
334 putc((unsigned char) *p, fp);
335 }
336 fputs("|\n", fp);
337
338 mutt_file_fclose(&fp);
339 FREE(&tmp);
340
341 if (--n < 0)
342 {
343 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
344 n = c_save_history;
346 }
347}
348
359static void remove_history_dups(enum HistoryClass hclass, const char *str)
360{
361 struct History *h = get_history(hclass);
362 if (!h)
363 return; /* disabled */
364
365 /* Remove dups from 0..last-1 compacting up. */
366 int source = 0;
367 int dest = 0;
368 while (source < h->last)
369 {
370 if (mutt_str_equal(h->hist[source], str))
371 FREE(&h->hist[source++]);
372 else
373 h->hist[dest++] = h->hist[source++];
374 }
375
376 /* Move 'last' entry up. */
377 h->hist[dest] = h->hist[source];
378 int old_last = h->last;
379 h->last = dest;
380
381 /* Fill in moved entries with NULL */
382 while (source > h->last)
383 h->hist[source--] = NULL;
384
385 /* Remove dups from last+1 .. `$history` compacting down. */
386 const short c_history = cs_subset_number(NeoMutt->sub, "history");
387 source = c_history;
388 dest = c_history;
389 while (source > old_last)
390 {
391 if (mutt_str_equal(h->hist[source], str))
392 FREE(&h->hist[source--]);
393 else
394 h->hist[dest--] = h->hist[source--];
395 }
396
397 /* Fill in moved entries with NULL */
398 while (dest > old_last)
399 h->hist[dest--] = NULL;
400}
401
409int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
410{
411 if (!search_buf || !matches)
412 return 0;
413
414 struct History *h = get_history(hclass);
415 if (!h)
416 return 0;
417
418 int match_count = 0;
419 int cur = h->last;
420 const short c_history = cs_subset_number(NeoMutt->sub, "history");
421 do
422 {
423 cur--;
424 if (cur < 0)
425 cur = c_history;
426 if (cur == h->last)
427 break;
428 if (mutt_istr_find(h->hist[cur], search_buf))
429 matches[match_count++] = h->hist[cur];
430 } while (match_count < c_history);
431
432 return match_count;
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 do
531 {
532 next++;
533 const short c_history = cs_subset_number(NeoMutt->sub, "history");
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 do
559 {
560 prev--;
561 const short c_history = cs_subset_number(NeoMutt->sub, "history");
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 int line = 0, hclass, read;
596 char *linebuf = NULL, *p = NULL;
597 size_t buflen;
598
599 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
600 if (!c_history_file)
601 return;
602
603 FILE *fp = mutt_file_fopen(c_history_file, "r");
604 if (!fp)
605 return;
606
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(_("Bad history file format (line %d)"), line);
614 break;
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 const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
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}
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:194
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
Convenience wrapper for the config headers.
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:260
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:720
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:618
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
#define mutt_file_mkstemp()
Definition: file.h:112
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:38
#define mutt_error(...)
Definition: logging.h:87
#define mutt_perror(...)
Definition: logging.h:88
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:48
@ HC_MAX
Definition: lib.h:56
static void remove_history_dups(enum HistoryClass hclass, const char *str)
De-dupe the history.
Definition: history.c:359
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:523
static int OldSize
Definition: history.c:98
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:177
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]
Definition: history.c:97
#define HC_FIRST
Definition: history.c:81
int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
Find matches in a history list.
Definition: history.c:409
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:105
static void save_history(enum HistoryClass hclass, const char *str)
Save one history string to a file.
Definition: history.c:308
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
void mutt_hist_free(void)
Free all the history lists.
Definition: history.c:438
static int dup_hash_dec(struct HashTable *dup_hash, char *str)
Decrease the refcount of a history string.
Definition: history.c:151
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:121
static void shrink_histfile(void)
Read, de-dupe and write the history file.
Definition: history.c:198
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:43
#define MAX(a, b)
Definition: memory.h:30
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition: charset.c:752
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition: charset.h:71
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:250
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:807
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:592
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:326
Key value store.
#define NONULL(x)
Definition: string2.h:37
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:97
Saved list of user-entered commands/searches.
Definition: history.c:89
short cur
Current history item.
Definition: history.c:91
short last
Last history item.
Definition: history.c:92
char ** hist
Array of history items.
Definition: history.c:90
Container for Accounts, Notifications.
Definition: neomutt.h:37
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39