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