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