1 /* 2 * Copyright (c) 1988 Mark Nudleman 3 * Copyright (c) 1988 Regents of the University of California. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms are permitted 7 * provided that the above copyright notice and this paragraph are 8 * duplicated in all such forms and that any documentation, 9 * advertising materials, and other materials related to such 10 * distribution and use acknowledge that the software was developed 11 * by Mark Nudleman and the University of California, Berkeley. The 12 * name of Mark Nudleman or the 13 * University may not be used to endorse or promote products derived 14 * from this software without specific prior written permission. 15 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED 17 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 18 */ 19 20 #ifndef lint 21 static char sccsid[] = "@(#)command.c 5.9 (Berkeley) 09/23/88"; 22 #endif /* not lint */ 23 24 /* 25 * User-level command processor. 26 */ 27 28 #include "less.h" 29 #include "position.h" 30 #include "cmd.h" 31 32 #define NO_MCA 0 33 #define MCA_DONE 1 34 #define MCA_MORE 2 35 36 extern int erase_char, kill_char; 37 extern int ispipe; 38 extern int sigs; 39 extern int quit_at_eof; 40 extern int hit_eof; 41 extern int sc_width; 42 extern int sc_height; 43 extern int sc_window; 44 extern int curr_ac; 45 extern int ac; 46 extern int quitting; 47 extern int scroll; 48 extern char *first_cmd; 49 extern char *every_first_cmd; 50 extern char *current_file; 51 extern int screen_trashed; /* The screen has been overwritten */ 52 53 static char cmdbuf[120]; /* Buffer for holding a multi-char command */ 54 static char *cp; /* Pointer into cmdbuf */ 55 static int cmd_col; /* Current column of the multi-char command */ 56 static int mca; /* The multicharacter command (action) */ 57 static int last_mca; /* The previous mca */ 58 static int number; /* The number typed by the user */ 59 static int wsearch; /* Search for matches (1) or non-matches (0) */ 60 61 /* 62 * Reset command buffer (to empty). 63 */ 64 cmd_reset() 65 { 66 cp = cmdbuf; 67 } 68 69 /* 70 * Backspace in command buffer. 71 */ 72 static int 73 cmd_erase() 74 { 75 if (cp == cmdbuf) 76 /* 77 * Backspace past beginning of the string: 78 * this usually means abort the command. 79 */ 80 return (1); 81 82 if (control_char(*--cp)) 83 { 84 /* 85 * Erase an extra character, for the carat. 86 */ 87 backspace(); 88 cmd_col--; 89 } 90 backspace(); 91 cmd_col--; 92 return (0); 93 } 94 95 /* 96 * Set up the display to start a new multi-character command. 97 */ 98 start_mca(action, prompt) 99 int action; 100 char *prompt; 101 { 102 lower_left(); 103 clear_eol(); 104 putstr(prompt); 105 cmd_col = strlen(prompt); 106 mca = action; 107 } 108 109 /* 110 * Process a single character of a multi-character command, such as 111 * a number, or the pattern of a search command. 112 */ 113 static int 114 cmd_char(c) 115 int c; 116 { 117 if (c == erase_char) 118 { 119 if (cmd_erase()) 120 return (1); 121 } else if (c == kill_char) 122 { 123 /* {{ Could do this faster, but who cares? }} */ 124 while (cmd_erase() == 0) 125 ; 126 } else if (cp >= &cmdbuf[sizeof(cmdbuf)-1]) 127 { 128 /* 129 * No room in the command buffer. 130 */ 131 bell(); 132 } else if (cmd_col >= sc_width-3) 133 { 134 /* 135 * No room on the screen. 136 * {{ Could get fancy here; maybe shift the displayed 137 * line and make room for more chars, like ksh. }} 138 */ 139 bell(); 140 } else 141 { 142 /* 143 * Append the character to the string. 144 */ 145 *cp++ = c; 146 if (control_char(c)) 147 { 148 putchr('^'); 149 cmd_col++; 150 c = carat_char(c); 151 } 152 putchr(c); 153 cmd_col++; 154 } 155 return (0); 156 } 157 158 /* 159 * Return the number currently in the command buffer. 160 */ 161 static int 162 cmd_int() 163 { 164 *cp = '\0'; 165 cp = cmdbuf; 166 return (atoi(cmdbuf)); 167 } 168 169 /* 170 * Move the cursor to lower left before executing a command. 171 * This looks nicer if the command takes a long time before 172 * updating the screen. 173 */ 174 static void 175 cmd_exec() 176 { 177 lower_left(); 178 flush(); 179 } 180 181 /* 182 * Display the appropriate prompt. 183 */ 184 static void 185 prompt() 186 { 187 register char *p; 188 189 if (first_cmd != NULL && *first_cmd != '\0') 190 { 191 /* 192 * No prompt necessary if commands are from first_cmd 193 * rather than from the user. 194 */ 195 return; 196 } 197 198 /* 199 * If nothing is displayed yet, display starting from line 1. 200 */ 201 if (position(TOP) == NULL_POSITION) 202 jump_back(1); 203 else if (screen_trashed) 204 repaint(); 205 206 /* 207 * If the -E flag is set and we've hit EOF on the last file, quit. 208 */ 209 if (quit_at_eof == 2 && hit_eof && curr_ac + 1 >= ac) 210 quit(); 211 212 /* 213 * Select the proper prompt and display it. 214 */ 215 lower_left(); 216 clear_eol(); 217 p = pr_string(); 218 if (p == NULL) 219 putchr(':'); 220 else 221 { 222 so_enter(); 223 putstr(p); 224 so_exit(); 225 } 226 } 227 228 /* 229 * Get command character. 230 * The character normally comes from the keyboard, 231 * but may come from the "first_cmd" string. 232 */ 233 static int 234 getcc() 235 { 236 if (first_cmd == NULL) 237 return (getchr()); 238 239 if (*first_cmd == '\0') 240 { 241 /* 242 * Reached end of first_cmd input. 243 */ 244 first_cmd = NULL; 245 if (cp > cmdbuf && position(TOP) == NULL_POSITION) 246 { 247 /* 248 * Command is incomplete, so try to complete it. 249 * There are only two cases: 250 * 1. We have "/string" but no newline. Add the \n. 251 * 2. We have a number but no command. Treat as #g. 252 * (This is all pretty hokey.) 253 */ 254 if (mca != A_DIGIT) 255 /* Not a number; must be search string */ 256 return ('\n'); 257 else 258 /* A number; append a 'g' */ 259 return ('g'); 260 } 261 return (getchr()); 262 } 263 return (*first_cmd++); 264 } 265 266 /* 267 * Execute a multicharacter command. 268 */ 269 static void 270 exec_mca() 271 { 272 register char *p; 273 274 *cp = '\0'; 275 cmd_exec(); 276 switch (mca) 277 { 278 case A_F_SEARCH: 279 search(1, cmdbuf, number, wsearch); 280 break; 281 case A_B_SEARCH: 282 search(0, cmdbuf, number, wsearch); 283 break; 284 case A_FIRSTCMD: 285 /* 286 * Skip leading spaces or + signs in the string. 287 */ 288 for (p = cmdbuf; *p == '+' || *p == ' '; p++) 289 ; 290 if (every_first_cmd != NULL) 291 free(every_first_cmd); 292 if (*p == '\0') 293 every_first_cmd = NULL; 294 else 295 every_first_cmd = save(p); 296 break; 297 case A_TOGGLE_OPTION: 298 toggle_option(cmdbuf, 1); 299 break; 300 case A_EXAMINE: 301 /* 302 * Ignore leading spaces in the filename. 303 */ 304 for (p = cmdbuf; *p == ' '; p++) 305 ; 306 edit(glob(p)); 307 break; 308 } 309 } 310 311 /* 312 * Add a character to a multi-character command. 313 */ 314 static int 315 mca_char(c) 316 int c; 317 { 318 switch (mca) 319 { 320 case 0: 321 /* 322 * Not in a multicharacter command. 323 */ 324 return (NO_MCA); 325 326 case A_PREFIX: 327 /* 328 * In the prefix of a command. 329 */ 330 return (NO_MCA); 331 332 case A_DIGIT: 333 /* 334 * Entering digits of a number. 335 * Terminated by a non-digit. 336 */ 337 if ((c < '0' || c > '9') && 338 c != erase_char && c != kill_char) 339 { 340 /* 341 * Not part of the number. 342 * Treat as a normal command character. 343 */ 344 number = cmd_int(); 345 mca = 0; 346 return (NO_MCA); 347 } 348 break; 349 350 case A_TOGGLE_OPTION: 351 /* 352 * Special case for the TOGGLE_OPTION command. 353 * if the option letter which was entered is a 354 * single-char option, execute the command immediately, 355 * so he doesn't have to hit RETURN. 356 */ 357 if (cp == cmdbuf && c != erase_char && c != kill_char && 358 single_char_option(c)) 359 { 360 cmdbuf[0] = c; 361 cmdbuf[1] = '\0'; 362 toggle_option(cmdbuf, 1); 363 return (MCA_DONE); 364 } 365 break; 366 } 367 368 /* 369 * Any other multicharacter command 370 * is terminated by a newline. 371 */ 372 if (c == '\n' || c == '\r') 373 { 374 /* 375 * Execute the command. 376 */ 377 exec_mca(); 378 return (MCA_DONE); 379 } 380 /* 381 * Append the char to the command buffer. 382 */ 383 if (cmd_char(c)) 384 /* 385 * Abort the multi-char command. 386 */ 387 return (MCA_DONE); 388 /* 389 * Need another character. 390 */ 391 return (MCA_MORE); 392 } 393 394 /* 395 * Main command processor. 396 * Accept and execute commands until a quit command, then return. 397 */ 398 public void 399 commands() 400 { 401 register int c; 402 register int action; 403 404 last_mca = 0; 405 scroll = (sc_height + 1) / 2; 406 407 for (;;) 408 { 409 mca = 0; 410 number = 0; 411 412 /* 413 * See if any signals need processing. 414 */ 415 if (sigs) 416 { 417 psignals(); 418 if (quitting) 419 quit(); 420 } 421 /* 422 * Display prompt and accept a character. 423 */ 424 cmd_reset(); 425 prompt(); 426 noprefix(); 427 c = getcc(); 428 429 again: if (sigs) 430 continue; 431 432 /* 433 * If we are in a multicharacter command, call mca_char. 434 * Otherwise we call cmd_decode to determine the 435 * action to be performed. 436 */ 437 if (mca) 438 switch (mca_char(c)) 439 { 440 case MCA_MORE: 441 /* 442 * Need another character. 443 */ 444 c = getcc(); 445 goto again; 446 case MCA_DONE: 447 /* 448 * Command has been handled by mca_char. 449 * Start clean with a prompt. 450 */ 451 continue; 452 case NO_MCA: 453 /* 454 * Not a multi-char command 455 * (at least, not anymore). 456 */ 457 break; 458 } 459 460 /* 461 * Decode the command character and decide what to do. 462 */ 463 switch (action = cmd_decode(c)) 464 { 465 case A_DIGIT: 466 /* 467 * First digit of a number. 468 */ 469 start_mca(A_DIGIT, ":"); 470 goto again; 471 472 case A_F_SCREEN: 473 /* 474 * Forward one screen. 475 */ 476 if (number <= 0) 477 number = sc_window; 478 if (number <= 0) 479 number = sc_height - 1; 480 cmd_exec(); 481 forward(number, 1); 482 break; 483 484 case A_B_SCREEN: 485 /* 486 * Backward one screen. 487 */ 488 if (number <= 0) 489 number = sc_window; 490 if (number <= 0) 491 number = sc_height - 1; 492 cmd_exec(); 493 backward(number, 1); 494 break; 495 496 case A_F_LINE: 497 /* 498 * Forward N (default 1) line. 499 */ 500 if (number <= 0) 501 number = 1; 502 cmd_exec(); 503 forward(number, 0); 504 break; 505 506 case A_B_LINE: 507 /* 508 * Backward N (default 1) line. 509 */ 510 if (number <= 0) 511 number = 1; 512 cmd_exec(); 513 backward(number, 0); 514 break; 515 516 case A_F_SCROLL: 517 /* 518 * Forward N lines 519 * (default same as last 'd' or 'u' command). 520 */ 521 if (number > 0) 522 scroll = number; 523 cmd_exec(); 524 forward(scroll, 0); 525 break; 526 527 case A_B_SCROLL: 528 /* 529 * Forward N lines 530 * (default same as last 'd' or 'u' command). 531 */ 532 if (number > 0) 533 scroll = number; 534 cmd_exec(); 535 backward(scroll, 0); 536 break; 537 538 case A_FREPAINT: 539 /* 540 * Flush buffers, then repaint screen. 541 * Don't flush the buffers on a pipe! 542 */ 543 if (!ispipe) 544 { 545 ch_init(0, 0); 546 clr_linenum(); 547 } 548 /* FALLTHRU */ 549 case A_REPAINT: 550 /* 551 * Repaint screen. 552 */ 553 cmd_exec(); 554 repaint(); 555 break; 556 557 case A_GOLINE: 558 /* 559 * Go to line N, default beginning of file. 560 */ 561 if (number <= 0) 562 number = 1; 563 cmd_exec(); 564 jump_back(number); 565 break; 566 567 case A_PERCENT: 568 /* 569 * Go to a specified percentage into the file. 570 */ 571 if (number < 0) 572 number = 0; 573 if (number > 100) 574 number = 100; 575 cmd_exec(); 576 jump_percent(number); 577 break; 578 579 case A_GOEND: 580 /* 581 * Go to line N, default end of file. 582 */ 583 cmd_exec(); 584 if (number <= 0) 585 jump_forw(); 586 else 587 jump_back(number); 588 break; 589 590 case A_STAT: 591 /* 592 * Print file name, etc. 593 */ 594 cmd_exec(); 595 lower_left(); 596 clear_eol(); 597 putstr(eq_message()); 598 lower_left(); 599 c = getcc(); 600 goto again; 601 case A_QUIT: 602 /* 603 * Exit. 604 */ 605 quit(); 606 607 case A_F_SEARCH: 608 case A_B_SEARCH: 609 /* 610 * Search for a pattern. 611 * Accept chars of the pattern until \n. 612 */ 613 if (number <= 0) 614 number = 1; 615 start_mca(action, (action==A_F_SEARCH) ? "/" : "?"); 616 last_mca = mca; 617 wsearch = 1; 618 c = getcc(); 619 if (c == '!') 620 { 621 /* 622 * Invert the sense of the search. 623 * Set wsearch to 0 and get a new 624 * character for the start of the pattern. 625 */ 626 start_mca(action, 627 (action==A_F_SEARCH) ? "!/" : "!?"); 628 wsearch = 0; 629 c = getcc(); 630 } 631 goto again; 632 633 case A_AGAIN_SEARCH: 634 /* 635 * Repeat previous search. 636 */ 637 if (number <= 0) 638 number = 1; 639 if (wsearch) 640 start_mca(last_mca, 641 (last_mca==A_F_SEARCH) ? "/" : "?"); 642 else 643 start_mca(last_mca, 644 (last_mca==A_F_SEARCH) ? "!/" : "!?"); 645 cmd_exec(); 646 search(mca==A_F_SEARCH, (char *)NULL, number, wsearch); 647 break; 648 649 case A_HELP: 650 /* 651 * Help. 652 */ 653 lower_left(); 654 clear_eol(); 655 putstr("help"); 656 cmd_exec(); 657 help(); 658 break; 659 660 case A_EXAMINE: 661 /* 662 * Edit a new file. Get the filename. 663 */ 664 cmd_reset(); 665 start_mca(A_EXAMINE, "Examine: "); 666 c = getcc(); 667 goto again; 668 669 case A_VISUAL: 670 /* 671 * Invoke an editor on the input file. 672 */ 673 if (ispipe) 674 { 675 error("Cannot edit standard input"); 676 break; 677 } 678 cmd_exec(); 679 editfile(); 680 ch_init(0, 0); 681 clr_linenum(); 682 break; 683 684 case A_NEXT_FILE: 685 /* 686 * Examine next file. 687 */ 688 if (number <= 0) 689 number = 1; 690 next_file(number); 691 break; 692 693 case A_PREV_FILE: 694 /* 695 * Examine previous file. 696 */ 697 if (number <= 0) 698 number = 1; 699 prev_file(number); 700 break; 701 702 case A_TOGGLE_OPTION: 703 /* 704 * Toggle a flag setting. 705 */ 706 cmd_reset(); 707 start_mca(A_TOGGLE_OPTION, "-"); 708 c = getcc(); 709 goto again; 710 711 case A_DISP_OPTION: 712 /* 713 * Report a flag setting. 714 */ 715 cmd_reset(); 716 start_mca(A_DISP_OPTION, "_"); 717 c = getcc(); 718 if (c == erase_char || c == kill_char) 719 break; 720 cmdbuf[0] = c; 721 cmdbuf[1] = '\0'; 722 toggle_option(cmdbuf, 0); 723 break; 724 725 case A_FIRSTCMD: 726 /* 727 * Set an initial command for new files. 728 */ 729 cmd_reset(); 730 start_mca(A_FIRSTCMD, "+"); 731 c = getcc(); 732 goto again; 733 734 case A_SETMARK: 735 /* 736 * Set a mark. 737 */ 738 lower_left(); 739 clear_eol(); 740 start_mca(A_SETMARK, "mark: "); 741 c = getcc(); 742 if (c == erase_char || c == kill_char) 743 break; 744 setmark(c); 745 break; 746 747 case A_GOMARK: 748 /* 749 * Go to a mark. 750 */ 751 lower_left(); 752 clear_eol(); 753 start_mca(A_GOMARK, "goto mark: "); 754 c = getcc(); 755 if (c == erase_char || c == kill_char) 756 break; 757 gomark(c); 758 break; 759 760 case A_PREFIX: 761 /* 762 * The command is incomplete (more chars are needed). 763 * Display the current char so the user knows 764 * what's going on and get another character. 765 */ 766 if (mca != A_PREFIX) 767 start_mca(A_PREFIX, "& "); 768 if (control_char(c)) 769 { 770 putchr('^'); 771 c = carat_char(c); 772 } 773 putchr(c); 774 c = getcc(); 775 goto again; 776 777 default: 778 bell(); 779 break; 780 } 781 } 782 } 783 784 static 785 editfile() 786 { 787 static int dolinenumber; 788 static char *editor; 789 int c; 790 char buf[MAXPATHLEN], *getenv(); 791 792 if (editor == NULL) { 793 editor = getenv("EDITOR"); 794 /* pass the line number to vi */ 795 if (editor == NULL || *editor == '\0') { 796 editor = "/usr/ucb/vi"; 797 dolinenumber = 1; 798 } 799 else 800 dolinenumber = 0; 801 } 802 if (dolinenumber && (c = currline(MIDDLE))) 803 (void)sprintf(buf, "%s +%d %s", editor, c, current_file); 804 else 805 (void)sprintf(buf, "%s %s", editor, current_file); 806 lsystem(buf); 807 } 808