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(®match, 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 }