aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2022-08-09 17:13:44 +0800
committerGitHub <noreply@github.com>2022-08-09 17:13:44 +0800
commitcd14efd281c260c92936f05bf5f91780f8912f81 (patch)
tree29b7778ae35f57f8c260f5e34dbb38a050929285
parenta5e846b9969b1dfbd7e92f437f3ac7905039b84f (diff)
downloadrneovim-cd14efd281c260c92936f05bf5f91780f8912f81.tar.gz
rneovim-cd14efd281c260c92936f05bf5f91780f8912f81.tar.bz2
rneovim-cd14efd281c260c92936f05bf5f91780f8912f81.zip
vim-patch:8.1.1823: command line history code is spread out (#19688)
Problem: Command line history code is spread out. Solution: Put the code in a new file. (Yegappan Lakshmanan, closes vim/vim#4779) Also graduate the +cmdline_hist feature. https://github.com/vim/vim/commit/d7663c22c6c1ff0f86b81371586fbc851d3a3e9e
-rw-r--r--src/nvim/cmdhist.c743
-rw-r--r--src/nvim/cmdhist.h33
-rw-r--r--src/nvim/eval.c1
-rw-r--r--src/nvim/eval/funcs.c82
-rw-r--r--src/nvim/ex_cmds.c2
-rw-r--r--src/nvim/ex_docmd.c4
-rw-r--r--src/nvim/ex_getln.c709
-rw-r--r--src/nvim/ex_getln.h24
-rw-r--r--src/nvim/memory.c1
-rw-r--r--src/nvim/normal.c3
-rw-r--r--src/nvim/search.c1
-rw-r--r--src/nvim/shada.c24
12 files changed, 835 insertions, 792 deletions
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
new file mode 100644
index 0000000000..625bd514ad
--- /dev/null
+++ b/src/nvim/cmdhist.c
@@ -0,0 +1,743 @@
+// This is an open source non-commercial project. Dear PVS-Studio, please check
+// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
+
+// cmdhist.c: Functions for the history of the command-line.
+
+#include "nvim/ascii.h"
+#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
+#include "nvim/ex_getln.h"
+#include "nvim/regexp.h"
+#include "nvim/strings.h"
+#include "nvim/ui.h"
+#include "nvim/vim.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "cmdhist.c.generated.h"
+#endif
+
+static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
+static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; ///< lastused entry
+/// identifying (unique) number of newest history entry
+static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 };
+static int hislen = 0; ///< actual length of history tables
+
+/// Return the length of the history tables
+int get_hislen(void)
+{
+ return hislen;
+}
+
+/// Return a pointer to a specified history table
+histentry_T *get_histentry(int hist_type)
+{
+ return history[hist_type];
+}
+
+void set_histentry(int hist_type, histentry_T *entry)
+{
+ history[hist_type] = entry;
+}
+
+int *get_hisidx(int hist_type)
+{
+ return &hisidx[hist_type];
+}
+
+int *get_hisnum(int hist_type)
+{
+ return &hisnum[hist_type];
+}
+
+/// Translate a history character to the associated type number
+HistoryType hist_char2type(const int c)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ switch (c) {
+ case ':':
+ return HIST_CMD;
+ case '=':
+ return HIST_EXPR;
+ case '@':
+ return HIST_INPUT;
+ case '>':
+ return HIST_DEBUG;
+ case NUL:
+ case '/':
+ case '?':
+ return HIST_SEARCH;
+ default:
+ return HIST_INVALID;
+ }
+ // Silence -Wreturn-type
+ return 0;
+}
+
+/// Table of history names.
+/// These names are used in :history and various hist...() functions.
+/// It is sufficient to give the significant prefix of a history name.
+static char *(history_names[]) = {
+ "cmd",
+ "search",
+ "expr",
+ "input",
+ "debug",
+ NULL
+};
+
+/// Function given to ExpandGeneric() to obtain the possible first
+/// arguments of the ":history command.
+char *get_history_arg(expand_T *xp, int idx)
+{
+ static char_u compl[2] = { NUL, NUL };
+ char *short_names = ":=@>?/";
+ int short_names_count = (int)STRLEN(short_names);
+ int history_name_count = ARRAY_SIZE(history_names) - 1;
+
+ if (idx < short_names_count) {
+ compl[0] = (char_u)short_names[idx];
+ return (char *)compl;
+ }
+ if (idx < short_names_count + history_name_count) {
+ return history_names[idx - short_names_count];
+ }
+ if (idx == short_names_count + history_name_count) {
+ return "all";
+ }
+ return NULL;
+}
+
+/// Initialize command line history.
+/// Also used to re-allocate history tables when size changes.
+void init_history(void)
+{
+ assert(p_hi >= 0 && p_hi <= INT_MAX);
+ int newlen = (int)p_hi;
+ int oldlen = hislen;
+
+ // If history tables size changed, reallocate them.
+ // Tables are circular arrays (current position marked by hisidx[type]).
+ // On copying them to the new arrays, we take the chance to reorder them.
+ if (newlen != oldlen) {
+ for (int type = 0; type < HIST_COUNT; type++) {
+ histentry_T *temp = (newlen
+ ? xmalloc((size_t)newlen * sizeof(*temp))
+ : NULL);
+
+ int j = hisidx[type];
+ if (j >= 0) {
+ // old array gets partitioned this way:
+ // [0 , i1 ) --> newest entries to be deleted
+ // [i1 , i1 + l1) --> newest entries to be copied
+ // [i1 + l1 , i2 ) --> oldest entries to be deleted
+ // [i2 , i2 + l2) --> oldest entries to be copied
+ int l1 = MIN(j + 1, newlen); // how many newest to copy
+ int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy
+ int i1 = j + 1 - l1; // copy newest from here
+ int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here
+
+ // copy as much entries as they fit to new table, reordering them
+ if (newlen) {
+ // copy oldest entries
+ memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp));
+ // copy newest entries
+ memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp));
+ }
+
+ // delete entries that don't fit in newlen, if any
+ for (int i = 0; i < i1; i++) {
+ hist_free_entry(history[type] + i);
+ }
+ for (int i = i1 + l1; i < i2; i++) {
+ hist_free_entry(history[type] + i);
+ }
+ }
+
+ // clear remaining space, if any
+ int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries
+ if (newlen) {
+ memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp));
+ }
+
+ hisidx[type] = l3 - 1;
+ xfree(history[type]);
+ history[type] = temp;
+ }
+ hislen = newlen;
+ }
+}
+
+static inline void hist_free_entry(histentry_T *hisptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ xfree(hisptr->hisstr);
+ tv_list_unref(hisptr->additional_elements);
+ clear_hist_entry(hisptr);
+}
+
+static inline void clear_hist_entry(histentry_T *hisptr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ memset(hisptr, 0, sizeof(*hisptr));
+}
+
+/// Check if command line 'str' is already in history.
+/// If 'move_to_front' is true, matching entry is moved to end of history.
+///
+/// @param move_to_front Move the entry to the front if it exists
+static int in_history(int type, char_u *str, int move_to_front, int sep)
+{
+ int last_i = -1;
+
+ if (hisidx[type] < 0) {
+ return false;
+ }
+ int i = hisidx[type];
+ do {
+ if (history[type][i].hisstr == NULL) {
+ return false;
+ }
+
+ // For search history, check that the separator character matches as
+ // well.
+ char_u *p = history[type][i].hisstr;
+ if (STRCMP(str, p) == 0
+ && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
+ if (!move_to_front) {
+ return true;
+ }
+ last_i = i;
+ break;
+ }
+ if (--i < 0) {
+ i = hislen - 1;
+ }
+ } while (i != hisidx[type]);
+
+ if (last_i >= 0) {
+ list_T *const list = history[type][i].additional_elements;
+ str = history[type][i].hisstr;
+ while (i != hisidx[type]) {
+ if (++i >= hislen) {
+ i = 0;
+ }
+ history[type][last_i] = history[type][i];
+ last_i = i;
+ }
+ tv_list_unref(list);
+ history[type][i].hisnum = ++hisnum[type];
+ history[type][i].hisstr = str;
+ history[type][i].timestamp = os_time();
+ history[type][i].additional_elements = NULL;
+ return true;
+ }
+ return false;
+}
+
+/// Convert history name to its HIST_ equivalent
+///
+/// Names are taken from the table above. When `name` is empty returns currently
+/// active history or HIST_DEFAULT, depending on `return_default` argument.
+///
+/// @param[in] name Converted name.
+/// @param[in] len Name length.
+/// @param[in] return_default Determines whether HIST_DEFAULT should be
+/// returned or value based on `ccline.cmdfirstc`.
+///
+/// @return Any value from HistoryType enum, including HIST_INVALID. May not
+/// return HIST_DEFAULT unless return_default is true.
+static HistoryType get_histtype(const char *const name, const size_t len, const bool return_default)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // No argument: use current history.
+ if (len == 0) {
+ return return_default ? HIST_DEFAULT : hist_char2type(get_cmdline_firstc());
+ }
+
+ for (HistoryType i = 0; history_names[i] != NULL; i++) {
+ if (STRNICMP(name, history_names[i], len) == 0) {
+ return i;
+ }
+ }
+
+ if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) {
+ return hist_char2type(name[0]);
+ }
+
+ return HIST_INVALID;
+}
+
+static int last_maptick = -1; // last seen maptick
+
+/// Add the given string to the given history. If the string is already in the
+/// history then it is moved to the front.
+///
+/// @param histype may be one of the HIST_ values.
+/// @param in_map consider maptick when inside a mapping
+/// @param sep separator character used (search hist)
+void add_to_history(int histype, char_u *new_entry, int in_map, int sep)
+{
+ histentry_T *hisptr;
+
+ if (hislen == 0 || histype == HIST_INVALID) { // no history
+ return;
+ }
+ assert(histype != HIST_DEFAULT);
+
+ if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) {
+ return;
+ }
+
+ // Searches inside the same mapping overwrite each other, so that only
+ // the last line is kept. Be careful not to remove a line that was moved
+ // down, only lines that were added.
+ if (histype == HIST_SEARCH && in_map) {
+ if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) {
+ // Current line is from the same mapping, remove it
+ hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
+ hist_free_entry(hisptr);
+ hisnum[histype]--;
+ if (--hisidx[HIST_SEARCH] < 0) {
+ hisidx[HIST_SEARCH] = hislen - 1;
+ }
+ }
+ last_maptick = -1;
+ }
+ if (!in_history(histype, new_entry, true, sep)) {
+ if (++hisidx[histype] == hislen) {
+ hisidx[histype] = 0;
+ }
+ hisptr = &history[histype][hisidx[histype]];
+ hist_free_entry(hisptr);
+
+ // Store the separator after the NUL of the string.
+ size_t len = STRLEN(new_entry);
+ hisptr->hisstr = vim_strnsave(new_entry, len + 2);
+ hisptr->timestamp = os_time();
+ hisptr->additional_elements = NULL;
+ hisptr->hisstr[len + 1] = (char_u)sep;
+
+ hisptr->hisnum = ++hisnum[histype];
+ if (histype == HIST_SEARCH && in_map) {
+ last_maptick = maptick;
+ }
+ }
+}
+
+/// Get identifier of newest history entry.
+///
+/// @param histype may be one of the HIST_ values.
+static int get_history_idx(int histype)
+{
+ if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
+ || hisidx[histype] < 0) {
+ return -1;
+ }
+
+ return history[histype][hisidx[histype]].hisnum;
+}
+
+/// Calculate history index from a number:
+///
+/// @param num > 0: seen as identifying number of a history entry
+/// < 0: relative position in history wrt newest entry
+/// @param histype may be one of the HIST_ values.
+static int calc_hist_idx(int histype, int num)
+{
+ int i;
+ int wrapped = false;
+
+ if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
+ || (i = hisidx[histype]) < 0 || num == 0) {
+ return -1;
+ }
+
+ histentry_T *hist = history[histype];
+ if (num > 0) {
+ while (hist[i].hisnum > num) {
+ if (--i < 0) {
+ if (wrapped) {
+ break;
+ }
+ i += hislen;
+ wrapped = true;
+ }
+ }
+ if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) {
+ return i;
+ }
+ } else if (-num <= hislen) {
+ i += num + 1;
+ if (i < 0) {
+ i += hislen;
+ }
+ if (hist[i].hisstr != NULL) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/// Get a history entry by its index.
+///
+/// @param histype may be one of the HIST_ values.
+static char_u *get_history_entry(int histype, int idx)
+{
+ idx = calc_hist_idx(histype, idx);
+ if (idx >= 0) {
+ return history[histype][idx].hisstr;
+ } else {
+ return (char_u *)"";
+ }
+}
+
+/// Clear all entries in a history
+///
+/// @param[in] histype One of the HIST_ values.
+///
+/// @return OK if there was something to clean and histype was one of HIST_
+/// values, FAIL otherwise.
+int clr_history(const int histype)
+{
+ if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
+ histentry_T *hisptr = history[histype];
+ for (int i = hislen; i--; hisptr++) {
+ hist_free_entry(hisptr);
+ }
+ hisidx[histype] = -1; // mark history as cleared
+ hisnum[histype] = 0; // reset identifier counter
+ return OK;
+ }
+ return FAIL;
+}
+
+/// Remove all entries matching {str} from a history.
+///
+/// @param histype may be one of the HIST_ values.
+static int del_history_entry(int histype, char_u *str)
+{
+ regmatch_T regmatch;
+ histentry_T *hisptr;
+ int idx;
+ int i;
+ int last;
+ bool found = false;
+
+ regmatch.regprog = NULL;
+ regmatch.rm_ic = false; // always match case
+ if (hislen != 0
+ && histype >= 0
+ && histype < HIST_COUNT
+ && *str != NUL
+ && (idx = hisidx[histype]) >= 0
+ && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING)) != NULL) {
+ i = last = idx;
+ do {
+ hisptr = &history[histype][i];
+ if (hisptr->hisstr == NULL) {
+ break;
+ }
+ if (vim_regexec(&regmatch, (char *)hisptr->hisstr, (colnr_T)0)) {
+ found = true;
+ hist_free_entry(hisptr);
+ } else {
+ if (i != last) {
+ history[histype][last] = *hisptr;
+ clear_hist_entry(hisptr);
+ }
+ if (--last < 0) {
+ last += hislen;
+ }
+ }
+ if (--i < 0) {
+ i += hislen;
+ }
+ } while (i != idx);
+ if (history[histype][idx].hisstr == NULL) {
+ hisidx[histype] = -1;
+ }
+ }
+ vim_regfree(regmatch.regprog);
+ return found;
+}
+
+/// Remove an indexed entry from a history.
+///
+/// @param histype may be one of the HIST_ values.
+static int del_history_idx(int histype, int idx)
+{
+ int i = calc_hist_idx(histype, idx);
+ if (i < 0) {
+ return false;
+ }
+ idx = hisidx[histype];
+ hist_free_entry(&history[histype][i]);
+
+ // When deleting the last added search string in a mapping, reset
+ // last_maptick, so that the last added search string isn't deleted again.
+ if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) {
+ last_maptick = -1;
+ }
+
+ while (i != idx) {
+ int j = (i + 1) % hislen;
+ history[histype][i] = history[histype][j];
+ i = j;
+ }
+ clear_hist_entry(&history[histype][idx]);
+ if (--i < 0) {
+ i += hislen;
+ }
+ hisidx[histype] = i;
+ return true;
+}
+
+/// "histadd()" function
+void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType histype;
+
+ rettv->vval.v_number = false;
+ if (check_secure()) {
+ return;
+ }
+ const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
+ if (histype != HIST_INVALID) {
+ char buf[NUMBUFLEN];
+ str = tv_get_string_buf(&argvars[1], buf);
+ if (*str != NUL) {
+ init_history();
+ add_to_history(histype, (char_u *)str, false, NUL);
+ rettv->vval.v_number = true;
+ return;
+ }
+ }
+}
+
+/// "histdel()" function
+void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ int n;
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ n = 0;
+ } else if (argvars[1].v_type == VAR_UNKNOWN) {
+ // only one argument: clear entire history
+ n = clr_history(get_histtype(str, strlen(str), false));
+ } else if (argvars[1].v_type == VAR_NUMBER) {
+ // index given: remove that entry
+ n = del_history_idx(get_histtype(str, strlen(str), false),
+ (int)tv_get_number(&argvars[1]));
+ } else {
+ // string given: remove all matching entries
+ char buf[NUMBUFLEN];
+ n = del_history_entry(get_histtype(str, strlen(str), false),
+ (char_u *)tv_get_string_buf(&argvars[1], buf));
+ }
+ rettv->vval.v_number = n;
+}
+
+/// "histget()" function
+void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ HistoryType type;
+ int idx;
+
+ const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
+ if (str == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ type = get_histtype(str, strlen(str), false);
+ if (argvars[1].v_type == VAR_UNKNOWN) {
+ idx = get_history_idx(type);
+ } else {
+ idx = (int)tv_get_number_chk(&argvars[1], NULL);
+ }
+ // -1 on type error
+ rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx));
+ }
+ rettv->v_type = VAR_STRING;
+}
+
+/// "histnr()" function
+void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ const char *const histname = tv_get_string_chk(&argvars[0]);
+ HistoryType i = histname == NULL
+ ? HIST_INVALID
+ : get_histtype(histname, strlen(histname), false);
+ if (i != HIST_INVALID) {
+ i = get_history_idx(i);
+ }
+ rettv->vval.v_number = i;
+}
+
+/// :history command - print a history
+void ex_history(exarg_T *eap)
+{
+ histentry_T *hist;
+ int histype1 = HIST_CMD;
+ int histype2 = HIST_CMD;
+ int hisidx1 = 1;
+ int hisidx2 = -1;
+ int idx;
+ int i, j, k;
+ char_u *end;
+ char_u *arg = (char_u *)eap->arg;
+
+ if (hislen == 0) {
+ msg(_("'history' option is zero"));
+ return;
+ }
+
+ if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
+ end = arg;
+ while (ASCII_ISALPHA(*end)
+ || vim_strchr(":=@>/?", *end) != NULL) {
+ end++;
+ }
+ histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false);
+ if (histype1 == HIST_INVALID) {
+ if (STRNICMP(arg, "all", end - arg) == 0) {
+ histype1 = 0;
+ histype2 = HIST_COUNT - 1;
+ } else {
+ semsg(_(e_trailing_arg), arg);
+ return;
+ }
+ } else {
+ histype2 = histype1;
+ }
+ } else {
+ end = arg;
+ }
+ if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
+ semsg(_(e_trailing_arg), end);
+ return;
+ }
+
+ for (; !got_int && histype1 <= histype2; histype1++) {
+ STRCPY(IObuff, "\n # ");
+ assert(history_names[histype1] != NULL);
+ STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
+ msg_puts_title((char *)IObuff);
+ idx = hisidx[histype1];
+ hist = history[histype1];
+ j = hisidx1;
+ k = hisidx2;
+ if (j < 0) {
+ j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum;
+ }
+ if (k < 0) {
+ k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum;
+ }
+ if (idx >= 0 && j <= k) {
+ for (i = idx + 1; !got_int; i++) {
+ if (i == hislen) {
+ i = 0;
+ }
+ if (hist[i].hisstr != NULL
+ && hist[i].hisnum >= j && hist[i].hisnum <= k) {
+ msg_putchar('\n');
+ snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
+ hist[i].hisnum);
+ if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) {
+ trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff),
+ Columns - 10, IOSIZE - (int)STRLEN(IObuff));
+ } else {
+ STRCAT(IObuff, hist[i].hisstr);
+ }
+ msg_outtrans((char *)IObuff);
+ ui_flush();
+ }
+ if (i == idx) {
+ break;
+ }
+ }
+ }
+ }
+}
+
+/// Iterate over history items
+///
+/// @warning No history-editing functions must be run while iteration is in
+/// progress.
+///
+/// @param[in] iter Pointer to the last history entry.
+/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
+/// parameter is not NULL.
+/// @param[in] zero If true then zero (but not free) returned items.
+///
+/// @warning When using this parameter user is
+/// responsible for calling clr_history()
+/// itself after iteration is over. If
+/// clr_history() is not called behaviour is
+/// undefined. No functions that work with
+/// history must be called during iteration
+/// in this case.
+/// @param[out] hist Next history entry.
+///
+/// @return Pointer used in next iteration or NULL to indicate that iteration
+/// was finished.
+const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero,
+ histentry_T *const hist)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
+{
+ *hist = (histentry_T) {
+ .hisstr = NULL
+ };
+ if (hisidx[history_type] == -1) {
+ return NULL;
+ }
+ histentry_T *const hstart = &(history[history_type][0]);
+ histentry_T *const hlast = &(history[history_type][hisidx[history_type]]);
+ const histentry_T *const hend = &(history[history_type][hislen - 1]);
+ histentry_T *hiter;
+ if (iter == NULL) {
+ histentry_T *hfirst = hlast;
+ do {
+ hfirst++;
+ if (hfirst > hend) {
+ hfirst = hstart;
+ }
+ if (hfirst->hisstr != NULL) {
+ break;
+ }
+ } while (hfirst != hlast);
+ hiter = hfirst;
+ } else {
+ hiter = (histentry_T *)iter;
+ }
+ if (hiter == NULL) {
+ return NULL;
+ }
+ *hist = *hiter;
+ if (zero) {
+ memset(hiter, 0, sizeof(*hiter));
+ }
+ if (hiter == hlast) {
+ return NULL;
+ }
+ hiter++;
+ return (const void *)((hiter > hend) ? hstart : hiter);
+}
+
+/// Get array of history items
+///
+/// @param[in] history_type Type of the history to get array for.
+/// @param[out] new_hisidx Location where last index in the new array should
+/// be saved.
+/// @param[out] new_hisnum Location where last history number in the new
+/// history should be saved.
+///
+/// @return Pointer to the array or NULL.
+histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
+ int **const new_hisnum)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ init_history();
+ *new_hisidx = &(hisidx[history_type]);
+ *new_hisnum = &(hisnum[history_type]);
+ return history[history_type];
+}
diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h
new file mode 100644
index 0000000000..797b79a5f0
--- /dev/null
+++ b/src/nvim/cmdhist.h
@@ -0,0 +1,33 @@
+#ifndef NVIM_CMDHIST_H
+#define NVIM_CMDHIST_H
+
+#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds_defs.h"
+#include "nvim/os/time.h"
+
+/// Present history tables
+typedef enum {
+ HIST_DEFAULT = -2, ///< Default (current) history.
+ HIST_INVALID = -1, ///< Unknown history.
+ HIST_CMD = 0, ///< Colon commands.
+ HIST_SEARCH, ///< Search commands.
+ HIST_EXPR, ///< Expressions (e.g. from entering = register).
+ HIST_INPUT, ///< input() lines.
+ HIST_DEBUG, ///< Debug commands.
+} HistoryType;
+
+/// Number of history tables
+#define HIST_COUNT (HIST_DEBUG + 1)
+
+/// History entry definition
+typedef struct hist_entry {
+ int hisnum; ///< Entry identifier number.
+ char_u *hisstr; ///< Actual entry, separator char after the NUL.
+ Timestamp timestamp; ///< Time when entry was added.
+ list_T *additional_elements; ///< Additional entries from ShaDa file.
+} histentry_T;
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "cmdhist.h.generated.h"
+#endif
+#endif // NVIM_CMDHIST_H
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 5911a93099..b0a207e5ba 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -19,6 +19,7 @@
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 060dc50f52..52386a67f6 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -13,6 +13,7 @@
#include "nvim/change.h"
#include "nvim/channel.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/context.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
@@ -4303,87 +4304,6 @@ static void f_haslocaldir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
-/// "histadd()" function
-static void f_histadd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType histype;
-
- rettv->vval.v_number = false;
- if (check_secure()) {
- return;
- }
- const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
- histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
- if (histype != HIST_INVALID) {
- char buf[NUMBUFLEN];
- str = tv_get_string_buf(&argvars[1], buf);
- if (*str != NUL) {
- init_history();
- add_to_history(histype, (char_u *)str, false, NUL);
- rettv->vval.v_number = true;
- return;
- }
- }
-}
-
-/// "histdel()" function
-static void f_histdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- int n;
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- n = 0;
- } else if (argvars[1].v_type == VAR_UNKNOWN) {
- // only one argument: clear entire history
- n = clr_history(get_histtype(str, strlen(str), false));
- } else if (argvars[1].v_type == VAR_NUMBER) {
- // index given: remove that entry
- n = del_history_idx(get_histtype(str, strlen(str), false),
- (int)tv_get_number(&argvars[1]));
- } else {
- // string given: remove all matching entries
- char buf[NUMBUFLEN];
- n = del_history_entry(get_histtype(str, strlen(str), false),
- (char_u *)tv_get_string_buf(&argvars[1], buf));
- }
- rettv->vval.v_number = n;
-}
-
-/// "histget()" function
-static void f_histget(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- HistoryType type;
- int idx;
-
- const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
- if (str == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- type = get_histtype(str, strlen(str), false);
- if (argvars[1].v_type == VAR_UNKNOWN) {
- idx = get_history_idx(type);
- } else {
- idx = (int)tv_get_number_chk(&argvars[1], NULL);
- }
- // -1 on type error
- rettv->vval.v_string = (char *)vim_strsave(get_history_entry(type, idx));
- }
- rettv->v_type = VAR_STRING;
-}
-
-/// "histnr()" function
-static void f_histnr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
-{
- const char *const history = tv_get_string_chk(&argvars[0]);
- HistoryType i = history == NULL
- ? HIST_INVALID
- : get_histtype(history, strlen(history), false);
- if (i != HIST_INVALID) {
- i = get_history_idx(i);
- }
- rettv->vval.v_number = i;
-}
-
/// "highlightID(name)" function
static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 9d1ac185d4..9ad65500e9 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -20,6 +20,7 @@
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/diff.h"
@@ -3343,6 +3344,7 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, char *sub, char *cmd, boo
if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) {
save_re_pat(RE_SUBST, (char_u *)pat, p_magic);
}
+ // put pattern in history
add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL);
}
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 4ac9847e53..86c5489cf2 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -13,6 +13,7 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/debugger.h"
#include "nvim/diff.h"
@@ -601,10 +602,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
if (next_cmdline == NULL) {
XFREE_CLEAR(cmdline_copy);
- //
+
// If the command was typed, remember it for the ':' register.
// Do this AFTER executing the command to make :@: work.
- //
if (getline_equal(fgetline, cookie, getexline)
&& new_last_cmdline != NULL) {
xfree(last_cmdline);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 07a0e68884..841345d625 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -19,6 +19,7 @@
#include "nvim/assert.h"
#include "nvim/buffer.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/cursor_shape.h"
#include "nvim/digraph.h"
@@ -214,12 +215,6 @@ static Array cmdline_block = ARRAY_DICT_INIT;
*/
typedef void *(*user_expand_func_T)(const char_u *, int, typval_T *);
-static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
-static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 }; // lastused entry
-static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 };
-// identifying (unique) number of newest history entry
-static int hislen = 0; // actual length of history tables
-
/// Flag for command_line_handle_key to ignore <C-c>
///
/// Used if it was received while processing highlight function in order for
@@ -869,7 +864,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent, bool init
may_trigger_modechanged();
init_history();
- s->hiscnt = hislen; // set hiscnt to impossible history value
+ s->hiscnt = get_hislen(); // set hiscnt to impossible history value
s->histype = hist_char2type(s->firstc);
do_digraph(-1); // init digraph typeahead
@@ -1682,12 +1677,12 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
for (;;) {
// one step backwards
if (!next_match) {
- if (s->hiscnt == hislen) {
+ if (s->hiscnt == get_hislen()) {
// first time
- s->hiscnt = hisidx[s->histype];
- } else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
- s->hiscnt = hislen - 1;
- } else if (s->hiscnt != hisidx[s->histype] + 1) {
+ s->hiscnt = *get_hisidx(s->histype);
+ } else if (s->hiscnt == 0 && *get_hisidx(s->histype) != get_hislen() - 1) {
+ s->hiscnt = get_hislen() - 1;
+ } else if (s->hiscnt != *get_hisidx(s->histype) + 1) {
s->hiscnt--;
} else {
// at top of list
@@ -1696,17 +1691,17 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
}
} else { // one step forwards
// on last entry, clear the line
- if (s->hiscnt == hisidx[s->histype]) {
- s->hiscnt = hislen;
+ if (s->hiscnt == *get_hisidx(s->histype)) {
+ s->hiscnt = get_hislen();
break;
}
// not on a history line, nothing to do
- if (s->hiscnt == hislen) {
+ if (s->hiscnt == get_hislen()) {
break;
}
- if (s->hiscnt == hislen - 1) {
+ if (s->hiscnt == get_hislen() - 1) {
// wrap around
s->hiscnt = 0;
} else {
@@ -1714,14 +1709,14 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
}
}
- if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) {
+ if (s->hiscnt < 0 || get_histentry(s->histype)[s->hiscnt].hisstr == NULL) {
s->hiscnt = s->save_hiscnt;
break;
}
if ((s->c != K_UP && s->c != K_DOWN)
|| s->hiscnt == s->save_hiscnt
- || STRNCMP(history[s->histype][s->hiscnt].hisstr,
+ || STRNCMP(get_histentry(s->histype)[s->hiscnt].hisstr,
s->lookfor, (size_t)j) == 0) {
break;
}
@@ -2103,7 +2098,7 @@ static int command_line_handle_key(CommandLineState *s)
case K_KPAGEUP:
case K_PAGEDOWN:
case K_KPAGEDOWN:
- if (s->histype == HIST_INVALID || hislen == 0 || s->firstc == NUL) {
+ if (s->histype == HIST_INVALID || get_hislen() == 0 || s->firstc == NUL) {
// no history
return command_line_not_changed(s);
}
@@ -2128,10 +2123,10 @@ static int command_line_handle_key(CommandLineState *s)
XFREE_CLEAR(ccline.cmdbuff);
s->xpc.xp_context = EXPAND_NOTHING;
- if (s->hiscnt == hislen) {
+ if (s->hiscnt == get_hislen()) {
p = s->lookfor; // back to the old one
} else {
- p = history[s->histype][s->hiscnt].hisstr;
+ p = get_histentry(s->histype)[s->hiscnt].hisstr;
}
if (s->histype == HIST_SEARCH
@@ -5868,309 +5863,6 @@ void globpath(char_u *path, char_u *file, garray_T *ga, int expand_options)
xfree(buf);
}
-/*********************************
-* Command line history stuff *
-*********************************/
-
-/// Translate a history character to the associated type number
-static HistoryType hist_char2type(const int c)
- FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
-{
- switch (c) {
- case ':':
- return HIST_CMD;
- case '=':
- return HIST_EXPR;
- case '@':
- return HIST_INPUT;
- case '>':
- return HIST_DEBUG;
- case NUL:
- case '/':
- case '?':
- return HIST_SEARCH;
- default:
- return HIST_INVALID;
- }
- // Silence -Wreturn-type
- return 0;
-}
-
-/*
- * Table of history names.
- * These names are used in :history and various hist...() functions.
- * It is sufficient to give the significant prefix of a history name.
- */
-
-static char *(history_names[]) =
-{
- "cmd",
- "search",
- "expr",
- "input",
- "debug",
- NULL
-};
-
-/*
- * Function given to ExpandGeneric() to obtain the possible first
- * arguments of the ":history command.
- */
-static char *get_history_arg(expand_T *xp, int idx)
-{
- static char_u compl[2] = { NUL, NUL };
- char *short_names = ":=@>?/";
- int short_names_count = (int)STRLEN(short_names);
- int history_name_count = ARRAY_SIZE(history_names) - 1;
-
- if (idx < short_names_count) {
- compl[0] = (char_u)short_names[idx];
- return (char *)compl;
- }
- if (idx < short_names_count + history_name_count) {
- return history_names[idx - short_names_count];
- }
- if (idx == short_names_count + history_name_count) {
- return "all";
- }
- return NULL;
-}
-
-/// Initialize command line history.
-/// Also used to re-allocate history tables when size changes.
-void init_history(void)
-{
- assert(p_hi >= 0 && p_hi <= INT_MAX);
- int newlen = (int)p_hi;
- int oldlen = hislen;
-
- // If history tables size changed, reallocate them.
- // Tables are circular arrays (current position marked by hisidx[type]).
- // On copying them to the new arrays, we take the chance to reorder them.
- if (newlen != oldlen) {
- for (int type = 0; type < HIST_COUNT; type++) {
- histentry_T *temp = (newlen
- ? xmalloc((size_t)newlen * sizeof(*temp))
- : NULL);
-
- int j = hisidx[type];
- if (j >= 0) {
- // old array gets partitioned this way:
- // [0 , i1 ) --> newest entries to be deleted
- // [i1 , i1 + l1) --> newest entries to be copied
- // [i1 + l1 , i2 ) --> oldest entries to be deleted
- // [i2 , i2 + l2) --> oldest entries to be copied
- int l1 = MIN(j + 1, newlen); // how many newest to copy
- int l2 = MIN(newlen, oldlen) - l1; // how many oldest to copy
- int i1 = j + 1 - l1; // copy newest from here
- int i2 = MAX(l1, oldlen - newlen + l1); // copy oldest from here
-
- // copy as much entries as they fit to new table, reordering them
- if (newlen) {
- // copy oldest entries
- memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp));
- // copy newest entries
- memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp));
- }
-
- // delete entries that don't fit in newlen, if any
- for (int i = 0; i < i1; i++) {
- hist_free_entry(history[type] + i);
- }
- for (int i = i1 + l1; i < i2; i++) {
- hist_free_entry(history[type] + i);
- }
- }
-
- // clear remaining space, if any
- int l3 = j < 0 ? 0 : MIN(newlen, oldlen); // number of copied entries
- if (newlen) {
- memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp));
- }
-
- hisidx[type] = l3 - 1;
- xfree(history[type]);
- history[type] = temp;
- }
- hislen = newlen;
- }
-}
-
-static inline void hist_free_entry(histentry_T *hisptr)
- FUNC_ATTR_NONNULL_ALL
-{
- xfree(hisptr->hisstr);
- tv_list_unref(hisptr->additional_elements);
- clear_hist_entry(hisptr);
-}
-
-static inline void clear_hist_entry(histentry_T *hisptr)
- FUNC_ATTR_NONNULL_ALL
-{
- memset(hisptr, 0, sizeof(*hisptr));
-}
-
-/// Check if command line 'str' is already in history.
-/// If 'move_to_front' is TRUE, matching entry is moved to end of history.
-///
-/// @param move_to_front Move the entry to the front if it exists
-static int in_history(int type, char_u *str, int move_to_front, int sep)
-{
- int i;
- int last_i = -1;
- char_u *p;
-
- if (hisidx[type] < 0) {
- return FALSE;
- }
- i = hisidx[type];
- do {
- if (history[type][i].hisstr == NULL) {
- return FALSE;
- }
-
- /* For search history, check that the separator character matches as
- * well. */
- p = history[type][i].hisstr;
- if (STRCMP(str, p) == 0
- && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) {
- if (!move_to_front) {
- return TRUE;
- }
- last_i = i;
- break;
- }
- if (--i < 0) {
- i = hislen - 1;
- }
- } while (i != hisidx[type]);
-
- if (last_i >= 0) {
- list_T *const list = history[type][i].additional_elements;
- str = history[type][i].hisstr;
- while (i != hisidx[type]) {
- if (++i >= hislen) {
- i = 0;
- }
- history[type][last_i] = history[type][i];
- last_i = i;
- }
- tv_list_unref(list);
- history[type][i].hisnum = ++hisnum[type];
- history[type][i].hisstr = str;
- history[type][i].timestamp = os_time();
- history[type][i].additional_elements = NULL;
- return true;
- }
- return false;
-}
-
-/// Convert history name to its HIST_ equivalent
-///
-/// Names are taken from the table above. When `name` is empty returns currently
-/// active history or HIST_DEFAULT, depending on `return_default` argument.
-///
-/// @param[in] name Converted name.
-/// @param[in] len Name length.
-/// @param[in] return_default Determines whether HIST_DEFAULT should be
-/// returned or value based on `ccline.cmdfirstc`.
-///
-/// @return Any value from HistoryType enum, including HIST_INVALID. May not
-/// return HIST_DEFAULT unless return_default is true.
-HistoryType get_histtype(const char *const name, const size_t len, const bool return_default)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- // No argument: use current history.
- if (len == 0) {
- return return_default ? HIST_DEFAULT : hist_char2type(ccline.cmdfirstc);
- }
-
- for (HistoryType i = 0; history_names[i] != NULL; i++) {
- if (STRNICMP(name, history_names[i], len) == 0) {
- return i;
- }
- }
-
- if (vim_strchr(":=@>?/", name[0]) != NULL && len == 1) {
- return hist_char2type(name[0]);
- }
-
- return HIST_INVALID;
-}
-
-static int last_maptick = -1; // last seen maptick
-
-/// Add the given string to the given history. If the string is already in the
-/// history then it is moved to the front. "histype" may be one of the HIST_
-/// values.
-///
-/// @parma in_map consider maptick when inside a mapping
-/// @param sep separator character used (search hist)
-void add_to_history(int histype, char_u *new_entry, int in_map, int sep)
-{
- histentry_T *hisptr;
-
- if (hislen == 0 || histype == HIST_INVALID) { // no history
- return;
- }
- assert(histype != HIST_DEFAULT);
-
- if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) {
- return;
- }
-
- /*
- * Searches inside the same mapping overwrite each other, so that only
- * the last line is kept. Be careful not to remove a line that was moved
- * down, only lines that were added.
- */
- if (histype == HIST_SEARCH && in_map) {
- if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) {
- // Current line is from the same mapping, remove it
- hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
- hist_free_entry(hisptr);
- --hisnum[histype];
- if (--hisidx[HIST_SEARCH] < 0) {
- hisidx[HIST_SEARCH] = hislen - 1;
- }
- }
- last_maptick = -1;
- }
- if (!in_history(histype, new_entry, true, sep)) {
- if (++hisidx[histype] == hislen) {
- hisidx[histype] = 0;
- }
- hisptr = &history[histype][hisidx[histype]];
- hist_free_entry(hisptr);
-
- // Store the separator after the NUL of the string.
- size_t len = STRLEN(new_entry);
- hisptr->hisstr = vim_strnsave(new_entry, len + 2);
- hisptr->timestamp = os_time();
- hisptr->additional_elements = NULL;
- hisptr->hisstr[len + 1] = (char_u)sep;
-
- hisptr->hisnum = ++hisnum[histype];
- if (histype == HIST_SEARCH && in_map) {
- last_maptick = maptick;
- }
- }
-}
-
-/*
- * Get identifier of newest history entry.
- * "histype" may be one of the HIST_ values.
- */
-int get_history_idx(int histype)
-{
- if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
- || hisidx[histype] < 0) {
- return -1;
- }
-
- return history[histype][hisidx[histype]].hisnum;
-}
-
/// Get pointer to the command line info to use. save_cmdline() may clear
/// ccline and put the previous value in ccline.prev_ccline.
static struct cmdline_info *get_ccline_ptr(void)
@@ -6292,168 +5984,10 @@ int get_cmdline_type(void)
return p->cmdfirstc;
}
-/*
- * Calculate history index from a number:
- * num > 0: seen as identifying number of a history entry
- * num < 0: relative position in history wrt newest entry
- * "histype" may be one of the HIST_ values.
- */
-static int calc_hist_idx(int histype, int num)
-{
- int i;
- histentry_T *hist;
- int wrapped = FALSE;
-
- if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
- || (i = hisidx[histype]) < 0 || num == 0) {
- return -1;
- }
-
- hist = history[histype];
- if (num > 0) {
- while (hist[i].hisnum > num) {
- if (--i < 0) {
- if (wrapped) {
- break;
- }
- i += hislen;
- wrapped = TRUE;
- }
- }
- if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) {
- return i;
- }
- } else if (-num <= hislen) {
- i += num + 1;
- if (i < 0) {
- i += hislen;
- }
- if (hist[i].hisstr != NULL) {
- return i;
- }
- }
- return -1;
-}
-
-/*
- * Get a history entry by its index.
- * "histype" may be one of the HIST_ values.
- */
-char_u *get_history_entry(int histype, int idx)
+/// Return the first character of the current command line.
+int get_cmdline_firstc(void)
{
- idx = calc_hist_idx(histype, idx);
- if (idx >= 0) {
- return history[histype][idx].hisstr;
- } else {
- return (char_u *)"";
- }
-}
-
-/// Clear all entries in a history
-///
-/// @param[in] histype One of the HIST_ values.
-///
-/// @return OK if there was something to clean and histype was one of HIST_
-/// values, FAIL otherwise.
-int clr_history(const int histype)
-{
- if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
- histentry_T *hisptr = history[histype];
- for (int i = hislen; i--; hisptr++) {
- hist_free_entry(hisptr);
- }
- hisidx[histype] = -1; // mark history as cleared
- hisnum[histype] = 0; // reset identifier counter
- return OK;
- }
- return FAIL;
-}
-
-/*
- * Remove all entries matching {str} from a history.
- * "histype" may be one of the HIST_ values.
- */
-int del_history_entry(int histype, char_u *str)
-{
- regmatch_T regmatch;
- histentry_T *hisptr;
- int idx;
- int i;
- int last;
- bool found = false;
-
- regmatch.regprog = NULL;
- regmatch.rm_ic = FALSE; // always match case
- if (hislen != 0
- && histype >= 0
- && histype < HIST_COUNT
- && *str != NUL
- && (idx = hisidx[histype]) >= 0
- && (regmatch.regprog = vim_regcomp((char *)str, RE_MAGIC + RE_STRING))
- != NULL) {
- i = last = idx;
- do {
- hisptr = &history[histype][i];
- if (hisptr->hisstr == NULL) {
- break;
- }
- if (vim_regexec(&regmatch, (char *)hisptr->hisstr, (colnr_T)0)) {
- found = true;
- hist_free_entry(hisptr);
- } else {
- if (i != last) {
- history[histype][last] = *hisptr;
- clear_hist_entry(hisptr);
- }
- if (--last < 0) {
- last += hislen;
- }
- }
- if (--i < 0) {
- i += hislen;
- }
- } while (i != idx);
- if (history[histype][idx].hisstr == NULL) {
- hisidx[histype] = -1;
- }
- }
- vim_regfree(regmatch.regprog);
- return found;
-}
-
-/*
- * Remove an indexed entry from a history.
- * "histype" may be one of the HIST_ values.
- */
-int del_history_idx(int histype, int idx)
-{
- int i, j;
-
- i = calc_hist_idx(histype, idx);
- if (i < 0) {
- return FALSE;
- }
- idx = hisidx[histype];
- hist_free_entry(&history[histype][i]);
-
- /* When deleting the last added search string in a mapping, reset
- * last_maptick, so that the last added search string isn't deleted again.
- */
- if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) {
- last_maptick = -1;
- }
-
- while (i != idx) {
- j = (i + 1) % hislen;
- history[histype][i] = history[histype][j];
- i = j;
- }
- clear_hist_entry(&history[histype][idx]);
- if (--i < 0) {
- i += hislen;
- }
- hisidx[histype] = i;
- return TRUE;
+ return ccline.cmdfirstc;
}
/// Get indices that specify a range within a list (not a range of text lines
@@ -6493,115 +6027,6 @@ int get_list_range(char_u **str, int *num1, int *num2)
return OK;
}
-/*
- * :history command - print a history
- */
-void ex_history(exarg_T *eap)
-{
- histentry_T *hist;
- int histype1 = HIST_CMD;
- int histype2 = HIST_CMD;
- int hisidx1 = 1;
- int hisidx2 = -1;
- int idx;
- int i, j, k;
- char_u *end;
- char_u *arg = (char_u *)eap->arg;
-
- if (hislen == 0) {
- msg(_("'history' option is zero"));
- return;
- }
-
- if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
- end = arg;
- while (ASCII_ISALPHA(*end)
- || vim_strchr(":=@>/?", *end) != NULL) {
- end++;
- }
- histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false);
- if (histype1 == HIST_INVALID) {
- if (STRNICMP(arg, "all", end - arg) == 0) {
- histype1 = 0;
- histype2 = HIST_COUNT - 1;
- } else {
- semsg(_(e_trailing_arg), arg);
- return;
- }
- } else {
- histype2 = histype1;
- }
- } else {
- end = arg;
- }
- if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
- semsg(_(e_trailing_arg), end);
- return;
- }
-
- for (; !got_int && histype1 <= histype2; ++histype1) {
- STRCPY(IObuff, "\n # ");
- assert(history_names[histype1] != NULL);
- STRCAT(STRCAT(IObuff, history_names[histype1]), " history");
- msg_puts_title((char *)IObuff);
- idx = hisidx[histype1];
- hist = history[histype1];
- j = hisidx1;
- k = hisidx2;
- if (j < 0) {
- j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum;
- }
- if (k < 0) {
- k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum;
- }
- if (idx >= 0 && j <= k) {
- for (i = idx + 1; !got_int; ++i) {
- if (i == hislen) {
- i = 0;
- }
- if (hist[i].hisstr != NULL
- && hist[i].hisnum >= j && hist[i].hisnum <= k) {
- msg_putchar('\n');
- snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
- hist[i].hisnum);
- if (vim_strsize((char *)hist[i].hisstr) > Columns - 10) {
- trunc_string((char *)hist[i].hisstr, (char *)IObuff + STRLEN(IObuff),
- Columns - 10, IOSIZE - (int)STRLEN(IObuff));
- } else {
- STRCAT(IObuff, hist[i].hisstr);
- }
- msg_outtrans((char *)IObuff);
- ui_flush();
- }
- if (i == idx) {
- break;
- }
- }
- }
- }
-}
-
-/// Translate a history type number to the associated character
-int hist_type2char(int type)
- FUNC_ATTR_CONST
-{
- switch (type) {
- case HIST_CMD:
- return ':';
- case HIST_SEARCH:
- return '/';
- case HIST_EXPR:
- return '=';
- case HIST_INPUT:
- return '@';
- case HIST_DEBUG:
- return '>';
- default:
- abort();
- }
- return NUL;
-}
-
void cmdline_init(void)
{
memset(&ccline, 0, sizeof(struct cmdline_info));
@@ -6688,18 +6113,18 @@ static int open_cmdwin(void)
// Fill the buffer with the history.
init_history();
- if (hislen > 0 && histtype != HIST_INVALID) {
- i = hisidx[histtype];
+ if (get_hislen() > 0 && histtype != HIST_INVALID) {
+ i = *get_hisidx(histtype);
if (i >= 0) {
lnum = 0;
do {
- if (++i == hislen) {
+ if (++i == get_hislen()) {
i = 0;
}
- if (history[histtype][i].hisstr != NULL) {
- ml_append(lnum++, (char *)history[histtype][i].hisstr, (colnr_T)0, false);
+ if (get_histentry(histtype)[i].hisstr != NULL) {
+ ml_append(lnum++, (char *)get_histentry(histtype)[i].hisstr, (colnr_T)0, false);
}
- } while (i != hisidx[histtype]);
+ } while (i != *get_hisidx(histtype));
}
}
@@ -6904,90 +6329,6 @@ char *script_get(exarg_T *const eap, size_t *const lenp)
return (char *)ga.ga_data;
}
-/// Iterate over history items
-///
-/// @warning No history-editing functions must be run while iteration is in
-/// progress.
-///
-/// @param[in] iter Pointer to the last history entry.
-/// @param[in] history_type Type of the history (HIST_*). Ignored if iter
-/// parameter is not NULL.
-/// @param[in] zero If true then zero (but not free) returned items.
-///
-/// @warning When using this parameter user is
-/// responsible for calling clr_history()
-/// itself after iteration is over. If
-/// clr_history() is not called behaviour is
-/// undefined. No functions that work with
-/// history must be called during iteration
-/// in this case.
-/// @param[out] hist Next history entry.
-///
-/// @return Pointer used in next iteration or NULL to indicate that iteration
-/// was finished.
-const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero,
- histentry_T *const hist)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
-{
- *hist = (histentry_T) {
- .hisstr = NULL
- };
- if (hisidx[history_type] == -1) {
- return NULL;
- }
- histentry_T *const hstart = &(history[history_type][0]);
- histentry_T *const hlast = (
- &(history[history_type][hisidx[history_type]]));
- const histentry_T *const hend = &(history[history_type][hislen - 1]);
- histentry_T *hiter;
- if (iter == NULL) {
- histentry_T *hfirst = hlast;
- do {
- hfirst++;
- if (hfirst > hend) {
- hfirst = hstart;
- }
- if (hfirst->hisstr != NULL) {
- break;
- }
- } while (hfirst != hlast);
- hiter = hfirst;
- } else {
- hiter = (histentry_T *)iter;
- }
- if (hiter == NULL) {
- return NULL;
- }
- *hist = *hiter;
- if (zero) {
- memset(hiter, 0, sizeof(*hiter));
- }
- if (hiter == hlast) {
- return NULL;
- }
- hiter++;
- return (const void *)((hiter > hend) ? hstart : hiter);
-}
-
-/// Get array of history items
-///
-/// @param[in] history_type Type of the history to get array for.
-/// @param[out] new_hisidx Location where last index in the new array should
-/// be saved.
-/// @param[out] new_hisnum Location where last history number in the new
-/// history should be saved.
-///
-/// @return Pointer to the array or NULL.
-histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
- int **const new_hisnum)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
-{
- init_history();
- *new_hisidx = &(hisidx[history_type]);
- *new_hisnum = &(hisnum[history_type]);
- return history[history_type];
-}
-
static void set_search_match(pos_T *t)
{
// First move cursor to end of match, then to the start. This
diff --git a/src/nvim/ex_getln.h b/src/nvim/ex_getln.h
index 1cc6faf87c..2f6929924d 100644
--- a/src/nvim/ex_getln.h
+++ b/src/nvim/ex_getln.h
@@ -3,8 +3,6 @@
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
-#include "nvim/ex_cmds_defs.h"
-#include "nvim/os/time.h"
#include "nvim/regexp_defs.h"
// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning.
@@ -39,30 +37,8 @@
#define VSE_SHELL 1 ///< escape for a shell command
#define VSE_BUFFER 2 ///< escape for a ":buffer" command
-/// Present history tables
-typedef enum {
- HIST_DEFAULT = -2, ///< Default (current) history.
- HIST_INVALID = -1, ///< Unknown history.
- HIST_CMD = 0, ///< Colon commands.
- HIST_SEARCH, ///< Search commands.
- HIST_EXPR, ///< Expressions (e.g. from entering = register).
- HIST_INPUT, ///< input() lines.
- HIST_DEBUG, ///< Debug commands.
-} HistoryType;
-
-/// Number of history tables
-#define HIST_COUNT (HIST_DEBUG + 1)
-
typedef char *(*CompleteListItemGetter)(expand_T *, int);
-/// History entry definition
-typedef struct hist_entry {
- int hisnum; ///< Entry identifier number.
- char_u *hisstr; ///< Actual entry, separator char after the NUL.
- Timestamp timestamp; ///< Time when entry was added.
- list_T *additional_elements; ///< Additional entries from ShaDa file.
-} histentry_T;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_getln.h.generated.h"
#endif
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 5ae7f7bbe3..4d00c0212e 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -619,6 +619,7 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
# include "nvim/buffer.h"
# include "nvim/charset.h"
+# include "nvim/cmdhist.h"
# include "nvim/diff.h"
# include "nvim/edit.h"
# include "nvim/eval/typval.h"
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 2d752eade7..064b73a56f 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -18,6 +18,7 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/digraph.h"
@@ -4379,9 +4380,11 @@ static void nv_ident(cmdarg_T *cap)
&& vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) {
STRCAT(buf, "\\>");
}
+
// put pattern in search history
init_history();
add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
+
(void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0,
NULL);
} else {
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 403e2f3aa4..b9aac59e1e 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -15,6 +15,7 @@
#include "nvim/buffer.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
#include "nvim/eval.h"
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 6e80b550d8..ee5322752f 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -16,11 +16,12 @@
#include "nvim/ascii.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
+#include "nvim/cmdhist.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
+#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
-#include "nvim/ex_getln.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
#include "nvim/globals.h"
@@ -2451,6 +2452,27 @@ static inline void find_removable_bufs(khash_t(bufset) *removable_bufs)
}
}
+/// Translate a history type number to the associated character
+static int hist_type2char(const int type)
+ FUNC_ATTR_CONST
+{
+ switch (type) {
+ case HIST_CMD:
+ return ':';
+ case HIST_SEARCH:
+ return '/';
+ case HIST_EXPR:
+ return '=';
+ case HIST_INPUT:
+ return '@';
+ case HIST_DEBUG:
+ return '>';
+ default:
+ abort();
+ }
+ return NUL;
+}
+
/// Write ShaDa file
///
/// @param[in] sd_writer Structure containing file writer definition.