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