mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
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.
d7663c22c6
This commit is contained in:
parent
a5e846b996
commit
cd14efd281
743
src/nvim/cmdhist.c
Normal file
743
src/nvim/cmdhist.c
Normal file
@ -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(®match, (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];
|
||||||
|
}
|
33
src/nvim/cmdhist.h
Normal file
33
src/nvim/cmdhist.h
Normal file
@ -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
|
@ -19,6 +19,7 @@
|
|||||||
#include "nvim/change.h"
|
#include "nvim/change.h"
|
||||||
#include "nvim/channel.h"
|
#include "nvim/channel.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
#include "nvim/edit.h"
|
#include "nvim/edit.h"
|
||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "nvim/change.h"
|
#include "nvim/change.h"
|
||||||
#include "nvim/channel.h"
|
#include "nvim/channel.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/context.h"
|
#include "nvim/context.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
#include "nvim/diff.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
|
/// "highlightID(name)" function
|
||||||
static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
static void f_hlID(typval_T *argvars, typval_T *rettv, FunPtr fptr)
|
||||||
{
|
{
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "nvim/buffer_updates.h"
|
#include "nvim/buffer_updates.h"
|
||||||
#include "nvim/change.h"
|
#include "nvim/change.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
#include "nvim/decoration.h"
|
#include "nvim/decoration.h"
|
||||||
#include "nvim/diff.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) {
|
if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) {
|
||||||
save_re_pat(RE_SUBST, (char_u *)pat, p_magic);
|
save_re_pat(RE_SUBST, (char_u *)pat, p_magic);
|
||||||
}
|
}
|
||||||
|
// put pattern in history
|
||||||
add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL);
|
add_to_history(HIST_SEARCH, (char_u *)pat, true, NUL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/change.h"
|
#include "nvim/change.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
#include "nvim/debugger.h"
|
#include "nvim/debugger.h"
|
||||||
#include "nvim/diff.h"
|
#include "nvim/diff.h"
|
||||||
@ -601,10 +602,9 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags)
|
|||||||
|
|
||||||
if (next_cmdline == NULL) {
|
if (next_cmdline == NULL) {
|
||||||
XFREE_CLEAR(cmdline_copy);
|
XFREE_CLEAR(cmdline_copy);
|
||||||
//
|
|
||||||
// If the command was typed, remember it for the ':' register.
|
// If the command was typed, remember it for the ':' register.
|
||||||
// Do this AFTER executing the command to make :@: work.
|
// Do this AFTER executing the command to make :@: work.
|
||||||
//
|
|
||||||
if (getline_equal(fgetline, cookie, getexline)
|
if (getline_equal(fgetline, cookie, getexline)
|
||||||
&& new_last_cmdline != NULL) {
|
&& new_last_cmdline != NULL) {
|
||||||
xfree(last_cmdline);
|
xfree(last_cmdline);
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "nvim/assert.h"
|
#include "nvim/assert.h"
|
||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
#include "nvim/cursor_shape.h"
|
#include "nvim/cursor_shape.h"
|
||||||
#include "nvim/digraph.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 *);
|
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>
|
/// Flag for command_line_handle_key to ignore <C-c>
|
||||||
///
|
///
|
||||||
/// Used if it was received while processing highlight function in order for
|
/// 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();
|
may_trigger_modechanged();
|
||||||
|
|
||||||
init_history();
|
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);
|
s->histype = hist_char2type(s->firstc);
|
||||||
do_digraph(-1); // init digraph typeahead
|
do_digraph(-1); // init digraph typeahead
|
||||||
|
|
||||||
@ -1682,12 +1677,12 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
|
|||||||
for (;;) {
|
for (;;) {
|
||||||
// one step backwards
|
// one step backwards
|
||||||
if (!next_match) {
|
if (!next_match) {
|
||||||
if (s->hiscnt == hislen) {
|
if (s->hiscnt == get_hislen()) {
|
||||||
// first time
|
// first time
|
||||||
s->hiscnt = hisidx[s->histype];
|
s->hiscnt = *get_hisidx(s->histype);
|
||||||
} else if (s->hiscnt == 0 && hisidx[s->histype] != hislen - 1) {
|
} else if (s->hiscnt == 0 && *get_hisidx(s->histype) != get_hislen() - 1) {
|
||||||
s->hiscnt = hislen - 1;
|
s->hiscnt = get_hislen() - 1;
|
||||||
} else if (s->hiscnt != hisidx[s->histype] + 1) {
|
} else if (s->hiscnt != *get_hisidx(s->histype) + 1) {
|
||||||
s->hiscnt--;
|
s->hiscnt--;
|
||||||
} else {
|
} else {
|
||||||
// at top of list
|
// at top of list
|
||||||
@ -1696,17 +1691,17 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match)
|
|||||||
}
|
}
|
||||||
} else { // one step forwards
|
} else { // one step forwards
|
||||||
// on last entry, clear the line
|
// on last entry, clear the line
|
||||||
if (s->hiscnt == hisidx[s->histype]) {
|
if (s->hiscnt == *get_hisidx(s->histype)) {
|
||||||
s->hiscnt = hislen;
|
s->hiscnt = get_hislen();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// not on a history line, nothing to do
|
// not on a history line, nothing to do
|
||||||
if (s->hiscnt == hislen) {
|
if (s->hiscnt == get_hislen()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->hiscnt == hislen - 1) {
|
if (s->hiscnt == get_hislen() - 1) {
|
||||||
// wrap around
|
// wrap around
|
||||||
s->hiscnt = 0;
|
s->hiscnt = 0;
|
||||||
} else {
|
} 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;
|
s->hiscnt = s->save_hiscnt;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((s->c != K_UP && s->c != K_DOWN)
|
if ((s->c != K_UP && s->c != K_DOWN)
|
||||||
|| s->hiscnt == s->save_hiscnt
|
|| 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) {
|
s->lookfor, (size_t)j) == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -2103,7 +2098,7 @@ static int command_line_handle_key(CommandLineState *s)
|
|||||||
case K_KPAGEUP:
|
case K_KPAGEUP:
|
||||||
case K_PAGEDOWN:
|
case K_PAGEDOWN:
|
||||||
case K_KPAGEDOWN:
|
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
|
// no history
|
||||||
return command_line_not_changed(s);
|
return command_line_not_changed(s);
|
||||||
}
|
}
|
||||||
@ -2128,10 +2123,10 @@ static int command_line_handle_key(CommandLineState *s)
|
|||||||
|
|
||||||
XFREE_CLEAR(ccline.cmdbuff);
|
XFREE_CLEAR(ccline.cmdbuff);
|
||||||
s->xpc.xp_context = EXPAND_NOTHING;
|
s->xpc.xp_context = EXPAND_NOTHING;
|
||||||
if (s->hiscnt == hislen) {
|
if (s->hiscnt == get_hislen()) {
|
||||||
p = s->lookfor; // back to the old one
|
p = s->lookfor; // back to the old one
|
||||||
} else {
|
} else {
|
||||||
p = history[s->histype][s->hiscnt].hisstr;
|
p = get_histentry(s->histype)[s->hiscnt].hisstr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s->histype == HIST_SEARCH
|
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);
|
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
|
/// Get pointer to the command line info to use. save_cmdline() may clear
|
||||||
/// ccline and put the previous value in ccline.prev_ccline.
|
/// ccline and put the previous value in ccline.prev_ccline.
|
||||||
static struct cmdline_info *get_ccline_ptr(void)
|
static struct cmdline_info *get_ccline_ptr(void)
|
||||||
@ -6292,168 +5984,10 @@ int get_cmdline_type(void)
|
|||||||
return p->cmdfirstc;
|
return p->cmdfirstc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// Return the first character of the current command line.
|
||||||
* Calculate history index from a number:
|
int get_cmdline_firstc(void)
|
||||||
* 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;
|
return ccline.cmdfirstc;
|
||||||
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)
|
|
||||||
{
|
|
||||||
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(®match, (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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get indices that specify a range within a list (not a range of text lines
|
/// 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;
|
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)
|
void cmdline_init(void)
|
||||||
{
|
{
|
||||||
memset(&ccline, 0, sizeof(struct cmdline_info));
|
memset(&ccline, 0, sizeof(struct cmdline_info));
|
||||||
@ -6688,18 +6113,18 @@ static int open_cmdwin(void)
|
|||||||
|
|
||||||
// Fill the buffer with the history.
|
// Fill the buffer with the history.
|
||||||
init_history();
|
init_history();
|
||||||
if (hislen > 0 && histtype != HIST_INVALID) {
|
if (get_hislen() > 0 && histtype != HIST_INVALID) {
|
||||||
i = hisidx[histtype];
|
i = *get_hisidx(histtype);
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
lnum = 0;
|
lnum = 0;
|
||||||
do {
|
do {
|
||||||
if (++i == hislen) {
|
if (++i == get_hislen()) {
|
||||||
i = 0;
|
i = 0;
|
||||||
}
|
}
|
||||||
if (history[histtype][i].hisstr != NULL) {
|
if (get_histentry(histtype)[i].hisstr != NULL) {
|
||||||
ml_append(lnum++, (char *)history[histtype][i].hisstr, (colnr_T)0, false);
|
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;
|
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)
|
static void set_search_match(pos_T *t)
|
||||||
{
|
{
|
||||||
// First move cursor to end of match, then to the start. This
|
// First move cursor to end of match, then to the start. This
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
#include "nvim/ex_cmds.h"
|
#include "nvim/ex_cmds.h"
|
||||||
#include "nvim/ex_cmds_defs.h"
|
|
||||||
#include "nvim/os/time.h"
|
|
||||||
#include "nvim/regexp_defs.h"
|
#include "nvim/regexp_defs.h"
|
||||||
|
|
||||||
// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning.
|
// Values for nextwild() and ExpandOne(). See ExpandOne() for meaning.
|
||||||
@ -39,30 +37,8 @@
|
|||||||
#define VSE_SHELL 1 ///< escape for a shell command
|
#define VSE_SHELL 1 ///< escape for a shell command
|
||||||
#define VSE_BUFFER 2 ///< escape for a ":buffer" 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);
|
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
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "ex_getln.h.generated.h"
|
# include "ex_getln.h.generated.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -619,6 +619,7 @@ char *arena_memdupz(Arena *arena, const char *buf, size_t size)
|
|||||||
|
|
||||||
# include "nvim/buffer.h"
|
# include "nvim/buffer.h"
|
||||||
# include "nvim/charset.h"
|
# include "nvim/charset.h"
|
||||||
|
# include "nvim/cmdhist.h"
|
||||||
# include "nvim/diff.h"
|
# include "nvim/diff.h"
|
||||||
# include "nvim/edit.h"
|
# include "nvim/edit.h"
|
||||||
# include "nvim/eval/typval.h"
|
# include "nvim/eval/typval.h"
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/change.h"
|
#include "nvim/change.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
#include "nvim/diff.h"
|
#include "nvim/diff.h"
|
||||||
#include "nvim/digraph.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))) {
|
&& vim_iswordp(mb_prevptr(get_cursor_line_ptr(), ptr))) {
|
||||||
STRCAT(buf, "\\>");
|
STRCAT(buf, "\\>");
|
||||||
}
|
}
|
||||||
|
|
||||||
// put pattern in search history
|
// put pattern in search history
|
||||||
init_history();
|
init_history();
|
||||||
add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
|
add_to_history(HIST_SEARCH, (char_u *)buf, true, NUL);
|
||||||
|
|
||||||
(void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0,
|
(void)normal_search(cap, cmdchar == '*' ? '/' : '?', (char_u *)buf, 0,
|
||||||
NULL);
|
NULL);
|
||||||
} else {
|
} else {
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/change.h"
|
#include "nvim/change.h"
|
||||||
#include "nvim/charset.h"
|
#include "nvim/charset.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/cursor.h"
|
#include "nvim/cursor.h"
|
||||||
#include "nvim/edit.h"
|
#include "nvim/edit.h"
|
||||||
#include "nvim/eval.h"
|
#include "nvim/eval.h"
|
||||||
|
@ -16,11 +16,12 @@
|
|||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
#include "nvim/buffer.h"
|
#include "nvim/buffer.h"
|
||||||
#include "nvim/buffer_defs.h"
|
#include "nvim/buffer_defs.h"
|
||||||
|
#include "nvim/cmdhist.h"
|
||||||
#include "nvim/eval/decode.h"
|
#include "nvim/eval/decode.h"
|
||||||
#include "nvim/eval/encode.h"
|
#include "nvim/eval/encode.h"
|
||||||
#include "nvim/eval/typval.h"
|
#include "nvim/eval/typval.h"
|
||||||
|
#include "nvim/ex_cmds.h"
|
||||||
#include "nvim/ex_docmd.h"
|
#include "nvim/ex_docmd.h"
|
||||||
#include "nvim/ex_getln.h"
|
|
||||||
#include "nvim/fileio.h"
|
#include "nvim/fileio.h"
|
||||||
#include "nvim/garray.h"
|
#include "nvim/garray.h"
|
||||||
#include "nvim/globals.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
|
/// Write ShaDa file
|
||||||
///
|
///
|
||||||
/// @param[in] sd_writer Structure containing file writer definition.
|
/// @param[in] sd_writer Structure containing file writer definition.
|
||||||
|
Loading…
Reference in New Issue
Block a user