mirror of
https://github.com/neovim/neovim.git
synced 2025-09-30 23:18:33 +00:00

Problem: [security]: overflow with count for :s command
Solution: Abort the :s command if the count is too large
If the count after the :s command is larger than what fits into a
(signed) long variable, abort with e_value_too_large.
Adds a test with INT_MAX as count and verify it correctly fails.
It seems the return value on Windows using mingw compiler wraps around,
so the initial test using :s/./b/9999999999999999999999999990 doesn't
fail there, since the count is wrapping around several times and finally
is no longer larger than 2147483647. So let's just use 2147483647 in the
test, which hopefully will always cause a failure
ac63787734
Co-authored-by: Christian Brabandt <cb@256bit.org>
765 lines
21 KiB
C
765 lines
21 KiB
C
// cmdhist.c: Functions for the history of the command-line.
|
|
|
|
#include <assert.h>
|
|
#include <limits.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "nvim/ascii.h"
|
|
#include "nvim/charset.h"
|
|
#include "nvim/cmdexpand_defs.h"
|
|
#include "nvim/cmdhist.h"
|
|
#include "nvim/eval/typval.h"
|
|
#include "nvim/ex_cmds.h"
|
|
#include "nvim/ex_cmds_defs.h"
|
|
#include "nvim/ex_getln.h"
|
|
#include "nvim/gettext.h"
|
|
#include "nvim/globals.h"
|
|
#include "nvim/macros.h"
|
|
#include "nvim/memory.h"
|
|
#include "nvim/message.h"
|
|
#include "nvim/option_vars.h"
|
|
#include "nvim/regexp.h"
|
|
#include "nvim/strings.h"
|
|
#include "nvim/types.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)
|
|
{
|
|
const char *short_names = ":=@>?/";
|
|
const int short_names_count = (int)strlen(short_names);
|
|
const int history_name_count = ARRAY_SIZE(history_names) - 1;
|
|
|
|
if (idx < short_names_count) {
|
|
xp->xp_buf[0] = short_names[idx];
|
|
xp->xp_buf[1] = NUL;
|
|
return xp->xp_buf;
|
|
}
|
|
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 (newlen == oldlen) { // history length didn't change
|
|
return;
|
|
}
|
|
|
|
// 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.
|
|
for (int type = 0; type < HIST_COUNT; type++) {
|
|
histentry_T *temp = (newlen > 0
|
|
? 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 > 0) {
|
|
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
|
|
{
|
|
CLEAR_POINTER(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, const char *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 *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) {
|
|
return false;
|
|
}
|
|
|
|
list_T *const list = history[type][i].additional_elements;
|
|
char *const save_hisstr = 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 = save_hisstr;
|
|
history[type][i].timestamp = os_time();
|
|
history[type][i].additional_elements = NULL;
|
|
return true;
|
|
}
|
|
|
|
/// 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(":=@>?/", (uint8_t)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, const char *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)) {
|
|
return;
|
|
}
|
|
|
|
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 = xstrnsave(new_entry, len + 2);
|
|
hisptr->timestamp = os_time();
|
|
hisptr->additional_elements = NULL;
|
|
hisptr->hisstr[len + 1] = (char)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;
|
|
|
|
if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
|
|
|| (i = hisidx[histype]) < 0 || num == 0) {
|
|
return -1;
|
|
}
|
|
|
|
histentry_T *hist = history[histype];
|
|
if (num > 0) {
|
|
int wrapped = false;
|
|
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 *get_history_entry(int histype, int idx)
|
|
{
|
|
idx = calc_hist_idx(histype, idx);
|
|
if (idx >= 0) {
|
|
return history[histype][idx].hisstr;
|
|
} else {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
/// 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 *str)
|
|
{
|
|
if (hislen == 0 || histype < 0 || histype >= HIST_COUNT || *str == NUL
|
|
|| hisidx[histype] < 0) {
|
|
return false;
|
|
}
|
|
|
|
const int idx = hisidx[histype];
|
|
regmatch_T regmatch;
|
|
regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING);
|
|
if (regmatch.regprog == NULL) {
|
|
return false;
|
|
}
|
|
|
|
regmatch.rm_ic = false; // always match case
|
|
|
|
bool found = false;
|
|
int i = idx, last = idx;
|
|
do {
|
|
histentry_T *hisptr = &history[histype][i];
|
|
if (hisptr->hisstr == NULL) {
|
|
break;
|
|
}
|
|
if (vim_regexec(®match, hisptr->hisstr, 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, EvalFuncData fptr)
|
|
{
|
|
rettv->vval.v_number = false;
|
|
if (check_secure()) {
|
|
return;
|
|
}
|
|
const char *str = tv_get_string_chk(&argvars[0]); // NULL on type error
|
|
HistoryType histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
|
|
if (histype == HIST_INVALID) {
|
|
return;
|
|
}
|
|
|
|
char buf[NUMBUFLEN];
|
|
str = tv_get_string_buf(&argvars[1], buf);
|
|
if (*str == NUL) {
|
|
return;
|
|
}
|
|
|
|
init_history();
|
|
add_to_history(histype, str, false, NUL);
|
|
rettv->vval.v_number = true;
|
|
}
|
|
|
|
/// "histdel()" function
|
|
void f_histdel(typval_T *argvars, typval_T *rettv, EvalFuncData 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 *)tv_get_string_buf(&argvars[1], buf));
|
|
}
|
|
rettv->vval.v_number = n;
|
|
}
|
|
|
|
/// "histget()" function
|
|
void f_histget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|
|
{
|
|
const char *const str = tv_get_string_chk(&argvars[0]); // NULL on type error
|
|
if (str == NULL) {
|
|
rettv->vval.v_string = NULL;
|
|
} else {
|
|
int idx;
|
|
HistoryType 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 = xstrdup(get_history_entry(type, idx));
|
|
}
|
|
rettv->v_type = VAR_STRING;
|
|
}
|
|
|
|
/// "histnr()" function
|
|
void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData 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)
|
|
{
|
|
int histype1 = HIST_CMD;
|
|
int histype2 = HIST_CMD;
|
|
int hisidx1 = 1;
|
|
int hisidx2 = -1;
|
|
char *end;
|
|
char *arg = eap->arg;
|
|
|
|
if (hislen == 0) {
|
|
msg(_("'history' option is zero"), 0);
|
|
return;
|
|
}
|
|
|
|
if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
|
|
end = arg;
|
|
while (ASCII_ISALPHA(*end)
|
|
|| vim_strchr(":=@>/?", (uint8_t)(*end)) != NULL) {
|
|
end++;
|
|
}
|
|
histype1 = get_histtype(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) {
|
|
if (*end != NUL) {
|
|
semsg(_(e_trailing_arg), end);
|
|
} else {
|
|
semsg(_(e_val_too_large), arg);
|
|
}
|
|
return;
|
|
}
|
|
|
|
for (; !got_int && histype1 <= histype2; histype1++) {
|
|
xstrlcpy(IObuff, "\n # ", IOSIZE);
|
|
assert(history_names[histype1] != NULL);
|
|
xstrlcat(IObuff, history_names[histype1], IOSIZE);
|
|
xstrlcat(IObuff, " history", IOSIZE);
|
|
msg_puts_title(IObuff);
|
|
int idx = hisidx[histype1];
|
|
histentry_T *hist = history[histype1];
|
|
int j = hisidx1;
|
|
int 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 (int 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(IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ',
|
|
hist[i].hisnum);
|
|
if (vim_strsize(hist[i].hisstr) > Columns - 10) {
|
|
trunc_string(hist[i].hisstr, IObuff + strlen(IObuff),
|
|
Columns - 10, IOSIZE - (int)strlen(IObuff));
|
|
} else {
|
|
xstrlcat(IObuff, hist[i].hisstr, IOSIZE);
|
|
}
|
|
msg_outtrans(IObuff, 0);
|
|
}
|
|
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) {
|
|
CLEAR_POINTER(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];
|
|
}
|