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