neovim

Neovim text editor
git clone https://git.dasho.dev/neovim.git
Log | Files | Refs | README

cmdhist.c (21598B)


      1 // cmdhist.c: Functions for the history of the command-line.
      2 
      3 #include <assert.h>
      4 #include <limits.h>
      5 #include <stdbool.h>
      6 #include <stdint.h>
      7 #include <stdio.h>
      8 #include <string.h>
      9 
     10 #include "nvim/ascii_defs.h"
     11 #include "nvim/charset.h"
     12 #include "nvim/cmdexpand_defs.h"
     13 #include "nvim/cmdhist.h"
     14 #include "nvim/errors.h"
     15 #include "nvim/eval/typval.h"
     16 #include "nvim/eval/typval_defs.h"
     17 #include "nvim/ex_cmds.h"
     18 #include "nvim/ex_cmds_defs.h"
     19 #include "nvim/ex_getln.h"
     20 #include "nvim/gettext_defs.h"
     21 #include "nvim/globals.h"
     22 #include "nvim/macros_defs.h"
     23 #include "nvim/memory.h"
     24 #include "nvim/message.h"
     25 #include "nvim/option_vars.h"
     26 #include "nvim/os/time.h"
     27 #include "nvim/regexp.h"
     28 #include "nvim/regexp_defs.h"
     29 #include "nvim/strings.h"
     30 #include "nvim/types_defs.h"
     31 #include "nvim/vim_defs.h"
     32 
     33 #include "cmdhist.c.generated.h"
     34 
     35 static histentry_T *(history[HIST_COUNT]) = { NULL, NULL, NULL, NULL, NULL };
     36 static int hisidx[HIST_COUNT] = { -1, -1, -1, -1, -1 };  ///< lastused entry
     37 /// identifying (unique) number of newest history entry
     38 static int hisnum[HIST_COUNT] = { 0, 0, 0, 0, 0 };
     39 static int hislen = 0;  ///< actual length of history tables
     40 
     41 /// Return the length of the history tables
     42 int get_hislen(void)
     43 {
     44  return hislen;
     45 }
     46 
     47 /// Return a pointer to a specified history table
     48 histentry_T *get_histentry(int hist_type)
     49 {
     50  return history[hist_type];
     51 }
     52 
     53 void set_histentry(int hist_type, histentry_T *entry)
     54 {
     55  history[hist_type] = entry;
     56 }
     57 
     58 int *get_hisidx(int hist_type)
     59 {
     60  return &hisidx[hist_type];
     61 }
     62 
     63 int *get_hisnum(int hist_type)
     64 {
     65  return &hisnum[hist_type];
     66 }
     67 
     68 /// Translate a history character to the associated type number
     69 HistoryType hist_char2type(const int c)
     70  FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
     71 {
     72  switch (c) {
     73  case ':':
     74    return HIST_CMD;
     75  case '=':
     76    return HIST_EXPR;
     77  case '@':
     78    return HIST_INPUT;
     79  case '>':
     80    return HIST_DEBUG;
     81  case NUL:
     82  case '/':
     83  case '?':
     84    return HIST_SEARCH;
     85  default:
     86    return HIST_INVALID;
     87  }
     88  // Silence -Wreturn-type
     89  return 0;
     90 }
     91 
     92 /// Table of history names.
     93 /// These names are used in :history and various hist...() functions.
     94 /// It is sufficient to give the significant prefix of a history name.
     95 static char *(history_names[]) = {
     96  "cmd",
     97  "search",
     98  "expr",
     99  "input",
    100  "debug",
    101  NULL
    102 };
    103 
    104 /// Function given to ExpandGeneric() to obtain the possible first
    105 /// arguments of the ":history command.
    106 char *get_history_arg(expand_T *xp, int idx)
    107 {
    108  const char *short_names = ":=@>?/";
    109  const int short_names_count = (int)strlen(short_names);
    110  const int history_name_count = ARRAY_SIZE(history_names) - 1;
    111 
    112  if (idx < short_names_count) {
    113    xp->xp_buf[0] = short_names[idx];
    114    xp->xp_buf[1] = NUL;
    115    return xp->xp_buf;
    116  }
    117  if (idx < short_names_count + history_name_count) {
    118    return history_names[idx - short_names_count];
    119  }
    120  if (idx == short_names_count + history_name_count) {
    121    return "all";
    122  }
    123  return NULL;
    124 }
    125 
    126 /// Initialize command line history.
    127 /// Also used to re-allocate history tables when size changes.
    128 void init_history(void)
    129 {
    130  assert(p_hi >= 0 && p_hi <= INT_MAX);
    131  int newlen = (int)p_hi;
    132  int oldlen = hislen;
    133 
    134  if (newlen == oldlen) {  // history length didn't change
    135    return;
    136  }
    137 
    138  // If history tables size changed, reallocate them.
    139  // Tables are circular arrays (current position marked by hisidx[type]).
    140  // On copying them to the new arrays, we take the chance to reorder them.
    141  for (int type = 0; type < HIST_COUNT; type++) {
    142    histentry_T *temp = (newlen > 0
    143                         ? xmalloc((size_t)newlen * sizeof(*temp))
    144                         : NULL);
    145 
    146    int j = hisidx[type];
    147    if (j >= 0) {
    148      // old array gets partitioned this way:
    149      // [0       , i1     ) --> newest entries to be deleted
    150      // [i1      , i1 + l1) --> newest entries to be copied
    151      // [i1 + l1 , i2     ) --> oldest entries to be deleted
    152      // [i2      , i2 + l2) --> oldest entries to be copied
    153      int l1 = MIN(j + 1, newlen);             // how many newest to copy
    154      int l2 = MIN(newlen, oldlen) - l1;       // how many oldest to copy
    155      int i1 = j + 1 - l1;                     // copy newest from here
    156      int i2 = MAX(l1, oldlen - newlen + l1);  // copy oldest from here
    157 
    158      // copy as much entries as they fit to new table, reordering them
    159      if (newlen) {
    160        // copy oldest entries
    161        memcpy(&temp[0], &history[type][i2], (size_t)l2 * sizeof(*temp));
    162        // copy newest entries
    163        memcpy(&temp[l2], &history[type][i1], (size_t)l1 * sizeof(*temp));
    164      }
    165 
    166      // delete entries that don't fit in newlen, if any
    167      for (int i = 0; i < i1; i++) {
    168        hist_free_entry(history[type] + i);
    169      }
    170      for (int i = i1 + l1; i < i2; i++) {
    171        hist_free_entry(history[type] + i);
    172      }
    173    }
    174 
    175    // clear remaining space, if any
    176    int l3 = j < 0 ? 0 : MIN(newlen, oldlen);  // number of copied entries
    177    if (newlen > 0) {
    178      memset(temp + l3, 0, (size_t)(newlen - l3) * sizeof(*temp));
    179    }
    180 
    181    hisidx[type] = l3 - 1;
    182    xfree(history[type]);
    183    history[type] = temp;
    184  }
    185  hislen = newlen;
    186 }
    187 
    188 static inline void hist_free_entry(histentry_T *hisptr)
    189  FUNC_ATTR_NONNULL_ALL
    190 {
    191  xfree(hisptr->hisstr);
    192  xfree(hisptr->additional_data);
    193  clear_hist_entry(hisptr);
    194 }
    195 
    196 static inline void clear_hist_entry(histentry_T *hisptr)
    197  FUNC_ATTR_NONNULL_ALL
    198 {
    199  CLEAR_POINTER(hisptr);
    200 }
    201 
    202 /// Check if command line 'str' is already in history.
    203 /// If 'move_to_front' is true, matching entry is moved to end of history.
    204 ///
    205 /// @param move_to_front  Move the entry to the front if it exists
    206 static int in_history(int type, const char *str, int move_to_front, int sep)
    207 {
    208  int last_i = -1;
    209 
    210  if (hisidx[type] < 0) {
    211    return false;
    212  }
    213  int i = hisidx[type];
    214  do {
    215    if (history[type][i].hisstr == NULL) {
    216      return false;
    217    }
    218 
    219    // For search history, check that the separator character matches as
    220    // well.
    221    char *p = history[type][i].hisstr;
    222    if (strcmp(str, p) == 0
    223        && (type != HIST_SEARCH || sep == p[history[type][i].hisstrlen + 1])) {
    224      if (!move_to_front) {
    225        return true;
    226      }
    227      last_i = i;
    228      break;
    229    }
    230    if (--i < 0) {
    231      i = hislen - 1;
    232    }
    233  } while (i != hisidx[type]);
    234 
    235  if (last_i < 0) {
    236    return false;
    237  }
    238 
    239  AdditionalData *ad = history[type][i].additional_data;
    240  char *const save_hisstr = history[type][i].hisstr;
    241  const size_t save_hisstrlen = history[type][i].hisstrlen;
    242  while (i != hisidx[type]) {
    243    if (++i >= hislen) {
    244      i = 0;
    245    }
    246    history[type][last_i] = history[type][i];
    247    last_i = i;
    248  }
    249  xfree(ad);
    250  history[type][i].hisnum = ++hisnum[type];
    251  history[type][i].hisstr = save_hisstr;
    252  history[type][i].hisstrlen = save_hisstrlen;
    253  history[type][i].timestamp = os_time();
    254  history[type][i].additional_data = NULL;
    255  return true;
    256 }
    257 
    258 /// Convert history name to its HIST_ equivalent
    259 ///
    260 /// Names are taken from the table above. When `name` is empty returns currently
    261 /// active history or HIST_DEFAULT, depending on `return_default` argument.
    262 ///
    263 /// @param[in]  name            Converted name.
    264 /// @param[in]  len             Name length.
    265 /// @param[in]  return_default  Determines whether HIST_DEFAULT should be
    266 ///                             returned or value based on `ccline.cmdfirstc`.
    267 ///
    268 /// @return Any value from HistoryType enum, including HIST_INVALID. May not
    269 ///         return HIST_DEFAULT unless return_default is true.
    270 static HistoryType get_histtype(const char *const name, const size_t len, const bool return_default)
    271  FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
    272 {
    273  // No argument: use current history.
    274  if (len == 0) {
    275    return return_default ? HIST_DEFAULT : hist_char2type(get_cmdline_firstc());
    276  }
    277 
    278  for (HistoryType i = 0; history_names[i] != NULL; i++) {
    279    if (STRNICMP(name, history_names[i], len) == 0) {
    280      return i;
    281    }
    282  }
    283 
    284  if (vim_strchr(":=@>?/", (uint8_t)name[0]) != NULL && len == 1) {
    285    return hist_char2type(name[0]);
    286  }
    287 
    288  return HIST_INVALID;
    289 }
    290 
    291 static int last_maptick = -1;           // last seen maptick
    292 
    293 /// Add the given string to the given history.  If the string is already in the
    294 /// history then it is moved to the front.
    295 ///
    296 /// @param histype  may be one of the HIST_ values.
    297 /// @param in_map   consider maptick when inside a mapping
    298 /// @param sep      separator character used (search hist)
    299 void add_to_history(int histype, const char *new_entry, size_t new_entrylen, bool in_map, int sep)
    300 {
    301  histentry_T *hisptr;
    302 
    303  if (hislen == 0 || histype == HIST_INVALID) {  // no history
    304    return;
    305  }
    306  assert(histype != HIST_DEFAULT);
    307 
    308  if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) && histype == HIST_SEARCH) {
    309    return;
    310  }
    311 
    312  // Searches inside the same mapping overwrite each other, so that only
    313  // the last line is kept.  Be careful not to remove a line that was moved
    314  // down, only lines that were added.
    315  if (histype == HIST_SEARCH && in_map) {
    316    if (maptick == last_maptick && hisidx[HIST_SEARCH] >= 0) {
    317      // Current line is from the same mapping, remove it
    318      hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]];
    319      hist_free_entry(hisptr);
    320      hisnum[histype]--;
    321      if (--hisidx[HIST_SEARCH] < 0) {
    322        hisidx[HIST_SEARCH] = hislen - 1;
    323      }
    324    }
    325    last_maptick = -1;
    326  }
    327 
    328  if (in_history(histype, new_entry, true, sep)) {
    329    return;
    330  }
    331 
    332  if (++hisidx[histype] == hislen) {
    333    hisidx[histype] = 0;
    334  }
    335  hisptr = &history[histype][hisidx[histype]];
    336  hist_free_entry(hisptr);
    337 
    338  // Store the separator after the NUL of the string.
    339  hisptr->hisstr = xstrnsave(new_entry, new_entrylen + 2);
    340  hisptr->timestamp = os_time();
    341  hisptr->additional_data = NULL;
    342  hisptr->hisstr[new_entrylen + 1] = (char)sep;
    343  hisptr->hisstrlen = new_entrylen;
    344 
    345  hisptr->hisnum = ++hisnum[histype];
    346  if (histype == HIST_SEARCH && in_map) {
    347    last_maptick = maptick;
    348  }
    349 }
    350 
    351 /// Get identifier of newest history entry.
    352 ///
    353 /// @param histype  may be one of the HIST_ values.
    354 static int get_history_idx(int histype)
    355 {
    356  if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
    357      || hisidx[histype] < 0) {
    358    return -1;
    359  }
    360 
    361  return history[histype][hisidx[histype]].hisnum;
    362 }
    363 
    364 /// Calculate history index from a number:
    365 ///
    366 /// @param num      > 0: seen as identifying number of a history entry
    367 ///                 < 0: relative position in history wrt newest entry
    368 /// @param histype  may be one of the HIST_ values.
    369 static int calc_hist_idx(int histype, int num)
    370 {
    371  int i;
    372 
    373  if (hislen == 0 || histype < 0 || histype >= HIST_COUNT
    374      || (i = hisidx[histype]) < 0 || num == 0) {
    375    return -1;
    376  }
    377 
    378  histentry_T *hist = history[histype];
    379  if (num > 0) {
    380    bool wrapped = false;
    381    while (hist[i].hisnum > num) {
    382      if (--i < 0) {
    383        if (wrapped) {
    384          break;
    385        }
    386        i += hislen;
    387        wrapped = true;
    388      }
    389    }
    390    if (i >= 0 && hist[i].hisnum == num && hist[i].hisstr != NULL) {
    391      return i;
    392    }
    393  } else if (-num <= hislen) {
    394    i += num + 1;
    395    if (i < 0) {
    396      i += hislen;
    397    }
    398    if (hist[i].hisstr != NULL) {
    399      return i;
    400    }
    401  }
    402  return -1;
    403 }
    404 
    405 /// Clear all entries in a history
    406 ///
    407 /// @param[in]  histype  One of the HIST_ values.
    408 ///
    409 /// @return OK if there was something to clean and histype was one of HIST_
    410 ///         values, FAIL otherwise.
    411 int clr_history(const int histype)
    412 {
    413  if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) {
    414    histentry_T *hisptr = history[histype];
    415    for (int i = hislen; i--; hisptr++) {
    416      hist_free_entry(hisptr);
    417    }
    418    hisidx[histype] = -1;  // mark history as cleared
    419    hisnum[histype] = 0;   // reset identifier counter
    420    return OK;
    421  }
    422  return FAIL;
    423 }
    424 
    425 /// Remove all entries matching {str} from a history.
    426 ///
    427 /// @param histype  may be one of the HIST_ values.
    428 static int del_history_entry(int histype, char *str)
    429 {
    430  if (hislen == 0 || histype < 0 || histype >= HIST_COUNT || *str == NUL
    431      || hisidx[histype] < 0) {
    432    return false;
    433  }
    434 
    435  const int idx = hisidx[histype];
    436  regmatch_T regmatch;
    437  regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING);
    438  if (regmatch.regprog == NULL) {
    439    return false;
    440  }
    441 
    442  regmatch.rm_ic = false;       // always match case
    443 
    444  bool found = false;
    445  int i = idx;
    446  int last = idx;
    447  do {
    448    histentry_T *hisptr = &history[histype][i];
    449    if (hisptr->hisstr == NULL) {
    450      break;
    451    }
    452    if (vim_regexec(&regmatch, hisptr->hisstr, 0)) {
    453      found = true;
    454      hist_free_entry(hisptr);
    455    } else {
    456      if (i != last) {
    457        history[histype][last] = *hisptr;
    458        clear_hist_entry(hisptr);
    459      }
    460      if (--last < 0) {
    461        last += hislen;
    462      }
    463    }
    464    if (--i < 0) {
    465      i += hislen;
    466    }
    467  } while (i != idx);
    468 
    469  if (history[histype][idx].hisstr == NULL) {
    470    hisidx[histype] = -1;
    471  }
    472 
    473  vim_regfree(regmatch.regprog);
    474  return found;
    475 }
    476 
    477 /// Remove an indexed entry from a history.
    478 ///
    479 /// @param histype  may be one of the HIST_ values.
    480 static int del_history_idx(int histype, int idx)
    481 {
    482  int i = calc_hist_idx(histype, idx);
    483  if (i < 0) {
    484    return false;
    485  }
    486  idx = hisidx[histype];
    487  hist_free_entry(&history[histype][i]);
    488 
    489  // When deleting the last added search string in a mapping, reset
    490  // last_maptick, so that the last added search string isn't deleted again.
    491  if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) {
    492    last_maptick = -1;
    493  }
    494 
    495  while (i != idx) {
    496    int j = (i + 1) % hislen;
    497    history[histype][i] = history[histype][j];
    498    i = j;
    499  }
    500  clear_hist_entry(&history[histype][idx]);
    501  if (--i < 0) {
    502    i += hislen;
    503  }
    504  hisidx[histype] = i;
    505  return true;
    506 }
    507 
    508 /// "histadd()" function
    509 void f_histadd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    510 {
    511  rettv->vval.v_number = false;
    512  if (check_secure()) {
    513    return;
    514  }
    515  const char *str = tv_get_string_chk(&argvars[0]);  // NULL on type error
    516  HistoryType histype = str != NULL ? get_histtype(str, strlen(str), false) : HIST_INVALID;
    517  if (histype == HIST_INVALID) {
    518    return;
    519  }
    520 
    521  char buf[NUMBUFLEN];
    522  str = tv_get_string_buf(&argvars[1], buf);
    523  if (*str == NUL) {
    524    return;
    525  }
    526 
    527  init_history();
    528  add_to_history(histype, str, strlen(str), false, NUL);
    529  rettv->vval.v_number = true;
    530 }
    531 
    532 /// "histdel()" function
    533 void f_histdel(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    534 {
    535  int n;
    536  const char *const str = tv_get_string_chk(&argvars[0]);  // NULL on type error
    537  if (str == NULL) {
    538    n = 0;
    539  } else if (argvars[1].v_type == VAR_UNKNOWN) {
    540    // only one argument: clear entire history
    541    n = clr_history(get_histtype(str, strlen(str), false));
    542  } else if (argvars[1].v_type == VAR_NUMBER) {
    543    // index given: remove that entry
    544    n = del_history_idx(get_histtype(str, strlen(str), false),
    545                        (int)tv_get_number(&argvars[1]));
    546  } else {
    547    // string given: remove all matching entries
    548    char buf[NUMBUFLEN];
    549    n = del_history_entry(get_histtype(str, strlen(str), false),
    550                          (char *)tv_get_string_buf(&argvars[1], buf));
    551  }
    552  rettv->vval.v_number = n;
    553 }
    554 
    555 /// "histget()" function
    556 void f_histget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    557 {
    558  const char *const str = tv_get_string_chk(&argvars[0]);  // NULL on type error
    559  if (str == NULL) {
    560    rettv->vval.v_string = NULL;
    561  } else {
    562    int idx;
    563    HistoryType type = get_histtype(str, strlen(str), false);
    564    if (argvars[1].v_type == VAR_UNKNOWN) {
    565      idx = get_history_idx(type);
    566    } else {
    567      idx = (int)tv_get_number_chk(&argvars[1], NULL);  // -1 on type error
    568    }
    569    idx = calc_hist_idx(type, idx);
    570    if (idx < 0) {
    571      rettv->vval.v_string = xstrnsave("", 0);
    572    } else {
    573      rettv->vval.v_string = xstrnsave(history[type][idx].hisstr,
    574                                       history[type][idx].hisstrlen);
    575    }
    576  }
    577  rettv->v_type = VAR_STRING;
    578 }
    579 
    580 /// "histnr()" function
    581 void f_histnr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
    582 {
    583  const char *const histname = tv_get_string_chk(&argvars[0]);
    584  HistoryType i = histname == NULL
    585                  ? HIST_INVALID
    586                  : get_histtype(histname, strlen(histname), false);
    587  if (i != HIST_INVALID) {
    588    rettv->vval.v_number = get_history_idx(i);
    589  } else {
    590    rettv->vval.v_number = HIST_INVALID;
    591  }
    592 }
    593 
    594 /// :history command - print a history
    595 void ex_history(exarg_T *eap)
    596 {
    597  int histype1 = HIST_CMD;
    598  int histype2 = HIST_CMD;
    599  int hisidx1 = 1;
    600  int hisidx2 = -1;
    601  char *end;
    602  char *arg = eap->arg;
    603 
    604  msg_ext_set_kind("list_cmd");
    605  if (hislen == 0) {
    606    msg(_("'history' option is zero"), 0);
    607    return;
    608  }
    609 
    610  if (!(ascii_isdigit(*arg) || *arg == '-' || *arg == ',')) {
    611    end = arg;
    612    while (ASCII_ISALPHA(*end)
    613           || vim_strchr(":=@>/?", (uint8_t)(*end)) != NULL) {
    614      end++;
    615    }
    616    histype1 = get_histtype(arg, (size_t)(end - arg), false);
    617    if (histype1 == HIST_INVALID) {
    618      if (STRNICMP(arg, "all", end - arg) == 0) {
    619        histype1 = 0;
    620        histype2 = HIST_COUNT - 1;
    621      } else {
    622        semsg(_(e_trailing_arg), arg);
    623        return;
    624      }
    625    } else {
    626      histype2 = histype1;
    627    }
    628  } else {
    629    end = arg;
    630  }
    631  if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) {
    632    if (*end != NUL) {
    633      semsg(_(e_trailing_arg), end);
    634    } else {
    635      semsg(_(e_val_too_large), arg);
    636    }
    637    return;
    638  }
    639 
    640  for (; !got_int && histype1 <= histype2; histype1++) {
    641    assert(history_names[histype1] != NULL);
    642    vim_snprintf(IObuff, IOSIZE, "\n      #  %s history", history_names[histype1]);
    643    msg_puts_title(IObuff);
    644    int idx = hisidx[histype1];
    645    histentry_T *hist = history[histype1];
    646    int j = hisidx1;
    647    int k = hisidx2;
    648    if (j < 0) {
    649      j = (-j > hislen) ? 0 : hist[(hislen + j + idx + 1) % hislen].hisnum;
    650    }
    651    if (k < 0) {
    652      k = (-k > hislen) ? 0 : hist[(hislen + k + idx + 1) % hislen].hisnum;
    653    }
    654    if (idx >= 0 && j <= k) {
    655      for (int i = idx + 1; !got_int; i++) {
    656        if (i == hislen) {
    657          i = 0;
    658        }
    659        if (hist[i].hisstr != NULL
    660            && hist[i].hisnum >= j && hist[i].hisnum <= k
    661            && !message_filtered(hist[i].hisstr)) {
    662          msg_putchar('\n');
    663          int len = snprintf(IObuff, IOSIZE,
    664                             "%c%6d  ", i == idx ? '>' : ' ', hist[i].hisnum);
    665          if (vim_strsize(hist[i].hisstr) > Columns - 10) {
    666            trunc_string(hist[i].hisstr, IObuff + len, Columns - 10, IOSIZE - len);
    667          } else {
    668            xstrlcpy(IObuff + len, hist[i].hisstr, (size_t)(IOSIZE - len));
    669          }
    670          msg_outtrans(IObuff, 0, false);
    671        }
    672        if (i == idx) {
    673          break;
    674        }
    675      }
    676    }
    677  }
    678 }
    679 
    680 /// Iterate over history items
    681 ///
    682 /// @warning No history-editing functions must be run while iteration is in
    683 ///          progress.
    684 ///
    685 /// @param[in]   iter          Pointer to the last history entry.
    686 /// @param[in]   history_type  Type of the history (HIST_*). Ignored if iter
    687 ///                            parameter is not NULL.
    688 /// @param[in]   zero          If true then zero (but not free) returned items.
    689 ///
    690 ///                            @warning When using this parameter user is
    691 ///                                     responsible for calling clr_history()
    692 ///                                     itself after iteration is over. If
    693 ///                                     clr_history() is not called behaviour is
    694 ///                                     undefined. No functions that work with
    695 ///                                     history must be called during iteration
    696 ///                                     in this case.
    697 /// @param[out]  hist          Next history entry.
    698 ///
    699 /// @return Pointer used in next iteration or NULL to indicate that iteration
    700 ///         was finished.
    701 const void *hist_iter(const void *const iter, const uint8_t history_type, const bool zero,
    702                      histentry_T *const hist)
    703  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(4)
    704 {
    705  *hist = (histentry_T) {
    706    .hisstr = NULL
    707  };
    708  if (hisidx[history_type] == -1) {
    709    return NULL;
    710  }
    711  histentry_T *const hstart = &(history[history_type][0]);
    712  histentry_T *const hlast = &(history[history_type][hisidx[history_type]]);
    713  const histentry_T *const hend = &(history[history_type][hislen - 1]);
    714  histentry_T *hiter;
    715  if (iter == NULL) {
    716    histentry_T *hfirst = hlast;
    717    do {
    718      hfirst++;
    719      if (hfirst > hend) {
    720        hfirst = hstart;
    721      }
    722      if (hfirst->hisstr != NULL) {
    723        break;
    724      }
    725    } while (hfirst != hlast);
    726    hiter = hfirst;
    727  } else {
    728    hiter = (histentry_T *)iter;
    729  }
    730  if (hiter == NULL) {
    731    return NULL;
    732  }
    733  *hist = *hiter;
    734  if (zero) {
    735    CLEAR_POINTER(hiter);
    736  }
    737  if (hiter == hlast) {
    738    return NULL;
    739  }
    740  hiter++;
    741  return (const void *)((hiter > hend) ? hstart : hiter);
    742 }
    743 
    744 /// Get array of history items
    745 ///
    746 /// @param[in]   history_type  Type of the history to get array for.
    747 /// @param[out]  new_hisidx    Location where last index in the new array should
    748 ///                            be saved.
    749 /// @param[out]  new_hisnum    Location where last history number in the new
    750 ///                            history should be saved.
    751 ///
    752 /// @return Pointer to the array or NULL.
    753 histentry_T *hist_get_array(const uint8_t history_type, int **const new_hisidx,
    754                            int **const new_hisnum)
    755  FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
    756 {
    757  init_history();
    758  *new_hisidx = &(hisidx[history_type]);
    759  *new_hisnum = &(hisnum[history_type]);
    760  return history[history_type];
    761 }