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