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