1 /* 2 * Copyright (C) 1984-2012 Mark Nudelman 3 * Modified for use with illumos by Garrett D'Amore. 4 * Copyright 2014 Garrett D'Amore <garrett@damore.org> 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information, see the README file. 10 */ 11 12 /* 13 * Functions which manipulate the command buffer. 14 * Used only by command() and related functions. 15 */ 16 17 #include <sys/stat.h> 18 19 #include "charset.h" 20 #include "cmd.h" 21 #include "less.h" 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 static int cmd_complete(int); 35 /* 36 * These variables are statics used by cmd_complete. 37 */ 38 static int in_completion = 0; 39 static char *tk_text; 40 static char *tk_original; 41 static char *tk_ipoint; 42 static char *tk_trial; 43 static struct textlist tk_tlist; 44 45 static int cmd_left(void); 46 static int cmd_right(void); 47 48 char openquote = '"'; 49 char closequote = '"'; 50 51 /* History file */ 52 #define HISTFILE_FIRST_LINE ".less-history-file:" 53 #define HISTFILE_SEARCH_SECTION ".search" 54 #define HISTFILE_SHELL_SECTION ".shell" 55 56 /* 57 * A mlist structure represents a command history. 58 */ 59 struct mlist { 60 struct mlist *next; 61 struct mlist *prev; 62 struct mlist *curr_mp; 63 char *string; 64 int modified; 65 }; 66 67 /* 68 * These are the various command histories that exist. 69 */ 70 struct mlist mlist_search = 71 { &mlist_search, &mlist_search, &mlist_search, NULL, 0 }; 72 void * const ml_search = (void *) &mlist_search; 73 74 struct mlist mlist_examine = 75 { &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 }; 76 void * const ml_examine = (void *) &mlist_examine; 77 78 struct mlist mlist_shell = 79 { &mlist_shell, &mlist_shell, &mlist_shell, NULL, 0 }; 80 void * const ml_shell = (void *) &mlist_shell; 81 82 /* 83 * History for the current command. 84 */ 85 static struct mlist *curr_mlist = NULL; 86 static int curr_cmdflags; 87 88 static char cmd_mbc_buf[MAX_UTF_CHAR_LEN]; 89 static int cmd_mbc_buf_len; 90 static int cmd_mbc_buf_index; 91 92 93 /* 94 * Reset command buffer (to empty). 95 */ 96 void 97 cmd_reset(void) 98 { 99 cp = cmdbuf; 100 *cp = '\0'; 101 cmd_col = 0; 102 cmd_offset = 0; 103 literal = 0; 104 cmd_mbc_buf_len = 0; 105 updown_match = -1; 106 } 107 108 /* 109 * Clear command line. 110 */ 111 void 112 clear_cmd(void) 113 { 114 cmd_col = prompt_col = 0; 115 cmd_mbc_buf_len = 0; 116 updown_match = -1; 117 } 118 119 /* 120 * Display an ASCII string, usually as a prompt for input, 121 * into the command buffer. 122 */ 123 void 124 cmd_putstr(char *s) 125 { 126 while (*s != '\0') { 127 putchr(*s++); 128 cmd_col++; 129 prompt_col++; 130 } 131 } 132 133 /* 134 * How many characters are in the command buffer? 135 */ 136 int 137 len_cmdbuf(void) 138 { 139 char *s = cmdbuf; 140 char *endline = s + strlen(s); 141 int len = 0; 142 143 while (*s != '\0') { 144 step_char(&s, +1, endline); 145 len++; 146 } 147 return (len); 148 } 149 150 /* 151 * Common part of cmd_step_right() and cmd_step_left(). 152 */ 153 static char * 154 cmd_step_common(char *p, LWCHAR ch, int len, int *pwidth, int *bswidth) 155 { 156 char *pr; 157 158 if (len == 1) { 159 pr = prchar((int)ch); 160 if (pwidth != NULL || bswidth != NULL) { 161 int prlen = strlen(pr); 162 if (pwidth != NULL) 163 *pwidth = prlen; 164 if (bswidth != NULL) 165 *bswidth = prlen; 166 } 167 } else { 168 pr = prutfchar(ch); 169 if (pwidth != NULL || bswidth != NULL) { 170 if (is_composing_char(ch)) { 171 if (pwidth != NULL) 172 *pwidth = 0; 173 if (bswidth != NULL) 174 *bswidth = 0; 175 } else if (is_ubin_char(ch)) { 176 int prlen = strlen(pr); 177 if (pwidth != NULL) 178 *pwidth = prlen; 179 if (bswidth != NULL) 180 *bswidth = prlen; 181 } else { 182 if (pwidth != NULL) 183 *pwidth = is_wide_char(ch) ? 2 : 1; 184 if (bswidth != NULL) 185 *bswidth = 1; 186 } 187 } 188 } 189 190 return (pr); 191 } 192 193 /* 194 * Step a pointer one character right in the command buffer. 195 */ 196 static char * 197 cmd_step_right(char **pp, int *pwidth, int *bswidth) 198 { 199 char *p = *pp; 200 LWCHAR ch = step_char(pp, +1, p + strlen(p)); 201 202 return (cmd_step_common(p, ch, *pp - p, pwidth, bswidth)); 203 } 204 205 /* 206 * Step a pointer one character left in the command buffer. 207 */ 208 static char * 209 cmd_step_left(char **pp, int *pwidth, int *bswidth) 210 { 211 char *p = *pp; 212 LWCHAR ch = step_char(pp, -1, cmdbuf); 213 214 return (cmd_step_common(*pp, ch, p - *pp, pwidth, bswidth)); 215 } 216 217 /* 218 * Repaint the line from cp onwards. 219 * Then position the cursor just after the char old_cp (a pointer into cmdbuf). 220 */ 221 static void 222 cmd_repaint(char *old_cp) 223 { 224 /* 225 * Repaint the line from the current position. 226 */ 227 clear_eol(); 228 while (*cp != '\0') { 229 char *np = cp; 230 int width; 231 char *pr = cmd_step_right(&np, &width, NULL); 232 if (cmd_col + width >= sc_width) 233 break; 234 cp = np; 235 putstr(pr); 236 cmd_col += width; 237 } 238 while (*cp != '\0') { 239 char *np = cp; 240 int width; 241 char *pr = cmd_step_right(&np, &width, NULL); 242 if (width > 0) 243 break; 244 cp = np; 245 putstr(pr); 246 } 247 248 /* 249 * Back up the cursor to the correct position. 250 */ 251 while (cp > old_cp) 252 cmd_left(); 253 } 254 255 /* 256 * Put the cursor at "home" (just after the prompt), 257 * and set cp to the corresponding char in cmdbuf. 258 */ 259 static void 260 cmd_home(void) 261 { 262 while (cmd_col > prompt_col) { 263 int width, bswidth; 264 265 cmd_step_left(&cp, &width, &bswidth); 266 while (bswidth-- > 0) 267 putbs(); 268 cmd_col -= width; 269 } 270 271 cp = &cmdbuf[cmd_offset]; 272 } 273 274 /* 275 * Shift the cmdbuf display left a half-screen. 276 */ 277 static void 278 cmd_lshift(void) 279 { 280 char *s; 281 char *save_cp; 282 int cols; 283 284 /* 285 * Start at the first displayed char, count how far to the 286 * right we'd have to move to reach the center of the screen. 287 */ 288 s = cmdbuf + cmd_offset; 289 cols = 0; 290 while (cols < (sc_width - prompt_col) / 2 && *s != '\0') { 291 int width; 292 cmd_step_right(&s, &width, NULL); 293 cols += width; 294 } 295 while (*s != '\0') { 296 int width; 297 char *ns = s; 298 cmd_step_right(&ns, &width, NULL); 299 if (width > 0) 300 break; 301 s = ns; 302 } 303 304 cmd_offset = s - cmdbuf; 305 save_cp = cp; 306 cmd_home(); 307 cmd_repaint(save_cp); 308 } 309 310 /* 311 * Shift the cmdbuf display right a half-screen. 312 */ 313 static void 314 cmd_rshift(void) 315 { 316 char *s; 317 char *save_cp; 318 int cols; 319 320 /* 321 * Start at the first displayed char, count how far to the 322 * left we'd have to move to traverse a half-screen width 323 * of displayed characters. 324 */ 325 s = cmdbuf + cmd_offset; 326 cols = 0; 327 while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf) { 328 int width; 329 cmd_step_left(&s, &width, NULL); 330 cols += width; 331 } 332 333 cmd_offset = s - cmdbuf; 334 save_cp = cp; 335 cmd_home(); 336 cmd_repaint(save_cp); 337 } 338 339 /* 340 * Move cursor right one character. 341 */ 342 static int 343 cmd_right(void) 344 { 345 char *pr; 346 char *ncp; 347 int width; 348 349 if (*cp == '\0') { 350 /* Already at the end of the line. */ 351 return (CC_OK); 352 } 353 ncp = cp; 354 pr = cmd_step_right(&ncp, &width, NULL); 355 if (cmd_col + width >= sc_width) 356 cmd_lshift(); 357 else if (cmd_col + width == sc_width - 1 && cp[1] != '\0') 358 cmd_lshift(); 359 cp = ncp; 360 cmd_col += width; 361 putstr(pr); 362 while (*cp != '\0') { 363 pr = cmd_step_right(&ncp, &width, NULL); 364 if (width > 0) 365 break; 366 putstr(pr); 367 cp = ncp; 368 } 369 return (CC_OK); 370 } 371 372 /* 373 * Move cursor left one character. 374 */ 375 static int 376 cmd_left(void) 377 { 378 char *ncp; 379 int width, bswidth; 380 381 if (cp <= cmdbuf) { 382 /* Already at the beginning of the line */ 383 return (CC_OK); 384 } 385 ncp = cp; 386 while (ncp > cmdbuf) { 387 cmd_step_left(&ncp, &width, &bswidth); 388 if (width > 0) 389 break; 390 } 391 if (cmd_col < prompt_col + width) 392 cmd_rshift(); 393 cp = ncp; 394 cmd_col -= width; 395 while (bswidth-- > 0) 396 putbs(); 397 return (CC_OK); 398 } 399 400 /* 401 * Insert a char into the command buffer, at the current position. 402 */ 403 static int 404 cmd_ichar(char *cs, int clen) 405 { 406 char *s; 407 408 if (strlen(cmdbuf) + clen >= sizeof (cmdbuf)-1) { 409 /* No room in the command buffer for another char. */ 410 ring_bell(); 411 return (CC_ERROR); 412 } 413 414 /* 415 * Make room for the new character (shift the tail of the buffer right). 416 */ 417 for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--) 418 s[clen] = s[0]; 419 /* 420 * Insert the character into the buffer. 421 */ 422 for (s = cp; s < cp + clen; s++) 423 *s = *cs++; 424 /* 425 * Reprint the tail of the line from the inserted char. 426 */ 427 updown_match = -1; 428 cmd_repaint(cp); 429 cmd_right(); 430 return (CC_OK); 431 } 432 433 /* 434 * Backspace in the command buffer. 435 * Delete the char to the left of the cursor. 436 */ 437 static int 438 cmd_erase(void) 439 { 440 char *s; 441 int clen; 442 443 if (cp == cmdbuf) { 444 /* 445 * Backspace past beginning of the buffer: 446 * this usually means abort the command. 447 */ 448 return (CC_QUIT); 449 } 450 /* 451 * Move cursor left (to the char being erased). 452 */ 453 s = cp; 454 cmd_left(); 455 clen = s - cp; 456 457 /* 458 * Remove the char from the buffer (shift the buffer left). 459 */ 460 for (s = cp; ; s++) { 461 s[0] = s[clen]; 462 if (s[0] == '\0') 463 break; 464 } 465 466 /* 467 * Repaint the buffer after the erased char. 468 */ 469 updown_match = -1; 470 cmd_repaint(cp); 471 472 /* 473 * We say that erasing the entire command string causes us 474 * to abort the current command, if CF_QUIT_ON_ERASE is set. 475 */ 476 if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0') 477 return (CC_QUIT); 478 return (CC_OK); 479 } 480 481 /* 482 * Delete the char under the cursor. 483 */ 484 static int 485 cmd_delete(void) 486 { 487 if (*cp == '\0') { 488 /* At end of string; there is no char under the cursor. */ 489 return (CC_OK); 490 } 491 /* 492 * Move right, then use cmd_erase. 493 */ 494 cmd_right(); 495 cmd_erase(); 496 return (CC_OK); 497 } 498 499 /* 500 * Delete the "word" to the left of the cursor. 501 */ 502 static int 503 cmd_werase(void) 504 { 505 if (cp > cmdbuf && cp[-1] == ' ') { 506 /* 507 * If the char left of cursor is a space, 508 * erase all the spaces left of cursor (to the first non-space). 509 */ 510 while (cp > cmdbuf && cp[-1] == ' ') 511 (void) cmd_erase(); 512 } else { 513 /* 514 * If the char left of cursor is not a space, 515 * erase all the nonspaces left of cursor (the whole "word"). 516 */ 517 while (cp > cmdbuf && cp[-1] != ' ') 518 (void) cmd_erase(); 519 } 520 return (CC_OK); 521 } 522 523 /* 524 * Delete the "word" under the cursor. 525 */ 526 static int 527 cmd_wdelete(void) 528 { 529 if (*cp == ' ') { 530 /* 531 * If the char under the cursor is a space, 532 * delete it and all the spaces right of cursor. 533 */ 534 while (*cp == ' ') 535 (void) cmd_delete(); 536 } else { 537 /* 538 * If the char under the cursor is not a space, 539 * delete it and all nonspaces right of cursor (the whole word). 540 */ 541 while (*cp != ' ' && *cp != '\0') 542 (void) cmd_delete(); 543 } 544 return (CC_OK); 545 } 546 547 /* 548 * Delete all chars in the command buffer. 549 */ 550 static int 551 cmd_kill(void) 552 { 553 if (cmdbuf[0] == '\0') { 554 /* Buffer is already empty; abort the current command. */ 555 return (CC_QUIT); 556 } 557 cmd_offset = 0; 558 cmd_home(); 559 *cp = '\0'; 560 updown_match = -1; 561 cmd_repaint(cp); 562 563 /* 564 * We say that erasing the entire command string causes us 565 * to abort the current command, if CF_QUIT_ON_ERASE is set. 566 */ 567 if (curr_cmdflags & CF_QUIT_ON_ERASE) 568 return (CC_QUIT); 569 return (CC_OK); 570 } 571 572 /* 573 * Select an mlist structure to be the current command history. 574 */ 575 void 576 set_mlist(void *mlist, int cmdflags) 577 { 578 curr_mlist = (struct mlist *)mlist; 579 curr_cmdflags = cmdflags; 580 581 /* Make sure the next up-arrow moves to the last string in the mlist. */ 582 if (curr_mlist != NULL) 583 curr_mlist->curr_mp = curr_mlist; 584 } 585 586 /* 587 * Move up or down in the currently selected command history list. 588 * Only consider entries whose first updown_match chars are equal to 589 * cmdbuf's corresponding chars. 590 */ 591 static int 592 cmd_updown(int action) 593 { 594 char *s; 595 struct mlist *ml; 596 597 if (curr_mlist == NULL) { 598 /* 599 * The current command has no history list. 600 */ 601 ring_bell(); 602 return (CC_OK); 603 } 604 605 if (updown_match < 0) { 606 updown_match = cp - cmdbuf; 607 } 608 609 /* 610 * Find the next history entry which matches. 611 */ 612 for (ml = curr_mlist->curr_mp; ; ) { 613 ml = (action == EC_UP) ? ml->prev : ml->next; 614 if (ml == curr_mlist) { 615 /* 616 * We reached the end (or beginning) of the list. 617 */ 618 break; 619 } 620 if (strncmp(cmdbuf, ml->string, updown_match) == 0) { 621 /* 622 * This entry matches; stop here. 623 * Copy the entry into cmdbuf and echo it on the screen. 624 */ 625 curr_mlist->curr_mp = ml; 626 s = ml->string; 627 if (s == NULL) 628 s = ""; 629 cmd_home(); 630 clear_eol(); 631 strlcpy(cmdbuf, s, sizeof (cmdbuf)); 632 for (cp = cmdbuf; *cp != '\0'; ) 633 cmd_right(); 634 return (CC_OK); 635 } 636 } 637 /* 638 * We didn't find a history entry that matches. 639 */ 640 ring_bell(); 641 return (CC_OK); 642 } 643 644 /* 645 * Add a string to a history list. 646 */ 647 void 648 cmd_addhist(struct mlist *mlist, const char *cmd) 649 { 650 struct mlist *ml; 651 652 /* 653 * Don't save a trivial command. 654 */ 655 if (strlen(cmd) == 0) 656 return; 657 658 /* 659 * Save the command unless it's a duplicate of the 660 * last command in the history. 661 */ 662 ml = mlist->prev; 663 if (ml == mlist || strcmp(ml->string, cmd) != 0) { 664 /* 665 * Did not find command in history. 666 * Save the command and put it at the end of the history list. 667 */ 668 ml = ecalloc(1, sizeof (struct mlist)); 669 ml->string = estrdup(cmd); 670 ml->next = mlist; 671 ml->prev = mlist->prev; 672 mlist->prev->next = ml; 673 mlist->prev = ml; 674 } 675 /* 676 * Point to the cmd just after the just-accepted command. 677 * Thus, an UPARROW will always retrieve the previous command. 678 */ 679 mlist->curr_mp = ml->next; 680 } 681 682 /* 683 * Accept the command in the command buffer. 684 * Add it to the currently selected history list. 685 */ 686 void 687 cmd_accept(void) 688 { 689 /* 690 * Nothing to do if there is no currently selected history list. 691 */ 692 if (curr_mlist == NULL) 693 return; 694 cmd_addhist(curr_mlist, cmdbuf); 695 curr_mlist->modified = 1; 696 } 697 698 /* 699 * Try to perform a line-edit function on the command buffer, 700 * using a specified char as a line-editing command. 701 * Returns: 702 * CC_PASS The char does not invoke a line edit function. 703 * CC_OK Line edit function done. 704 * CC_QUIT The char requests the current command to be aborted. 705 */ 706 static int 707 cmd_edit(int c) 708 { 709 int action; 710 int flags; 711 712 #define not_in_completion() in_completion = 0 713 714 /* 715 * See if the char is indeed a line-editing command. 716 */ 717 flags = 0; 718 if (curr_mlist == NULL) 719 /* 720 * No current history; don't accept history manipulation cmds. 721 */ 722 flags |= EC_NOHISTORY; 723 if (curr_mlist == ml_search) 724 /* 725 * In a search command; don't accept file-completion cmds. 726 */ 727 flags |= EC_NOCOMPLETE; 728 729 action = editchar(c, flags); 730 731 switch (action) { 732 case EC_RIGHT: 733 not_in_completion(); 734 return (cmd_right()); 735 case EC_LEFT: 736 not_in_completion(); 737 return (cmd_left()); 738 case EC_W_RIGHT: 739 not_in_completion(); 740 while (*cp != '\0' && *cp != ' ') 741 cmd_right(); 742 while (*cp == ' ') 743 cmd_right(); 744 return (CC_OK); 745 case EC_W_LEFT: 746 not_in_completion(); 747 while (cp > cmdbuf && cp[-1] == ' ') 748 cmd_left(); 749 while (cp > cmdbuf && cp[-1] != ' ') 750 cmd_left(); 751 return (CC_OK); 752 case EC_HOME: 753 not_in_completion(); 754 cmd_offset = 0; 755 cmd_home(); 756 cmd_repaint(cp); 757 return (CC_OK); 758 case EC_END: 759 not_in_completion(); 760 while (*cp != '\0') 761 cmd_right(); 762 return (CC_OK); 763 case EC_INSERT: 764 not_in_completion(); 765 return (CC_OK); 766 case EC_BACKSPACE: 767 not_in_completion(); 768 return (cmd_erase()); 769 case EC_LINEKILL: 770 not_in_completion(); 771 return (cmd_kill()); 772 case EC_ABORT: 773 not_in_completion(); 774 (void) cmd_kill(); 775 return (CC_QUIT); 776 case EC_W_BACKSPACE: 777 not_in_completion(); 778 return (cmd_werase()); 779 case EC_DELETE: 780 not_in_completion(); 781 return (cmd_delete()); 782 case EC_W_DELETE: 783 not_in_completion(); 784 return (cmd_wdelete()); 785 case EC_LITERAL: 786 literal = 1; 787 return (CC_OK); 788 case EC_UP: 789 case EC_DOWN: 790 not_in_completion(); 791 return (cmd_updown(action)); 792 case EC_F_COMPLETE: 793 case EC_B_COMPLETE: 794 case EC_EXPAND: 795 return (cmd_complete(action)); 796 case EC_NOACTION: 797 return (CC_OK); 798 default: 799 not_in_completion(); 800 return (CC_PASS); 801 } 802 } 803 804 /* 805 * Insert a string into the command buffer, at the current position. 806 */ 807 static int 808 cmd_istr(char *str) 809 { 810 char *s; 811 int action; 812 char *endline = str + strlen(str); 813 814 for (s = str; *s != '\0'; ) { 815 char *os = s; 816 step_char(&s, +1, endline); 817 action = cmd_ichar(os, s - os); 818 if (action != CC_OK) { 819 ring_bell(); 820 return (action); 821 } 822 } 823 return (CC_OK); 824 } 825 826 /* 827 * Find the beginning and end of the "current" word. 828 * This is the word which the cursor (cp) is inside or at the end of. 829 * Return pointer to the beginning of the word and put the 830 * cursor at the end of the word. 831 */ 832 static char * 833 delimit_word(void) 834 { 835 char *word; 836 char *p; 837 int delim_quoted = 0; 838 int meta_quoted = 0; 839 char *esc = get_meta_escape(); 840 int esclen = strlen(esc); 841 842 /* 843 * Move cursor to end of word. 844 */ 845 if (*cp != ' ' && *cp != '\0') { 846 /* 847 * Cursor is on a nonspace. 848 * Move cursor right to the next space. 849 */ 850 while (*cp != ' ' && *cp != '\0') 851 cmd_right(); 852 } 853 854 /* 855 * Find the beginning of the word which the cursor is in. 856 */ 857 if (cp == cmdbuf) 858 return (NULL); 859 /* 860 * If we have an unbalanced quote (that is, an open quote 861 * without a corresponding close quote), we return everything 862 * from the open quote, including spaces. 863 */ 864 for (word = cmdbuf; word < cp; word++) 865 if (*word != ' ') 866 break; 867 if (word >= cp) 868 return (cp); 869 for (p = cmdbuf; p < cp; p++) { 870 if (meta_quoted) { 871 meta_quoted = 0; 872 } else if (esclen > 0 && p + esclen < cp && 873 strncmp(p, esc, esclen) == 0) { 874 meta_quoted = 1; 875 p += esclen - 1; 876 } else if (delim_quoted) { 877 if (*p == closequote) 878 delim_quoted = 0; 879 } else { /* (!delim_quoted) */ 880 if (*p == openquote) 881 delim_quoted = 1; 882 else if (*p == ' ') 883 word = p+1; 884 } 885 } 886 return (word); 887 } 888 889 /* 890 * Set things up to enter completion mode. 891 * Expand the word under the cursor into a list of filenames 892 * which start with that word, and set tk_text to that list. 893 */ 894 static void 895 init_compl(void) 896 { 897 char *word; 898 char c; 899 900 free(tk_text); 901 tk_text = NULL; 902 /* 903 * Find the original (uncompleted) word in the command buffer. 904 */ 905 word = delimit_word(); 906 if (word == NULL) 907 return; 908 /* 909 * Set the insertion point to the point in the command buffer 910 * where the original (uncompleted) word now sits. 911 */ 912 tk_ipoint = word; 913 /* 914 * Save the original (uncompleted) word 915 */ 916 free(tk_original); 917 tk_original = ecalloc(cp-word+1, sizeof (char)); 918 (void) strncpy(tk_original, word, cp-word); 919 /* 920 * Get the expanded filename. 921 * This may result in a single filename, or 922 * a blank-separated list of filenames. 923 */ 924 c = *cp; 925 *cp = '\0'; 926 if (*word != openquote) { 927 tk_text = fcomplete(word); 928 } else { 929 char *qword = shell_quote(word+1); 930 if (qword == NULL) 931 tk_text = fcomplete(word+1); 932 else 933 tk_text = fcomplete(qword); 934 free(qword); 935 } 936 *cp = c; 937 } 938 939 /* 940 * Return the next word in the current completion list. 941 */ 942 static char * 943 next_compl(int action, char *prev) 944 { 945 switch (action) { 946 case EC_F_COMPLETE: 947 return (forw_textlist(&tk_tlist, prev)); 948 case EC_B_COMPLETE: 949 return (back_textlist(&tk_tlist, prev)); 950 } 951 /* Cannot happen */ 952 return ("?"); 953 } 954 955 /* 956 * Complete the filename before (or under) the cursor. 957 * cmd_complete may be called multiple times. The global in_completion 958 * remembers whether this call is the first time (create the list), 959 * or a subsequent time (step thru the list). 960 */ 961 static int 962 cmd_complete(int action) 963 { 964 char *s; 965 966 if (!in_completion || action == EC_EXPAND) { 967 /* 968 * Expand the word under the cursor and 969 * use the first word in the expansion 970 * (or the entire expansion if we're doing EC_EXPAND). 971 */ 972 init_compl(); 973 if (tk_text == NULL) { 974 ring_bell(); 975 return (CC_OK); 976 } 977 if (action == EC_EXPAND) { 978 /* 979 * Use the whole list. 980 */ 981 tk_trial = tk_text; 982 } else { 983 /* 984 * Use the first filename in the list. 985 */ 986 in_completion = 1; 987 init_textlist(&tk_tlist, tk_text); 988 tk_trial = next_compl(action, NULL); 989 } 990 } else { 991 /* 992 * We already have a completion list. 993 * Use the next/previous filename from the list. 994 */ 995 tk_trial = next_compl(action, tk_trial); 996 } 997 998 /* 999 * Remove the original word, or the previous trial completion. 1000 */ 1001 while (cp > tk_ipoint) 1002 (void) cmd_erase(); 1003 1004 if (tk_trial == NULL) { 1005 /* 1006 * There are no more trial completions. 1007 * Insert the original (uncompleted) filename. 1008 */ 1009 in_completion = 0; 1010 if (cmd_istr(tk_original) != CC_OK) 1011 goto fail; 1012 } else { 1013 /* 1014 * Insert trial completion. 1015 */ 1016 if (cmd_istr(tk_trial) != CC_OK) 1017 goto fail; 1018 /* 1019 * If it is a directory, append a slash. 1020 */ 1021 if (is_dir(tk_trial)) { 1022 if (cp > cmdbuf && cp[-1] == closequote) 1023 (void) cmd_erase(); 1024 s = lgetenv("LESSSEPARATOR"); 1025 if (s == NULL) 1026 s = "/"; 1027 if (cmd_istr(s) != CC_OK) 1028 goto fail; 1029 } 1030 } 1031 1032 return (CC_OK); 1033 1034 fail: 1035 in_completion = 0; 1036 ring_bell(); 1037 return (CC_OK); 1038 } 1039 1040 /* 1041 * Process a single character of a multi-character command, such as 1042 * a number, or the pattern of a search command. 1043 * Returns: 1044 * CC_OK The char was accepted. 1045 * CC_QUIT The char requests the command to be aborted. 1046 * CC_ERROR The char could not be accepted due to an error. 1047 */ 1048 int 1049 cmd_char(int c) 1050 { 1051 int action; 1052 int len; 1053 1054 if (!utf_mode) { 1055 cmd_mbc_buf[0] = c & 0xff; 1056 len = 1; 1057 } else { 1058 /* Perform strict validation in all possible cases. */ 1059 if (cmd_mbc_buf_len == 0) { 1060 retry: 1061 cmd_mbc_buf_index = 1; 1062 *cmd_mbc_buf = c & 0xff; 1063 if (isascii((unsigned char)c)) 1064 cmd_mbc_buf_len = 1; 1065 else if (IS_UTF8_LEAD(c)) { 1066 cmd_mbc_buf_len = utf_len(c); 1067 return (CC_OK); 1068 } else { 1069 /* UTF8_INVALID or stray UTF8_TRAIL */ 1070 ring_bell(); 1071 return (CC_ERROR); 1072 } 1073 } else if (IS_UTF8_TRAIL(c)) { 1074 cmd_mbc_buf[cmd_mbc_buf_index++] = c & 0xff; 1075 if (cmd_mbc_buf_index < cmd_mbc_buf_len) 1076 return (CC_OK); 1077 if (!is_utf8_well_formed(cmd_mbc_buf)) { 1078 /* 1079 * complete, but not well formed 1080 * (non-shortest form), sequence 1081 */ 1082 cmd_mbc_buf_len = 0; 1083 ring_bell(); 1084 return (CC_ERROR); 1085 } 1086 } else { 1087 /* Flush incomplete (truncated) sequence. */ 1088 cmd_mbc_buf_len = 0; 1089 ring_bell(); 1090 /* Handle new char. */ 1091 goto retry; 1092 } 1093 1094 len = cmd_mbc_buf_len; 1095 cmd_mbc_buf_len = 0; 1096 } 1097 1098 if (literal) { 1099 /* 1100 * Insert the char, even if it is a line-editing char. 1101 */ 1102 literal = 0; 1103 return (cmd_ichar(cmd_mbc_buf, len)); 1104 } 1105 1106 /* 1107 * See if it is a line-editing character. 1108 */ 1109 if (in_mca() && len == 1) { 1110 action = cmd_edit(c); 1111 switch (action) { 1112 case CC_OK: 1113 case CC_QUIT: 1114 return (action); 1115 case CC_PASS: 1116 break; 1117 } 1118 } 1119 1120 /* 1121 * Insert the char into the command buffer. 1122 */ 1123 return (cmd_ichar(cmd_mbc_buf, len)); 1124 } 1125 1126 /* 1127 * Return the number currently in the command buffer. 1128 */ 1129 off_t 1130 cmd_int(long *frac) 1131 { 1132 char *p; 1133 off_t n = 0; 1134 int err; 1135 1136 for (p = cmdbuf; *p >= '0' && *p <= '9'; p++) 1137 n = (n * 10) + (*p - '0'); 1138 *frac = 0; 1139 if (*p++ == '.') { 1140 *frac = getfraction(&p, NULL, &err); 1141 /* {{ do something if err is set? }} */ 1142 } 1143 return (n); 1144 } 1145 1146 /* 1147 * Return a pointer to the command buffer. 1148 */ 1149 char * 1150 get_cmdbuf(void) 1151 { 1152 return (cmdbuf); 1153 } 1154 1155 /* 1156 * Return the last (most recent) string in the current command history. 1157 */ 1158 char * 1159 cmd_lastpattern(void) 1160 { 1161 if (curr_mlist == NULL) 1162 return (NULL); 1163 return (curr_mlist->curr_mp->prev->string); 1164 } 1165 1166 /* 1167 * Get the name of the history file. 1168 */ 1169 static char * 1170 histfile_name(void) 1171 { 1172 char *home; 1173 char *name; 1174 1175 /* See if filename is explicitly specified by $LESSHISTFILE. */ 1176 name = lgetenv("LESSHISTFILE"); 1177 if (name != NULL && *name != '\0') { 1178 if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0) 1179 /* $LESSHISTFILE == "-" means don't use history file */ 1180 return (NULL); 1181 return (estrdup(name)); 1182 } 1183 1184 /* Otherwise, file is in $HOME if enabled. */ 1185 if (strcmp(LESSHISTFILE, "-") == 0) 1186 return (NULL); 1187 home = lgetenv("HOME"); 1188 if (home == NULL || *home == '\0') { 1189 return (NULL); 1190 } 1191 return (easprintf("%s/%s", home, LESSHISTFILE)); 1192 } 1193 1194 /* 1195 * Initialize history from a .lesshist file. 1196 */ 1197 void 1198 init_cmdhist(void) 1199 { 1200 struct mlist *ml = NULL; 1201 char line[CMDBUF_SIZE]; 1202 char *filename; 1203 FILE *f; 1204 char *p; 1205 1206 filename = histfile_name(); 1207 if (filename == NULL) 1208 return; 1209 f = fopen(filename, "r"); 1210 free(filename); 1211 if (f == NULL) 1212 return; 1213 if (fgets(line, sizeof (line), f) == NULL || 1214 strncmp(line, HISTFILE_FIRST_LINE, 1215 strlen(HISTFILE_FIRST_LINE)) != 0) { 1216 (void) fclose(f); 1217 return; 1218 } 1219 while (fgets(line, sizeof (line), f) != NULL) { 1220 for (p = line; *p != '\0'; p++) { 1221 if (*p == '\n' || *p == '\r') { 1222 *p = '\0'; 1223 break; 1224 } 1225 } 1226 if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0) 1227 ml = &mlist_search; 1228 else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0) { 1229 ml = &mlist_shell; 1230 } else if (*line == '"') { 1231 if (ml != NULL) 1232 cmd_addhist(ml, line+1); 1233 } 1234 } 1235 (void) fclose(f); 1236 } 1237 1238 /* 1239 * 1240 */ 1241 static void 1242 save_mlist(struct mlist *ml, FILE *f) 1243 { 1244 int histsize = 0; 1245 int n; 1246 char *s; 1247 1248 s = lgetenv("LESSHISTSIZE"); 1249 if (s != NULL) 1250 histsize = atoi(s); 1251 if (histsize == 0) 1252 histsize = 100; 1253 1254 ml = ml->prev; 1255 for (n = 0; n < histsize; n++) { 1256 if (ml->string == NULL) 1257 break; 1258 ml = ml->prev; 1259 } 1260 for (ml = ml->next; ml->string != NULL; ml = ml->next) 1261 (void) fprintf(f, "\"%s\n", ml->string); 1262 } 1263 1264 /* 1265 * 1266 */ 1267 void 1268 save_cmdhist(void) 1269 { 1270 char *filename; 1271 FILE *f; 1272 int modified = 0; 1273 int do_chmod = 1; 1274 struct stat statbuf; 1275 int r; 1276 1277 if (mlist_search.modified) 1278 modified = 1; 1279 if (mlist_shell.modified) 1280 modified = 1; 1281 if (!modified) 1282 return; 1283 filename = histfile_name(); 1284 if (filename == NULL) 1285 return; 1286 f = fopen(filename, "w"); 1287 free(filename); 1288 if (f == NULL) 1289 return; 1290 1291 /* Make history file readable only by owner. */ 1292 r = fstat(fileno(f), &statbuf); 1293 if (r == -1 || !S_ISREG(statbuf.st_mode)) 1294 /* Don't chmod if not a regular file. */ 1295 do_chmod = 0; 1296 if (do_chmod) 1297 (void) fchmod(fileno(f), 0600); 1298 1299 (void) fprintf(f, "%s\n", HISTFILE_FIRST_LINE); 1300 1301 (void) fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION); 1302 save_mlist(&mlist_search, f); 1303 1304 (void) fprintf(f, "%s\n", HISTFILE_SHELL_SECTION); 1305 save_mlist(&mlist_shell, f); 1306 1307 (void) fclose(f); 1308 } 1309