summaryrefslogtreecommitdiff
path: root/history.c
diff options
context:
space:
mode:
Diffstat (limited to 'history.c')
-rw-r--r--history.c161
1 files changed, 140 insertions, 21 deletions
diff --git a/history.c b/history.c
index 81d4c16..aade5e3 100644
--- a/history.c
+++ b/history.c
@@ -1,6 +1,6 @@
/* history.c -- standalone history library */
-/* Copyright (C) 1989-2021 Free Software Foundation, Inc.
+/* Copyright (C) 1989-2024 Free Software Foundation, Inc.
This file contains the GNU History Library (History), a set of
routines for managing the text of previously typed lines.
@@ -42,6 +42,7 @@
# endif
# include <unistd.h>
#endif
+#include "posixtime.h"
#include <errno.h>
@@ -60,24 +61,39 @@ extern int errno;
#define MAX_HISTORY_INITIAL_SIZE 8192
/* The number of slots to increase the_history by. */
-#define DEFAULT_HISTORY_GROW_SIZE 50
+#define DEFAULT_HISTORY_GROW_SIZE 256
static char *hist_inittime (void);
+static int history_list_grow_size (void);
+static void history_list_resize (int); /* XXX - size_t? */
+static void advance_history (void);
+
/* **************************************************************** */
/* */
/* History Functions */
/* */
/* **************************************************************** */
-/* An array of HIST_ENTRY. This is where we store the history. */
+/* An array of HIST_ENTRY. This is where we store the history. the_history is
+ a roving pointer somewhere into this, so the user-visible history list is
+ a window into real_history starting at the_history and extending
+ history_length entries. */
+static HIST_ENTRY **real_history = (HIST_ENTRY **)NULL;
+
+/* The current number of slots allocated to the input_history. */
+static int real_history_size = 0;
+
+/* A pointer to somewhere in real_history, where the user-visible history
+ starts. */
static HIST_ENTRY **the_history = (HIST_ENTRY **)NULL;
/* Non-zero means that we have enforced a limit on the amount of
history that we save. */
static int history_stifled;
-/* The current number of slots allocated to the input_history. */
+/* The number of history entries available for user use, starting at the_history.
+ real_history_size - history_size == the_history - real_history */
static int history_size;
/* If HISTORY_STIFLED is non-zero, then this is the maximum number of
@@ -89,12 +105,59 @@ int max_input_history; /* backwards compatibility */
life easier for outside callers. */
int history_offset;
-/* The number of strings currently stored in the history list. */
+/* The number of strings currently stored in the history list. This is
+ always <= real_history_length */
int history_length;
/* The logical `base' of the history array. It defaults to 1. */
int history_base = 1;
+/* Compute the number of bits required to store a given nonnegative integer.
+
+ NOTE: _bit_length(0) == 0 */
+static inline unsigned
+_bit_length(unsigned n)
+{
+ /* This implementation is for simplicity, not for performance, but it is
+ fast enough for our purposes here. */
+ unsigned count = 0;
+ while (n)
+ {
+ n >>= 1;
+ count++;
+ }
+ return count;
+}
+
+/* Compute a grow size that adjusts to the size of the history.
+
+ We aim to grow by roughly sqrt(history_size) in order to amortize the cost of
+ realloc() and memmove(). This reduces the total cost of the memmoves from
+ O(N^2) to O(N*sqrt(N)). */
+static int
+history_list_grow_size (void)
+{
+ int width, r;
+ /* Handle small positive values and guard against history_length accidentally
+ being negative. */
+ const int MIN_BITS = 10;
+ if (history_length < (1 << MIN_BITS))
+ return DEFAULT_HISTORY_GROW_SIZE;
+
+ /* For other values, we just want something easy to compute that grows roughly
+ as sqrt(N), where N=history_length. We use approximately 2^((k+1)/2),
+ where k is the bit length of N. This bounds the value between sqrt(2N) and
+ 2*sqrt(N). */
+ width = MIN_BITS + _bit_length(history_length >> MIN_BITS);
+
+ /* If width is odd then this is 2^((width+1)/2). An even width gives a value
+ of 3*2^((width-2)/2) ~ 1.06*2^((width+1)/2). */
+ r = (1 << (width / 2)) + (1 << ((width - 1) / 2));
+
+ /* Make sure we always expand the list by at least DEFAULT_HISTORY_GROW_SIZE */
+ return ((r < DEFAULT_HISTORY_GROW_SIZE) ? DEFAULT_HISTORY_GROW_SIZE : r);
+}
+
/* Return the current HISTORY_STATE of the history. */
HISTORY_STATE *
history_get_history_state (void)
@@ -261,7 +324,7 @@ hist_inittime (void)
time_t t;
char ts[64], *ret;
- t = (time_t) time ((time_t *)0);
+ t = getnow ();
#if defined (HAVE_VSNPRINTF) /* assume snprintf if vsnprintf exists */
snprintf (ts, sizeof (ts) - 1, "X%lu", (unsigned long) t);
#else
@@ -273,6 +336,48 @@ hist_inittime (void)
return ret;
}
+/* Ensure space for new history entries by resetting the start pointer
+ (the_history) and resizing real_history if necessary. */
+static void
+history_list_resize (int new_size)
+{
+ /* Do nothing there is already enough user space. history_length is always <=
+ real_history_size */
+ if (new_size < history_length)
+ return;
+
+ /* If we need to, reset the_history to the start of real_history and
+ start over. */
+ if (the_history != real_history)
+ memmove (real_history, the_history, history_length * sizeof (HIST_ENTRY *));
+
+ /* Don't bother if real_history_size is already big enough, since at this
+ point the_history == real_history and we will set history_size to
+ real_history_size. We don't shrink the history list. */
+ if (new_size > real_history_size)
+ {
+ real_history = (HIST_ENTRY **) xrealloc (real_history, new_size * sizeof (HIST_ENTRY *));
+ real_history_size = new_size;
+ }
+ the_history = real_history;
+ history_size = real_history_size;
+
+ if (history_size > history_length)
+ memset (real_history + history_length, 0, (history_size - history_length) * sizeof (HIST_ENTRY *));
+}
+
+static void
+advance_history (void)
+{
+ /* Advance 'the_history' pointer to simulate dropping the first entry. */
+ the_history++;
+ history_size--;
+
+ /* If full, move all the entries (and trailing NULL) to the beginning. */
+ if (history_length == history_size)
+ history_list_resize (history_length + history_list_grow_size ());
+}
+
/* Place STRING at the end of the history list. The data field
is set to NULL. */
void
@@ -283,8 +388,6 @@ add_history (const char *string)
if (history_stifled && (history_length == history_max_entries))
{
- register int i;
-
/* If the history is stifled, and history_length is zero,
and it equals history_max_entries, we don't save items. */
if (history_length == 0)
@@ -294,9 +397,8 @@ add_history (const char *string)
if (the_history[0])
(void) free_history_entry (the_history[0]);
- /* Copy the rest of the entries, moving down one slot. Copy includes
- trailing NULL. */
- memmove (the_history, the_history + 1, history_length * sizeof (HIST_ENTRY *));
+ /* Advance the pointer into real_history, resizing if necessary. */
+ advance_history ();
new_length = history_length;
history_base++;
@@ -305,23 +407,20 @@ add_history (const char *string)
{
if (history_size == 0)
{
+ int initial_size;
if (history_stifled && history_max_entries > 0)
- history_size = (history_max_entries > MAX_HISTORY_INITIAL_SIZE)
+ initial_size = (history_max_entries > MAX_HISTORY_INITIAL_SIZE)
? MAX_HISTORY_INITIAL_SIZE
: history_max_entries + 2;
else
- history_size = DEFAULT_HISTORY_INITIAL_SIZE;
- the_history = (HIST_ENTRY **)xmalloc (history_size * sizeof (HIST_ENTRY *));
+ initial_size = DEFAULT_HISTORY_INITIAL_SIZE;
+ history_list_resize (initial_size);
new_length = 1;
}
else
{
if (history_length == (history_size - 1))
- {
- history_size += DEFAULT_HISTORY_GROW_SIZE;
- the_history = (HIST_ENTRY **)
- xrealloc (the_history, history_size * sizeof (HIST_ENTRY *));
- }
+ history_list_resize (real_history_size + history_list_grow_size ());
new_length = history_length + 1;
}
}
@@ -460,7 +559,7 @@ _hs_replace_history_data (int which, histdata_t *old, histdata_t *new)
}
last = -1;
- for (i = 0; i < history_length; i++)
+ for (i = history_length - 1; i >= 0; i--)
{
entry = the_history[i];
if (entry == 0)
@@ -477,7 +576,27 @@ _hs_replace_history_data (int which, histdata_t *old, histdata_t *new)
entry = the_history[last];
entry->data = new; /* XXX - we don't check entry->old */
}
-}
+}
+
+int
+_hs_search_history_data (histdata_t *needle)
+{
+ register int i;
+ HIST_ENTRY *entry;
+
+ if (history_length == 0 || the_history == 0)
+ return -1;
+
+ for (i = history_length - 1; i >= 0; i--)
+ {
+ entry = the_history[i];
+ if (entry == 0)
+ continue;
+ if (entry->data == needle)
+ return i;
+ }
+ return -1;
+}
/* Remove history element WHICH from the history. The removed
element is returned to you so you can free the line, data,