1 /* 2 * Copyright (C) 1984-2026 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 size_t cmd_mbc_buf_len; 117 static size_t 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) ptr_diff(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) ptr_diff(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) ptr_diff(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 size_t len = ptr_diff(cp, word); 997 if (tk_original != NULL) 998 free(tk_original); 999 tk_original = (char *) ecalloc(len+1, sizeof(char)); 1000 strncpy(tk_original, word, len); 1001 tk_original[len] = '\0'; 1002 } 1003 1004 #if TAB_COMPLETE_FILENAME 1005 /* 1006 * Find the beginning and end of the "current" word. 1007 * This is the word which the cursor (cp) is inside or at the end of. 1008 * Return pointer to the beginning of the word and put the 1009 * cursor at the end of the word. 1010 */ 1011 static char * delimit_word(void) 1012 { 1013 char *word; 1014 #if SPACES_IN_FILENAMES 1015 char *p; 1016 int delim_quoted = FALSE; 1017 int meta_quoted = FALSE; 1018 constant char *esc = get_meta_escape(); 1019 size_t esclen = strlen(esc); 1020 #endif 1021 1022 /* 1023 * Move cursor to end of word. 1024 */ 1025 if (*cp != ' ' && *cp != '\0') 1026 { 1027 /* 1028 * Cursor is on a nonspace. 1029 * Move cursor right to the next space. 1030 */ 1031 while (*cp != ' ' && *cp != '\0') 1032 cmd_right(); 1033 } else if (cp > cmdbuf && cp[-1] != ' ') 1034 { 1035 /* 1036 * Cursor is on a space, and char to the left is a nonspace. 1037 * We're already at the end of the word. 1038 */ 1039 ; 1040 #if 0 1041 } else 1042 { 1043 /* 1044 * Cursor is on a space and char to the left is a space. 1045 * Huh? There's no word here. 1046 */ 1047 return (NULL); 1048 #endif 1049 } 1050 /* 1051 * Find the beginning of the word which the cursor is in. 1052 */ 1053 if (cp == cmdbuf) 1054 return (NULL); 1055 #if SPACES_IN_FILENAMES 1056 /* 1057 * If we have an unbalanced quote (that is, an open quote 1058 * without a corresponding close quote), we return everything 1059 * from the open quote, including spaces. 1060 */ 1061 for (word = cmdbuf; word < cp; word++) 1062 if (*word != ' ') 1063 break; 1064 if (word >= cp) 1065 return (cp); 1066 for (p = cmdbuf; p < cp; p++) 1067 { 1068 if (meta_quoted) 1069 { 1070 meta_quoted = FALSE; 1071 } else if (esclen > 0 && p + esclen < cp && 1072 strncmp(p, esc, esclen) == 0) 1073 { 1074 meta_quoted = TRUE; 1075 p += esclen - 1; 1076 } else if (delim_quoted) 1077 { 1078 if (*p == closequote) 1079 delim_quoted = FALSE; 1080 } else /* (!delim_quoted) */ 1081 { 1082 if (*p == openquote) 1083 delim_quoted = TRUE; 1084 else if (*p == ' ') 1085 word = p+1; 1086 } 1087 } 1088 #endif 1089 return (word); 1090 } 1091 1092 /* 1093 * Set things up to enter file completion mode. 1094 * Expand the word under the cursor into a list of filenames 1095 * which start with that word, and set tk_text to that list. 1096 */ 1097 static void init_file_compl(void) 1098 { 1099 char *word; 1100 char c; 1101 1102 /* 1103 * Find the original (uncompleted) word in the command buffer. 1104 */ 1105 word = delimit_word(); 1106 if (word == NULL) 1107 return; 1108 /* 1109 * Set the insertion point to the point in the command buffer 1110 * where the original (uncompleted) word now sits. 1111 */ 1112 tk_ipoint = word; 1113 set_tk_original(word); 1114 /* 1115 * Get the expanded filename. 1116 * This may result in a single filename, or 1117 * a blank-separated list of filenames. 1118 */ 1119 c = *cp; 1120 *cp = '\0'; 1121 if (*word != openquote) 1122 { 1123 tk_text = fcomplete(word); 1124 } else 1125 { 1126 #if MSDOS_COMPILER 1127 char *qword = NULL; 1128 #else 1129 char *qword = shell_quote(word+1); 1130 #endif 1131 if (qword == NULL) 1132 tk_text = fcomplete(word+1); 1133 else 1134 { 1135 tk_text = fcomplete(qword); 1136 free(qword); 1137 } 1138 } 1139 *cp = c; 1140 } 1141 #endif /* TAB_COMPLETE_FILENAME */ 1142 1143 /* 1144 * Set things up to enter option completion mode. 1145 */ 1146 static void init_opt_compl(void) 1147 { 1148 tk_ipoint = cmdbuf; 1149 set_tk_original(cmdbuf); 1150 tk_text = findopts_name(cmdbuf); 1151 } 1152 1153 /* 1154 * Return the next word in the current completion list. 1155 */ 1156 static constant char * next_compl(int action, constant char *prev) 1157 { 1158 switch (action) 1159 { 1160 case EC_F_COMPLETE: 1161 return (forw_textlist(&tk_tlist, prev)); 1162 case EC_B_COMPLETE: 1163 return (back_textlist(&tk_tlist, prev)); 1164 } 1165 /* Cannot happen */ 1166 return ("?"); 1167 } 1168 1169 /* 1170 * Complete the filename before (or under) the cursor. 1171 * cmd_complete may be called multiple times. The global in_completion 1172 * remembers whether this call is the first time (create the list), 1173 * or a subsequent time (step thru the list). 1174 */ 1175 static int cmd_complete(int action) 1176 { 1177 constant char *s; 1178 1179 if (!in_completion || action == EC_EXPAND) 1180 { 1181 /* 1182 * Expand the word under the cursor and 1183 * use the first word in the expansion 1184 * (or the entire expansion if we're doing EC_EXPAND). 1185 */ 1186 if (tk_text != NULL) 1187 { 1188 free(tk_text); 1189 tk_text = NULL; 1190 } 1191 if (curr_cmdflags & CF_OPTION) 1192 init_opt_compl(); 1193 else 1194 #if TAB_COMPLETE_FILENAME 1195 init_file_compl(); 1196 #else 1197 quit(QUIT_ERROR); /* cannot happen */ 1198 #endif /* TAB_COMPLETE_FILENAME */ 1199 if (tk_text == NULL) 1200 { 1201 lbell(); 1202 return (CC_OK); 1203 } 1204 if (action == EC_EXPAND) 1205 { 1206 /* 1207 * Use the whole list. 1208 */ 1209 tk_trial = tk_text; 1210 } else 1211 { 1212 /* 1213 * Use the first filename in the list. 1214 */ 1215 in_completion = TRUE; 1216 init_textlist(&tk_tlist, tk_text); 1217 tk_trial = next_compl(action, (char*)NULL); 1218 } 1219 } else 1220 { 1221 /* 1222 * We already have a completion list. 1223 * Use the next/previous filename from the list. 1224 */ 1225 tk_trial = next_compl(action, tk_trial); 1226 } 1227 1228 /* 1229 * Remove the original word, or the previous trial completion. 1230 */ 1231 while (cp > tk_ipoint) 1232 (void) cmd_erase(); 1233 1234 if (tk_trial == NULL) 1235 { 1236 /* 1237 * There are no more trial completions. 1238 * Insert the original (uncompleted) filename. 1239 */ 1240 in_completion = FALSE; 1241 if (cmd_istr(tk_original) != CC_OK) 1242 goto fail; 1243 } else 1244 { 1245 /* 1246 * Insert trial completion. 1247 */ 1248 if (cmd_istr(tk_trial) != CC_OK) 1249 goto fail; 1250 /* 1251 * If it is a directory, append a slash. 1252 */ 1253 if (is_dir(tk_trial)) 1254 { 1255 if (cp > cmdbuf && cp[-1] == closequote) 1256 (void) cmd_erase(); 1257 s = lgetenv("LESSSEPARATOR"); 1258 if (s == NULL) 1259 s = PATHNAME_SEP; 1260 if (cmd_istr(s) != CC_OK) 1261 goto fail; 1262 } 1263 } 1264 1265 return (CC_OK); 1266 1267 fail: 1268 in_completion = FALSE; 1269 lbell(); 1270 return (CC_OK); 1271 } 1272 1273 /* 1274 * Build a UTF-8 char in cmd_mbc_buf. 1275 * Returns: 1276 * CC_OK Char has been stored but we don't have a complete UTF-8 sequence yet. 1277 * CC_ERROR This is an invalid UTF-8 sequence. 1278 * CC_PASS There is a complete UTF-8 sequence in cmd_mbc_buf. 1279 * The length of the complete sequence is returned in *plen. 1280 */ 1281 static int cmd_uchar(char c, size_t *plen) 1282 { 1283 if (!utf_mode) 1284 { 1285 cmd_mbc_buf[0] = c; 1286 *plen = 1; 1287 } else 1288 { 1289 /* Perform strict validation in all possible cases. */ 1290 if (cmd_mbc_buf_len == 0) 1291 { 1292 retry: 1293 cmd_mbc_buf_index = 1; 1294 *cmd_mbc_buf = c; 1295 if (IS_ASCII_OCTET(c)) 1296 cmd_mbc_buf_len = 1; 1297 #if MSDOS_COMPILER || OS2 1298 else if (c == '\340' && IS_ASCII_OCTET(peekcc())) 1299 { 1300 /* Assume a special key. */ 1301 cmd_mbc_buf_len = 1; 1302 } 1303 #endif 1304 else if (IS_UTF8_LEAD(c)) 1305 { 1306 cmd_mbc_buf_len = utf_len(c); 1307 return (CC_OK); 1308 } else 1309 { 1310 /* UTF8_INVALID or stray UTF8_TRAIL */ 1311 lbell(); 1312 return (CC_ERROR); 1313 } 1314 } else if (IS_UTF8_TRAIL(c)) 1315 { 1316 cmd_mbc_buf[cmd_mbc_buf_index++] = c; 1317 if (cmd_mbc_buf_index < cmd_mbc_buf_len) 1318 return (CC_OK); 1319 if (!is_utf8_well_formed(cmd_mbc_buf, cmd_mbc_buf_index)) 1320 { 1321 /* complete, but not well formed (non-shortest form), sequence */ 1322 cmd_mbc_buf_len = 0; 1323 lbell(); 1324 return (CC_ERROR); 1325 } 1326 } else 1327 { 1328 /* Flush incomplete (truncated) sequence. */ 1329 cmd_mbc_buf_len = 0; 1330 lbell(); 1331 /* Handle new char. */ 1332 goto retry; 1333 } 1334 1335 *plen = cmd_mbc_buf_len; 1336 cmd_mbc_buf_len = 0; 1337 } 1338 return (CC_PASS); 1339 } 1340 1341 /* 1342 * Process a single character of a multi-character command, such as 1343 * a number, or the pattern of a search command. 1344 * Returns: 1345 * CC_OK The char was accepted. 1346 * CC_QUIT The char requests the command to be aborted. 1347 * CC_ERROR The char could not be accepted due to an error. 1348 */ 1349 static int cmd_char2(char c, lbool stay_in_completion) 1350 { 1351 size_t len; 1352 int action = cmd_uchar(c, &len); 1353 if (action != CC_PASS) 1354 return (action); 1355 1356 if (literal) 1357 { 1358 /* 1359 * Insert the char, even if it is a line-editing char. 1360 */ 1361 literal = FALSE; 1362 return (cmd_ichar(cmd_mbc_buf, len)); 1363 } 1364 1365 /* 1366 * See if it is a line-editing character. 1367 */ 1368 if (in_mca() && len == 1) 1369 { 1370 action = cmd_edit(c, stay_in_completion); 1371 switch (action) 1372 { 1373 case CC_OK: 1374 case CC_QUIT: 1375 return (action); 1376 case CC_PASS: 1377 break; 1378 } 1379 } 1380 1381 /* 1382 * Insert the char into the command buffer. 1383 */ 1384 return (cmd_ichar(cmd_mbc_buf, len)); 1385 } 1386 1387 public int cmd_char(char c) 1388 { 1389 return cmd_char2(c, FALSE); 1390 } 1391 1392 /* 1393 * Copy an ASCII string to the command buffer. 1394 */ 1395 public int cmd_setstring(constant char *s, lbool uc) 1396 { 1397 while (*s != '\0') 1398 { 1399 int action; 1400 char c = *s++; 1401 if (uc && ASCII_IS_LOWER(c)) 1402 c = ASCII_TO_UPPER(c); 1403 action = cmd_char2(c, TRUE); 1404 if (action != CC_OK) 1405 return (action); 1406 } 1407 cmd_repaint_curr(); 1408 return (CC_OK); 1409 } 1410 1411 /* 1412 * Return the number currently in the command buffer. 1413 */ 1414 public LINENUM cmd_int(mutable long *frac) 1415 { 1416 constant char *p; 1417 LINENUM n = 0; 1418 1419 for (p = cmdbuf; *p >= '0' && *p <= '9'; p++) 1420 { 1421 if (ckd_mul(&n, n, 10) || ckd_add(&n, n, *p - '0')) 1422 { 1423 error(LM(Integer_is_too_big), NULL_PARG); 1424 return (0); 1425 } 1426 } 1427 if (frac != NULL) 1428 { 1429 *frac = 0; 1430 if (*p++ == '.') 1431 { 1432 /* {{ Just ignore error in fractional part. }} */ 1433 (void) getfraction(&p, frac); 1434 } 1435 } 1436 return (n); 1437 } 1438 1439 /* 1440 * Return a pointer to the command buffer. 1441 */ 1442 public constant char * get_cmdbuf(void) 1443 { 1444 if (cmd_mbc_buf_index < cmd_mbc_buf_len) 1445 /* Don't return buffer containing an incomplete multibyte char. */ 1446 return (NULL); 1447 return (cmdbuf); 1448 } 1449 1450 #if CMD_HISTORY 1451 /* 1452 * Return the last (most recent) string in the current command history. 1453 */ 1454 public constant char * cmd_lastpattern(void) 1455 { 1456 if (curr_mlist == NULL) 1457 return (NULL); 1458 return (curr_mlist->curr_mp->prev->string); 1459 } 1460 #endif 1461 1462 #if CMD_HISTORY 1463 /* 1464 */ 1465 static int mlist_size(struct mlist *ml) 1466 { 1467 int size = 0; 1468 for (ml = ml->next; ml->string != NULL; ml = ml->next) 1469 ++size; 1470 return size; 1471 } 1472 1473 /* 1474 * Get the name of the history file. 1475 */ 1476 static char * histfile_find(lbool must_exist) 1477 { 1478 constant char *home = lgetenv("HOME"); 1479 char *name; 1480 1481 /* Try in $XDG_STATE_HOME, then in $HOME/.local/state, then in $XDG_DATA_HOME, then in $HOME. */ 1482 #if OS2 1483 if (isnullenv(home)) 1484 home = lgetenv("INIT"); 1485 #endif 1486 name = dirfile(lgetenv("XDG_STATE_HOME"), &LESSHISTFILE[1], must_exist); 1487 if (name == NULL) 1488 { 1489 char *dir = dirfile(home, ".local/state", TRUE); 1490 if (dir != NULL) 1491 { 1492 name = dirfile(dir, &LESSHISTFILE[1], must_exist); 1493 free(dir); 1494 } 1495 } 1496 if (name == NULL) 1497 name = dirfile(lgetenv("XDG_DATA_HOME"), &LESSHISTFILE[1], must_exist); 1498 if (name == NULL) 1499 name = dirfile(home, LESSHISTFILE, must_exist); 1500 return (name); 1501 } 1502 1503 static char * histfile_name(lbool must_exist) 1504 { 1505 constant char *name; 1506 char *wname; 1507 1508 /* See if filename is explicitly specified by $LESSHISTFILE. */ 1509 name = lgetenv("LESSHISTFILE"); 1510 if (!isnullenv(name)) 1511 { 1512 if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0) 1513 /* $LESSHISTFILE == "-" means don't use a history file. */ 1514 return (NULL); 1515 return (save(name)); 1516 } 1517 1518 /* See if history file is disabled in the build. */ 1519 if (strcmp(LESSHISTFILE, "") == 0 || strcmp(LESSHISTFILE, "-") == 0) 1520 return (NULL); 1521 1522 wname = NULL; 1523 if (!must_exist) 1524 { 1525 /* If we're writing the file and the file already exists, use it. */ 1526 wname = histfile_find(TRUE); 1527 } 1528 if (wname == NULL) 1529 wname = histfile_find(must_exist); 1530 return (wname); 1531 } 1532 1533 /* 1534 * Read a .lesshst file and call a callback for each line in the file. 1535 */ 1536 static void read_cmdhist2(void (*action)(void*,struct mlist*,constant char*), void *uparam, int skip_search, int skip_shell) 1537 { 1538 struct mlist *ml = NULL; 1539 char line[CMDBUF_SIZE]; 1540 char *filename; 1541 FILE *f; 1542 int *skip = NULL; 1543 1544 filename = histfile_name(TRUE); 1545 if (filename == NULL) 1546 return; 1547 f = fopen(filename, "r"); 1548 free(filename); 1549 if (f == NULL) 1550 return; 1551 if (fgets(line, sizeof(line), f) == NULL || 1552 strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0) 1553 { 1554 fclose(f); 1555 return; 1556 } 1557 while (fgets(line, sizeof(line), f) != NULL) 1558 { 1559 char *p; 1560 for (p = line; *p != '\0'; p++) 1561 { 1562 if (*p == '\n' || *p == '\r') 1563 { 1564 *p = '\0'; 1565 break; 1566 } 1567 } 1568 if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0) 1569 { 1570 ml = &mlist_search; 1571 skip = &skip_search; 1572 } else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0) 1573 { 1574 #if SHELL_ESCAPE || PIPEC 1575 ml = &mlist_shell; 1576 skip = &skip_shell; 1577 #else 1578 ml = NULL; 1579 skip = NULL; 1580 (void) skip_shell; 1581 #endif 1582 } else if (strcmp(line, HISTFILE_MARK_SECTION) == 0) 1583 { 1584 ml = NULL; 1585 } else if (*line == '"') 1586 { 1587 if (ml != NULL) 1588 { 1589 if (skip != NULL && *skip > 0) 1590 --(*skip); 1591 else 1592 (*action)(uparam, ml, line+1); 1593 } 1594 } else if (*line == 'm') 1595 { 1596 (*action)(uparam, NULL, line); 1597 } 1598 } 1599 fclose(f); 1600 } 1601 1602 static void read_cmdhist(void (*action)(void*,struct mlist*,constant char*), void *uparam, lbool skip_search, lbool skip_shell) 1603 { 1604 if (!secure_allow(SF_HISTORY)) 1605 return; 1606 read_cmdhist2(action, uparam, skip_search, skip_shell); 1607 (*action)(uparam, NULL, NULL); /* signal end of file */ 1608 } 1609 1610 static void addhist_init(void *uparam, struct mlist *ml, constant char *string) 1611 { 1612 (void) uparam; 1613 if (ml != NULL) 1614 cmd_addhist(ml, string, FALSE); 1615 else if (string != NULL) 1616 restore_mark(string); 1617 } 1618 #endif /* CMD_HISTORY */ 1619 1620 /* 1621 * Initialize history from a .lesshist file. 1622 */ 1623 public void init_cmdhist(void) 1624 { 1625 #if CMD_HISTORY 1626 read_cmdhist(&addhist_init, NULL, 0, 0); 1627 #endif /* CMD_HISTORY */ 1628 } 1629 1630 /* 1631 * Write the header for a section of the history file. 1632 */ 1633 #if CMD_HISTORY 1634 static void write_mlist_header(struct mlist *ml, FILE *f) 1635 { 1636 if (ml == &mlist_search) 1637 fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION); 1638 #if SHELL_ESCAPE || PIPEC 1639 else if (ml == &mlist_shell) 1640 fprintf(f, "%s\n", HISTFILE_SHELL_SECTION); 1641 #endif 1642 } 1643 1644 /* 1645 * Write all modified entries in an mlist to the history file. 1646 */ 1647 static void write_mlist(struct mlist *ml, FILE *f) 1648 { 1649 for (ml = ml->next; ml->string != NULL; ml = ml->next) 1650 { 1651 if (!ml->modified) 1652 continue; 1653 fprintf(f, "\"%s\n", ml->string); 1654 ml->modified = FALSE; 1655 } 1656 ml->modified = FALSE; /* entire mlist is now unmodified */ 1657 } 1658 1659 /* 1660 * Make a temp name in the same directory as filename. 1661 */ 1662 static char * make_tempname(constant char *filename) 1663 { 1664 char lastch; 1665 char *tempname = ecalloc(1, strlen(filename)+1); 1666 strcpy(tempname, filename); 1667 lastch = tempname[strlen(tempname)-1]; 1668 tempname[strlen(tempname)-1] = (lastch == 'Q') ? 'Z' : 'Q'; 1669 return tempname; 1670 } 1671 1672 struct save_ctx 1673 { 1674 struct mlist *mlist; 1675 FILE *fout; 1676 }; 1677 1678 /* 1679 * Copy entries from the saved history file to a new file. 1680 * At the end of each mlist, append any new entries 1681 * created during this session. 1682 */ 1683 static void copy_hist(void *uparam, struct mlist *ml, constant char *string) 1684 { 1685 struct save_ctx *ctx = (struct save_ctx *) uparam; 1686 1687 if (ml != NULL && ml != ctx->mlist) { 1688 /* We're changing mlists. */ 1689 if (ctx->mlist) 1690 /* Append any new entries to the end of the current mlist. */ 1691 write_mlist(ctx->mlist, ctx->fout); 1692 /* Write the header for the new mlist. */ 1693 ctx->mlist = ml; 1694 write_mlist_header(ctx->mlist, ctx->fout); 1695 } 1696 1697 if (string == NULL) /* End of file */ 1698 { 1699 /* Write any sections that were not in the original file. */ 1700 if (mlist_search.modified) 1701 { 1702 write_mlist_header(&mlist_search, ctx->fout); 1703 write_mlist(&mlist_search, ctx->fout); 1704 } 1705 #if SHELL_ESCAPE || PIPEC 1706 if (mlist_shell.modified) 1707 { 1708 write_mlist_header(&mlist_shell, ctx->fout); 1709 write_mlist(&mlist_shell, ctx->fout); 1710 } 1711 #endif 1712 } else if (ml != NULL) 1713 { 1714 /* Copy mlist entry. */ 1715 fprintf(ctx->fout, "\"%s\n", string); 1716 } 1717 /* Skip marks */ 1718 } 1719 #endif /* CMD_HISTORY */ 1720 1721 /* 1722 * Make a file readable only by its owner. 1723 */ 1724 static void make_file_private(FILE *f) 1725 { 1726 #if HAVE_FCHMOD 1727 lbool do_chmod = TRUE; 1728 #if HAVE_STAT 1729 less_stat_t statbuf; 1730 int r = less_fstat(fileno(f), &statbuf); 1731 if (r < 0 || !S_ISREG(statbuf.st_mode)) 1732 /* Don't chmod if not a regular file. */ 1733 do_chmod = FALSE; 1734 #endif 1735 if (do_chmod) 1736 fchmod(fileno(f), 0600); 1737 #endif 1738 } 1739 1740 /* 1741 * Does the history file need to be updated? 1742 */ 1743 #if CMD_HISTORY 1744 static lbool histfile_modified(void) 1745 { 1746 if (mlist_search.modified) 1747 return TRUE; 1748 #if SHELL_ESCAPE || PIPEC 1749 if (mlist_shell.modified) 1750 return TRUE; 1751 #endif 1752 if (marks_modified) 1753 return TRUE; 1754 return FALSE; 1755 } 1756 #endif 1757 1758 /* 1759 * Update the .lesshst file. 1760 */ 1761 public void save_cmdhist(void) 1762 { 1763 #if CMD_HISTORY 1764 char *histname; 1765 char *tempname; 1766 int skip_search; 1767 int skip_shell; 1768 struct save_ctx ctx; 1769 constant char *s; 1770 FILE *fout; 1771 int histsize = 0; 1772 1773 if (!secure_allow(SF_HISTORY) || !histfile_modified()) 1774 return; 1775 histname = histfile_name(0); 1776 if (histname == NULL) 1777 return; 1778 tempname = make_tempname(histname); 1779 fout = fopen(tempname, "w"); 1780 if (fout != NULL) 1781 { 1782 make_file_private(fout); 1783 s = lgetenv("LESSHISTSIZE"); 1784 if (s != NULL) 1785 histsize = atoi(s); 1786 if (histsize <= 0) 1787 histsize = 100; 1788 skip_search = mlist_size(&mlist_search) - histsize; 1789 #if SHELL_ESCAPE || PIPEC 1790 skip_shell = mlist_size(&mlist_shell) - histsize; 1791 #else 1792 skip_shell = 0; /* not actually used */ 1793 #endif 1794 fprintf(fout, "%s\n", HISTFILE_FIRST_LINE); 1795 ctx.fout = fout; 1796 ctx.mlist = NULL; 1797 read_cmdhist(©_hist, &ctx, skip_search, skip_shell); 1798 save_marks(fout, HISTFILE_MARK_SECTION); 1799 fclose(fout); 1800 #if MSDOS_COMPILER==WIN32C 1801 /* 1802 * Windows rename doesn't remove an existing file, 1803 * making it useless for atomic operations. Sigh. 1804 */ 1805 remove(histname); 1806 #endif 1807 rename(tempname, histname); 1808 } 1809 free(tempname); 1810 free(histname); 1811 #endif /* CMD_HISTORY */ 1812 } 1813