NeoMutt  2021-02-05-666-ge300cd
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 =
209  cs_subset_path(NeoMutt->sub, "history_file");
210  FILE *fp = mutt_file_fopen(c_history_file, "r");
211  if (!fp)
212  return;
213 
214  const bool c_history_remove_dups =
215  cs_subset_bool(NeoMutt->sub, "history_remove_dups");
216  const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
217  if (c_history_remove_dups)
218  {
219  for (hclass = 0; hclass < HC_MAX; hclass++)
220  dup_hashes[hclass] = mutt_hash_new(MAX(10, c_save_history * 2), MUTT_HASH_STRDUP_KEYS);
221  }
222 
223  line = 0;
224  while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
225  {
226  if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
227  (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
228  {
229  mutt_error(_("Bad history file format (line %d)"), line);
230  goto cleanup;
231  }
232  /* silently ignore too high class (probably newer neomutt) */
233  if (hclass >= HC_MAX)
234  continue;
235  *p = '\0';
236  if (c_history_remove_dups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
237  {
238  regen_file = true;
239  continue;
240  }
241  n[hclass]++;
242  }
243 
244  if (!regen_file)
245  {
246  for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
247  {
248  if (n[hclass] > c_save_history)
249  {
250  regen_file = true;
251  break;
252  }
253  }
254  }
255 
256  if (regen_file)
257  {
258  fp_tmp = mutt_file_mkstemp();
259  if (!fp_tmp)
260  {
261  mutt_perror(_("Can't create temporary file"));
262  goto cleanup;
263  }
264  rewind(fp);
265  line = 0;
266  while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
267  {
268  if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
269  (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
270  {
271  mutt_error(_("Bad history file format (line %d)"), line);
272  goto cleanup;
273  }
274  if (hclass >= HC_MAX)
275  continue;
276  *p = '\0';
277  if (c_history_remove_dups && (dup_hash_dec(dup_hashes[hclass], linebuf + read) > 0))
278  {
279  continue;
280  }
281  *p = '|';
282  if (n[hclass]-- <= c_save_history)
283  fprintf(fp_tmp, "%s\n", linebuf);
284  }
285  }
286 
287 cleanup:
288  mutt_file_fclose(&fp);
289  FREE(&linebuf);
290  if (fp_tmp)
291  {
292  if ((fflush(fp_tmp) == 0) && (fp = fopen(NONULL(c_history_file), "w")))
293  {
294  rewind(fp_tmp);
295  mutt_file_copy_stream(fp_tmp, fp);
296  mutt_file_fclose(&fp);
297  }
298  mutt_file_fclose(&fp_tmp);
299  }
300  if (c_history_remove_dups)
301  for (hclass = 0; hclass < HC_MAX; hclass++)
302  mutt_hash_free(&dup_hashes[hclass]);
303 }
304 
310 static void save_history(enum HistoryClass hclass, const char *str)
311 {
312  static int n = 0;
313  char *tmp = NULL;
314 
315  if (!str || (*str == '\0')) /* This shouldn't happen, but it's safer. */
316  return;
317 
318  const char *const c_history_file =
319  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);
325  const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
326  mutt_ch_convert_string(&tmp, c_charset, "utf-8", MUTT_ICONV_NO_FLAGS);
327 
328  /* Format of a history item (1 line): "<histclass>:<string>|".
329  * We add a '|' in order to avoid lines ending with '\'. */
330  fprintf(fp, "%d:", (int) hclass);
331  for (char *p = tmp; *p; p++)
332  {
333  /* Don't copy \n as a history item must fit on one line. The string
334  * shouldn't contain such a character anyway, but as this can happen
335  * in practice, we must deal with that. */
336  if (*p != '\n')
337  putc((unsigned char) *p, fp);
338  }
339  fputs("|\n", fp);
340 
341  mutt_file_fclose(&fp);
342  FREE(&tmp);
343 
344  if (--n < 0)
345  {
346  const short c_save_history = cs_subset_number(NeoMutt->sub, "save_history");
347  n = c_save_history;
348  shrink_histfile();
349  }
350 }
351 
362 static void remove_history_dups(enum HistoryClass hclass, const char *str)
363 {
364  struct History *h = get_history(hclass);
365  if (!h)
366  return; /* disabled */
367 
368  /* Remove dups from 0..last-1 compacting up. */
369  int source = 0;
370  int dest = 0;
371  while (source < h->last)
372  {
373  if (mutt_str_equal(h->hist[source], str))
374  FREE(&h->hist[source++]);
375  else
376  h->hist[dest++] = h->hist[source++];
377  }
378 
379  /* Move 'last' entry up. */
380  h->hist[dest] = h->hist[source];
381  int old_last = h->last;
382  h->last = dest;
383 
384  /* Fill in moved entries with NULL */
385  while (source > h->last)
386  h->hist[source--] = NULL;
387 
388  /* Remove dups from last+1 .. `$history` compacting down. */
389  const short c_history = cs_subset_number(NeoMutt->sub, "history");
390  source = c_history;
391  dest = c_history;
392  while (source > old_last)
393  {
394  if (mutt_str_equal(h->hist[source], str))
395  FREE(&h->hist[source--]);
396  else
397  h->hist[dest--] = h->hist[source--];
398  }
399 
400  /* Fill in moved entries with NULL */
401  while (dest > old_last)
402  h->hist[dest--] = NULL;
403 }
404 
412 int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
413 {
414  if (!search_buf || !matches)
415  return 0;
416 
417  struct History *h = get_history(hclass);
418  if (!h)
419  return 0;
420 
421  int match_count = 0;
422  int cur = h->last;
423  const short c_history = cs_subset_number(NeoMutt->sub, "history");
424  do
425  {
426  cur--;
427  if (cur < 0)
428  cur = c_history;
429  if (cur == h->last)
430  break;
431  if (mutt_istr_find(h->hist[cur], search_buf))
432  matches[match_count++] = h->hist[cur];
433  } while (match_count < c_history);
434 
435  return match_count;
436 }
437 
441 void mutt_hist_free(void)
442 {
443  if (!NeoMutt)
444  return;
445 
446  const short c_history = cs_subset_number(NeoMutt->sub, "history");
447  for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
448  {
449  struct History *h = &Histories[hclass];
450  if (!h->hist)
451  continue;
452 
453  /* The array has (`$history`+1) elements */
454  for (int i = 0; i <= c_history; i++)
455  {
456  FREE(&h->hist[i]);
457  }
458  FREE(&h->hist);
459  }
460 }
461 
468 void mutt_hist_init(void)
469 {
470  const short c_history = cs_subset_number(NeoMutt->sub, "history");
471  if (c_history == OldSize)
472  return;
473 
474  for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
475  init_history(&Histories[hclass]);
476 
477  OldSize = c_history;
478 }
479 
486 void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
487 {
488  struct History *h = get_history(hclass);
489  if (!h)
490  return; /* disabled */
491 
492  if (*str)
493  {
494  int prev = h->last - 1;
495  const short c_history = cs_subset_number(NeoMutt->sub, "history");
496  if (prev < 0)
497  prev = c_history;
498 
499  /* don't add to prompt history:
500  * - lines beginning by a space
501  * - repeated lines */
502  if ((*str != ' ') && (!h->hist[prev] || !mutt_str_equal(h->hist[prev], str)))
503  {
504  const bool c_history_remove_dups =
505  cs_subset_bool(NeoMutt->sub, "history_remove_dups");
506  if (c_history_remove_dups)
507  remove_history_dups(hclass, str);
508  const short c_save_history =
509  cs_subset_number(NeoMutt->sub, "save_history");
510  const char *const c_history_file =
511  cs_subset_path(NeoMutt->sub, "history_file");
512  if (save && (c_save_history != 0) && c_history_file)
513  save_history(hclass, str);
514  mutt_str_replace(&h->hist[h->last++], str);
515  if (h->last > c_history)
516  h->last = 0;
517  }
518  }
519  h->cur = h->last; /* reset to the last entry */
520 }
521 
529 char *mutt_hist_next(enum HistoryClass hclass)
530 {
531  struct History *h = get_history(hclass);
532  if (!h)
533  return ""; /* disabled */
534 
535  int next = h->cur;
536  do
537  {
538  next++;
539  const short c_history = cs_subset_number(NeoMutt->sub, "history");
540  if (next > c_history)
541  next = 0;
542  if (next == h->last)
543  break;
544  } while (!h->hist[next]);
545 
546  h->cur = next;
547  return NONULL(h->hist[h->cur]);
548 }
549 
557 char *mutt_hist_prev(enum HistoryClass hclass)
558 {
559  struct History *h = get_history(hclass);
560  if (!h)
561  return ""; /* disabled */
562 
563  int prev = h->cur;
564  do
565  {
566  prev--;
567  const short c_history = cs_subset_number(NeoMutt->sub, "history");
568  if (prev < 0)
569  prev = c_history;
570  if (prev == h->last)
571  break;
572  } while (!h->hist[prev]);
573 
574  h->cur = prev;
575  return NONULL(h->hist[h->cur]);
576 }
577 
586 {
587  struct History *h = get_history(hclass);
588  if (!h)
589  return; /* disabled */
590 
591  h->cur = h->last;
592 }
593 
600 {
601  int line = 0, hclass, read;
602  char *linebuf = NULL, *p = NULL;
603  size_t buflen;
604 
605  const char *const c_history_file =
606  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  while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, MUTT_RL_NO_FLAGS)))
615  {
616  read = 0;
617  if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
618  (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
619  {
620  mutt_error(_("Bad history file format (line %d)"), line);
621  break;
622  }
623  /* silently ignore too high class (probably newer neomutt) */
624  if (hclass >= HC_MAX)
625  continue;
626  *p = '\0';
627  p = mutt_str_dup(linebuf + read);
628  if (p)
629  {
630  const char *const c_charset = cs_subset_string(NeoMutt->sub, "charset");
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 
669 void 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 }
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:904
static struct History Histories[HC_MAX]
Definition: history.c:97
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:327
#define NONULL(x)
Definition: string2.h:37
void * mutt_mem_calloc(size_t nmemb, size_t size)
Allocate zeroed memory on the heap.
Definition: memory.c:50
bool cs_subset_bool(const struct ConfigSubset *sub, const char *name)
Get a boolean config item by name.
Definition: helpers.c:73
A Hash Table.
Definition: hash.h:87
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:529
#define mutt_error(...)
Definition: logging.h:88
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:449
void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
Add a string to a history.
Definition: history.c:486
#define HC_FIRST
Definition: history.c:81
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
#define _(a)
Definition: message.h:28
int mutt_ch_convert_string(char **ps, const char *from, const char *to, uint8_t flags)
Convert a string between encodings.
Definition: charset.c:758
static int OldSize
Definition: history.c:98
#define MUTT_ICONV_NO_FLAGS
No flags are set.
Definition: charset.h:71
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:667
short cs_subset_number(const struct ConfigSubset *sub, const char *name)
Get a number config item by name.
Definition: helpers.c:169
#define mutt_perror(...)
Definition: logging.h:89
Container for Accounts, Notifications.
Definition: neomutt.h:36
static void save_history(enum HistoryClass hclass, const char *str)
Save one history string to a file.
Definition: history.c:310
Convenience wrapper for the config headers.
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:369
#define MAX(a, b)
Definition: memory.h:30
void mutt_hist_read_file(void)
Read the History from a file.
Definition: history.c:599
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
Convenience wrapper for the core headers.
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition: history.c:121
bool mutt_hist_at_scratch(enum HistoryClass hclass)
Is the current History position at the &#39;scratch&#39; place?
Definition: history.c:652
Saved list of user-entered commands/searches.
Definition: history.c:88
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 * 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:362
const char * cs_subset_string(const struct ConfigSubset *sub, const char *name)
Get a string config item by name.
Definition: helpers.c:317
#define mutt_file_mkstemp()
Definition: file.h:108
int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
Find matches in a history list.
Definition: history.c:412
const char * cs_subset_path(const struct ConfigSubset *sub, const char *name)
Get a path config item by name.
Definition: helpers.c:194
void mutt_hist_init(void)
Create a set of empty History ring buffers.
Definition: history.c:468
short cur
Definition: history.c:91
char * mutt_hist_prev(enum HistoryClass hclass)
Get the previous string in a History.
Definition: history.c:557
HistoryClass
Type to differentiate different histories.
Definition: lib.h:46
void * data
User-supplied data.
Definition: hash.h:47
const char * mutt_istr_find(const char *haystack, const char *needle)
Find first occurrence of string (ignoring case)
Definition: string.c:689
int mutt_file_copy_stream(FILE *fp_in, FILE *fp_out)
Copy the contents of one file into another.
Definition: file.c:271
char * mutt_str_replace(char **p, const char *s)
Replace one string with another.
Definition: string.c:446
static int dup_hash_dec(struct HashTable *dup_hash, char *str)
Decrease the refcount of a history string.
Definition: history.c:151
#define MUTT_RL_NO_FLAGS
No flags are set.
Definition: file.h:38
short last
Definition: history.c:92
#define FREE(x)
Definition: memory.h:40
void mutt_hash_delete(struct HashTable *table, const char *strkey, const void *data)
Remove an element from a Hash Table.
Definition: hash.c:419
static void shrink_histfile(void)
Read, de-dupe and write the history file.
Definition: history.c:198
The item stored in a Hash Table.
Definition: hash.h:43
char ** hist
Definition: history.c:90
struct ConfigSubset * sub
Inherited config items.
Definition: neomutt.h:39
void mutt_hist_reset_state(enum HistoryClass hclass)
Move the &#39;current&#39; position to the end of the History.
Definition: history.c:585
#define MUTT_HASH_STRDUP_KEYS
make a copy of the keys
Definition: hash.h:102
Convenience wrapper for the library headers.
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:589
Definition: lib.h:55
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:251
void mutt_hist_free(void)
Free all the history lists.
Definition: history.c:441
static int dup_hash_inc(struct HashTable *dup_hash, char *str)
Increase the refcount of a history string.
Definition: history.c:177