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