changeset: 7044:e66c6c0e8cc6
user:      Kevin McCarthy <ke...@8t8.us>
date:      Fri May 12 18:31:36 2017 -0700
link:      http://dev.mutt.org/hg/mutt/rev/e66c6c0e8cc6

Add $history_remove_dups option to remove dups from history ring.

When set, duplicate entries will be removed from the history ring when
a new entry is added.  The duplicate removal rearranges the history
ring such that created empty slots are right after the "last" position
in the ring, preserving the most history.

Rewrite the next/prev functions to take into account that blank slots can
now be in the middle of the array.

changeset: 7045:7a4cc1750940
user:      Kevin McCarthy <ke...@8t8.us>
date:      Fri May 12 18:31:41 2017 -0700
link:      http://dev.mutt.org/hg/mutt/rev/7a4cc1750940

Also remove duplicates from the history file.

When $history_remove_dups is set, remove duplicates from the history
file when it is periodically compacted.

diffs (364 lines):

diff -r 149f842ed1d0 -r 7a4cc1750940 doc/manual.xml.head
--- a/doc/manual.xml.head       Fri May 12 09:16:33 2017 -0700
+++ b/doc/manual.xml.head       Fri May 12 18:31:41 2017 -0700
@@ -587,7 +587,8 @@
 Mutt maintains a history for the built-in editor.  The number of items
 is controlled by the <link linkend="history">$history</link> variable
 and can be made persistent using an external file specified using <link
-linkend="history-file">$history_file</link>.  You may cycle through them
+linkend="history-file">$history_file</link> and <link
+linkend="save-history">$save_history</link>.  You may cycle through them
 at an editor prompt by using the <literal>&lt;history-up&gt;</literal>
 and/or <literal>&lt;history-down&gt;</literal> commands.  Mutt will
 remember the currently entered text as you cycle through history, and
@@ -610,9 +611,11 @@
 
 <para>
 Mutt automatically filters out consecutively repeated items from the
-history. It also mimics the behavior of some shells by ignoring items
-starting with a space. The latter feature can be useful in macros to not
-clobber the history's valuable entries with unwanted entries.
+history.  If <link linkend="history-remove-dups">$history_remove_dups</link>
+is set, all repeated items are removed from the history.  It also mimics the
+behavior of some shells by ignoring items starting with a space. The latter
+feature can be useful in macros to not clobber the history's valuable entries
+with unwanted entries.
 </para>
 
 </sect2>
diff -r 149f842ed1d0 -r 7a4cc1750940 history.c
--- a/history.c Fri May 12 09:16:33 2017 -0700
+++ b/history.c Fri May 12 18:31:41 2017 -0700
@@ -23,6 +23,46 @@
 #include "mutt.h"
 #include "history.h"
 
+#include <stdint.h>
+
+/* This history ring grows from 0..HistSize, with last marking the
+ * where new entries go:
+ *         0        the oldest entry in the ring
+ *         1        entry
+ *         ...
+ *         x-1      most recently entered text
+ *  last-> x        NULL  (this will be overwritten next)
+ *         x+1      NULL
+ *         ...
+ *         HistSize NULL
+ *
+ * Once the array fills up, it is used as a ring.  last points where a new
+ * entry will go.  Older entries are "up", and wrap around:
+ *         0        entry
+ *         1        entry
+ *         ...
+ *         y-1      most recently entered text
+ *  last-> y        entry (this will be overwritten next)
+ *         y+1      the oldest entry in the ring
+ *         ...
+ *         HistSize entry
+ *
+ * When $history_remove_dups is set, duplicate entries are scanned and removed
+ * each time a new entry is added.  In order to preserve the history ring size,
+ * entries 0..last are compacted up.  Entries last+1..HistSize are
+ * compacted down:
+ *         0        entry
+ *         1        entry
+ *         ...
+ *         z-1      most recently entered text
+ *  last-> z        entry (this will be overwritten next)
+ *         z+1      NULL
+ *         z+2      NULL
+ *         ...
+ *                  the oldest entry in the ring
+ *                  next oldest entry
+ *         HistSize entry
+ */
 struct history
 {
   char **hist;
@@ -94,22 +134,69 @@
   FREE (&linebuf);
 }
 
