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