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.17 (Berkeley) 12/28/88"; 22 #endif /* not lint */ 23 24 #include <sys/param.h> 25 #include <stdio.h> 26 #include <ctype.h> 27 #include <less.h> 28 29 #define NO_MCA 0 30 #define MCA_DONE 1 31 #define MCA_MORE 2 32 33 extern int erase_char, kill_char, werase_char; 34 extern int ispipe; 35 extern int sigs; 36 extern int quit_at_eof; 37 extern int hit_eof; 38 extern int sc_width; 39 extern int sc_height; 40 extern int sc_window; 41 extern int curr_ac; 42 extern int ac; 43 extern int quitting; 44 extern int scroll; 45 extern int screen_trashed; /* The screen has been overwritten */ 46 47 static char cmdbuf[120]; /* Buffer for holding a multi-char command */ 48 static char *cp; /* Pointer into cmdbuf */ 49 static int cmd_col; /* Current column of the multi-char command */ 50 static int longprompt; /* if stat command instead of prompt */ 51 static int mca; /* The multicharacter command (action) */ 52 static int last_mca; /* The previous mca */ 53 static int number; /* The number typed by the user */ 54 static int wsearch; /* Search for matches (1) or non-matches (0) */ 55 56 #define CMD_RESET cp = cmdbuf /* reset command buffer to empty */ 57 #define CMD_EXEC lower_left(); flush() 58 59 /* backspace in command buffer. */ 60 static 61 cmd_erase() 62 { 63 /* 64 * backspace past beginning of the string: this usually means 65 * abort the command. 66 */ 67 if (cp == cmdbuf) 68 return(1); 69 70 /* erase an extra character, for the carat. */ 71 if (CONTROL_CHAR(*--cp)) { 72 backspace(); 73 --cmd_col; 74 } 75 76 backspace(); 77 --cmd_col; 78 return(0); 79 } 80 81 /* set up the display to start a new multi-character command. */ 82 start_mca(action, prompt) 83 int action; 84 char *prompt; 85 { 86 lower_left(); 87 clear_eol(); 88 putstr(prompt); 89 cmd_col = strlen(prompt); 90 mca = action; 91 } 92 93 /* 94 * process a single character of a multi-character command, such as 95 * a number, or the pattern of a search command. 96 */ 97 static 98 cmd_char(c) 99 int c; 100 { 101 if (c == erase_char) 102 return(cmd_erase()); 103 /* in this order, in case werase == erase_char */ 104 if (c == werase_char) { 105 if (cp > cmdbuf) { 106 while (isspace(cp[-1]) && !cmd_erase()); 107 while (!isspace(cp[-1]) && !cmd_erase()); 108 while (isspace(cp[-1]) && !cmd_erase()); 109 } 110 return(cp == cmdbuf); 111 } 112 if (c == kill_char) { 113 while (!cmd_erase()); 114 return(1); 115 } 116 /* 117 * No room in the command buffer, or no room on the screen; 118 * {{ Could get fancy here; maybe shift the displayed line 119 * and make room for more chars, like ksh. }} 120 */ 121 if (cp >= &cmdbuf[sizeof(cmdbuf)-1] || cmd_col >= sc_width-3) 122 bell(); 123 else { 124 *cp++ = c; 125 if (CONTROL_CHAR(c)) { 126 putchr('^'); 127 cmd_col++; 128 c = CARAT_CHAR(c); 129 } 130 putchr(c); 131 cmd_col++; 132 } 133 return(0); 134 } 135 136 prompt() 137 { 138 extern int linenums, short_file; 139 extern char *current_name, *firstsearch, *next_name; 140 off_t len, pos, ch_length(), position(), forw_line(); 141 char pbuf[40]; 142 143 /* 144 * if nothing is displayed yet, display starting from line 1; 145 * if search string provided, go there instead. 146 */ 147 if (position(TOP) == NULL_POSITION) { 148 if (forw_line((off_t)0) == NULL_POSITION) 149 return(0); 150 if (!firstsearch || !search(1, firstsearch, 1, 1)) 151 jump_back(1); 152 } 153 else if (screen_trashed) 154 repaint(); 155 156 /* if no -e flag and we've hit EOF on the last file, quit. */ 157 if ((!quit_at_eof || short_file) && hit_eof && curr_ac + 1 >= ac) 158 quit(); 159 160 /* select the proper prompt and display it. */ 161 lower_left(); 162 clear_eol(); 163 if (longprompt) { 164 so_enter(); 165 putstr(current_name); 166 putstr(":"); 167 if (!ispipe) { 168 (void)sprintf(pbuf, " file %d/%d", curr_ac + 1, ac); 169 putstr(pbuf); 170 } 171 if (linenums) { 172 (void)sprintf(pbuf, " line %d", currline(BOTTOM)); 173 putstr(pbuf); 174 } 175 if ((pos = position(BOTTOM)) != NULL_POSITION) { 176 (void)sprintf(pbuf, " byte %ld", pos); 177 putstr(pbuf); 178 if (!ispipe && (len = ch_length())) { 179 (void)sprintf(pbuf, "/%ld pct %ld%%", 180 len, ((100 * pos) / len)); 181 putstr(pbuf); 182 } 183 } 184 so_exit(); 185 longprompt = 0; 186 } 187 else { 188 so_enter(); 189 putstr(current_name); 190 if (hit_eof) 191 if (next_name) { 192 (void)sprintf(pbuf, ": END (next file: %s)", 193 next_name); 194 putstr(pbuf); 195 } 196 else 197 putstr(": END"); 198 else if (!ispipe && 199 (pos = position(BOTTOM)) != NULL_POSITION && 200 (len = ch_length())) { 201 (void)sprintf(pbuf, " (%ld%%)", ((100 * pos) / len)); 202 putstr(pbuf); 203 } 204 so_exit(); 205 } 206 return(1); 207 } 208 209 /* get command character. */ 210 static 211 getcc() 212 { 213 extern int cmdstack; 214 int ch; 215 216 /* left over from error() routine. */ 217 if (cmdstack) { 218 ch = cmdstack; 219 cmdstack = NULL; 220 return(ch); 221 } 222 if (cp > cmdbuf && position(TOP) == NULL_POSITION) { 223 /* 224 * Command is incomplete, so try to complete it. 225 * There are only two cases: 226 * 1. We have "/string" but no newline. Add the \n. 227 * 2. We have a number but no command. Treat as #g. 228 * (This is all pretty hokey.) 229 */ 230 if (mca != A_DIGIT) 231 /* Not a number; must be search string */ 232 return('\n'); 233 else 234 /* A number; append a 'g' */ 235 return('g'); 236 } 237 return(getchr()); 238 } 239 240 /* execute a multicharacter command. */ 241 static 242 exec_mca() 243 { 244 extern int file; 245 extern char *tagfile; 246 register char *p; 247 char *glob(); 248 249 *cp = '\0'; 250 CMD_EXEC; 251 switch (mca) { 252 case A_F_SEARCH: 253 (void)search(1, cmdbuf, number, wsearch); 254 break; 255 case A_B_SEARCH: 256 (void)search(0, cmdbuf, number, wsearch); 257 break; 258 case A_EXAMINE: 259 for (p = cmdbuf; isspace(*p); ++p); 260 (void)edit(glob(p)); 261 break; 262 case A_TAGFILE: 263 for (p = cmdbuf; isspace(*p); ++p); 264 findtag(p); 265 if (tagfile == NULL) 266 break; 267 if (edit(tagfile)) 268 (void)tagsearch(); 269 break; 270 } 271 } 272 273 /* add a character to a multi-character command. */ 274 static 275 mca_char(c) 276 int c; 277 { 278 switch (mca) { 279 case 0: /* not in a multicharacter command. */ 280 case A_PREFIX: /* in the prefix of a command. */ 281 return(NO_MCA); 282 case A_DIGIT: 283 /* 284 * Entering digits of a number. 285 * Terminated by a non-digit. 286 */ 287 if (!isascii(c) || !isdigit(c) && 288 c != erase_char && c != kill_char && c != werase_char) { 289 /* 290 * Not part of the number. 291 * Treat as a normal command character. 292 */ 293 *cp = '\0'; 294 number = atoi(cmdbuf); 295 CMD_RESET; 296 mca = 0; 297 return(NO_MCA); 298 } 299 break; 300 } 301 302 /* 303 * Any other multicharacter command 304 * is terminated by a newline. 305 */ 306 if (c == '\n' || c == '\r') { 307 exec_mca(); 308 return(MCA_DONE); 309 } 310 311 /* append the char to the command buffer. */ 312 if (cmd_char(c)) 313 return(MCA_DONE); 314 315 return(MCA_MORE); 316 } 317 318 /* 319 * Main command processor. 320 * Accept and execute commands until a quit command, then return. 321 */ 322 commands() 323 { 324 register int c; 325 register int action; 326 327 last_mca = 0; 328 scroll = (sc_height + 1) / 2; 329 330 for (;;) { 331 mca = 0; 332 number = 0; 333 334 /* 335 * See if any signals need processing. 336 */ 337 if (sigs) { 338 psignals(); 339 if (quitting) 340 quit(); 341 } 342 /* 343 * Display prompt and accept a character. 344 */ 345 CMD_RESET; 346 if (!prompt()) { 347 next_file(1); 348 continue; 349 } 350 noprefix(); 351 c = getcc(); 352 353 again: if (sigs) 354 continue; 355 356 /* 357 * If we are in a multicharacter command, call mca_char. 358 * Otherwise we call cmd_decode to determine the 359 * action to be performed. 360 */ 361 if (mca) 362 switch (mca_char(c)) { 363 case MCA_MORE: 364 /* 365 * Need another character. 366 */ 367 c = getcc(); 368 goto again; 369 case MCA_DONE: 370 /* 371 * Command has been handled by mca_char. 372 * Start clean with a prompt. 373 */ 374 continue; 375 case NO_MCA: 376 /* 377 * Not a multi-char command 378 * (at least, not anymore). 379 */ 380 break; 381 } 382 383 /* decode the command character and decide what to do. */ 384 switch (action = cmd_decode(c)) { 385 case A_DIGIT: /* first digit of a number */ 386 start_mca(A_DIGIT, ":"); 387 goto again; 388 case A_F_SCREEN: /* forward one screen */ 389 CMD_EXEC; 390 if (number <= 0 && (number = sc_window) <= 0) 391 number = sc_height - 1; 392 forward(number, 1); 393 break; 394 case A_B_SCREEN: /* backward one screen */ 395 CMD_EXEC; 396 if (number <= 0 && (number = sc_window) <= 0) 397 number = sc_height - 1; 398 backward(number, 1); 399 break; 400 case A_F_LINE: /* forward N (default 1) line */ 401 CMD_EXEC; 402 forward(number <= 0 ? 1 : number, 0); 403 break; 404 case A_B_LINE: /* backward N (default 1) line */ 405 CMD_EXEC; 406 backward(number <= 0 ? 1 : number, 0); 407 break; 408 case A_F_SCROLL: /* forward N lines */ 409 CMD_EXEC; 410 if (number > 0) 411 scroll = number; 412 forward(scroll, 0); 413 break; 414 case A_B_SCROLL: /* backward N lines */ 415 CMD_EXEC; 416 if (number > 0) 417 scroll = number; 418 backward(scroll, 0); 419 break; 420 case A_FREPAINT: /* flush buffers and repaint */ 421 if (!ispipe) { 422 ch_init(0, 0); 423 clr_linenum(); 424 } 425 /* FALLTHROUGH */ 426 case A_REPAINT: /* repaint the screen */ 427 CMD_EXEC; 428 repaint(); 429 break; 430 case A_GOLINE: /* go to line N, default 1 */ 431 CMD_EXEC; 432 if (number <= 0) 433 number = 1; 434 jump_back(number); 435 break; 436 case A_PERCENT: /* go to percent of file */ 437 CMD_EXEC; 438 if (number < 0) 439 number = 0; 440 else if (number > 100) 441 number = 100; 442 jump_percent(number); 443 break; 444 case A_GOEND: /* go to line N, default end */ 445 CMD_EXEC; 446 if (number <= 0) 447 jump_forw(); 448 else 449 jump_back(number); 450 break; 451 case A_STAT: /* print file name, etc. */ 452 longprompt = 1; 453 continue; 454 case A_QUIT: /* exit */ 455 quit(); 456 case A_F_SEARCH: /* search for a pattern */ 457 case A_B_SEARCH: 458 if (number <= 0) 459 number = 1; 460 start_mca(action, (action==A_F_SEARCH) ? "/" : "?"); 461 last_mca = mca; 462 wsearch = 1; 463 c = getcc(); 464 if (c == '!') { 465 /* 466 * Invert the sense of the search; set wsearch 467 * to 0 and get a new character for the start 468 * of the pattern. 469 */ 470 start_mca(action, 471 (action == A_F_SEARCH) ? "!/" : "!?"); 472 wsearch = 0; 473 c = getcc(); 474 } 475 goto again; 476 case A_AGAIN_SEARCH: /* repeat previous search */ 477 if (number <= 0) 478 number = 1; 479 if (wsearch) 480 start_mca(last_mca, 481 (last_mca == A_F_SEARCH) ? "/" : "?"); 482 else 483 start_mca(last_mca, 484 (last_mca == A_F_SEARCH) ? "!/" : "!?"); 485 CMD_EXEC; 486 (void)search(mca == A_F_SEARCH, (char *)NULL, 487 number, wsearch); 488 break; 489 case A_HELP: /* help */ 490 lower_left(); 491 clear_eol(); 492 putstr("help"); 493 CMD_EXEC; 494 help(); 495 break; 496 case A_TAGFILE: /* tag a new file */ 497 CMD_RESET; 498 start_mca(A_TAGFILE, "Tag: "); 499 c = getcc(); 500 goto again; 501 case A_FILE_LIST: /* show list of file names */ 502 CMD_EXEC; 503 showlist(); 504 repaint(); 505 break; 506 case A_EXAMINE: /* edit a new file */ 507 CMD_RESET; 508 start_mca(A_EXAMINE, "Examine: "); 509 c = getcc(); 510 goto again; 511 case A_VISUAL: /* invoke the editor */ 512 if (ispipe) { 513 error("Cannot edit standard input"); 514 break; 515 } 516 CMD_EXEC; 517 editfile(); 518 ch_init(0, 0); 519 clr_linenum(); 520 break; 521 case A_NEXT_FILE: /* examine next file */ 522 if (number <= 0) 523 number = 1; 524 next_file(number); 525 break; 526 case A_PREV_FILE: /* examine previous file */ 527 if (number <= 0) 528 number = 1; 529 prev_file(number); 530 break; 531 case A_SETMARK: /* set a mark */ 532 lower_left(); 533 clear_eol(); 534 start_mca(A_SETMARK, "mark: "); 535 c = getcc(); 536 if (c == erase_char || c == kill_char) 537 break; 538 setmark(c); 539 break; 540 case A_GOMARK: /* go to mark */ 541 lower_left(); 542 clear_eol(); 543 start_mca(A_GOMARK, "goto mark: "); 544 c = getcc(); 545 if (c == erase_char || c == kill_char) 546 break; 547 gomark(c); 548 break; 549 case A_PREFIX: 550 /* 551 * The command is incomplete (more chars are needed). 552 * Display the current char so the user knows what's 553 * going on and get another character. 554 */ 555 if (mca != A_PREFIX) 556 start_mca(A_PREFIX, ""); 557 if (CONTROL_CHAR(c)) { 558 putchr('^'); 559 c = CARAT_CHAR(c); 560 } 561 putchr(c); 562 c = getcc(); 563 goto again; 564 default: 565 bell(); 566 break; 567 } 568 } 569 } 570 571 static 572 editfile() 573 { 574 extern char *current_file; 575 static int dolinenumber; 576 static char *editor; 577 int c; 578 char buf[MAXPATHLEN * 2 + 20], *getenv(); 579 580 if (editor == NULL) { 581 editor = getenv("EDITOR"); 582 /* pass the line number to vi */ 583 if (editor == NULL || *editor == '\0') { 584 #define EDIT_PGM "/usr/ucb/vi" 585 editor = EDIT_PGM; 586 dolinenumber = 1; 587 } 588 else 589 dolinenumber = 0; 590 } 591 if (dolinenumber && (c = currline(MIDDLE))) 592 (void)sprintf(buf, "%s +%d %s", editor, c, current_file); 593 else 594 (void)sprintf(buf, "%s %s", editor, current_file); 595 lsystem(buf); 596 } 597 598 static 599 showlist() 600 { 601 extern int sc_width; 602 extern char **av; 603 register int indx, width; 604 int len; 605 char *p; 606 607 if (ac <= 0) { 608 error("No files provided as arguments."); 609 return; 610 } 611 for (width = indx = 0; indx < ac;) { 612 p = strcmp(av[indx], "-") ? av[indx] : "stdin"; 613 len = strlen(p) + 1; 614 if (curr_ac == indx) 615 len += 2; 616 if (width + len + 1 >= sc_width) { 617 if (!width) { 618 if (curr_ac == indx) 619 putchr('['); 620 putstr(p); 621 if (curr_ac == indx) 622 putchr(']'); 623 ++indx; 624 } 625 width = 0; 626 putchr('\n'); 627 continue; 628 } 629 if (width) 630 putchr(' '); 631 if (curr_ac == indx) 632 putchr('['); 633 putstr(p); 634 if (curr_ac == indx) 635 putchr(']'); 636 width += len; 637 ++indx; 638 } 639 putchr('\n'); 640 error((char *)NULL); 641 } 642