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