NeoMutt  2023-05-17-56-ga67199
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
98static struct History Histories[HC_MAX];
101static int OldSize = 0;
102
108static struct History *get_history(enum HistoryClass hclass)
109{
110 const short c_history = cs_subset_number(NeoMutt->sub, "history");
111 if ((hclass >= HC_MAX) || (c_history == 0))
112 return NULL;
113
114 struct History *hist = &Histories[hclass];
115 return hist->hist ? hist : NULL;
116}
117
124static void init_history(struct History *h)
125{
126 if (OldSize != 0)
127 {
128 if (h->hist)
129 {
130 for (int i = 0; i <= OldSize; i++)
131 FREE(&h->hist[i]);
132 FREE(&h->hist);
133 }
134 }
135
136 const short c_history = cs_subset_number(NeoMutt->sub, "history");
137 if (c_history != 0)
138 h->hist = mutt_mem_calloc(c_history + 1, sizeof(char *));
139
140 h->cur = 0;
141 h->last = 0;
142}
143
154static int dup_hash_dec(struct HashTable *dup_hash, char *str)
155{
156 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
157 if (!he)
158 return -1;
159
160 uintptr_t count = (uintptr_t) he->data;
161 if (count <= 1)
162 {
163 mutt_hash_delete(dup_hash, str, NULL);
164 return 0;
165 }
166
167 count--;
168 he->data = (void *) count;
169 return count;
170}
171
180static int dup_hash_inc(struct HashTable *dup_hash, char *str)
181{
182 uintptr_t count;
183
184 struct HashElem *he = mutt_hash_find_elem(dup_hash, str);
185 if (!he)
186 {
187 count = 1;
188 mutt_hash_insert(dup_hash, str, (void *) count);
189 return count;
190 }
191
192 count = (uintptr_t) he->data;
193 count++;
194 he->data = (void *) count;
195 return count;
196}
197
201static void shrink_histfile(void)
202{
203 FILE *fp_tmp = NULL;
204 int n[HC_MAX] = { 0 };
205 int line, hclass, read;
206 char *linebuf = NULL, *p = NULL;
207 size_t buflen;
208 bool regen_file = false;
209 struct HashTable *dup_hashes[HC_MAX] = { 0 };
210
211 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
212 FILE *fp = mutt_file_fopen(c_history_file, "r");
213 if (!fp)
214 return;
215
216 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
217 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
218 if (c_history_remove_dups)
219 {
220 for (hclass = 0; hclass < HC_MAX; hclass++)
221 dup_hashes[hclass] = mutt_hash_new(MAX(10, c_save_history * 2), MUTT_HASH_STRDUP_KEYS);
222 }
223
224 line = 0;
225 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
226 {
227 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
228 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
229 {
230 mutt_error(_("Bad history file format (line %d)"), line);
231 goto cleanup;
232 }
233 /* silently ignore too high class (probably newer neomutt) */
234 if (hclass >= HC_MAX)
235 continue;
236 *p = '\0';
237 if (c_history_remove_dups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
238 {
239 regen_file = true;
240 continue;
241 }
242 n[hclass]++;
243 }
244
245 if (!regen_file)
246 {
247 for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
248 {
249 if (n[hclass] > c_save_history)
250 {
251 regen_file = true;
252 break;
253 }
254 }
255 }
256
257 if (regen_file)
258 {
259 fp_tmp = mutt_file_mkstemp();
260 if (!fp_tmp)
261 {
262 mutt_perror(_("Can't create temporary file"));
263 goto cleanup;
264 }
265 rewind(fp);
266 line = 0;
267 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
268 {
269 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
270 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
271 {
272 mutt_error(_("Bad history file format (line %d)"), line);
273 goto cleanup;
274 }
275 if (hclass >= HC_MAX)
276 continue;
277 *p = '\0';
278 if (c_history_remove_dups && (dup_hash_dec(dup_hashes[hclass], linebuf + read) > 0))
279 {
280 continue;
281 }
282 *p = '|';
283 if (n[hclass]-- <= c_save_history)
284 fprintf(fp_tmp, "%s\n", linebuf);
285 }
286 }
287
288cleanup:
289 mutt_file_fclose(&fp);
290 FREE(&linebuf);
291 if (fp_tmp)
292 {
293 if ((fflush(fp_tmp) == 0) && (fp = fopen(NONULL(c_history_file), "w")))
294 {
295 rewind(fp_tmp);
296 mutt_file_copy_stream(fp_tmp, fp);
297 mutt_file_fclose(&fp);
298 }
299 mutt_file_fclose(&fp_tmp);
300 }
301 if (c_history_remove_dups)
302 for (hclass = 0; hclass < HC_MAX; hclass++)
303 mutt_hash_free(&dup_hashes[hclass]);
304}
305
311static void save_history(enum HistoryClass hclass, const char *str)
312{
313 static int n = 0;
314 char *tmp = NULL;
315
316 if (!str || (*str == '\0')) /* This shouldn't happen, but it's safer. */
317 return;
318
319 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
320 FILE *fp = mutt_file_fopen(c_history_file, "a");
321 if (!fp)
322 return;
323
324 tmp = mutt_str_dup(str);
326
327 /* Format of a history item (1 line): "<histclass>:<string>|".
328 * We add a '|' in order to avoid lines ending with '\'. */
329 fprintf(fp, "%d:", (int) hclass);
330 for (char *p = tmp; *p; p++)
331 {
332 /* Don't copy \n as a history item must fit on one line. The string
333 * shouldn't contain such a character anyway, but as this can happen
334 * in practice, we must deal with that. */
335 if (*p != '\n')
336 putc((unsigned char) *p, fp);
337 }
338 fputs("|\n", fp);
339
340 mutt_file_fclose(&fp);
341 FREE(&tmp);
342
343 if (--n < 0)
344 {
345 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
346 n = c_save_history;
348 }
349}
350
361static void remove_history_dups(enum HistoryClass hclass, const char *str)
362{
363 struct History *h = get_history(hclass);
364 if (!h)
365 return; /* disabled */
366
367 /* Remove dups from 0..last-1 compacting up. */
368 int source = 0;
369 int dest = 0;
370 while (source < h->last)
371 {
372 if (mutt_str_equal(h->hist[source], str))
373 FREE(&h->hist[source++]);
374 else
375 h->hist[dest++] = h->hist[source++];
376 }
377
378 /* Move 'last' entry up. */
379 h->hist[dest] = h->hist[source];
380 int old_last = h->last;
381 h->last = dest;
382
383 /* Fill in moved entries with NULL */
384 while (source > h->last)
385 h->hist[source--] = NULL;
386
387 /* Remove dups from last+1 .. `$history` compacting down. */
388 const short c_history = cs_subset_number(NeoMutt->sub, "history");
389 source = c_history;
390 dest = c_history;
391 while (source > old_last)
392 {
393 if (mutt_str_equal(h->hist[source], str))
394 FREE(&h->hist[source--]);
395 else
396 h->hist[dest--] = h->hist[source--];
397 }
398
399 /* Fill in moved entries with NULL */
400 while (dest > old_last)
401 h->hist[dest--] = NULL;
402}
403
411int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
412{
413 if (!search_buf || !matches)
414 return 0;
415
416 struct History *h = get_history(hclass);
417 if (!h)
418 return 0;
419
420 int match_count = 0;
421 int cur = h->last;
422 const short c_history = cs_subset_number(NeoMutt->sub, "history");
423 do
424 {
425 cur--;
426 if (cur < 0)
427 cur = c_history;
428 if (cur == h->last)
429 break;
430 if (mutt_istr_find(h->hist[cur], search_buf))
431 matches[match_count++] = h->hist[cur];
432 } while (match_count < c_history);
433
434 return match_count;
435}
436
441{
442 if (!NeoMutt)
443 return;
444
445 const short c_history = cs_subset_number(NeoMutt->sub, "history");
446 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
447 {
448 struct History *h = &Histories[hclass];
449 if (!h->hist)
450 continue;
451
452 /* The array has (`$history`+1) elements */
453 for (int i = 0; i <= c_history; i++)
454 {
455 FREE(&h->hist[i]);
456 }
457 FREE(&h->hist);
458 }
459}
460
468{
469 const short c_history = cs_subset_number(NeoMutt->sub, "history");
470 if (c_history == OldSize)
471 return;
472
473 for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
474 init_history(&Histories[hclass]);
475
476 OldSize = c_history;
477}
478
485void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
486{
487 struct History *h = get_history(hclass);
488 if (!h)
489 return; /* disabled */
490
491 if (*str)
492 {
493 int prev = h->last - 1;
494 const short c_history = cs_subset_number(NeoMutt->sub, "history");
495 if (prev < 0)
496 prev = c_history;
497
498 /* don't add to prompt history:
499 * - lines beginning by a space
500 * - repeated lines */
501 if ((*str != ' ') && (!h->hist[prev] || !mutt_str_equal(h->hist[prev], str)))
502 {
503 const bool c_history_remove_dups = cs_subset_bool(NeoMutt->sub, "history_remove_dups");
504 if (c_history_remove_dups)
505 remove_history_dups(hclass, str);
506 const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
507 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
508 if (save && (c_save_history != 0) && c_history_file)
509 save_history(hclass, str);
510 mutt_str_replace(&h->hist[h->last++], str);
511 if (h->last > c_history)
512 h->last = 0;
513 }
514 }
515 h->cur = h->last; /* reset to the last entry */
516}
517
525char *mutt_hist_next(enum HistoryClass hclass)
526{
527 struct History *h = get_history(hclass);
528 if (!h)
529 return ""; /* disabled */
530
531 int next = h->cur;
532 const short c_history = cs_subset_number(NeoMutt->sub, "history");
533 do
534 {
535 next++;
536 if (next > c_history)
537 next = 0;
538 if (next == h->last)
539 break;
540 } while (!h->hist[next]);
541
542 h->cur = next;
543 return NONULL(h->hist[h->cur]);
544}
545
553char *mutt_hist_prev(enum HistoryClass hclass)
554{
555 struct History *h = get_history(hclass);
556 if (!h)
557 return ""; /* disabled */
558
559 int prev = h->cur;
560 const short c_history = cs_subset_number(NeoMutt->sub, "history");
561 do
562 {
563 prev--;
564 if (prev < 0)
565 prev = c_history;
566 if (prev == h->last)
567 break;
568 } while (!h->hist[prev]);
569
570 h->cur = prev;
571 return NONULL(h->hist[h->cur]);
572}
573
582{
583 struct History *h = get_history(hclass);
584 if (!h)
585 return; /* disabled */
586
587 h->cur = h->last;
588}
589
596{
597 int line = 0, hclass, read;
598 char *linebuf = NULL, *p = NULL;
599 size_t buflen;
600
601 const char *const c_history_file = cs_subset_path(NeoMutt->sub, "history_file");
602 if (!c_history_file)
603 return;
604
605 FILE *fp = mutt_file_fopen(c_history_file, "r");
606 if (!fp)
607 return;
608
609 const char *const c_charset = cc_charset();
610 while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
611 {
612 read = 0;
613 if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
614 (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
615 {
616 mutt_error(_("Bad history file format (line %d)"), line);
617 break;
618 }
619 /* silently ignore too high class (probably newer neomutt) */
620 if (hclass >= HC_MAX)
621 continue;
622 *p = '\0';
623 p = mutt_str_dup(linebuf + read);
624 if (p)
625 {
626 mutt_ch_convert_string(&p, "utf-8", c_charset, MUTT_ICONV_NO_FLAGS);
627 mutt_hist_add(hclass, p, false);
628 FREE(&p);
629 }
630 }
631
632 mutt_file_fclose(&fp);
633 FREE(&linebuf);
634}
635
648{
649 struct History *h = get_history(hclass);
650 if (!h)
651 return false; /* disabled */
652
653 return h->cur == h->last;
654}
655
664void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
665{
666 struct History *h = get_history(hclass);
667 if (!h)
668 return; /* disabled */
669
670 /* Don't check if str has a value because the scratch buffer may contain
671 * an old garbage value that should be overwritten */
672 mutt_str_replace(&h->hist[h->last], str);
673}
const char * cc_charset(void)
Get the cached value of $charset.
Definition: cache.c:106
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:738
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:634
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:150
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:39
#define mutt_error(...)
Definition: logging2.h:90
#define mutt_perror(...)
Definition: logging2.h:91
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:112
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:361
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:525
static int OldSize
The previous number of history entries to save.
Definition: history.c:101
void mutt_hist_read_file(void)
Read the History from a file.
Definition: history.c:595
static int dup_hash_inc(struct HashTable *dup_hash, char *str)
Increase the refcount of a history string.
Definition: history.c:180
void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
Save a temporary string to the History.
Definition: history.c:664
static struct History Histories[HC_MAX]
Command histories, one for each HistoryClass.
Definition: history.c:98
#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:411
void mutt_hist_init(void)
Create a set of empty History ring buffers.
Definition: history.c:467
bool mutt_hist_at_scratch(enum HistoryClass hclass)
Is the current History position at the 'scratch' place?
Definition: history.c:647
static struct History * get_history(enum HistoryClass hclass)
Get a particular history.
Definition: history.c:108
static void save_history(enum HistoryClass hclass, const char *str)
Save one history string to a file.
Definition: history.c:311
void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
Add a string to a history.
Definition: history.c:485
void mutt_hist_reset_state(enum HistoryClass hclass)
Move the 'current' position to the end of the History.
Definition: history.c:581
void mutt_hist_free(void)
Free all the history lists.
Definition: history.c:440
static int dup_hash_dec(struct HashTable *dup_hash, char *str)
Decrease the refcount of a history string.
Definition: history.c:154
char * mutt_hist_prev(enum HistoryClass hclass)
Get the previous string in a History.
Definition: history.c:553
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition: history.c:124
static void shrink_histfile(void)
Read, de-dupe and write the history file.
Definition: history.c:201
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:822
#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:251
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:798
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:593
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:327
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:98
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
#define mutt_file_mkstemp()
Definition: tmp.h:40