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