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