+static int dup_hash_dec (HASH *dup_hash, char *s)
+{
+  struct hash_elem *elem;
+  uintptr_t count;
+
+  elem = hash_find_elem (dup_hash, s);
+  if (!elem)
+    return -1;
+
+  count = (uintptr_t)elem->data;
+  if (count <= 1)
+  {
+    hash_delete (dup_hash, s, NULL, NULL);
+    return 0;
+  }
+
+  count--;
+  elem->data = (void *)count;
+  return count;
+}
+
+static int dup_hash_inc (HASH *dup_hash, char *s)
+{
+  struct hash_elem *elem;
+  uintptr_t count;
+
+  elem = hash_find_elem (dup_hash, s);
+  if (!elem)
+  {
+    count = 1;
+    hash_insert (dup_hash, s, (void *)count);
+    return count;
+  }
+
+  count = (uintptr_t)elem->data;
+  count++;
+  elem->data = (void *)count;
+  return count;
+}
+
 static void shrink_histfile (void)
 {
   char tmpfname[_POSIX_PATH_MAX];
   FILE *f, *tmp = NULL;
   int n[HC_LAST] = { 0 };
-  int line, hclass;
-  char *linebuf = NULL;
+  int line, hclass, read;
+  char *linebuf = NULL, *p;
   size_t buflen;
+  int regen_file = 0;
+  HASH *dup_hashes[HC_LAST] = { 0 };
 
   if ((f = fopen (HistFile, "r")) == NULL)
     return;
 
+  if (option (OPTHISTREMOVEDUPS))
+    for (hclass = 0; hclass < HC_LAST; hclass++)
+      dup_hashes[hclass] = hash_create (MAX (10, SaveHist * 2), 
MUTT_HASH_STRDUP_KEYS);
+
   line = 0;
   while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL)
   {
-    if (sscanf (linebuf, "%d", &hclass) < 1 || hclass < 0)
+    if (sscanf (linebuf, "%d:%n", &hclass, &read) < 1 || read == 0 ||
+        *(p = linebuf + strlen (linebuf) - 1) != '|' || hclass < 0)
     {
       mutt_error (_("Bad history file format (line %d)"), line);
       goto cleanup;
@@ -117,32 +204,49 @@
     /* silently ignore too high class (probably newer mutt) */
     if (hclass >= HC_LAST)
       continue;
+    *p = '\0';
+    if (option (OPTHISTREMOVEDUPS) &&
+        (dup_hash_inc (dup_hashes[hclass], linebuf + read) > 1))
+    {
+      regen_file = 1;
+      continue;
+    }
     n[hclass]++;
   }
 
-  for(hclass = HC_FIRST; hclass < HC_LAST; hclass++)
-    if (n[hclass] > SaveHist)
+  if (!regen_file)
+    for(hclass = HC_FIRST; hclass < HC_LAST; hclass++)
+      if (n[hclass] > SaveHist)
+      {
+        regen_file = 1;
+        break;
+      }
+
+  if (regen_file)
+  {
+    mutt_mktemp (tmpfname, sizeof (tmpfname));
+    if ((tmp = safe_fopen (tmpfname, "w+")) == NULL)
     {
-      mutt_mktemp (tmpfname, sizeof (tmpfname));
-      if ((tmp = safe_fopen (tmpfname, "w+")) == NULL)
-        mutt_perror (tmpfname);
-      break;
+      mutt_perror (tmpfname);
+      goto cleanup;
     }
-
-  if (tmp != NULL)
-  {
     rewind (f);
     line = 0;
     while ((linebuf = mutt_read_line (linebuf, &buflen, f, &line, 0)) != NULL)
     {
-      if (sscanf (linebuf, "%d", &hclass) < 1 || hclass < 0)
+      if (sscanf (linebuf, "%d:%n", &hclass, &read) < 1 || read == 0 ||
+          *(p = linebuf + strlen (linebuf) - 1) != '|' || hclass < 0)
       {
         mutt_error (_("Bad history file format (line %d)"), line);
         goto cleanup;
       }
-      /* silently ignore too high class (probably newer mutt) */
       if (hclass >= HC_LAST)
        continue;
+      *p = '\0';
+      if (option (OPTHISTREMOVEDUPS) &&
+          (dup_hash_dec (dup_hashes[hclass], linebuf + read) != 0))
+        continue;
+      *p = '|';
       if (n[hclass]-- <= SaveHist)
         fprintf (tmp, "%s\n", linebuf);
     }
@@ -163,6 +267,9 @@
     safe_fclose (&tmp);
     unlink (tmpfname);
   }
+  if (option (OPTHISTREMOVEDUPS))
+    for (hclass = 0; hclass < HC_LAST; hclass++)
+      hash_destroy (&dup_hashes[hclass], NULL);
 }
 
 static void save_history (history_class_t hclass, const char *s)
@@ -206,6 +313,51 @@
   }
 }
 
