1 /* 2 * Copyright (C) 1984-2026 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 * User-level command processor. 13 */ 14 15 #include "less.h" 16 #if MSDOS_COMPILER==WIN32C 17 #include <windows.h> 18 #endif 19 #include "position.h" 20 #include "option.h" 21 #include "cmd.h" 22 23 extern int erase_char, erase2_char, kill_char; 24 extern int sigs; 25 extern int quit_if_one_screen; 26 extern lbool one_screen; 27 extern int sc_width; 28 extern int sc_height; 29 extern char *kent; 30 extern lbool kent_mapped; 31 extern int swindow; 32 extern int jump_sline; 33 extern lbool quitting; 34 extern int wscroll; 35 extern int top_scroll; 36 extern lbool ignore_eoi; 37 extern int hshift; 38 extern int bs_mode; 39 extern int proc_backspace; 40 extern int show_attn; 41 extern int less_is_more; 42 extern int chopline; 43 extern POSITION highest_hilite; 44 extern char *every_first_cmd; 45 extern char version[]; 46 extern struct scrpos initial_scrpos; 47 extern IFILE curr_ifile; 48 extern void *ml_search; 49 extern void *ml_examine; 50 extern int wheel_lines; 51 extern int def_search_type; 52 extern int hilite_target; 53 extern lbool search_wrapped; 54 extern int no_paste; 55 extern lbool pasting; 56 extern int no_edit_warn; 57 extern lbool read_error; 58 extern POSITION soft_eof; 59 extern POSITION search_incr_start; 60 extern char *first_cmd_at_prompt; 61 extern lbool prompting; 62 #if SHELL_ESCAPE || PIPEC 63 extern void *ml_shell; 64 #endif 65 #if EDITOR 66 extern constant char *editproto; 67 #endif 68 #if OSC8_LINK 69 extern char *osc8_uri; 70 #endif 71 extern int shift_count; 72 extern lbool forw_prompt; 73 extern int incr_search; 74 extern lbool full_screen; 75 #if MSDOS_COMPILER==WIN32C 76 extern int utf_mode; 77 extern unsigned less_acp; 78 #endif 79 80 #if SHELL_ESCAPE 81 static char *shellcmd = NULL; /* For holding last shell command for "!!" */ 82 #endif 83 static int mca; /* The multicharacter command (action) */ 84 static int search_type; /* The previous type of search */ 85 static int last_search_type; /* Type of last executed search */ 86 static LINENUM number; /* The number typed by the user */ 87 static long fraction; /* The fractional part of the number */ 88 static struct loption *curropt; 89 static lbool opt_lower; 90 static int optflag; 91 static lbool optgetname; 92 static POSITION toppos; 93 static POSITION bottompos; 94 static int save_hshift; 95 static int save_bs_mode; 96 static int save_proc_backspace; 97 static int screen_trashed_value = 0; 98 static lbool literal_char = FALSE; 99 static lbool ignoring_input = FALSE; 100 static struct scrpos search_incr_pos = { NULL_POSITION, 0 }; 101 static int search_incr_hshift; 102 #if HAVE_TIME 103 static time_type ignoring_input_time; 104 #endif 105 #if PIPEC 106 static char pipec; 107 #endif 108 109 /* Stack of ungotten chars (via ungetcc) */ 110 struct ungot { 111 struct ungot *ug_next; 112 char ug_char; 113 lbool ug_end_command; 114 }; 115 static struct ungot* ungot = NULL; 116 117 static void multi_search(constant char *pattern, int n, int silent); 118 119 /* 120 * Move the cursor to start of prompt line before executing a command. 121 * This looks nicer if the command takes a long time before 122 * updating the screen. 123 */ 124 public void cmd_exec(void) 125 { 126 clear_attn(); 127 clear_bot(); 128 flush(); 129 } 130 131 /* 132 * Indicate we are reading a multi-character command. 133 */ 134 static void set_mca(int action) 135 { 136 mca = action; 137 clear_bot(); 138 clear_cmd(); 139 } 140 141 /* 142 * Indicate we are not reading a multi-character command. 143 */ 144 static void clear_mca(void) 145 { 146 if (mca == 0) 147 return; 148 mca = 0; 149 } 150 151 /* 152 * Set up the display to start a new multi-character command. 153 */ 154 static void start_mca(int action, constant char *prompt, void *mlist, int cmdflags) 155 { 156 set_mca(action); 157 cmd_putstr(prompt); 158 set_mlist(mlist, cmdflags); 159 } 160 161 public lbool in_mca(void) 162 { 163 return (mca != 0 && mca != A_PREFIX); 164 } 165 166 /* 167 * Set up the display to start a new search command. 168 */ 169 static void mca_search1(void) 170 { 171 int i; 172 173 #if HILITE_SEARCH 174 if (search_type & SRCH_FILTER) 175 set_mca(A_FILTER); 176 else 177 #endif 178 if (search_type & SRCH_FORW) 179 set_mca(A_F_SEARCH); 180 else 181 set_mca(A_B_SEARCH); 182 183 if (search_type & SRCH_NO_MATCH) 184 cmd_putstr("Non-match "); 185 if (search_type & SRCH_FIRST_FILE) 186 cmd_putstr("First-file "); 187 if (search_type & SRCH_PAST_EOF) 188 cmd_putstr("EOF-ignore "); 189 if (search_type & SRCH_NO_MOVE) 190 cmd_putstr("Keep-pos "); 191 if (search_type & SRCH_NO_REGEX) 192 cmd_putstr("Regex-off "); 193 if (search_type & SRCH_WRAP) 194 cmd_putstr("Wrap "); 195 for (i = 1; i <= NUM_SEARCH_COLORS; i++) 196 { 197 if (search_type & SRCH_SUBSEARCH(i)) 198 { 199 char buf[INT_STRLEN_BOUND(int)+8]; 200 SNPRINTF1(buf, sizeof(buf), "Sub-%d ", i); 201 cmd_putstr(buf); 202 } 203 } 204 if (literal_char) 205 cmd_putstr("Lit "); 206 207 #if HILITE_SEARCH 208 if (search_type & SRCH_FILTER) 209 cmd_putstr("&/"); 210 else 211 #endif 212 if (search_type & SRCH_FORW) 213 cmd_putstr("/"); 214 else 215 cmd_putstr("?"); 216 forw_prompt = FALSE; 217 } 218 219 static void mca_search(void) 220 { 221 if (incr_search) 222 { 223 /* Remember where the incremental search started. */ 224 get_scrpos(&search_incr_pos, TOP); 225 search_incr_start = search_pos(search_type); 226 search_incr_hshift = hshift; 227 } 228 mca_search1(); 229 set_mlist(ml_search, 0); 230 } 231 232 /* 233 * Set up the display to start a new toggle-option command. 234 */ 235 static void mca_opt_toggle(void) 236 { 237 int no_prompt = (optflag & OPT_NO_PROMPT); 238 int flag = (optflag & ~OPT_NO_PROMPT); 239 constant char *dash = (flag == OPT_NO_TOGGLE) ? "_" : "-"; 240 241 set_mca(A_OPT_TOGGLE); 242 cmd_putstr(dash); 243 if (optgetname) 244 cmd_putstr(dash); 245 if (no_prompt) 246 cmd_putstr("(P)"); 247 switch (flag) 248 { 249 case OPT_UNSET: 250 cmd_putstr("+"); 251 break; 252 case OPT_SET: 253 cmd_putstr("!"); 254 break; 255 } 256 forw_prompt = FALSE; 257 set_mlist(NULL, CF_OPTION); 258 } 259 260 /* 261 * Execute a multicharacter command. 262 */ 263 static void exec_mca(void) 264 { 265 constant char *cbuf; 266 267 cmd_exec(); 268 cbuf = get_cmdbuf(); 269 if (cbuf == NULL) 270 return; 271 272 switch (mca) 273 { 274 case A_F_SEARCH: 275 case A_B_SEARCH: 276 multi_search(cbuf, (int) number, 0); 277 break; 278 #if HILITE_SEARCH 279 case A_FILTER: 280 search_type ^= SRCH_NO_MATCH; 281 set_filter_pattern(cbuf, search_type); 282 soft_eof = NULL_POSITION; 283 break; 284 #endif 285 case A_FIRSTCMD: 286 /* 287 * Skip leading spaces or + signs in the string. 288 */ 289 while (*cbuf == '+' || *cbuf == ' ') 290 cbuf++; 291 if (every_first_cmd != NULL) 292 free(every_first_cmd); 293 if (*cbuf == '\0') 294 every_first_cmd = NULL; 295 else 296 every_first_cmd = save(cbuf); 297 break; 298 case A_OPT_TOGGLE: 299 toggle_option(curropt, opt_lower, cbuf, optflag); 300 curropt = NULL; 301 break; 302 case A_F_BRACKET: 303 match_brac(cbuf[0], cbuf[1], 1, (int) number); 304 break; 305 case A_B_BRACKET: 306 match_brac(cbuf[1], cbuf[0], 0, (int) number); 307 break; 308 #if EXAMINE 309 case A_EXAMINE: { 310 char *p; 311 if (!secure_allow(SF_EXAMINE)) 312 break; 313 p = save(cbuf); 314 edit_list(p); 315 free(p); 316 #if TAGS 317 /* If tag structure is loaded then clean it up. */ 318 cleantags(); 319 #endif 320 break; } 321 #endif 322 #if SHELL_ESCAPE 323 case A_SHELL: { 324 /* 325 * !! just uses whatever is in shellcmd. 326 * Otherwise, copy cmdbuf to shellcmd, 327 * expanding any special characters ("%" or "#"). 328 */ 329 constant char *done_msg = (*cbuf == CONTROL('P')) ? NULL : "!done"; 330 if (done_msg == NULL) 331 ++cbuf; 332 if (*cbuf != '!') 333 { 334 if (shellcmd != NULL) 335 free(shellcmd); 336 shellcmd = fexpand(cbuf); 337 } 338 if (!secure_allow(SF_SHELL)) 339 break; 340 if (shellcmd == NULL) 341 shellcmd = ""; 342 lsystem(shellcmd, done_msg); 343 break; } 344 case A_PSHELL: { 345 constant char *done_msg = (*cbuf == CONTROL('P')) ? NULL : "#done"; 346 if (done_msg == NULL) 347 ++cbuf; 348 if (!secure_allow(SF_SHELL)) 349 break; 350 lsystem(pr_expand(cbuf), done_msg); 351 break; } 352 #endif 353 #if PIPEC 354 case A_PIPE: { 355 constant char *done_msg = (*cbuf == CONTROL('P')) ? NULL : "|done"; 356 if (done_msg == NULL) 357 ++cbuf; 358 if (!secure_allow(SF_PIPE)) 359 break; 360 (void) pipe_mark(pipec, cbuf); 361 if (done_msg != NULL) 362 error(done_msg, NULL_PARG); 363 break; } 364 #endif 365 } 366 } 367 368 /* 369 * Is a character an erase or kill char? 370 */ 371 static lbool is_erase_char(char c) 372 { 373 return (c == erase_char || c == erase2_char || c == kill_char); 374 } 375 376 /* 377 * Is a character a carriage return or newline? 378 */ 379 static lbool is_newline_char(char c) 380 { 381 return (c == '\n' || c == '\r'); 382 } 383 384 /* 385 * Handle the first char of an option (after the initial dash). 386 */ 387 static int mca_opt_first_char(char c) 388 { 389 int no_prompt = (optflag & OPT_NO_PROMPT); 390 int flag = (optflag & ~OPT_NO_PROMPT); 391 if (flag == OPT_NO_TOGGLE) 392 { 393 switch (c) 394 { 395 case '_': 396 /* "__" = long option name. */ 397 optgetname = TRUE; 398 mca_opt_toggle(); 399 return (MCA_MORE); 400 } 401 } else 402 { 403 switch (c) 404 { 405 case '+': 406 /* "-+" = UNSET. */ 407 optflag = no_prompt | ((flag == OPT_UNSET) ? 408 OPT_TOGGLE : OPT_UNSET); 409 mca_opt_toggle(); 410 return (MCA_MORE); 411 case '!': 412 /* "-!" = SET */ 413 optflag = no_prompt | ((flag == OPT_SET) ? 414 OPT_TOGGLE : OPT_SET); 415 mca_opt_toggle(); 416 return (MCA_MORE); 417 case CONTROL('P'): 418 optflag ^= OPT_NO_PROMPT; 419 mca_opt_toggle(); 420 return (MCA_MORE); 421 case '-': 422 /* "--" = long option name. */ 423 optgetname = TRUE; 424 mca_opt_toggle(); 425 return (MCA_MORE); 426 } 427 } 428 /* Char was not handled here. */ 429 return (NO_MCA); 430 } 431 432 /* 433 * Add a char to a long option name. 434 * See if we've got a match for an option name yet. 435 * If so, display the complete name and stop 436 * accepting chars until user hits RETURN. 437 */ 438 static int mca_opt_nonfirst_char(char c) 439 { 440 constant char *p; 441 constant char *oname; 442 lbool ambig; 443 struct loption *was_curropt; 444 445 if (curropt != NULL) 446 { 447 /* Already have a match for the name. */ 448 if (is_erase_char(c)) 449 return (MCA_DONE); 450 /* {{ Checking for TAB here is ugly. 451 * Also doesn't extend well -- can't do BACKTAB this way 452 * because it's a multichar sequence. }} */ 453 if (c != '\t') 454 return (MCA_MORE); 455 } 456 /* 457 * Add char to cmd buffer and try to match 458 * the option name. 459 */ 460 if (cmd_char(c) == CC_QUIT) 461 return (MCA_DONE); 462 p = get_cmdbuf(); 463 if (p == NULL || p[0] == '\0') 464 return (MCA_MORE); 465 opt_lower = ASCII_IS_LOWER(p[0]); 466 was_curropt = curropt; 467 curropt = findopt_name(&p, &oname, &ambig); 468 if (curropt != NULL) 469 { 470 if (was_curropt == NULL) 471 { 472 /* 473 * Got a match. 474 * Remember the option and 475 * display the full option name. 476 */ 477 cmd_reset(); 478 mca_opt_toggle(); 479 cmd_setstring(oname, !opt_lower); 480 } 481 } else if (!ambig) 482 { 483 lbell(); 484 } 485 return (MCA_MORE); 486 } 487 488 /* 489 * Handle a char of an option toggle command. 490 */ 491 static int mca_opt_char(char c) 492 { 493 PARG parg; 494 495 /* 496 * This may be a short option (single char), 497 * or one char of a long option name, 498 * or one char of the option parameter. 499 */ 500 if (curropt == NULL && cmdbuf_empty()) 501 { 502 int ret = mca_opt_first_char(c); 503 if (ret != NO_MCA) 504 return (ret); 505 } 506 if (optgetname) 507 { 508 /* We're getting a long option name. */ 509 if (!is_newline_char(c) && c != '=') 510 return (mca_opt_nonfirst_char(c)); 511 if (curropt == NULL) 512 { 513 parg.p_string = get_cmdbuf(); 514 if (parg.p_string == NULL) 515 return (MCA_MORE); 516 error("There is no --%s option", &parg); 517 return (MCA_DONE); 518 } 519 optgetname = FALSE; 520 cmd_reset(); 521 } else 522 { 523 if (is_erase_char(c)) 524 return (NO_MCA); 525 if (curropt != NULL) 526 /* We're getting the option parameter. */ 527 return (NO_MCA); 528 curropt = findopt(c); 529 if (curropt == NULL) 530 { 531 parg.p_string = propt(c); 532 error("There is no %s option", &parg); 533 return (MCA_DONE); 534 } 535 opt_lower = ASCII_IS_LOWER(c); 536 } 537 /* 538 * If the option which was entered does not take a 539 * parameter, toggle the option immediately, 540 * so user doesn't have to hit RETURN. 541 */ 542 if ((optflag & ~OPT_NO_PROMPT) != OPT_TOGGLE || 543 !opt_has_param(curropt)) 544 { 545 toggle_option(curropt, opt_lower, "", optflag); 546 return (MCA_DONE); 547 } 548 /* 549 * Display a prompt appropriate for the option parameter. 550 */ 551 start_mca(A_OPT_TOGGLE, opt_prompt(curropt), NULL, CF_OPTION); 552 return (MCA_MORE); 553 } 554 555 /* 556 * Normalize search type. 557 */ 558 public int norm_search_type(int st) 559 { 560 /* WRAP and PAST_EOF are mutually exclusive. */ 561 if ((st & (SRCH_PAST_EOF|SRCH_WRAP)) == (SRCH_PAST_EOF|SRCH_WRAP)) 562 st ^= SRCH_PAST_EOF; 563 return st; 564 } 565 566 /* 567 * Handle a char of a search command. 568 */ 569 static int mca_search_char(char c) 570 { 571 int flag = 0; 572 573 /* 574 * Certain characters as the first char of 575 * the pattern have special meaning: 576 * ! Toggle the NO_MATCH flag 577 * * Toggle the PAST_EOF flag 578 * @ Toggle the FIRST_FILE flag 579 */ 580 if (!cmdbuf_empty() || literal_char) 581 { 582 lbool was_literal_char = literal_char; 583 literal_char = FALSE; 584 if (was_literal_char) 585 mca_search1(); 586 return (NO_MCA); 587 } 588 589 switch (c) 590 { 591 case '*': 592 if (less_is_more) 593 break; 594 case CONTROL('E'): /* ignore END of file */ 595 if (mca != A_FILTER) 596 flag = SRCH_PAST_EOF; 597 search_type &= ~SRCH_WRAP; 598 break; 599 case '@': 600 if (less_is_more) 601 break; 602 case CONTROL('F'): /* FIRST file */ 603 if (mca != A_FILTER) 604 flag = SRCH_FIRST_FILE; 605 break; 606 case CONTROL('K'): /* KEEP position */ 607 if (mca != A_FILTER) 608 flag = SRCH_NO_MOVE; 609 break; 610 case CONTROL('S'): { /* SUBSEARCH */ 611 char buf[INT_STRLEN_BOUND(int)+24]; 612 SNPRINTF1(buf, sizeof(buf), "Sub-pattern (1-%d):", NUM_SEARCH_COLORS); 613 clear_bot(); 614 cmd_putstr(buf); 615 flush(); 616 c = getcc(); 617 if (c >= '1' && c <= '0'+NUM_SEARCH_COLORS) 618 flag = SRCH_SUBSEARCH(c-'0'); 619 else 620 flag = -1; /* calls mca_search() below to repaint */ 621 break; } 622 case CONTROL('W'): /* WRAP around */ 623 if (mca != A_FILTER) 624 flag = SRCH_WRAP; 625 break; 626 case CONTROL('R'): /* Don't use REGULAR EXPRESSIONS */ 627 flag = SRCH_NO_REGEX; 628 break; 629 case CONTROL('N'): /* NOT match */ 630 case '!': 631 flag = SRCH_NO_MATCH; 632 break; 633 case CONTROL('L'): 634 literal_char = TRUE; 635 flag = -1; 636 break; 637 } 638 639 if (flag != 0) 640 { 641 if (flag != -1) 642 search_type = norm_search_type(search_type ^ flag); 643 mca_search(); 644 return (MCA_MORE); 645 } 646 return (NO_MCA); 647 } 648 649 /* 650 * Jump back to the starting position of an incremental search. 651 */ 652 static void jump_search_incr_pos(void) 653 { 654 if (search_incr_pos.pos == NULL_POSITION) 655 return; 656 hshift = search_incr_hshift; 657 jump_loc(search_incr_pos.pos, search_incr_pos.ln); 658 } 659 660 /* 661 * Handle a character of a multi-character command. 662 */ 663 static int mca_char(char c) 664 { 665 int ret; 666 667 switch (mca) 668 { 669 case 0: 670 /* 671 * We're not in a multicharacter command. 672 */ 673 return (NO_MCA); 674 675 case A_PREFIX: 676 /* 677 * In the prefix of a command. 678 * This not considered a multichar command 679 * (even tho it uses cmdbuf, etc.). 680 * It is handled in the commands() switch. 681 */ 682 return (NO_MCA); 683 684 case A_DIGIT: 685 /* 686 * Entering digits of a number. 687 * Terminated by a non-digit. 688 */ 689 if ((c >= '0' && c <= '9') || c == '.') 690 break; 691 switch (editchar(c, ECF_PEEK|ECF_NOHISTORY|ECF_NOCOMPLETE|ECF_NORIGHTLEFT)) 692 { 693 case A_NOACTION: 694 /* 695 * Ignore this char and get another one. 696 */ 697 return (MCA_MORE); 698 case A_INVALID: 699 /* 700 * Not part of the number. 701 * End the number and treat this char 702 * as a normal command character. 703 */ 704 number = cmd_int(&fraction); 705 clear_mca(); 706 cmd_accept(); 707 return (NO_MCA); 708 } 709 break; 710 711 case A_OPT_TOGGLE: 712 ret = mca_opt_char(c); 713 if (ret != NO_MCA) 714 return (ret); 715 break; 716 717 case A_F_SEARCH: 718 case A_B_SEARCH: 719 case A_FILTER: 720 ret = mca_search_char(c); 721 if (ret != NO_MCA) 722 return (ret); 723 break; 724 725 default: 726 /* Other multicharacter command. */ 727 break; 728 } 729 730 /* 731 * The multichar command is terminated by a newline. 732 */ 733 if (is_newline_char(c)) 734 { 735 if (pasting && no_paste) 736 { 737 /* Ignore pasted input after (and including) the first newline */ 738 start_ignoring_input(); 739 return (MCA_MORE); 740 } 741 /* Execute the command. */ 742 exec_mca(); 743 return (MCA_DONE); 744 } 745 746 /* 747 * Append the char to the command buffer. 748 */ 749 if (cmd_char(c) == CC_QUIT) 750 /* 751 * Abort the multi-char command. 752 */ 753 return (MCA_DONE); 754 755 switch (mca) 756 { 757 case A_F_BRACKET: 758 case A_B_BRACKET: 759 if (len_cmdbuf() >= 2) 760 { 761 /* 762 * Special case for the bracket-matching commands. 763 * Execute the command after getting exactly two 764 * characters from the user. 765 */ 766 exec_mca(); 767 return (MCA_DONE); 768 } 769 break; 770 case A_F_SEARCH: 771 case A_B_SEARCH: 772 if (incr_search) 773 { 774 /* Incremental search: do a search after every input char. */ 775 int st = (search_type & (SRCH_FORW|SRCH_BACK|SRCH_NO_MATCH|SRCH_NO_REGEX|SRCH_NO_MOVE|SRCH_WRAP|SRCH_SUBSEARCH_ALL)); 776 ssize_t save_updown; 777 constant char *pattern = get_cmdbuf(); 778 if (pattern == NULL) 779 return (MCA_MORE); 780 /* Defer searching if more chars of the pattern are available. */ 781 if (ttyin_ready()) 782 return (MCA_MORE); 783 /* 784 * Must save updown_match because mca_search 785 * reinits it. That breaks history scrolling. 786 * {{ This is ugly. mca_search probably shouldn't call set_mlist. }} 787 */ 788 save_updown = save_updown_match(); 789 cmd_exec(); 790 if (*pattern == '\0') 791 { 792 /* User has backspaced to an empty pattern. */ 793 undo_search(TRUE); 794 jump_search_incr_pos(); 795 } else 796 { 797 if (search(st | SRCH_INCR, pattern, 1) != 0) 798 { 799 /* No match, invalid pattern, etc. */ 800 undo_search(TRUE); 801 jump_search_incr_pos(); 802 } 803 } 804 /* Redraw the search prompt and search string. */ 805 if (is_screen_trashed() || !full_screen) 806 { 807 lclear(); 808 repaint(); 809 } 810 mca_search1(); 811 restore_updown_match(save_updown); 812 cmd_repaint(NULL); 813 } 814 break; 815 } 816 817 /* 818 * Need another character. 819 */ 820 return (MCA_MORE); 821 } 822 823 /* 824 * Discard any buffered file data. 825 */ 826 static void clear_buffers(void) 827 { 828 if (!(ch_getflags() & CH_CANSEEK)) 829 return; 830 ch_flush(); 831 clr_linenum(); 832 #if HILITE_SEARCH 833 clr_hilite(); 834 #endif 835 set_line_contig_pos(NULL_POSITION); 836 } 837 838 public void screen_trashed_num(int trashed) 839 { 840 screen_trashed_value = trashed; 841 } 842 843 public void screen_trashed(void) 844 { 845 screen_trashed_num(1); 846 } 847 848 public int is_screen_trashed(void) 849 { 850 return screen_trashed_value; 851 } 852 853 /* 854 * Make sure the screen is displayed. 855 */ 856 static void make_display(void) 857 { 858 /* 859 * If nothing is displayed yet, display starting from initial_scrpos. 860 */ 861 if (empty_screen()) 862 { 863 if (initial_scrpos.pos == NULL_POSITION) 864 jump_loc(ch_zero(), 1); 865 else 866 jump_loc(initial_scrpos.pos, initial_scrpos.ln); 867 } else if (is_screen_trashed() || !full_screen) 868 { 869 int save_top_scroll = top_scroll; 870 lbool save_ignore_eoi = ignore_eoi; 871 top_scroll = 1; 872 ignore_eoi = FALSE; 873 if (is_screen_trashed() == 2) 874 { 875 /* Special case used by ignore_eoi: re-open the input file 876 * and jump to the end of the file. */ 877 reopen_curr_ifile(); 878 jump_forw(); 879 } 880 repaint(); 881 top_scroll = save_top_scroll; 882 ignore_eoi = save_ignore_eoi; 883 } 884 } 885 886 /* 887 * Display any message that needs to be displayed before the prompt. 888 */ 889 static void prompt_message(void) 890 { 891 if (read_error) 892 { 893 error("read error", NULL_PARG); 894 read_error = FALSE; 895 } 896 if (search_wrapped) 897 { 898 if (search_type & SRCH_BACK) 899 error("Search hit top; continuing at bottom", NULL_PARG); 900 else 901 error("Search hit bottom; continuing at top", NULL_PARG); 902 search_wrapped = FALSE; 903 } 904 #if OSC8_LINK 905 if (osc8_uri != NULL) 906 { 907 PARG parg; 908 parg.p_string = osc8_uri; 909 error("Link: %s", &parg); 910 free(osc8_uri); 911 osc8_uri = NULL; 912 } 913 #endif 914 } 915 916 /* 917 * Display the appropriate prompt. 918 */ 919 static void prompt(void) 920 { 921 constant char *p; 922 int attr; 923 #if MSDOS_COMPILER==WIN32C 924 WCHAR w[MAX_PATH*2]; 925 char a[MAX_PATH*2]; 926 #endif 927 928 if (ungot != NULL && !ungot->ug_end_command) 929 { 930 /* 931 * No prompt necessary if commands are from 932 * ungotten chars rather than from the user. 933 */ 934 return; 935 } 936 937 /* 938 * Make sure the screen is displayed. 939 */ 940 make_display(); 941 942 if (hilite_target) 943 draw_target_attn(TRUE); /* Redraw target line for --hilite-target. */ 944 toppos = position(TOP); 945 bottompos = position(BOTTOM_PLUS_ONE); 946 947 /* 948 * If we've hit EOF on the last file and the -E flag is set, quit. 949 */ 950 if (get_quit_at_eof() == OPT_ONPLUS && 951 eof_displayed(FALSE) && !(ch_getflags() & CH_HELPFILE) && 952 next_ifile(curr_ifile) == NULL_IFILE) 953 quit(QUIT_OK); 954 955 /* 956 * If the entire file is displayed and the -F flag is set, quit. 957 */ 958 if (quit_if_one_screen && 959 entire_file_displayed() && !(ch_getflags() & CH_HELPFILE) && 960 next_ifile(curr_ifile) == NULL_IFILE) 961 quit(QUIT_OK); 962 quit_if_one_screen = 0; /* only get one chance at this */ 963 if (first_cmd_at_prompt != NULL) 964 { 965 ungetsc(first_cmd_at_prompt); 966 first_cmd_at_prompt = NULL; 967 return; 968 } 969 970 #if MSDOS_COMPILER==WIN32C 971 /* 972 * In Win32, display the file name in the window title. 973 * {{ Seems like this should be done in edit_ifile, not on every prompt. }} 974 */ 975 if (!(ch_getflags() & CH_HELPFILE)) 976 { 977 WCHAR w[MAX_PATH+16]; 978 p = pr_expand("Less?f - %f."); 979 MultiByteToWideChar(less_acp, 0, p, -1, w, countof(w)); 980 SetConsoleTitleW(w); 981 } 982 #endif 983 984 /* 985 * Select the proper prompt and display it. 986 */ 987 /* 988 * If the previous action was a forward movement, 989 * don't clear the bottom line of the display; 990 * just print the prompt since the forward movement guarantees 991 * that we're in the right position to display the prompt. 992 * Clearing the line could cause a problem: for example, if the last 993 * line displayed ended at the right screen edge without a newline, 994 * then clearing would clear the last displayed line rather than 995 * the prompt line. 996 */ 997 if (!forw_prompt) 998 clear_bot(); 999 clear_cmd(); 1000 forw_prompt = FALSE; 1001 prompt_message(); 1002 /* We called make_display above, but if prompt_message displayed 1003 * a message longer than the screen width, we may have trashed 1004 * the screen and need to call make_display again. */ 1005 if (is_screen_trashed()) 1006 make_display(); 1007 p = pr_string(); 1008 if (p == NULL || *p == '\0') 1009 { 1010 p = ":"; 1011 attr = AT_NORMAL|AT_COLOR_PROMPT; 1012 } else 1013 { 1014 attr = AT_STANDOUT|AT_COLOR_PROMPT; 1015 #if MSDOS_COMPILER==WIN32C 1016 MultiByteToWideChar(less_acp, 0, p, -1, w, countof(w)); 1017 WideCharToMultiByte(utf_mode ? CP_UTF8 : GetConsoleOutputCP(), 1018 0, w, -1, a, sizeof(a), NULL, NULL); 1019 p = a; 1020 #endif 1021 } 1022 #if HILITE_SEARCH 1023 if (is_filtering()) 1024 { 1025 constant char *amp = "& "; 1026 load_line(p, attr, strlen(amp)+1); 1027 putstr(amp); 1028 } else 1029 #endif 1030 { 1031 load_line(p, attr, 1); 1032 } 1033 put_line(FALSE); 1034 clear_eol(); 1035 resume_screen(); 1036 prompting = TRUE; 1037 } 1038 1039 /* 1040 * Display the less version message. 1041 */ 1042 public void dispversion(void) 1043 { 1044 PARG parg; 1045 1046 parg.p_string = version; 1047 error("less %s", &parg); 1048 } 1049 1050 /* 1051 * Return a character to complete a partial command, if possible. 1052 */ 1053 static char getcc_end_command(void) 1054 { 1055 int ch; 1056 switch (mca) 1057 { 1058 case A_DIGIT: 1059 /* We have a number but no command. Treat as #g. */ 1060 return ('g'); 1061 case A_F_SEARCH: 1062 case A_B_SEARCH: 1063 case A_FILTER: 1064 /* We have "/string" but no newline. Add the \n. */ 1065 return ('\n'); 1066 default: 1067 /* Some other incomplete command. Let user complete it. */ 1068 if (ungot != NULL) 1069 return ('\0'); 1070 ch = getchr(); 1071 if (ch < 0) ch = '\0'; 1072 return (char) ch; 1073 } 1074 } 1075 1076 /* 1077 * Get a command character from the ungotten stack. 1078 */ 1079 static char get_ungot(lbool *p_end_command) 1080 { 1081 struct ungot *ug = ungot; 1082 char c = ug->ug_char; 1083 if (p_end_command != NULL) 1084 *p_end_command = ug->ug_end_command; 1085 ungot = ug->ug_next; 1086 free(ug); 1087 return c; 1088 } 1089 1090 /* 1091 * Delete all ungotten characters. 1092 */ 1093 public void getcc_clear(void) 1094 { 1095 while (ungot != NULL) 1096 (void) get_ungot(NULL); 1097 } 1098 1099 /* 1100 * Get command character. 1101 * The character normally comes from the keyboard, 1102 * but may come from ungotten characters 1103 * (characters previously given to ungetcc or ungetsc). 1104 */ 1105 static char getccu(void) 1106 { 1107 int c = 0; 1108 while (c == 0 && sigs == 0) 1109 { 1110 if (ungot == NULL) 1111 { 1112 /* Normal case: no ungotten chars. 1113 * Get char from the user. */ 1114 c = getchr(); 1115 if (c < 0) c = '\0'; 1116 } else 1117 { 1118 /* Ungotten chars available: 1119 * Take the top of stack (most recent). */ 1120 lbool end_command; 1121 c = get_ungot(&end_command); 1122 if (end_command) 1123 c = getcc_end_command(); 1124 } 1125 } 1126 return ((char) c); 1127 } 1128 1129 /* 1130 * Get a command character, but if we receive the orig sequence, 1131 * convert it to the repl sequence. 1132 */ 1133 static char getcc_repl(char constant *orig, char constant *repl, char (*gr_getc)(void), void (*gr_ungetc)(char)) 1134 { 1135 char c; 1136 char keys[16]; 1137 size_t ki = 0; 1138 1139 c = (*gr_getc)(); 1140 if (orig == NULL || orig[0] == '\0') 1141 return c; 1142 for (;;) 1143 { 1144 keys[ki] = c; 1145 if (c != orig[ki] || ki >= sizeof(keys)-1) 1146 { 1147 /* This is not orig we have been receiving. 1148 * If we have stashed chars in keys[], 1149 * unget them and return the first one. */ 1150 while (ki > 0) 1151 (*gr_ungetc)(keys[ki--]); 1152 return keys[0]; 1153 } 1154 if (orig[++ki] == '\0') 1155 { 1156 /* We've received the full orig sequence. 1157 * Return the repl sequence. */ 1158 ki = strlen(repl)-1; 1159 while (ki > 0) 1160 (*gr_ungetc)(repl[ki--]); 1161 return repl[0]; 1162 } 1163 /* We've received a partial orig sequence (ki chars of it). 1164 * Get next char and see if it continues to match orig. */ 1165 c = (*gr_getc)(); 1166 } 1167 } 1168 1169 /* 1170 * Get command character. 1171 */ 1172 public char getcc(void) 1173 { 1174 /* Replace kent (keypad Enter) with a newline. 1175 * However don't do this if kent is mapped to a command via lesskey. */ 1176 return getcc_repl(kent_mapped ? NULL : kent, "\n", getccu, ungetcc); 1177 } 1178 1179 /* 1180 * "Unget" a command character. 1181 * The next getcc() will return this character. 1182 */ 1183 public void ungetcc(char c) 1184 { 1185 struct ungot *ug = (struct ungot *) ecalloc(1, sizeof(struct ungot)); 1186 1187 ug->ug_char = c; 1188 ug->ug_next = ungot; 1189 ungot = ug; 1190 } 1191 1192 /* 1193 * "Unget" a command character. 1194 * If any other chars are already ungotten, put this one after those. 1195 */ 1196 static void ungetcc_back1(char c, lbool end_command) 1197 { 1198 struct ungot *ug = (struct ungot *) ecalloc(1, sizeof(struct ungot)); 1199 ug->ug_char = c; 1200 ug->ug_end_command = end_command; 1201 ug->ug_next = NULL; 1202 if (ungot == NULL) 1203 ungot = ug; 1204 else 1205 { 1206 struct ungot *pu; 1207 for (pu = ungot; pu->ug_next != NULL; pu = pu->ug_next) 1208 continue; 1209 pu->ug_next = ug; 1210 } 1211 } 1212 1213 public void ungetcc_back(char c) 1214 { 1215 ungetcc_back1(c, FALSE); 1216 } 1217 1218 public void ungetcc_end_command(void) 1219 { 1220 ungetcc_back1('\0', TRUE); 1221 } 1222 1223 /* 1224 * Unget a whole string of command characters. 1225 * The next sequence of getcc()'s will return this string. 1226 */ 1227 public void ungetsc(constant char *s) 1228 { 1229 while (*s != '\0') 1230 ungetcc_back(*s++); 1231 } 1232 1233 /* 1234 * Peek the next command character, without consuming it. 1235 */ 1236 public char peekcc(void) 1237 { 1238 char c = getcc(); 1239 ungetcc(c); 1240 return c; 1241 } 1242 1243 /* 1244 * Search for a pattern, possibly in multiple files. 1245 * If SRCH_FIRST_FILE is set, begin searching at the first file. 1246 * If SRCH_PAST_EOF is set, continue the search thru multiple files. 1247 */ 1248 static void multi_search(constant char *pattern, int n, int silent) 1249 { 1250 int nomore; 1251 IFILE save_ifile; 1252 lbool changed_file; 1253 1254 changed_file = FALSE; 1255 save_ifile = save_curr_ifile(); 1256 1257 if ((search_type & (SRCH_FORW|SRCH_BACK)) == 0) 1258 search_type |= SRCH_FORW; 1259 if (search_type & SRCH_FIRST_FILE) 1260 { 1261 /* 1262 * Start at the first (or last) file 1263 * in the command line list. 1264 */ 1265 if (search_type & SRCH_FORW) 1266 nomore = edit_first(); 1267 else 1268 nomore = edit_last(); 1269 if (nomore) 1270 { 1271 unsave_ifile(save_ifile); 1272 return; 1273 } 1274 changed_file = TRUE; 1275 search_type &= ~SRCH_FIRST_FILE; 1276 } 1277 1278 for (;;) 1279 { 1280 n = search(search_type, pattern, n); 1281 /* 1282 * The SRCH_NO_MOVE flag doesn't "stick": it gets cleared 1283 * after being used once. This allows "n" to work after 1284 * using a /@@ search. 1285 */ 1286 search_type &= ~SRCH_NO_MOVE; 1287 last_search_type = search_type; 1288 if (n == 0) 1289 { 1290 /* 1291 * Found it. 1292 */ 1293 unsave_ifile(save_ifile); 1294 return; 1295 } 1296 1297 if (n < 0) 1298 /* 1299 * Some kind of error in the search. 1300 * Error message has been printed by search(). 1301 */ 1302 break; 1303 1304 if ((search_type & SRCH_PAST_EOF) == 0) 1305 /* 1306 * We didn't find a match, but we're 1307 * supposed to search only one file. 1308 */ 1309 break; 1310 /* 1311 * Move on to the next file. 1312 */ 1313 if (search_type & SRCH_FORW) 1314 nomore = edit_next(1); 1315 else 1316 nomore = edit_prev(1); 1317 if (nomore) 1318 break; 1319 changed_file = TRUE; 1320 } 1321 1322 /* 1323 * Didn't find it. 1324 * Print an error message if we haven't already. 1325 */ 1326 if (n > 0 && !silent) 1327 { 1328 PARG parg; 1329 parg.p_string = prev_pattern_text(); 1330 if (parg.p_string == NULL) /* {{ can this happen? }} */ 1331 error("Pattern not found", NULL_PARG); 1332 else 1333 error("Pattern not found: %s", &parg); 1334 } 1335 1336 if (changed_file) 1337 { 1338 /* 1339 * Restore the file we were originally viewing. 1340 */ 1341 reedit_ifile(save_ifile); 1342 } else 1343 { 1344 unsave_ifile(save_ifile); 1345 } 1346 } 1347 1348 /* 1349 * Forward forever, or until a highlighted line appears. 1350 */ 1351 static int forw_loop(int action) 1352 { 1353 POSITION prev_hilite; 1354 1355 if (ch_getflags() & CH_HELPFILE) 1356 return (A_NOACTION); 1357 1358 cmd_exec(); 1359 jump_forw_buffered(); 1360 highest_hilite = prev_hilite = 0; 1361 ignore_eoi = TRUE; 1362 while (!sigs) 1363 { 1364 if (action != A_F_FOREVER && highest_hilite > prev_hilite) 1365 { 1366 lbell(); 1367 if (action == A_F_UNTIL_HILITE) 1368 break; 1369 prev_hilite = highest_hilite; 1370 } 1371 make_display(); 1372 forward(1, FALSE, FALSE, FALSE); 1373 } 1374 highest_hilite = NULL_POSITION; 1375 ignore_eoi = FALSE; 1376 ch_set_eof(); 1377 1378 /* 1379 * This gets us back in "F mode" after processing 1380 * a non-abort signal (e.g. window-change). 1381 */ 1382 if (sigs && !ABORT_SIGS()) 1383 return (action); 1384 1385 return (A_NOACTION); 1386 } 1387 1388 /* 1389 * Ignore subsequent (pasted) input chars. 1390 */ 1391 public void start_ignoring_input(void) 1392 { 1393 ignoring_input = TRUE; 1394 #if HAVE_TIME 1395 ignoring_input_time = get_time(); 1396 #endif 1397 } 1398 1399 /* 1400 * Stop ignoring input chars. 1401 */ 1402 public void stop_ignoring_input(void) 1403 { 1404 ignoring_input = FALSE; 1405 pasting = FALSE; 1406 } 1407 1408 /* 1409 * Are we ignoring input chars? 1410 */ 1411 public lbool is_ignoring_input(int action) 1412 { 1413 if (!ignoring_input) 1414 return FALSE; 1415 if (action == A_END_PASTE) 1416 stop_ignoring_input(); 1417 #if HAVE_TIME 1418 if (get_time() >= ignoring_input_time + MAX_PASTE_IGNORE_SEC) 1419 stop_ignoring_input(); 1420 #endif 1421 /* 1422 * Don't ignore prefix chars so we can parse a full command 1423 * (which might be A_END_PASTE). 1424 */ 1425 return (action != A_PREFIX); 1426 } 1427 1428 /* 1429 * Main command processor. 1430 * Accept and execute commands until a quit command. 1431 */ 1432 public void commands(void) 1433 { 1434 char c; 1435 int action; 1436 constant char *cbuf; 1437 constant char *msg; 1438 int newaction; 1439 int save_jump_sline; 1440 int save_search_type; 1441 constant char *extra; 1442 PARG parg; 1443 IFILE old_ifile; 1444 IFILE new_ifile; 1445 #if TAGS 1446 constant char *tagfile; 1447 #endif 1448 1449 search_type = SRCH_FORW; 1450 newaction = A_NOACTION; 1451 1452 for (;;) 1453 { 1454 clear_mca(); 1455 cmd_accept(); 1456 number = 0; 1457 curropt = NULL; 1458 1459 /* 1460 * See if any signals need processing. 1461 */ 1462 if (sigs) 1463 { 1464 psignals(); 1465 if (quitting) 1466 quit(QUIT_SAVED_STATUS); 1467 } 1468 1469 /* 1470 * See if window size changed, for systems that don't 1471 * generate SIGWINCH. 1472 */ 1473 check_winch(); 1474 1475 /* 1476 * Display prompt and accept a character. 1477 */ 1478 cmd_reset(); 1479 prompt(); 1480 if (sigs) 1481 continue; 1482 if (newaction == A_NOACTION) 1483 c = getcc(); 1484 1485 again: 1486 if (c == READ_AGAIN) 1487 continue; 1488 if (sigs) 1489 continue; 1490 1491 if (newaction != A_NOACTION) 1492 { 1493 action = newaction; 1494 newaction = A_NOACTION; 1495 } else 1496 { 1497 /* 1498 * If we are in a multicharacter command, call mca_char. 1499 * Otherwise we call fcmd_decode to determine the 1500 * action to be performed. 1501 */ 1502 if (mca) 1503 switch (mca_char(c)) 1504 { 1505 case MCA_MORE: 1506 /* 1507 * Need another character. 1508 */ 1509 c = getcc(); 1510 goto again; 1511 case MCA_DONE: 1512 /* 1513 * Command has been handled by mca_char. 1514 * Start clean with a prompt. 1515 */ 1516 continue; 1517 case NO_MCA: 1518 /* 1519 * Not a multi-char command 1520 * (at least, not anymore). 1521 */ 1522 break; 1523 } 1524 1525 /* 1526 * Decode the command character and decide what to do. 1527 */ 1528 extra = NULL; 1529 if (mca) 1530 { 1531 /* 1532 * We're in a multichar command. 1533 * Add the character to the command buffer 1534 * and display it on the screen. 1535 * If the user backspaces past the start 1536 * of the line, abort the command. 1537 */ 1538 if (cmd_char(c) == CC_QUIT || cmdbuf_empty()) 1539 continue; 1540 cbuf = get_cmdbuf(); 1541 if (cbuf == NULL) 1542 { 1543 c = getcc(); 1544 goto again; 1545 } 1546 action = fcmd_decode(cbuf, &extra); 1547 } else 1548 { 1549 /* 1550 * Don't use cmd_char if we're starting fresh 1551 * at the beginning of a command, because we 1552 * don't want to echo the command until we know 1553 * it is a multichar command. We also don't 1554 * want erase_char/kill_char to be treated 1555 * as line editing characters. 1556 */ 1557 char tbuf[2]; 1558 tbuf[0] = c; 1559 tbuf[1] = '\0'; 1560 action = fcmd_decode(tbuf, &extra); 1561 } 1562 /* 1563 * If an "extra" string was returned, 1564 * process it as a string of command characters. 1565 */ 1566 if (extra != NULL) 1567 ungetsc(extra); 1568 } 1569 /* 1570 * Clear the cmdbuf string. 1571 * (But not if we're in the prefix of a command, 1572 * because the partial command string is kept there.) 1573 */ 1574 if (action != A_PREFIX) 1575 cmd_reset(); 1576 1577 if (is_ignoring_input(action)) 1578 continue; 1579 1580 switch (action) 1581 { 1582 case A_START_PASTE: 1583 if (no_paste) 1584 start_ignoring_input(); 1585 break; 1586 1587 case A_DIGIT: 1588 /* 1589 * First digit of a number. 1590 */ 1591 start_mca(A_DIGIT, ":", NULL, CF_QUIT_ON_ERASE); 1592 goto again; 1593 1594 case A_F_WINDOW: 1595 /* 1596 * Forward one window (and set the window size). 1597 */ 1598 if (number > 0) 1599 swindow = (int) number; 1600 /* FALLTHRU */ 1601 case A_F_SCREEN: 1602 /* 1603 * Forward one screen. 1604 */ 1605 if (number <= 0) 1606 number = get_swindow(); 1607 cmd_exec(); 1608 if (show_attn) 1609 set_attnpos(bottompos); 1610 forward((int) number, FALSE, TRUE, FALSE); 1611 break; 1612 1613 case A_B_WINDOW: 1614 /* 1615 * Backward one window (and set the window size). 1616 */ 1617 if (number > 0) 1618 swindow = (int) number; 1619 /* FALLTHRU */ 1620 case A_B_SCREEN: 1621 /* 1622 * Backward one screen. 1623 */ 1624 if (number <= 0) 1625 number = get_swindow(); 1626 cmd_exec(); 1627 if (show_attn && toppos != NULL_POSITION && toppos > ch_zero()) 1628 set_attnpos(toppos-1); 1629 backward((int) number, FALSE, TRUE, FALSE); 1630 break; 1631 1632 case A_F_LINE: 1633 case A_F_NEWLINE: 1634 /* 1635 * Forward N (default 1) line. 1636 */ 1637 if (number <= 0) 1638 number = 1; 1639 cmd_exec(); 1640 if (show_attn == OPT_ONPLUS && number > 1) 1641 set_attnpos(bottompos); 1642 forward((int) number, FALSE, FALSE, action == A_F_NEWLINE && !chopline); 1643 break; 1644 1645 case A_B_LINE: 1646 case A_B_NEWLINE: 1647 /* 1648 * Backward N (default 1) line. 1649 */ 1650 if (number <= 0) 1651 number = 1; 1652 cmd_exec(); 1653 if (show_attn == OPT_ONPLUS && number > 1 && toppos != NULL_POSITION && toppos > ch_zero()) 1654 set_attnpos(toppos-1); 1655 backward((int) number, FALSE, FALSE, action == A_B_NEWLINE && !chopline); 1656 break; 1657 1658 case A_F_MOUSE: 1659 /* 1660 * Forward wheel_lines lines. 1661 */ 1662 cmd_exec(); 1663 forward(wheel_lines, FALSE, FALSE, FALSE); 1664 break; 1665 1666 case A_B_MOUSE: 1667 /* 1668 * Backward wheel_lines lines. 1669 */ 1670 cmd_exec(); 1671 backward(wheel_lines, FALSE, FALSE, FALSE); 1672 break; 1673 1674 case A_L_MOUSE: 1675 /* 1676 * Left wheel_lines columns. 1677 */ 1678 cmd_exec(); 1679 pos_rehead(FALSE); 1680 if (wheel_lines > hshift) 1681 hshift = 0; 1682 else 1683 hshift -= wheel_lines; 1684 screen_trashed(); 1685 break; 1686 1687 case A_R_MOUSE: 1688 /* 1689 * Right wheel_lines columns. 1690 */ 1691 cmd_exec(); 1692 pos_rehead(FALSE); 1693 hshift += wheel_lines; 1694 screen_trashed(); 1695 break; 1696 1697 case A_FF_LINE: 1698 /* 1699 * Force forward N (default 1) line. 1700 */ 1701 if (number <= 0) 1702 number = 1; 1703 cmd_exec(); 1704 if (show_attn == OPT_ONPLUS && number > 1) 1705 set_attnpos(bottompos); 1706 forward((int) number, TRUE, FALSE, FALSE); 1707 break; 1708 1709 case A_BF_LINE: 1710 /* 1711 * Force backward N (default 1) line. 1712 */ 1713 if (number <= 0) 1714 number = 1; 1715 cmd_exec(); 1716 if (show_attn == OPT_ONPLUS && number > 1 && toppos != NULL_POSITION && toppos > ch_zero()) 1717 set_attnpos(toppos-1); 1718 backward((int) number, TRUE, FALSE, FALSE); 1719 break; 1720 1721 case A_FF_SCREEN: 1722 /* 1723 * Force forward one screen. 1724 */ 1725 if (number <= 0) 1726 number = get_swindow(); 1727 cmd_exec(); 1728 if (show_attn == OPT_ONPLUS) 1729 set_attnpos(bottompos); 1730 forward((int) number, TRUE, FALSE, FALSE); 1731 break; 1732 1733 case A_BF_SCREEN: 1734 /* 1735 * Force backward one screen. 1736 */ 1737 if (number <= 0) 1738 number = get_swindow(); 1739 cmd_exec(); 1740 if (show_attn == OPT_ONPLUS && toppos != NULL_POSITION && toppos > ch_zero()) 1741 set_attnpos(toppos-1); 1742 backward((int) number, TRUE, FALSE, FALSE); 1743 break; 1744 1745 case A_F_FOREVER: 1746 case A_F_FOREVER_BELL: 1747 case A_F_UNTIL_HILITE: 1748 /* 1749 * Forward forever, ignoring EOF. 1750 */ 1751 if (get_altfilename(curr_ifile) != NULL) 1752 error("Warning: command may not work correctly when file is viewed via LESSOPEN", NULL_PARG); 1753 if (show_attn) 1754 set_attnpos(bottompos); 1755 newaction = forw_loop(action); 1756 break; 1757 1758 case A_F_SCROLL: 1759 /* 1760 * Forward N lines 1761 * (default same as last 'd' or 'u' command). 1762 */ 1763 if (number > 0) 1764 wscroll = (int) number; 1765 cmd_exec(); 1766 if (show_attn == OPT_ONPLUS) 1767 set_attnpos(bottompos); 1768 forward(wscroll, FALSE, FALSE, FALSE); 1769 break; 1770 1771 case A_B_SCROLL: 1772 /* 1773 * Forward N lines 1774 * (default same as last 'd' or 'u' command). 1775 */ 1776 if (number > 0) 1777 wscroll = (int) number; 1778 cmd_exec(); 1779 if (show_attn == OPT_ONPLUS && toppos != NULL_POSITION && toppos > ch_zero()) 1780 set_attnpos(toppos-1); 1781 backward(wscroll, FALSE, FALSE, FALSE); 1782 break; 1783 1784 case A_FREPAINT: 1785 /* 1786 * Flush buffers, then repaint screen. 1787 * Don't flush the buffers on a pipe! 1788 */ 1789 clear_buffers(); 1790 /* FALLTHRU */ 1791 case A_REPAINT: 1792 /* 1793 * Repaint screen. 1794 */ 1795 cmd_exec(); 1796 repaint(); 1797 break; 1798 1799 case A_GOLINE: 1800 /* 1801 * Go to line N, default beginning of file. 1802 * If N <= 0, ignore jump_sline in order to avoid 1803 * empty lines before the beginning of the file. 1804 */ 1805 save_jump_sline = jump_sline; 1806 if (number <= 0) 1807 { 1808 number = 1; 1809 jump_sline = 0; 1810 } 1811 cmd_exec(); 1812 jump_back(number); 1813 jump_sline = save_jump_sline; 1814 break; 1815 1816 case A_PERCENT: 1817 /* 1818 * Go to a specified percentage into the file. 1819 */ 1820 if (number < 0) 1821 { 1822 number = 0; 1823 fraction = 0; 1824 } 1825 if (number > 100 || (number == 100 && fraction != 0)) 1826 { 1827 number = 100; 1828 fraction = 0; 1829 } 1830 cmd_exec(); 1831 jump_percent((int) number, fraction); 1832 break; 1833 1834 case A_GOEND: 1835 /* 1836 * Go to line N, default end of file. 1837 */ 1838 cmd_exec(); 1839 if (number <= 0) 1840 jump_forw(); 1841 else 1842 jump_back(number); 1843 break; 1844 1845 case A_GOEND_BUF: 1846 /* 1847 * Go to line N, default last buffered byte. 1848 */ 1849 cmd_exec(); 1850 if (number <= 0) 1851 jump_forw_buffered(); 1852 else 1853 jump_back(number); 1854 break; 1855 1856 case A_GOPOS: 1857 /* 1858 * Go to a specified byte position in the file. 1859 */ 1860 cmd_exec(); 1861 if (number < 0) 1862 number = 0; 1863 jump_line_loc((POSITION) number, jump_sline); 1864 break; 1865 1866 case A_STAT: 1867 /* 1868 * Print file name, etc. 1869 */ 1870 if (ch_getflags() & CH_HELPFILE) 1871 break; 1872 cmd_exec(); 1873 parg.p_string = eq_message(); 1874 error("%s", &parg); 1875 break; 1876 1877 case A_VERSION: 1878 /* 1879 * Print version number. 1880 */ 1881 cmd_exec(); 1882 dispversion(); 1883 break; 1884 1885 case A_QUIT: 1886 /* 1887 * Exit. 1888 */ 1889 if (curr_ifile != NULL_IFILE && 1890 ch_getflags() & CH_HELPFILE) 1891 { 1892 /* 1893 * Quit while viewing the help file 1894 * just means return to viewing the 1895 * previous file. 1896 */ 1897 hshift = save_hshift; 1898 bs_mode = save_bs_mode; 1899 proc_backspace = save_proc_backspace; 1900 if (edit_prev(1) == 0) 1901 break; 1902 } 1903 if (extra != NULL) 1904 quit(*extra); 1905 quit(QUIT_OK); 1906 break; 1907 1908 /* 1909 * Define abbreviation for a commonly used sequence below. 1910 */ 1911 #define DO_SEARCH() \ 1912 if (number <= 0) number = 1; \ 1913 mca_search(); \ 1914 cmd_exec(); \ 1915 multi_search(NULL, (int) number, 0); 1916 1917 case A_F_SEARCH: 1918 /* 1919 * Search forward for a pattern. 1920 * Get the first char of the pattern. 1921 */ 1922 search_type = SRCH_FORW | def_search_type; 1923 if (number <= 0) 1924 number = 1; 1925 literal_char = FALSE; 1926 mca_search(); 1927 c = getcc(); 1928 goto again; 1929 1930 case A_B_SEARCH: 1931 /* 1932 * Search backward for a pattern. 1933 * Get the first char of the pattern. 1934 */ 1935 search_type = SRCH_BACK | def_search_type; 1936 if (number <= 0) 1937 number = 1; 1938 literal_char = FALSE; 1939 mca_search(); 1940 c = getcc(); 1941 goto again; 1942 1943 case A_OSC8_F_SEARCH: 1944 #if OSC8_LINK 1945 cmd_exec(); 1946 if (number <= 0) 1947 number = 1; 1948 osc8_search(SRCH_FORW, NULL, number); 1949 #else 1950 error("Command not available", NULL_PARG); 1951 #endif 1952 break; 1953 1954 case A_OSC8_B_SEARCH: 1955 #if OSC8_LINK 1956 cmd_exec(); 1957 if (number <= 0) 1958 number = 1; 1959 osc8_search(SRCH_BACK, NULL, number); 1960 #else 1961 error("Command not available", NULL_PARG); 1962 #endif 1963 break; 1964 1965 case A_OSC8_OPEN: 1966 #if OSC8_LINK 1967 if (secure_allow(SF_OSC8_OPEN)) 1968 { 1969 cmd_exec(); 1970 osc8_open(); 1971 break; 1972 } 1973 #endif 1974 error("Command not available", NULL_PARG); 1975 break; 1976 1977 case A_OSC8_JUMP: 1978 #if OSC8_LINK 1979 cmd_exec(); 1980 osc8_jump(); 1981 #else 1982 error("Command not available", NULL_PARG); 1983 #endif 1984 break; 1985 1986 case A_FILTER: 1987 #if HILITE_SEARCH 1988 search_type = SRCH_FORW | SRCH_FILTER; 1989 literal_char = FALSE; 1990 mca_search(); 1991 c = getcc(); 1992 goto again; 1993 #else 1994 error("Command not available", NULL_PARG); 1995 break; 1996 #endif 1997 1998 case A_AGAIN_SEARCH: 1999 /* 2000 * Repeat previous search. 2001 */ 2002 search_type = last_search_type; 2003 DO_SEARCH(); 2004 break; 2005 2006 case A_T_AGAIN_SEARCH: 2007 /* 2008 * Repeat previous search, multiple files. 2009 */ 2010 search_type = last_search_type | SRCH_PAST_EOF; 2011 DO_SEARCH(); 2012 break; 2013 2014 case A_REVERSE_SEARCH: 2015 /* 2016 * Repeat previous search, in reverse direction. 2017 */ 2018 save_search_type = search_type = last_search_type; 2019 search_type = SRCH_REVERSE(search_type); 2020 DO_SEARCH(); 2021 last_search_type = save_search_type; 2022 break; 2023 2024 case A_T_REVERSE_SEARCH: 2025 /* 2026 * Repeat previous search, 2027 * multiple files in reverse direction. 2028 */ 2029 save_search_type = search_type = last_search_type; 2030 search_type = SRCH_REVERSE(search_type) | SRCH_PAST_EOF; 2031 DO_SEARCH(); 2032 last_search_type = save_search_type; 2033 break; 2034 2035 case A_UNDO_SEARCH: 2036 case A_CLR_SEARCH: 2037 /* 2038 * Clear search string highlighting. 2039 */ 2040 cmd_exec(); 2041 undo_search(action == A_CLR_SEARCH); 2042 break; 2043 2044 case A_HELP: 2045 /* 2046 * Help. 2047 */ 2048 if (ch_getflags() & CH_HELPFILE) 2049 break; 2050 cmd_exec(); 2051 save_hshift = hshift; 2052 hshift = 0; 2053 save_bs_mode = bs_mode; 2054 bs_mode = BS_SPECIAL; 2055 save_proc_backspace = proc_backspace; 2056 proc_backspace = OPT_OFF; 2057 (void) edit(FAKE_HELPFILE); 2058 break; 2059 2060 case A_EXAMINE: 2061 /* 2062 * Edit a new file. Get the filename. 2063 */ 2064 #if EXAMINE 2065 if (secure_allow(SF_EXAMINE)) 2066 { 2067 start_mca(A_EXAMINE, "Examine: ", ml_examine, 0); 2068 c = getcc(); 2069 goto again; 2070 } 2071 #endif 2072 error("Command not available", NULL_PARG); 2073 break; 2074 2075 case A_VISUAL: 2076 /* 2077 * Invoke an editor on the input file. 2078 */ 2079 #if EDITOR 2080 if (secure_allow(SF_EDIT)) 2081 { 2082 if (ch_getflags() & CH_HELPFILE) 2083 break; 2084 if (strcmp(get_filename(curr_ifile), "-") == 0) 2085 { 2086 error("Cannot edit standard input", NULL_PARG); 2087 break; 2088 } 2089 if (!no_edit_warn && get_altfilename(curr_ifile) != NULL) 2090 { 2091 error("WARNING: This file was viewed via LESSOPEN", NULL_PARG); 2092 } 2093 start_mca(A_SHELL, "!", ml_shell, 0); 2094 /* 2095 * Expand the editor prototype string 2096 * and pass it to the system to execute. 2097 * (Make sure the screen is displayed so the 2098 * expansion of "+%lm" works.) 2099 */ 2100 make_display(); 2101 cmd_exec(); 2102 lsystem(pr_expand(editproto), NULL); 2103 break; 2104 } 2105 #endif 2106 error("Command not available", NULL_PARG); 2107 break; 2108 2109 case A_NEXT_FILE: 2110 /* 2111 * Examine next file. 2112 */ 2113 #if TAGS 2114 if (ntags()) 2115 { 2116 error("No next file", NULL_PARG); 2117 break; 2118 } 2119 #endif 2120 if (number <= 0) 2121 number = 1; 2122 cmd_exec(); 2123 if (edit_next((int) number)) 2124 { 2125 if (get_quit_at_eof() && eof_displayed(FALSE) && 2126 !(ch_getflags() & CH_HELPFILE)) 2127 quit(QUIT_OK); 2128 parg.p_string = (number > 1) ? "(N-th) " : ""; 2129 error("No %snext file", &parg); 2130 } 2131 break; 2132 2133 case A_PREV_FILE: 2134 /* 2135 * Examine previous file. 2136 */ 2137 #if TAGS 2138 if (ntags()) 2139 { 2140 error("No previous file", NULL_PARG); 2141 break; 2142 } 2143 #endif 2144 if (number <= 0) 2145 number = 1; 2146 cmd_exec(); 2147 if (edit_prev((int) number)) 2148 { 2149 parg.p_string = (number > 1) ? "(N-th) " : ""; 2150 error("No %sprevious file", &parg); 2151 } 2152 break; 2153 2154 case A_NEXT_TAG: 2155 /* 2156 * Jump to the next tag in the current tag list. 2157 */ 2158 #if TAGS 2159 if (number <= 0) 2160 number = 1; 2161 tagfile = nexttag((int) number); 2162 if (tagfile == NULL) 2163 { 2164 error("No next tag", NULL_PARG); 2165 break; 2166 } 2167 cmd_exec(); 2168 if (edit(tagfile) == 0) 2169 { 2170 POSITION pos = tagsearch(); 2171 if (pos != NULL_POSITION) 2172 jump_loc(pos, jump_sline); 2173 } 2174 #else 2175 error("Command not available", NULL_PARG); 2176 #endif 2177 break; 2178 2179 case A_PREV_TAG: 2180 /* 2181 * Jump to the previous tag in the current tag list. 2182 */ 2183 #if TAGS 2184 if (number <= 0) 2185 number = 1; 2186 tagfile = prevtag((int) number); 2187 if (tagfile == NULL) 2188 { 2189 error("No previous tag", NULL_PARG); 2190 break; 2191 } 2192 cmd_exec(); 2193 if (edit(tagfile) == 0) 2194 { 2195 POSITION pos = tagsearch(); 2196 if (pos != NULL_POSITION) 2197 jump_loc(pos, jump_sline); 2198 } 2199 #else 2200 error("Command not available", NULL_PARG); 2201 #endif 2202 break; 2203 2204 case A_INDEX_FILE: 2205 /* 2206 * Examine a particular file. 2207 */ 2208 if (number <= 0) 2209 number = 1; 2210 cmd_exec(); 2211 if (edit_index((int) number)) 2212 error("No such file", NULL_PARG); 2213 break; 2214 2215 case A_REMOVE_FILE: 2216 /* 2217 * Remove a file from the input file list. 2218 */ 2219 if (ch_getflags() & CH_HELPFILE) 2220 break; 2221 old_ifile = curr_ifile; 2222 new_ifile = getoff_ifile(curr_ifile); 2223 cmd_exec(); 2224 if (new_ifile == NULL_IFILE) 2225 { 2226 lbell(); 2227 break; 2228 } 2229 if (edit_ifile(new_ifile) != 0) 2230 { 2231 reedit_ifile(old_ifile); 2232 break; 2233 } 2234 del_ifile(old_ifile); 2235 break; 2236 2237 case A_OPT_TOGGLE: 2238 /* 2239 * Change the setting of an option. 2240 */ 2241 optflag = OPT_TOGGLE; 2242 optgetname = FALSE; 2243 mca_opt_toggle(); 2244 c = getcc(); 2245 msg = opt_toggle_disallowed(c); 2246 if (msg != NULL) 2247 { 2248 error(msg, NULL_PARG); 2249 break; 2250 } 2251 goto again; 2252 2253 case A_DISP_OPTION: 2254 /* 2255 * Report the setting of an option. 2256 */ 2257 optflag = OPT_NO_TOGGLE; 2258 optgetname = FALSE; 2259 mca_opt_toggle(); 2260 c = getcc(); 2261 goto again; 2262 2263 case A_FIRSTCMD: 2264 /* 2265 * Set an initial command for new files. 2266 */ 2267 start_mca(A_FIRSTCMD, "+", NULL, 0); 2268 c = getcc(); 2269 goto again; 2270 2271 case A_SHELL: 2272 case A_PSHELL: 2273 /* 2274 * Shell escape. 2275 */ 2276 #if SHELL_ESCAPE 2277 if (secure_allow(SF_SHELL)) 2278 { 2279 start_mca(action, (action == A_SHELL) ? "!" : "#", ml_shell, 0); 2280 c = getcc(); 2281 goto again; 2282 } 2283 #endif 2284 error("Command not available", NULL_PARG); 2285 break; 2286 2287 case A_SETMARK: 2288 case A_SETMARKBOT: 2289 /* 2290 * Set a mark. 2291 */ 2292 if (ch_getflags() & CH_HELPFILE) 2293 { 2294 if (ungot != NULL) 2295 { 2296 /* 2297 * Probably from a lesskey file, in which case there 2298 * is probably an ungotten letter from the "extra" string. 2299 * Eat it so it is not interpreted as a command. 2300 */ 2301 (void) getcc(); 2302 } 2303 break; 2304 } 2305 start_mca(A_SETMARK, "set mark: ", NULL, 0); 2306 c = getcc(); 2307 make_display(); 2308 cmd_exec(); 2309 if (is_erase_char(c) || is_newline_char(c)) 2310 break; 2311 setmark(c, action == A_SETMARKBOT ? BOTTOM : TOP, number); 2312 repaint(); 2313 break; 2314 2315 case A_CLRMARK: 2316 /* 2317 * Clear a mark. 2318 */ 2319 start_mca(A_CLRMARK, "clear mark: ", NULL, 0); 2320 c = getcc(); 2321 cmd_exec(); 2322 if (is_erase_char(c) || is_newline_char(c)) 2323 break; 2324 clrmark(c); 2325 repaint(); 2326 break; 2327 2328 case A_GOMARK: 2329 /* 2330 * Jump to a marked position. 2331 */ 2332 start_mca(A_GOMARK, "goto mark: ", NULL, 0); 2333 c = getcc(); 2334 if (is_erase_char(c) || is_newline_char(c)) 2335 break; 2336 cmd_exec(); 2337 gomark(c, number); 2338 break; 2339 2340 case A_PIPE: 2341 /* 2342 * Write part of the input to a pipe to a shell command. 2343 */ 2344 #if PIPEC 2345 if (secure_allow(SF_PIPE)) 2346 { 2347 start_mca(A_PIPE, "|mark: ", NULL, 0); 2348 c = getcc(); 2349 if (is_erase_char(c)) 2350 break; 2351 if (is_newline_char(c)) 2352 c = '.'; 2353 if (badmark(c)) 2354 break; 2355 pipec = c; 2356 start_mca(A_PIPE, "!", ml_shell, 0); 2357 c = getcc(); 2358 goto again; 2359 } 2360 #endif 2361 error("Command not available", NULL_PARG); 2362 break; 2363 2364 case A_B_BRACKET: 2365 case A_F_BRACKET: 2366 start_mca(action, "Brackets: ", NULL, 0); 2367 c = getcc(); 2368 goto again; 2369 2370 case A_LSHIFT: 2371 /* 2372 * Shift view left. 2373 */ 2374 if (number > 0) 2375 shift_count = (int) number; 2376 else 2377 number = (shift_count > 0) ? shift_count : sc_width / 2; 2378 if (number > hshift) 2379 number = hshift; 2380 pos_rehead(FALSE); 2381 hshift -= (int) number; 2382 screen_trashed(); 2383 cmd_exec(); 2384 break; 2385 2386 case A_RSHIFT: 2387 /* 2388 * Shift view right. 2389 */ 2390 if (number > 0) 2391 shift_count = (int) number; 2392 else 2393 number = (shift_count > 0) ? shift_count : sc_width / 2; 2394 pos_rehead(FALSE); 2395 hshift += (int) number; 2396 screen_trashed(); 2397 cmd_exec(); 2398 break; 2399 2400 case A_LLSHIFT: 2401 /* 2402 * Shift view left to margin. 2403 */ 2404 pos_rehead(FALSE); 2405 hshift = 0; 2406 screen_trashed(); 2407 cmd_exec(); 2408 break; 2409 2410 case A_RRSHIFT: 2411 /* 2412 * Shift view right to view rightmost char on screen. 2413 */ 2414 pos_rehead(FALSE); 2415 hshift = rrshift(); 2416 screen_trashed(); 2417 cmd_exec(); 2418 break; 2419 2420 case A_PREFIX: 2421 /* 2422 * The command is incomplete (more chars are needed). 2423 * Display the current char, so the user knows 2424 * what's going on, and get another character. 2425 */ 2426 if (mca != A_PREFIX) 2427 { 2428 cmd_reset(); 2429 start_mca(A_PREFIX, " ", NULL, CF_QUIT_ON_ERASE); 2430 (void) cmd_char(c); 2431 } 2432 c = getcc(); 2433 goto again; 2434 2435 case A_NOACTION: 2436 break; 2437 2438 default: 2439 lbell(); 2440 break; 2441 } 2442 } 2443 } 2444