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