NeoMutt  2020-09-25
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 "lib.h"
78 
79 #define HC_FIRST HC_CMD
80 
86 struct History
87 {
88  char **hist;
89  short cur;
90  short last;
91 };
92 
93 /* global vars used for the string-history routines */
94 
95 static struct History Histories[HC_MAX];
96 static int OldSize = 0;
97 
103 static struct History *get_history(enum HistoryClass hclass)
104 {
105  if ((hclass >= HC_MAX) || (C_History == 0))
106  return NULL;
107 
108  struct History *hist = &Histories[hclass];
109  return hist->hist ? hist : NULL;
110 }
111 
118 static void init_history(struct History *h)
119 {
120  if (OldSize != 0)
121  {
122  if (h->hist)
123  {
124  for (int i = 0; i <= OldSize; i++)
125  FREE(&h->hist[i]);
126  FREE(&h->hist);
127  }
128  }
129 
130  if (C_History != 0)
131  h->hist = mutt_mem_calloc(C_History + 1, sizeof(char *));
132 
133  h->cur = 0;
134  h->last = 0;
135 }
136 
147 static int dup_hash_dec(struct HashTable *dup_hash, char *str)
148 {
149  struct HashElem *elem = mutt_hash_find_elem(dup_hash, str);
150  if (!elem)
151  return -1;
152 
153  uintptr_t count = (uintptr_t) elem->data;
154  if (count <= 1)
155  {
156  mutt_hash_delete(dup_hash, str, NULL);
157  return 0;
158  }
159 
160  count--;
161  elem->data = (void *) count;
162  return count;
163 }
164 
173 static int dup_hash_inc(struct HashTable *dup_hash, char *str)
174 {
175  uintptr_t count;
176 
177  struct HashElem *elem = mutt_hash_find_elem(dup_hash, str);
178  if (!elem)
179  {
180  count = 1;
181  mutt_hash_insert(dup_hash, str, (void *) count);
182  return count;
183  }
184 
185  count = (uintptr_t) elem->data;
186  count++;
187  elem->data = (void *) count;
188  return count;
189 }
190 
194 static void shrink_histfile(void)
195 {
196  FILE *fp_tmp = NULL;
197  int n[HC_MAX] = { 0 };
198  int line, hclass, read;
199  char *linebuf = NULL, *p = NULL;
200  size_t buflen;
201  bool regen_file = false;
202  struct HashTable *dup_hashes[HC_MAX] = { 0 };
203 
204  FILE *fp = mutt_file_fopen(C_HistoryFile, "r");
205  if (!fp)
206  return;
207 
209  for (hclass = 0; hclass < HC_MAX; hclass++)
210  dup_hashes[hclass] = mutt_hash_new(MAX(10, C_SaveHistory * 2), MUTT_HASH_STRDUP_KEYS);
211 
212  line = 0;
213  while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, 0)))
214  {
215  if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
216  (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
217  {
218  mutt_error(_("Bad history file format (line %d)"), line);
219  goto cleanup;
220  }
221  /* silently ignore too high class (probably newer neomutt) */
222  if (hclass >= HC_MAX)
223  continue;
224  *p = '\0';
225  if (C_HistoryRemoveDups && (dup_hash_inc(dup_hashes[hclass], linebuf + read) > 1))
226  {
227  regen_file = true;
228  continue;
229  }
230  n[hclass]++;
231  }
232 
233  if (!regen_file)
234  {
235  for (hclass = HC_FIRST; hclass < HC_MAX; hclass++)
236  {
237  if (n[hclass] > C_SaveHistory)
238  {
239  regen_file = true;
240  break;
241  }
242  }
243  }
244 
245  if (regen_file)
246  {
247  fp_tmp = mutt_file_mkstemp();
248  if (!fp_tmp)
249  {
250  mutt_perror(_("Can't create temporary file"));
251  goto cleanup;
252  }
253  rewind(fp);
254  line = 0;
255  while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, 0)))
256  {
257  if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
258  (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
259  {
260  mutt_error(_("Bad history file format (line %d)"), line);
261  goto cleanup;
262  }
263  if (hclass >= HC_MAX)
264  continue;
265  *p = '\0';
266  if (C_HistoryRemoveDups && (dup_hash_dec(dup_hashes[hclass], linebuf + read) > 0))
267  {
268  continue;
269  }
270  *p = '|';
271  if (n[hclass]-- <= C_SaveHistory)
272  fprintf(fp_tmp, "%s\n", linebuf);
273  }
274  }
275 
276 cleanup:
277  mutt_file_fclose(&fp);
278  FREE(&linebuf);
279  if (fp_tmp)
280  {
281  if ((fflush(fp_tmp) == 0) && (fp = fopen(NONULL(C_HistoryFile), "w")))
282  {
283  rewind(fp_tmp);
284  mutt_file_copy_stream(fp_tmp, fp);
285  mutt_file_fclose(&fp);
286  }
287  mutt_file_fclose(&fp_tmp);
288  }
290  for (hclass = 0; hclass < HC_MAX; hclass++)
291  mutt_hash_free(&dup_hashes[hclass]);
292 }
293 
299 static void save_history(enum HistoryClass hclass, const char *str)
300 {
301  static int n = 0;
302  char *tmp = NULL;
303 
304  if (!str || (*str == '\0')) /* This shouldn't happen, but it's safer. */
305  return;
306 
307  FILE *fp = mutt_file_fopen(C_HistoryFile, "a");
308  if (!fp)
309  return;
310 
311  tmp = mutt_str_dup(str);
312  mutt_ch_convert_string(&tmp, C_Charset, "utf-8", 0);
313 
314  /* Format of a history item (1 line): "<histclass>:<string>|".
315  * We add a '|' in order to avoid lines ending with '\'. */
316  fprintf(fp, "%d:", (int) hclass);
317  for (char *p = tmp; *p; p++)
318  {
319  /* Don't copy \n as a history item must fit on one line. The string
320  * shouldn't contain such a character anyway, but as this can happen
321  * in practice, we must deal with that. */
322  if (*p != '\n')
323  putc((unsigned char) *p, fp);
324  }
325  fputs("|\n", fp);
326 
327  mutt_file_fclose(&fp);
328  FREE(&tmp);
329 
330  if (--n < 0)
331  {
332  n = C_SaveHistory;
333  shrink_histfile();
334  }
335 }
336 
347 static void remove_history_dups(enum HistoryClass hclass, const char *str)
348 {
349  struct History *h = get_history(hclass);
350  if (!h)
351  return; /* disabled */
352 
353  /* Remove dups from 0..last-1 compacting up. */
354  int source = 0;
355  int dest = 0;
356  while (source < h->last)
357  {
358  if (mutt_str_equal(h->hist[source], str))
359  FREE(&h->hist[source++]);
360  else
361  h->hist[dest++] = h->hist[source++];
362  }
363 
364  /* Move 'last' entry up. */
365  h->hist[dest] = h->hist[source];
366  int old_last = h->last;
367  h->last = dest;
368 
369  /* Fill in moved entries with NULL */
370  while (source > h->last)
371  h->hist[source--] = NULL;
372 
373  /* Remove dups from last+1 .. C_History compacting down. */
374  source = C_History;
375  dest = C_History;
376  while (source > old_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  /* Fill in moved entries with NULL */
385  while (dest > old_last)
386  h->hist[dest--] = NULL;
387 }
388 
396 int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
397 {
398  if (!search_buf || !matches)
399  return 0;
400 
401  struct History *h = get_history(hclass);
402  if (!h)
403  return 0;
404 
405  int match_count = 0;
406  int cur = h->last;
407  do
408  {
409  cur--;
410  if (cur < 0)
411  cur = C_History;
412  if (cur == h->last)
413  break;
414  if (mutt_istr_find(h->hist[cur], search_buf))
415  matches[match_count++] = h->hist[cur];
416  } while (match_count < C_History);
417 
418  return match_count;
419 }
420 
424 void mutt_hist_free(void)
425 {
426  for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
427  {
428  struct History *h = &Histories[hclass];
429  if (!h->hist)
430  continue;
431 
432  /* The array has (C_History+1) elements */
433  for (int i = 0; i <= C_History; i++)
434  {
435  FREE(&h->hist[i]);
436  }
437  FREE(&h->hist);
438  }
439 }
440 
447 void mutt_hist_init(void)
448 {
449  if (C_History == OldSize)
450  return;
451 
452  for (enum HistoryClass hclass = HC_FIRST; hclass < HC_MAX; hclass++)
453  init_history(&Histories[hclass]);
454 
455  OldSize = C_History;
456 }
457 
464 void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
465 {
466  struct History *h = get_history(hclass);
467  if (!h)
468  return; /* disabled */
469 
470  if (*str)
471  {
472  int prev = h->last - 1;
473  if (prev < 0)
474  prev = C_History;
475 
476  /* don't add to prompt history:
477  * - lines beginning by a space
478  * - repeated lines */
479  if ((*str != ' ') && (!h->hist[prev] || !mutt_str_equal(h->hist[prev], str)))
480  {
482  remove_history_dups(hclass, str);
483  if (save && (C_SaveHistory != 0) && C_HistoryFile)
484  save_history(hclass, str);
485  mutt_str_replace(&h->hist[h->last++], str);
486  if (h->last > C_History)
487  h->last = 0;
488  }
489  }
490  h->cur = h->last; /* reset to the last entry */
491 }
492 
500 char *mutt_hist_next(enum HistoryClass hclass)
501 {
502  struct History *h = get_history(hclass);
503  if (!h)
504  return ""; /* disabled */
505 
506  int next = h->cur;
507  do
508  {
509  next++;
510  if (next > C_History)
511  next = 0;
512  if (next == h->last)
513  break;
514  } while (!h->hist[next]);
515 
516  h->cur = next;
517  return NONULL(h->hist[h->cur]);
518 }
519 
527 char *mutt_hist_prev(enum HistoryClass hclass)
528 {
529  struct History *h = get_history(hclass);
530  if (!h)
531  return ""; /* disabled */
532 
533  int prev = h->cur;
534  do
535  {
536  prev--;
537  if (prev < 0)
538  prev = C_History;
539  if (prev == h->last)
540  break;
541  } while (!h->hist[prev]);
542 
543  h->cur = prev;
544  return NONULL(h->hist[h->cur]);
545 }
546 
555 {
556  struct History *h = get_history(hclass);
557  if (!h)
558  return; /* disabled */
559 
560  h->cur = h->last;
561 }
562 
569 {
570  int line = 0, hclass, read;
571  char *linebuf = NULL, *p = NULL;
572  size_t buflen;
573 
574  if (!C_HistoryFile)
575  return;
576 
577  FILE *fp = mutt_file_fopen(C_HistoryFile, "r");
578  if (!fp)
579  return;
580 
581  while ((linebuf = mutt_file_read_line(linebuf, &buflen, fp, &line, 0)))
582  {
583  read = 0;
584  if ((sscanf(linebuf, "%d:%n", &hclass, &read) < 1) || (read == 0) ||
585  (*(p = linebuf + strlen(linebuf) - 1) != '|') || (hclass < 0))
586  {
587  mutt_error(_("Bad history file format (line %d)"), line);
588  break;
589  }
590  /* silently ignore too high class (probably newer neomutt) */
591  if (hclass >= HC_MAX)
592  continue;
593  *p = '\0';
594  p = mutt_str_dup(linebuf + read);
595  if (p)
596  {
597  mutt_ch_convert_string(&p, "utf-8", C_Charset, 0);
598  mutt_hist_add(hclass, p, false);
599  FREE(&p);
600  }
601  }
602 
603  mutt_file_fclose(&fp);
604  FREE(&linebuf);
605 }
606 
619 {
620  struct History *h = get_history(hclass);
621  if (!h)
622  return false; /* disabled */
623 
624  return h->cur == h->last;
625 }
626 
635 void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
636 {
637  struct History *h = get_history(hclass);
638  if (!h)
639  return; /* disabled */
640 
641  /* Don't check if str has a value because the scratch buffer may contain
642  * an old garbage value that should be overwritten */
643  mutt_str_replace(&h->hist[h->last], str);
644 }
bool mutt_str_equal(const char *a, const char *b)
Compare two strings.
Definition: string.c:871
static struct History Histories[HC_MAX]
Definition: history.c:95
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
A Hash Table.
Definition: hash.h:84
char * mutt_hist_next(enum HistoryClass hclass)
Get the next string in a History.
Definition: history.c:500
#define mutt_perror(...)
Definition: logging.h:85
int mutt_ch_convert_string(char **ps, const char *from, const char *to, int flags)
Convert a string between encodings.
Definition: charset.c:754
void mutt_hash_free(struct HashTable **ptr)
Free a hash table.
Definition: hash.c:447
void mutt_hist_add(enum HistoryClass hclass, const char *str, bool save)
Add a string to a history.
Definition: history.c:464
#define HC_FIRST
Definition: history.c:79
char * mutt_str_dup(const char *str)
Copy a string, safely.
Definition: string.c:370
#define _(a)
Definition: message.h:28
char * mutt_file_read_line(char *line, size_t *size, FILE *fp, int *line_num, int flags)
Read a line from a file.
Definition: file.c:667
static int OldSize
Definition: history.c:96
static void save_history(enum HistoryClass hclass, const char *str)
Save one history string to a file.
Definition: history.c:299
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:568
int mutt_file_fclose(FILE **fp)
Close a FILE handle (and NULL the pointer)
Definition: file.c:153
Read/write command history from/to a file.
short C_History
Config: Number of history entries to keep in memory per category.
Definition: config.c:37
static void init_history(struct History *h)
Set up a new History ring buffer.
Definition: history.c:118
bool mutt_hist_at_scratch(enum HistoryClass hclass)
Is the current History position at the &#39;scratch&#39; place?
Definition: history.c:618
Saved list of user-entered commands/searches.
Definition: history.c:86
void mutt_hist_save_scratch(enum HistoryClass hclass, const char *str)
Save a temporary string to the History.
Definition: history.c:635
static struct History * get_history(enum HistoryClass hclass)
Get a particular history.
Definition: history.c:103
static void remove_history_dups(enum HistoryClass hclass, const char *str)
De-dupe the history.
Definition: history.c:347
#define mutt_file_mkstemp()
Definition: file.h:106
int mutt_hist_search(const char *search_buf, enum HistoryClass hclass, char **matches)
Find matches in a history list.
Definition: history.c:396
short C_SaveHistory
Config: Number of history entries to save per category.
Definition: config.c:40
void mutt_hist_init(void)
Create a set of empty History ring buffers.
Definition: history.c:447
short cur
Definition: history.c:89
char * mutt_hist_prev(enum HistoryClass hclass)
Get the previous string in a History.
Definition: history.c:527
HistoryClass
Type to differentiate different histories.
Definition: lib.h:52
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:656
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:147
#define mutt_error(...)
Definition: logging.h:84
char * C_HistoryFile
Config: File to save history in.
Definition: config.c:38
bool C_HistoryRemoveDups
Config: Remove duplicate entries from the history.
Definition: config.c:39
short last
Definition: history.c:90
#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:194
The item stored in a Hash Table.
Definition: hash.h:43
char ** hist
Definition: history.c:88
void mutt_hist_reset_state(enum HistoryClass hclass)
Move the &#39;current&#39; position to the end of the History.
Definition: history.c:554
#define MUTT_HASH_STRDUP_KEYS
make a copy of the keys
Definition: hash.h:99
Convenience wrapper for the library headers.
FILE * mutt_file_fopen(const char *path, const char *mode)
Call fopen() safely.
Definition: file.c:588
Definition: lib.h:61
struct HashTable * mutt_hash_new(size_t num_elems, HashFlags flags)
Create a new Hash Table (with string keys)
Definition: hash.c:251
char * C_Charset
Config: Default character set for displaying text on screen.
Definition: charset.c:53
void mutt_hist_free(void)
Free all the history lists.
Definition: history.c:424
static int dup_hash_inc(struct HashTable *dup_hash, char *str)
Increase the refcount of a history string.
Definition: history.c:173