NeoMutt  2022-04-29-145-g9b6a0e
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 
88 struct History
89 {
90  char **hist;
91  short cur;
92  short last;
93 };
94 
95 /* global vars used for the string-history routines */
96 
97 static struct History Histories[HC_MAX];
98 static int OldSize = 0;
99 
105 static 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 
121 static 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 
151 static 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 
177 static 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 
198 static 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 
285 cleanup:
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 
308 static 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;
345  shrink_histfile();
346  }
347 }
348 
359 static 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 
409 int 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 
438 void mutt_hist_free(void)
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 
465 void mutt_hist_init(void)
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 
483 void 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 
523 char *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 
551 char *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 
662 void 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 }
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_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
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
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:152
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
#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
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 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
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
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:259
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 struct History * get_history(enum HistoryClass hclass)
Get a particular history.
Definition: history.c:105
static void remove_history_dups(enum HistoryClass hclass, const char *str)
De-dupe the history.
Definition: history.c:359
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
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:523
#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 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
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition: history.c:121
char * mutt_hist_prev(enum HistoryClass hclass)
Get the previous string in a History.
Definition: history.c:551
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
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:569
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:784
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