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