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