+/* When removing dups, we want the created "blanks" to be right below the
+ * resulting h->last position.  See the comment section above 'struct history'.
+ */
+static void remove_history_dups (history_class_t hclass, const char *s)
+{
+  int source, dest, old_last;
+  struct history *h = GET_HISTORY(hclass);
+
+  if (!HistSize || !h)
+    return; /* disabled */
+
+  /* Remove dups from 0..last-1 compacting up. */
+  source = dest = 0;
+  while (source < h->last)
+  {
+    if (!mutt_strcmp (h->hist[source], s))
+      FREE (&h->hist[source++]);
+    else
+      h->hist[dest++] = h->hist[source++];
+  }
+
+  /* Move 'last' entry up. */
+  h->hist[dest] = h->hist[source];
+  old_last = h->last;
+  h->last = dest;
+
+  /* Fill in moved entries with NULL */
+  while (source > h->last)
+    h->hist[source--] = NULL;
+
+  /* Remove dups from last+1 .. HistSize compacting down. */
+  source = dest = HistSize;
+  while (source > old_last)
+  {
+    if (!mutt_strcmp (h->hist[source], s))
+      FREE (&h->hist[source--]);
+    else
+      h->hist[dest--] = h->hist[source--];
+  }
+
+  /* Fill in moved entries with NULL */
+  while (dest > old_last)
+    h->hist[dest--] = NULL;
+}
+
 void mutt_init_history(void)
 {
   history_class_t hclass;
@@ -238,6 +390,8 @@
      */
     if (*s != ' ' && (!h->hist[prev] || mutt_strcmp (h->hist[prev], s) != 0))
     {
+      if (option (OPTHISTREMOVEDUPS))
+        remove_history_dups (hclass, s);
       if (save && SaveHist)
         save_history (hclass, s);
       mutt_str_replace (&h->hist[h->last++], s);
@@ -256,13 +410,17 @@
   if (!HistSize || !h)
     return (""); /* disabled */
 
-  next = h->cur + 1;
-  if (next > HistSize)
-    next = 0;
-  if (h->hist[next] || (next == h->last))
-    h->cur = next;
-  else
-    h->cur = 0;
+  next = h->cur;
+  do
+  {
+    next++;
+    if (next > HistSize)
+      next = 0;
+    if (next == h->last)
+      break;
+  } while (h->hist[next] == NULL);
+
+  h->cur = next;
   return (h->hist[h->cur] ? h->hist[h->cur] : "");
 }
 
@@ -274,15 +432,17 @@
   if (!HistSize || !h)
     return (""); /* disabled */
 
-  prev = h->cur - 1;
-  if (prev < 0)
+  prev = h->cur;
+  do
   {
-    prev = HistSize;
-    while ((prev > 0) && (prev != h->last) && (h->hist[prev] == NULL))
-      prev--;
-  }
-  if (h->hist[prev] || (prev == h->last))
-    h->cur = prev;
+    prev--;
+    if (prev < 0)
+      prev = HistSize;
+    if (prev == h->last)
+      break;
+  } while (h->hist[prev] == NULL);
+
+  h->cur = prev;
   return (h->hist[h->cur] ? h->hist[h->cur] : "");
 }
 
diff -r 149f842ed1d0 -r 7a4cc1750940 init.h
--- a/init.h    Fri May 12 09:16:33 2017 -0700
+++ b/init.h    Fri May 12 18:31:41 2017 -0700
@@ -1083,6 +1083,15 @@
   /*
   ** .pp
   ** The file in which Mutt will save its history.
+  ** .pp
+  ** Also see $$save_history.
+  */
+  { "history_remove_dups", DT_BOOL, R_NONE, OPTHISTREMOVEDUPS, 0 },
+  /*
+  ** .pp
+  ** When \fIset\fP, all of the string history will be scanned for duplicates
+  ** when a new entry is added.  Duplicate entries in the $$history_file will
+  ** also be removed when it is periodically compacted.
   */
   { "honor_disposition", DT_BOOL, R_NONE, OPTHONORDISP, 0 },
   /*
diff -r 149f842ed1d0 -r 7a4cc1750940 mutt.h
--- a/mutt.h    Fri May 12 09:16:33 2017 -0700
+++ b/mutt.h    Fri May 12 18:31:41 2017 -0700
@@ -374,6 +374,7 @@
   OPTHIDETHREADSUBJECT,
   OPTHIDETOPLIMITED,
   OPTHIDETOPMISSING,
+  OPTHISTREMOVEDUPS,
   OPTHONORDISP,
   OPTIGNORELWS,
   OPTIGNORELISTREPLYTO,

Reply via email to