1 /* 2 * Copyright (C) 1984-2022 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information, see the README file. 8 */ 9 10 11 /* 12 * Functions which manipulate the command buffer. 13 * Used only by command() and related functions. 14 */ 15 16 #include "less.h" 17 #include "cmd.h" 18 #include "charset.h" 19 #if HAVE_STAT 20 #include <sys/stat.h> 21 #endif 22 23 extern int sc_width; 24 extern int utf_mode; 25 extern int no_hist_dups; 26 extern int marks_modified; 27 extern int secure; 28 29 static char cmdbuf[CMDBUF_SIZE]; /* Buffer for holding a multi-char command */ 30 static int cmd_col; /* Current column of the cursor */ 31 static int prompt_col; /* Column of cursor just after prompt */ 32 static char *cp; /* Pointer into cmdbuf */ 33 static int cmd_offset; /* Index into cmdbuf of first displayed char */ 34 static int literal; /* Next input char should not be interpreted */ 35 public int updown_match = -1; /* Prefix length in up/down movement */ 36 37 #if TAB_COMPLETE_FILENAME 38 static int cmd_complete LESSPARAMS((int action)); 39 /* 40 * These variables are statics used by cmd_complete. 41 */ 42 static int in_completion = 0; 43 static char *tk_text; 44 static char *tk_original; 45 static char *tk_ipoint; 46 static char *tk_trial = NULL; 47 static struct textlist tk_tlist; 48 #endif 49 50 static int cmd_left(); 51 static int cmd_right(); 52 53 #if SPACES_IN_FILENAMES 54 public char openquote = '"'; 55 public char closequote = '"'; 56 #endif 57 58 #if CMD_HISTORY 59 60 /* History file */ 61 #define HISTFILE_FIRST_LINE ".less-history-file:" 62 #define HISTFILE_SEARCH_SECTION ".search" 63 #define HISTFILE_SHELL_SECTION ".shell" 64 #define HISTFILE_MARK_SECTION ".mark" 65 66 /* 67 * A mlist structure represents a command history. 68 */ 69 struct mlist 70 { 71 struct mlist *next; 72 struct mlist *prev; 73 struct mlist *curr_mp; 74 char *string; 75 int modified; 76 }; 77 78 /* 79 * These are the various command histories that exist. 80 */ 81 struct mlist mlist_search = 82 { &mlist_search, &mlist_search, &mlist_search, NULL, 0 }; 83 public void *ml_search = (void *) &mlist_search; 84 85 struct mlist mlist_examine = 86 { &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 }; 87 public void *ml_examine = (void *) &mlist_examine; 88 89 #if SHELL_ESCAPE || PIPEC 90 struct mlist mlist_shell = 91 { &mlist_shell, &mlist_shell, &mlist_shell, NULL, 0 }; 92 public void *ml_shell = (void *) &mlist_shell; 93 #endif 94 95 #else /* CMD_HISTORY */ 96 97 /* If CMD_HISTORY is off, these are just flags. */ 98 public void *ml_search = (void *)1; 99 public void *ml_examine = (void *)2; 100 #if SHELL_ESCAPE || PIPEC 101 public void *ml_shell = (void *)3; 102 #endif 103 104 #endif /* CMD_HISTORY */ 105 106 /* 107 * History for the current command. 108 */ 109 static struct mlist *curr_mlist = NULL; 110 static int curr_cmdflags; 111 112 static char cmd_mbc_buf[MAX_UTF_CHAR_LEN]; 113 static int cmd_mbc_buf_len; 114 static int cmd_mbc_buf_index; 115 116 117 /* 118 * Reset command buffer (to empty). 119 */ 120 public void 121 cmd_reset(VOID_PARAM) 122 { 123 cp = cmdbuf; 124 *cp = '\0'; 125 cmd_col = 0; 126 cmd_offset = 0; 127 literal = 0; 128 cmd_mbc_buf_len = 0; 129 updown_match = -1; 130 } 131 132 /* 133 * Clear command line. 134 */ 135 public void 136 clear_cmd(VOID_PARAM) 137 { 138 cmd_col = prompt_col = 0; 139 cmd_mbc_buf_len = 0; 140 updown_match = -1; 141 } 142 143 /* 144 * Display a string, usually as a prompt for input into the command buffer. 145 */ 146 public void 147 cmd_putstr(s) 148 constant char *s; 149 { 150 LWCHAR prev_ch = 0; 151 LWCHAR ch; 152 constant char *endline = s + strlen(s); 153 while (*s != '\0') 154 { 155 char *ns = (char *) s; 156 int width; 157 ch = step_char(&ns, +1, endline); 158 while (s < ns) 159 putchr(*s++); 160 if (!utf_mode) 161 width = 1; 162 else if (is_composing_char(ch) || is_combining_char(prev_ch, ch)) 163 width = 0; 164 else 165 width = is_wide_char(ch) ? 2 : 1; 166 cmd_col += width; 167 prompt_col += width; 168 prev_ch = ch; 169 } 170 } 171 172 /* 173 * How many characters are in the command buffer? 174 */ 175 public int 176 len_cmdbuf(VOID_PARAM) 177 { 178 char *s = cmdbuf; 179 char *endline = s + strlen(s); 180 int len = 0; 181 182 while (*s != '\0') 183 { 184 step_char(&s, +1, endline); 185 len++; 186 } 187 return (len); 188 } 189 190 /* 191 * Common part of cmd_step_right() and cmd_step_left(). 192 * {{ Returning pwidth and bswidth separately is a historical artifact 193 * since they're always the same. Maybe clean this up someday. }} 194 */ 195 static char * 196 cmd_step_common(p, ch, len, pwidth, bswidth) 197 char *p; 198 LWCHAR ch; 199 int len; 200 int *pwidth; 201 int *bswidth; 202 { 203 char *pr; 204 int width; 205 206 if (len == 1) 207 { 208 pr = prchar((int) ch); 209 width = (int) strlen(pr); 210 } else 211 { 212 pr = prutfchar(ch); 213 if (is_composing_char(ch)) 214 width = 0; 215 else if (is_ubin_char(ch)) 216 width = (int) strlen(pr); 217 else 218 { 219 LWCHAR prev_ch = step_char(&p, -1, cmdbuf); 220 if (is_combining_char(prev_ch, ch)) 221 width = 0; 222 else 223 width = is_wide_char(ch) ? 2 : 1; 224 } 225 } 226 if (pwidth != NULL) 227 *pwidth = width; 228 if (bswidth != NULL) 229 *bswidth = width; 230 return (pr); 231 } 232 233 /* 234 * Step a pointer one character right in the command buffer. 235 */ 236 static char * 237 cmd_step_right(pp, pwidth, bswidth) 238 char **pp; 239 int *pwidth; 240 int *bswidth; 241 { 242 char *p = *pp; 243 LWCHAR ch = step_char(pp, +1, p + strlen(p)); 244 245 return cmd_step_common(p, ch, *pp - p, pwidth, bswidth); 246 } 247 248 /* 249 * Step a pointer one character left in the command buffer. 250 */ 251 static char * 252 cmd_step_left(pp, pwidth, bswidth) 253 char **pp; 254 int *pwidth; 255 int *bswidth; 256 { 257 char *p = *pp; 258 LWCHAR ch = step_char(pp, -1, cmdbuf); 259 260 return cmd_step_common(*pp, ch, p - *pp, pwidth, bswidth); 261 } 262 263 /* 264 * Put the cursor at "home" (just after the prompt), 265 * and set cp to the corresponding char in cmdbuf. 266 */ 267 static void 268 cmd_home(VOID_PARAM) 269 { 270 while (cmd_col > prompt_col) 271 { 272 int width, bswidth; 273 274 cmd_step_left(&cp, &width, &bswidth); 275 while (bswidth-- > 0) 276 putbs(); 277 cmd_col -= width; 278 } 279 280 cp = &cmdbuf[cmd_offset]; 281 } 282 283 /* 284 * Repaint the line from cp onwards. 285 * Then position the cursor just after the char old_cp (a pointer into cmdbuf). 286 */ 287 public void 288 cmd_repaint(old_cp) 289 constant char *old_cp; 290 { 291 /* 292 * Repaint the line from the current position. 293 */ 294 if (old_cp == NULL) 295 { 296 old_cp = cp; 297 cmd_home(); 298 } 299 clear_eol(); 300 while (*cp != '\0') 301 { 302 char *np = cp; 303 int width; 304 char *pr = cmd_step_right(&np, &width, NULL); 305 if (cmd_col + width >= sc_width) 306 break; 307 cp = np; 308 putstr(pr); 309 cmd_col += width; 310 } 311 while (*cp != '\0') 312 { 313 char *np = cp; 314 int width; 315 char *pr = cmd_step_right(&np, &width, NULL); 316 if (width > 0) 317 break; 318 cp = np; 319 putstr(pr); 320 } 321 322 /* 323 * Back up the cursor to the correct position. 324 */ 325 while (cp > old_cp) 326 cmd_left(); 327 } 328 329 /* 330 * Shift the cmdbuf display left a half-screen. 331 */ 332 static void 333 cmd_lshift(VOID_PARAM) 334 { 335 char *s; 336 char *save_cp; 337 int cols; 338 339 /* 340 * Start at the first displayed char, count how far to the 341 * right we'd have to move to reach the center of the screen. 342 */ 343 s = cmdbuf + cmd_offset; 344 cols = 0; 345 while (cols < (sc_width - prompt_col) / 2 && *s != '\0') 346 { 347 int width; 348 cmd_step_right(&s, &width, NULL); 349 cols += width; 350 } 351 while (*s != '\0') 352 { 353 int width; 354 char *ns = s; 355 cmd_step_right(&ns, &width, NULL); 356 if (width > 0) 357 break; 358 s = ns; 359 } 360 361 cmd_offset = (int) (s - cmdbuf); 362 save_cp = cp; 363 cmd_home(); 364 cmd_repaint(save_cp); 365 } 366 367 /* 368 * Shift the cmdbuf display right a half-screen. 369 */ 370 static void 371 cmd_rshift(VOID_PARAM) 372 { 373 char *s; 374 char *save_cp; 375 int cols; 376 377 /* 378 * Start at the first displayed char, count how far to the 379 * left we'd have to move to traverse a half-screen width 380 * of displayed characters. 381 */ 382 s = cmdbuf + cmd_offset; 383 cols = 0; 384 while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf) 385 { 386 int width; 387 cmd_step_left(&s, &width, NULL); 388 cols += width; 389 } 390 391 cmd_offset = (int) (s - cmdbuf); 392 save_cp = cp; 393 cmd_home(); 394 cmd_repaint(save_cp); 395 } 396 397 /* 398 * Move cursor right one character. 399 */ 400 static int 401 cmd_right(VOID_PARAM) 402 { 403 char *pr; 404 char *ncp; 405 int width; 406 407 if (*cp == '\0') 408 { 409 /* Already at the end of the line. */ 410 return (CC_OK); 411 } 412 ncp = cp; 413 pr = cmd_step_right(&ncp, &width, NULL); 414 if (cmd_col + width >= sc_width) 415 cmd_lshift(); 416 else if (cmd_col + width == sc_width - 1 && cp[1] != '\0') 417 cmd_lshift(); 418 cp = ncp; 419 cmd_col += width; 420 putstr(pr); 421 while (*cp != '\0') 422 { 423 pr = cmd_step_right(&ncp, &width, NULL); 424 if (width > 0) 425 break; 426 putstr(pr); 427 cp = ncp; 428 } 429 return (CC_OK); 430 } 431 432 /* 433 * Move cursor left one character. 434 */ 435 static int 436 cmd_left(VOID_PARAM) 437 { 438 char *ncp; 439 int width = 0; 440 int bswidth = 0; 441 442 if (cp <= cmdbuf) 443 { 444 /* Already at the beginning of the line */ 445 return (CC_OK); 446 } 447 ncp = cp; 448 while (ncp > cmdbuf) 449 { 450 cmd_step_left(&ncp, &width, &bswidth); 451 if (width > 0) 452 break; 453 } 454 if (cmd_col < prompt_col + width) 455 cmd_rshift(); 456 cp = ncp; 457 cmd_col -= width; 458 while (bswidth-- > 0) 459 putbs(); 460 return (CC_OK); 461 } 462 463 /* 464 * Insert a char into the command buffer, at the current position. 465 */ 466 static int 467 cmd_ichar(cs, clen) 468 char *cs; 469 int clen; 470 { 471 char *s; 472 473 if (strlen(cmdbuf) + clen >= sizeof(cmdbuf)-1) 474 { 475 /* No room in the command buffer for another char. */ 476 bell(); 477 return (CC_ERROR); 478 } 479 480 /* 481 * Make room for the new character (shift the tail of the buffer right). 482 */ 483 for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--) 484 s[clen] = s[0]; 485 /* 486 * Insert the character into the buffer. 487 */ 488 for (s = cp; s < cp + clen; s++) 489 *s = *cs++; 490 /* 491 * Reprint the tail of the line from the inserted char. 492 */ 493 updown_match = -1; 494 cmd_repaint(cp); 495 cmd_right(); 496 return (CC_OK); 497 } 498 499 /* 500 * Backspace in the command buffer. 501 * Delete the char to the left of the cursor. 502 */ 503 static int 504 cmd_erase(VOID_PARAM) 505 { 506 char *s; 507 int clen; 508 509 if (cp == cmdbuf) 510 { 511 /* 512 * Backspace past beginning of the buffer: 513 * this usually means abort the command. 514 */ 515 return (CC_QUIT); 516 } 517 /* 518 * Move cursor left (to the char being erased). 519 */ 520 s = cp; 521 cmd_left(); 522 clen = (int) (s - cp); 523 524 /* 525 * Remove the char from the buffer (shift the buffer left). 526 */ 527 for (s = cp; ; s++) 528 { 529 s[0] = s[clen]; 530 if (s[0] == '\0') 531 break; 532 } 533 534 /* 535 * Repaint the buffer after the erased char. 536 */ 537 updown_match = -1; 538 cmd_repaint(cp); 539 540 /* 541 * We say that erasing the entire command string causes us 542 * to abort the current command, if CF_QUIT_ON_ERASE is set. 543 */ 544 if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0') 545 return (CC_QUIT); 546 return (CC_OK); 547 } 548 549 /* 550 * Delete the char under the cursor. 551 */ 552 static int 553 cmd_delete(VOID_PARAM) 554 { 555 if (*cp == '\0') 556 { 557 /* At end of string; there is no char under the cursor. */ 558 return (CC_OK); 559 } 560 /* 561 * Move right, then use cmd_erase. 562 */ 563 cmd_right(); 564 cmd_erase(); 565 return (CC_OK); 566 } 567 568 /* 569 * Delete the "word" to the left of the cursor. 570 */ 571 static int 572 cmd_werase(VOID_PARAM) 573 { 574 if (cp > cmdbuf && cp[-1] == ' ') 575 { 576 /* 577 * If the char left of cursor is a space, 578 * erase all the spaces left of cursor (to the first non-space). 579 */ 580 while (cp > cmdbuf && cp[-1] == ' ') 581 (void) cmd_erase(); 582 } else 583 { 584 /* 585 * If the char left of cursor is not a space, 586 * erase all the nonspaces left of cursor (the whole "word"). 587 */ 588 while (cp > cmdbuf && cp[-1] != ' ') 589 (void) cmd_erase(); 590 } 591 return (CC_OK); 592 } 593 594 /* 595 * Delete the "word" under the cursor. 596 */ 597 static int 598 cmd_wdelete(VOID_PARAM) 599 { 600 if (*cp == ' ') 601 { 602 /* 603 * If the char under the cursor is a space, 604 * delete it and all the spaces right of cursor. 605 */ 606 while (*cp == ' ') 607 (void) cmd_delete(); 608 } else 609 { 610 /* 611 * If the char under the cursor is not a space, 612 * delete it and all nonspaces right of cursor (the whole word). 613 */ 614 while (*cp != ' ' && *cp != '\0') 615 (void) cmd_delete(); 616 } 617 return (CC_OK); 618 } 619 620 /* 621 * Delete all chars in the command buffer. 622 */ 623 static int 624 cmd_kill(VOID_PARAM) 625 { 626 if (cmdbuf[0] == '\0') 627 { 628 /* Buffer is already empty; abort the current command. */ 629 return (CC_QUIT); 630 } 631 cmd_offset = 0; 632 cmd_home(); 633 *cp = '\0'; 634 updown_match = -1; 635 cmd_repaint(cp); 636 637 /* 638 * We say that erasing the entire command string causes us 639 * to abort the current command, if CF_QUIT_ON_ERASE is set. 640 */ 641 if (curr_cmdflags & CF_QUIT_ON_ERASE) 642 return (CC_QUIT); 643 return (CC_OK); 644 } 645 646 /* 647 * Select an mlist structure to be the current command history. 648 */ 649 public void 650 set_mlist(mlist, cmdflags) 651 void *mlist; 652 int cmdflags; 653 { 654 #if CMD_HISTORY 655 curr_mlist = (struct mlist *) mlist; 656 curr_cmdflags = cmdflags; 657 658 /* Make sure the next up-arrow moves to the last string in the mlist. */ 659 if (curr_mlist != NULL) 660 curr_mlist->curr_mp = curr_mlist; 661 #endif 662 } 663 664 #if CMD_HISTORY 665 /* 666 * Move up or down in the currently selected command history list. 667 * Only consider entries whose first updown_match chars are equal to 668 * cmdbuf's corresponding chars. 669 */ 670 static int 671 cmd_updown(action) 672 int action; 673 { 674 constant char *s; 675 struct mlist *ml; 676 677 if (curr_mlist == NULL) 678 { 679 /* 680 * The current command has no history list. 681 */ 682 bell(); 683 return (CC_OK); 684 } 685 686 if (updown_match < 0) 687 { 688 updown_match = (int) (cp - cmdbuf); 689 } 690 691 /* 692 * Find the next history entry which matches. 693 */ 694 for (ml = curr_mlist->curr_mp;;) 695 { 696 ml = (action == EC_UP) ? ml->prev : ml->next; 697 if (ml == curr_mlist) 698 { 699 /* 700 * We reached the end (or beginning) of the list. 701 */ 702 break; 703 } 704 if (strncmp(cmdbuf, ml->string, updown_match) == 0) 705 { 706 /* 707 * This entry matches; stop here. 708 * Copy the entry into cmdbuf and echo it on the screen. 709 */ 710 curr_mlist->curr_mp = ml; 711 s = ml->string; 712 if (s == NULL) 713 s = ""; 714 cmd_offset = 0; 715 cmd_home(); 716 clear_eol(); 717 strcpy(cmdbuf, s); 718 for (cp = cmdbuf; *cp != '\0'; ) 719 cmd_right(); 720 return (CC_OK); 721 } 722 } 723 /* 724 * We didn't find a history entry that matches. 725 */ 726 bell(); 727 return (CC_OK); 728 } 729 #endif 730 731 /* 732 * 733 */ 734 static void 735 ml_link(mlist, ml) 736 struct mlist *mlist; 737 struct mlist *ml; 738 { 739 ml->next = mlist; 740 ml->prev = mlist->prev; 741 mlist->prev->next = ml; 742 mlist->prev = ml; 743 } 744 745 /* 746 * 747 */ 748 static void 749 ml_unlink(ml) 750 struct mlist *ml; 751 { 752 ml->prev->next = ml->next; 753 ml->next->prev = ml->prev; 754 } 755 756 /* 757 * Add a string to an mlist. 758 */ 759 public void 760 cmd_addhist(mlist, cmd, modified) 761 struct mlist *mlist; 762 constant char *cmd; 763 int modified; 764 { 765 #if CMD_HISTORY 766 struct mlist *ml; 767 768 /* 769 * Don't save a trivial command. 770 */ 771 if (strlen(cmd) == 0) 772 return; 773 774 if (no_hist_dups) 775 { 776 struct mlist *next = NULL; 777 for (ml = mlist->next; ml->string != NULL; ml = next) 778 { 779 next = ml->next; 780 if (strcmp(ml->string, cmd) == 0) 781 { 782 ml_unlink(ml); 783 free(ml->string); 784 free(ml); 785 } 786 } 787 } 788 789 /* 790 * Save the command unless it's a duplicate of the 791 * last command in the history. 792 */ 793 ml = mlist->prev; 794 if (ml == mlist || strcmp(ml->string, cmd) != 0) 795 { 796 /* 797 * Did not find command in history. 798 * Save the command and put it at the end of the history list. 799 */ 800 ml = (struct mlist *) ecalloc(1, sizeof(struct mlist)); 801 ml->string = save(cmd); 802 ml->modified = modified; 803 ml_link(mlist, ml); 804 } 805 /* 806 * Point to the cmd just after the just-accepted command. 807 * Thus, an UPARROW will always retrieve the previous command. 808 */ 809 mlist->curr_mp = ml->next; 810 #endif 811 } 812 813 /* 814 * Accept the command in the command buffer. 815 * Add it to the currently selected history list. 816 */ 817 public void 818 cmd_accept(VOID_PARAM) 819 { 820 #if CMD_HISTORY 821 /* 822 * Nothing to do if there is no currently selected history list. 823 */ 824 if (curr_mlist == NULL || curr_mlist == ml_examine) 825 return; 826 cmd_addhist(curr_mlist, cmdbuf, 1); 827 curr_mlist->modified = 1; 828 #endif 829 } 830 831 /* 832 * Try to perform a line-edit function on the command buffer, 833 * using a specified char as a line-editing command. 834 * Returns: 835 * CC_PASS The char does not invoke a line edit function. 836 * CC_OK Line edit function done. 837 * CC_QUIT The char requests the current command to be aborted. 838 */ 839 static int 840 cmd_edit(c) 841 int c; 842 { 843 int action; 844 int flags; 845 846 #if TAB_COMPLETE_FILENAME 847 #define not_in_completion() in_completion = 0 848 #else 849 #define not_in_completion(VOID_PARAM) 850 #endif 851 852 /* 853 * See if the char is indeed a line-editing command. 854 */ 855 flags = 0; 856 #if CMD_HISTORY 857 if (curr_mlist == NULL) 858 /* 859 * No current history; don't accept history manipulation cmds. 860 */ 861 flags |= ECF_NOHISTORY; 862 #endif 863 #if TAB_COMPLETE_FILENAME 864 if (curr_mlist == ml_search || curr_mlist == NULL) 865 /* 866 * Don't accept file-completion cmds in contexts 867 * such as search pattern, digits, long option name, etc. 868 */ 869 flags |= ECF_NOCOMPLETE; 870 #endif 871 872 action = editchar(c, flags); 873 874 switch (action) 875 { 876 case A_NOACTION: 877 return (CC_OK); 878 case EC_RIGHT: 879 not_in_completion(); 880 return (cmd_right()); 881 case EC_LEFT: 882 not_in_completion(); 883 return (cmd_left()); 884 case EC_W_RIGHT: 885 not_in_completion(); 886 while (*cp != '\0' && *cp != ' ') 887 cmd_right(); 888 while (*cp == ' ') 889 cmd_right(); 890 return (CC_OK); 891 case EC_W_LEFT: 892 not_in_completion(); 893 while (cp > cmdbuf && cp[-1] == ' ') 894 cmd_left(); 895 while (cp > cmdbuf && cp[-1] != ' ') 896 cmd_left(); 897 return (CC_OK); 898 case EC_HOME: 899 not_in_completion(); 900 cmd_offset = 0; 901 cmd_home(); 902 cmd_repaint(cp); 903 return (CC_OK); 904 case EC_END: 905 not_in_completion(); 906 while (*cp != '\0') 907 cmd_right(); 908 return (CC_OK); 909 case EC_INSERT: 910 not_in_completion(); 911 return (CC_OK); 912 case EC_BACKSPACE: 913 not_in_completion(); 914 return (cmd_erase()); 915 case EC_LINEKILL: 916 not_in_completion(); 917 return (cmd_kill()); 918 case EC_ABORT: 919 not_in_completion(); 920 (void) cmd_kill(); 921 return (CC_QUIT); 922 case EC_W_BACKSPACE: 923 not_in_completion(); 924 return (cmd_werase()); 925 case EC_DELETE: 926 not_in_completion(); 927 return (cmd_delete()); 928 case EC_W_DELETE: 929 not_in_completion(); 930 return (cmd_wdelete()); 931 case EC_LITERAL: 932 literal = 1; 933 return (CC_OK); 934 #if CMD_HISTORY 935 case EC_UP: 936 case EC_DOWN: 937 not_in_completion(); 938 return (cmd_updown(action)); 939 #endif 940 #if TAB_COMPLETE_FILENAME 941 case EC_F_COMPLETE: 942 case EC_B_COMPLETE: 943 case EC_EXPAND: 944 return (cmd_complete(action)); 945 #endif 946 default: 947 not_in_completion(); 948 return (CC_PASS); 949 } 950 } 951 952 #if TAB_COMPLETE_FILENAME 953 /* 954 * Insert a string into the command buffer, at the current position. 955 */ 956 static int 957 cmd_istr(str) 958 char *str; 959 { 960 char *s; 961 int action; 962 char *endline = str + strlen(str); 963 964 for (s = str; *s != '\0'; ) 965 { 966 char *os = s; 967 step_char(&s, +1, endline); 968 action = cmd_ichar(os, s - os); 969 if (action != CC_OK) 970 return (action); 971 } 972 return (CC_OK); 973 } 974 975 /* 976 * Find the beginning and end of the "current" word. 977 * This is the word which the cursor (cp) is inside or at the end of. 978 * Return pointer to the beginning of the word and put the 979 * cursor at the end of the word. 980 */ 981 static char * 982 delimit_word(VOID_PARAM) 983 { 984 char *word; 985 #if SPACES_IN_FILENAMES 986 char *p; 987 int delim_quoted = 0; 988 int meta_quoted = 0; 989 constant char *esc = get_meta_escape(); 990 int esclen = (int) strlen(esc); 991 #endif 992 993 /* 994 * Move cursor to end of word. 995 */ 996 if (*cp != ' ' && *cp != '\0') 997 { 998 /* 999 * Cursor is on a nonspace. 1000 * Move cursor right to the next space. 1001 */ 1002 while (*cp != ' ' && *cp != '\0') 1003 cmd_right(); 1004 } else if (cp > cmdbuf && cp[-1] != ' ') 1005 { 1006 /* 1007 * Cursor is on a space, and char to the left is a nonspace. 1008 * We're already at the end of the word. 1009 */ 1010 ; 1011 #if 0 1012 } else 1013 { 1014 /* 1015 * Cursor is on a space and char to the left is a space. 1016 * Huh? There's no word here. 1017 */ 1018 return (NULL); 1019 #endif 1020 } 1021 /* 1022 * Find the beginning of the word which the cursor is in. 1023 */ 1024 if (cp == cmdbuf) 1025 return (NULL); 1026 #if SPACES_IN_FILENAMES 1027 /* 1028 * If we have an unbalanced quote (that is, an open quote 1029 * without a corresponding close quote), we return everything 1030 * from the open quote, including spaces. 1031 */ 1032 for (word = cmdbuf; word < cp; word++) 1033 if (*word != ' ') 1034 break; 1035 if (word >= cp) 1036 return (cp); 1037 for (p = cmdbuf; p < cp; p++) 1038 { 1039 if (meta_quoted) 1040 { 1041 meta_quoted = 0; 1042 } else if (esclen > 0 && p + esclen < cp && 1043 strncmp(p, esc, esclen) == 0) 1044 { 1045 meta_quoted = 1; 1046 p += esclen - 1; 1047 } else if (delim_quoted) 1048 { 1049 if (*p == closequote) 1050 delim_quoted = 0; 1051 } else /* (!delim_quoted) */ 1052 { 1053 if (*p == openquote) 1054 delim_quoted = 1; 1055 else if (*p == ' ') 1056 word = p+1; 1057 } 1058 } 1059 #endif 1060 return (word); 1061 } 1062 1063 /* 1064 * Set things up to enter completion mode. 1065 * Expand the word under the cursor into a list of filenames 1066 * which start with that word, and set tk_text to that list. 1067 */ 1068 static void 1069 init_compl(VOID_PARAM) 1070 { 1071 char *word; 1072 char c; 1073 1074 /* 1075 * Get rid of any previous tk_text. 1076 */ 1077 if (tk_text != NULL) 1078 { 1079 free(tk_text); 1080 tk_text = NULL; 1081 } 1082 /* 1083 * Find the original (uncompleted) word in the command buffer. 1084 */ 1085 word = delimit_word(); 1086 if (word == NULL) 1087 return; 1088 /* 1089 * Set the insertion point to the point in the command buffer 1090 * where the original (uncompleted) word now sits. 1091 */ 1092 tk_ipoint = word; 1093 /* 1094 * Save the original (uncompleted) word 1095 */ 1096 if (tk_original != NULL) 1097 free(tk_original); 1098 tk_original = (char *) ecalloc(cp-word+1, sizeof(char)); 1099 strncpy(tk_original, word, cp-word); 1100 /* 1101 * Get the expanded filename. 1102 * This may result in a single filename, or 1103 * a blank-separated list of filenames. 1104 */ 1105 c = *cp; 1106 *cp = '\0'; 1107 if (*word != openquote) 1108 { 1109 tk_text = fcomplete(word); 1110 } else 1111 { 1112 #if MSDOS_COMPILER 1113 char *qword = NULL; 1114 #else 1115 char *qword = shell_quote(word+1); 1116 #endif 1117 if (qword == NULL) 1118 tk_text = fcomplete(word+1); 1119 else 1120 { 1121 tk_text = fcomplete(qword); 1122 free(qword); 1123 } 1124 } 1125 *cp = c; 1126 } 1127 1128 /* 1129 * Return the next word in the current completion list. 1130 */ 1131 static char * 1132 next_compl(action, prev) 1133 int action; 1134 char *prev; 1135 { 1136 switch (action) 1137 { 1138 case EC_F_COMPLETE: 1139 return (forw_textlist(&tk_tlist, prev)); 1140 case EC_B_COMPLETE: 1141 return (back_textlist(&tk_tlist, prev)); 1142 } 1143 /* Cannot happen */ 1144 return ("?"); 1145 } 1146 1147 /* 1148 * Complete the filename before (or under) the cursor. 1149 * cmd_complete may be called multiple times. The global in_completion 1150 * remembers whether this call is the first time (create the list), 1151 * or a subsequent time (step thru the list). 1152 */ 1153 static int 1154 cmd_complete(action) 1155 int action; 1156 { 1157 char *s; 1158 1159 if (!in_completion || action == EC_EXPAND) 1160 { 1161 /* 1162 * Expand the word under the cursor and 1163 * use the first word in the expansion 1164 * (or the entire expansion if we're doing EC_EXPAND). 1165 */ 1166 init_compl(); 1167 if (tk_text == NULL) 1168 { 1169 bell(); 1170 return (CC_OK); 1171 } 1172 if (action == EC_EXPAND) 1173 { 1174 /* 1175 * Use the whole list. 1176 */ 1177 tk_trial = tk_text; 1178 } else 1179 { 1180 /* 1181 * Use the first filename in the list. 1182 */ 1183 in_completion = 1; 1184 init_textlist(&tk_tlist, tk_text); 1185 tk_trial = next_compl(action, (char*)NULL); 1186 } 1187 } else 1188 { 1189 /* 1190 * We already have a completion list. 1191 * Use the next/previous filename from the list. 1192 */ 1193 tk_trial = next_compl(action, tk_trial); 1194 } 1195 1196 /* 1197 * Remove the original word, or the previous trial completion. 1198 */ 1199 while (cp > tk_ipoint) 1200 (void) cmd_erase(); 1201 1202 if (tk_trial == NULL) 1203 { 1204 /* 1205 * There are no more trial completions. 1206 * Insert the original (uncompleted) filename. 1207 */ 1208 in_completion = 0; 1209 if (cmd_istr(tk_original) != CC_OK) 1210 goto fail; 1211 } else 1212 { 1213 /* 1214 * Insert trial completion. 1215 */ 1216 if (cmd_istr(tk_trial) != CC_OK) 1217 goto fail; 1218 /* 1219 * If it is a directory, append a slash. 1220 */ 1221 if (is_dir(tk_trial)) 1222 { 1223 if (cp > cmdbuf && cp[-1] == closequote) 1224 (void) cmd_erase(); 1225 s = lgetenv("LESSSEPARATOR"); 1226 if (s == NULL) 1227 s = PATHNAME_SEP; 1228 if (cmd_istr(s) != CC_OK) 1229 goto fail; 1230 } 1231 } 1232 1233 return (CC_OK); 1234 1235 fail: 1236 in_completion = 0; 1237 bell(); 1238 return (CC_OK); 1239 } 1240 1241 #endif /* TAB_COMPLETE_FILENAME */ 1242 1243 /* 1244 * Process a single character of a multi-character command, such as 1245 * a number, or the pattern of a search command. 1246 * Returns: 1247 * CC_OK The char was accepted. 1248 * CC_QUIT The char requests the command to be aborted. 1249 * CC_ERROR The char could not be accepted due to an error. 1250 */ 1251 public int 1252 cmd_char(c) 1253 int c; 1254 { 1255 int action; 1256 int len; 1257 1258 if (!utf_mode) 1259 { 1260 cmd_mbc_buf[0] = c; 1261 len = 1; 1262 } else 1263 { 1264 /* Perform strict validation in all possible cases. */ 1265 if (cmd_mbc_buf_len == 0) 1266 { 1267 retry: 1268 cmd_mbc_buf_index = 1; 1269 *cmd_mbc_buf = c; 1270 if (IS_ASCII_OCTET(c)) 1271 cmd_mbc_buf_len = 1; 1272 #if MSDOS_COMPILER || OS2 1273 else if (c == (unsigned char) '\340' && IS_ASCII_OCTET(peekcc())) 1274 { 1275 /* Assume a special key. */ 1276 cmd_mbc_buf_len = 1; 1277 } 1278 #endif 1279 else if (IS_UTF8_LEAD(c)) 1280 { 1281 cmd_mbc_buf_len = utf_len(c); 1282 return (CC_OK); 1283 } else 1284 { 1285 /* UTF8_INVALID or stray UTF8_TRAIL */ 1286 bell(); 1287 return (CC_ERROR); 1288 } 1289 } else if (IS_UTF8_TRAIL(c)) 1290 { 1291 cmd_mbc_buf[cmd_mbc_buf_index++] = c; 1292 if (cmd_mbc_buf_index < cmd_mbc_buf_len) 1293 return (CC_OK); 1294 if (!is_utf8_well_formed(cmd_mbc_buf, cmd_mbc_buf_index)) 1295 { 1296 /* complete, but not well formed (non-shortest form), sequence */ 1297 cmd_mbc_buf_len = 0; 1298 bell(); 1299 return (CC_ERROR); 1300 } 1301 } else 1302 { 1303 /* Flush incomplete (truncated) sequence. */ 1304 cmd_mbc_buf_len = 0; 1305 bell(); 1306 /* Handle new char. */ 1307 goto retry; 1308 } 1309 1310 len = cmd_mbc_buf_len; 1311 cmd_mbc_buf_len = 0; 1312 } 1313 1314 if (literal) 1315 { 1316 /* 1317 * Insert the char, even if it is a line-editing char. 1318 */ 1319 literal = 0; 1320 return (cmd_ichar(cmd_mbc_buf, len)); 1321 } 1322 1323 /* 1324 * See if it is a line-editing character. 1325 */ 1326 if (in_mca() && len == 1) 1327 { 1328 action = cmd_edit(c); 1329 switch (action) 1330 { 1331 case CC_OK: 1332 case CC_QUIT: 1333 return (action); 1334 case CC_PASS: 1335 break; 1336 } 1337 } 1338 1339 /* 1340 * Insert the char into the command buffer. 1341 */ 1342 return (cmd_ichar(cmd_mbc_buf, len)); 1343 } 1344 1345 /* 1346 * Return the number currently in the command buffer. 1347 */ 1348 public LINENUM 1349 cmd_int(frac) 1350 long *frac; 1351 { 1352 char *p; 1353 LINENUM n = 0; 1354 int err; 1355 1356 for (p = cmdbuf; *p >= '0' && *p <= '9'; p++) 1357 { 1358 LINENUM nn = (n * 10) + (*p - '0'); 1359 if (nn < n) 1360 { 1361 error("Integer is too big", NULL_PARG); 1362 return (0); 1363 } 1364 n = nn; 1365 } 1366 *frac = 0; 1367 if (*p++ == '.') 1368 { 1369 *frac = getfraction(&p, NULL, &err); 1370 /* {{ do something if err is set? }} */ 1371 } 1372 return (n); 1373 } 1374 1375 /* 1376 * Return a pointer to the command buffer. 1377 */ 1378 public char * 1379 get_cmdbuf(VOID_PARAM) 1380 { 1381 if (cmd_mbc_buf_index < cmd_mbc_buf_len) 1382 /* Don't return buffer containing an incomplete multibyte char. */ 1383 return (NULL); 1384 return (cmdbuf); 1385 } 1386 1387 #if CMD_HISTORY 1388 /* 1389 * Return the last (most recent) string in the current command history. 1390 */ 1391 public char * 1392 cmd_lastpattern(VOID_PARAM) 1393 { 1394 if (curr_mlist == NULL) 1395 return (NULL); 1396 return (curr_mlist->curr_mp->prev->string); 1397 } 1398 #endif 1399 1400 #if CMD_HISTORY 1401 /* 1402 */ 1403 static int 1404 mlist_size(ml) 1405 struct mlist *ml; 1406 { 1407 int size = 0; 1408 for (ml = ml->next; ml->string != NULL; ml = ml->next) 1409 ++size; 1410 return size; 1411 } 1412 1413 /* 1414 * Get the name of the history file. 1415 */ 1416 static char * 1417 histfile_find(must_exist) 1418 int must_exist; 1419 { 1420 char *home = lgetenv("HOME"); 1421 char *name = NULL; 1422 1423 /* Try in $XDG_STATE_HOME, then in $HOME/.local/state, then in $XDG_DATA_HOME, then in $HOME. */ 1424 #if OS2 1425 if (isnullenv(home)) 1426 home = lgetenv("INIT"); 1427 #endif 1428 name = dirfile(lgetenv("XDG_STATE_HOME"), &LESSHISTFILE[1], must_exist); 1429 if (name == NULL) 1430 { 1431 char *dir = dirfile(home, ".local/state", 1); 1432 if (dir != NULL) 1433 { 1434 name = dirfile(dir, &LESSHISTFILE[1], must_exist); 1435 free(dir); 1436 } 1437 } 1438 if (name == NULL) 1439 name = dirfile(lgetenv("XDG_DATA_HOME"), &LESSHISTFILE[1], must_exist); 1440 if (name == NULL) 1441 name = dirfile(home, LESSHISTFILE, must_exist); 1442 return (name); 1443 } 1444 1445 static char * 1446 histfile_name(must_exist) 1447 int must_exist; 1448 { 1449 char *name; 1450 1451 /* See if filename is explicitly specified by $LESSHISTFILE. */ 1452 name = lgetenv("LESSHISTFILE"); 1453 if (!isnullenv(name)) 1454 { 1455 if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0) 1456 /* $LESSHISTFILE == "-" means don't use a history file. */ 1457 return (NULL); 1458 return (save(name)); 1459 } 1460 1461 /* See if history file is disabled in the build. */ 1462 if (strcmp(LESSHISTFILE, "") == 0 || strcmp(LESSHISTFILE, "-") == 0) 1463 return (NULL); 1464 1465 name = NULL; 1466 if (!must_exist) 1467 { 1468 /* If we're writing the file and the file already exists, use it. */ 1469 name = histfile_find(1); 1470 } 1471 if (name == NULL) 1472 name = histfile_find(must_exist); 1473 return (name); 1474 } 1475 1476 /* 1477 * Read a .lesshst file and call a callback for each line in the file. 1478 */ 1479 static void 1480 read_cmdhist2(action, uparam, skip_search, skip_shell) 1481 void (*action)(void*,struct mlist*,char*); 1482 void *uparam; 1483 int skip_search; 1484 int skip_shell; 1485 { 1486 struct mlist *ml = NULL; 1487 char line[CMDBUF_SIZE]; 1488 char *filename; 1489 FILE *f; 1490 char *p; 1491 int *skip = NULL; 1492 1493 filename = histfile_name(1); 1494 if (filename == NULL) 1495 return; 1496 f = fopen(filename, "r"); 1497 free(filename); 1498 if (f == NULL) 1499 return; 1500 if (fgets(line, sizeof(line), f) == NULL || 1501 strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0) 1502 { 1503 fclose(f); 1504 return; 1505 } 1506 while (fgets(line, sizeof(line), f) != NULL) 1507 { 1508 for (p = line; *p != '\0'; p++) 1509 { 1510 if (*p == '\n' || *p == '\r') 1511 { 1512 *p = '\0'; 1513 break; 1514 } 1515 } 1516 if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0) 1517 { 1518 ml = &mlist_search; 1519 skip = &skip_search; 1520 } else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0) 1521 { 1522 #if SHELL_ESCAPE || PIPEC 1523 ml = &mlist_shell; 1524 skip = &skip_shell; 1525 #else 1526 ml = NULL; 1527 skip = NULL; 1528 #endif 1529 } else if (strcmp(line, HISTFILE_MARK_SECTION) == 0) 1530 { 1531 ml = NULL; 1532 } else if (*line == '"') 1533 { 1534 if (ml != NULL) 1535 { 1536 if (skip != NULL && *skip > 0) 1537 --(*skip); 1538 else 1539 (*action)(uparam, ml, line+1); 1540 } 1541 } else if (*line == 'm') 1542 { 1543 (*action)(uparam, NULL, line); 1544 } 1545 } 1546 fclose(f); 1547 } 1548 1549 static void 1550 read_cmdhist(action, uparam, skip_search, skip_shell) 1551 void (*action)(void*,struct mlist*,char*); 1552 void *uparam; 1553 int skip_search; 1554 int skip_shell; 1555 { 1556 if (secure) 1557 return; 1558 read_cmdhist2(action, uparam, skip_search, skip_shell); 1559 (*action)(uparam, NULL, NULL); /* signal end of file */ 1560 } 1561 1562 static void 1563 addhist_init(uparam, ml, string) 1564 void *uparam; 1565 struct mlist *ml; 1566 char constant *string; 1567 { 1568 if (ml != NULL) 1569 cmd_addhist(ml, string, 0); 1570 else if (string != NULL) 1571 restore_mark((char*)string); /* stupid const cast */ 1572 } 1573 #endif /* CMD_HISTORY */ 1574 1575 /* 1576 * Initialize history from a .lesshist file. 1577 */ 1578 public void 1579 init_cmdhist(VOID_PARAM) 1580 { 1581 #if CMD_HISTORY 1582 read_cmdhist(&addhist_init, NULL, 0, 0); 1583 #endif /* CMD_HISTORY */ 1584 } 1585 1586 /* 1587 * Write the header for a section of the history file. 1588 */ 1589 #if CMD_HISTORY 1590 static void 1591 write_mlist_header(ml, f) 1592 struct mlist *ml; 1593 FILE *f; 1594 { 1595 if (ml == &mlist_search) 1596 fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION); 1597 #if SHELL_ESCAPE || PIPEC 1598 else if (ml == &mlist_shell) 1599 fprintf(f, "%s\n", HISTFILE_SHELL_SECTION); 1600 #endif 1601 } 1602 1603 /* 1604 * Write all modified entries in an mlist to the history file. 1605 */ 1606 static void 1607 write_mlist(ml, f) 1608 struct mlist *ml; 1609 FILE *f; 1610 { 1611 for (ml = ml->next; ml->string != NULL; ml = ml->next) 1612 { 1613 if (!ml->modified) 1614 continue; 1615 fprintf(f, "\"%s\n", ml->string); 1616 ml->modified = 0; 1617 } 1618 ml->modified = 0; /* entire mlist is now unmodified */ 1619 } 1620 1621 /* 1622 * Make a temp name in the same directory as filename. 1623 */ 1624 static char * 1625 make_tempname(filename) 1626 char *filename; 1627 { 1628 char lastch; 1629 char *tempname = ecalloc(1, strlen(filename)+1); 1630 strcpy(tempname, filename); 1631 lastch = tempname[strlen(tempname)-1]; 1632 tempname[strlen(tempname)-1] = (lastch == 'Q') ? 'Z' : 'Q'; 1633 return tempname; 1634 } 1635 1636 struct save_ctx 1637 { 1638 struct mlist *mlist; 1639 FILE *fout; 1640 }; 1641 1642 /* 1643 * Copy entries from the saved history file to a new file. 1644 * At the end of each mlist, append any new entries 1645 * created during this session. 1646 */ 1647 static void 1648 copy_hist(uparam, ml, string) 1649 void *uparam; 1650 struct mlist *ml; 1651 char constant *string; 1652 { 1653 struct save_ctx *ctx = (struct save_ctx *) uparam; 1654 1655 if (ml != NULL && ml != ctx->mlist) { 1656 /* We're changing mlists. */ 1657 if (ctx->mlist) 1658 /* Append any new entries to the end of the current mlist. */ 1659 write_mlist(ctx->mlist, ctx->fout); 1660 /* Write the header for the new mlist. */ 1661 ctx->mlist = ml; 1662 write_mlist_header(ctx->mlist, ctx->fout); 1663 } 1664 1665 if (string == NULL) /* End of file */ 1666 { 1667 /* Write any sections that were not in the original file. */ 1668 if (mlist_search.modified) 1669 { 1670 write_mlist_header(&mlist_search, ctx->fout); 1671 write_mlist(&mlist_search, ctx->fout); 1672 } 1673 #if SHELL_ESCAPE || PIPEC 1674 if (mlist_shell.modified) 1675 { 1676 write_mlist_header(&mlist_shell, ctx->fout); 1677 write_mlist(&mlist_shell, ctx->fout); 1678 } 1679 #endif 1680 } else if (ml != NULL) 1681 { 1682 /* Copy mlist entry. */ 1683 fprintf(ctx->fout, "\"%s\n", string); 1684 } 1685 /* Skip marks */ 1686 } 1687 #endif /* CMD_HISTORY */ 1688 1689 /* 1690 * Make a file readable only by its owner. 1691 */ 1692 static void 1693 make_file_private(f) 1694 FILE *f; 1695 { 1696 #if HAVE_FCHMOD 1697 int do_chmod = 1; 1698 #if HAVE_STAT 1699 struct stat statbuf; 1700 int r = fstat(fileno(f), &statbuf); 1701 if (r < 0 || !S_ISREG(statbuf.st_mode)) 1702 /* Don't chmod if not a regular file. */ 1703 do_chmod = 0; 1704 #endif 1705 if (do_chmod) 1706 fchmod(fileno(f), 0600); 1707 #endif 1708 } 1709 1710 /* 1711 * Does the history file need to be updated? 1712 */ 1713 #if CMD_HISTORY 1714 static int 1715 histfile_modified(VOID_PARAM) 1716 { 1717 if (mlist_search.modified) 1718 return 1; 1719 #if SHELL_ESCAPE || PIPEC 1720 if (mlist_shell.modified) 1721 return 1; 1722 #endif 1723 if (marks_modified) 1724 return 1; 1725 return 0; 1726 } 1727 #endif 1728 1729 /* 1730 * Update the .lesshst file. 1731 */ 1732 public void 1733 save_cmdhist(VOID_PARAM) 1734 { 1735 #if CMD_HISTORY 1736 char *histname; 1737 char *tempname; 1738 int skip_search; 1739 int skip_shell; 1740 struct save_ctx ctx; 1741 char *s; 1742 FILE *fout = NULL; 1743 int histsize = 0; 1744 1745 if (secure || !histfile_modified()) 1746 return; 1747 histname = histfile_name(0); 1748 if (histname == NULL) 1749 return; 1750 tempname = make_tempname(histname); 1751 fout = fopen(tempname, "w"); 1752 if (fout != NULL) 1753 { 1754 make_file_private(fout); 1755 s = lgetenv("LESSHISTSIZE"); 1756 if (s != NULL) 1757 histsize = atoi(s); 1758 if (histsize <= 0) 1759 histsize = 100; 1760 skip_search = mlist_size(&mlist_search) - histsize; 1761 #if SHELL_ESCAPE || PIPEC 1762 skip_shell = mlist_size(&mlist_shell) - histsize; 1763 #endif 1764 fprintf(fout, "%s\n", HISTFILE_FIRST_LINE); 1765 ctx.fout = fout; 1766 ctx.mlist = NULL; 1767 read_cmdhist(©_hist, &ctx, skip_search, skip_shell); 1768 save_marks(fout, HISTFILE_MARK_SECTION); 1769 fclose(fout); 1770 #if MSDOS_COMPILER==WIN32C 1771 /* 1772 * Windows rename doesn't remove an existing file, 1773 * making it useless for atomic operations. Sigh. 1774 */ 1775 remove(histname); 1776 #endif 1777 rename(tempname, histname); 1778 } 1779 free(tempname); 1780 free(histname); 1781 #endif /* CMD_HISTORY */ 1782 } 1783