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.23 (Berkeley) 03/08/93"; 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 %qd", pos); 167 putstr(pbuf); 168 if (!ispipe && (len = ch_length())) { 169 (void)sprintf(pbuf, "/%qd pct %qd%%", 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, " (%qd%%)", ((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 off_t position(); 206 207 /* left over from error() routine. */ 208 if (cmdstack) { 209 ch = cmdstack; 210 cmdstack = NULL; 211 return(ch); 212 } 213 if (cp > cmdbuf && position(TOP) == NULL_POSITION) { 214 /* 215 * Command is incomplete, so try to complete it. 216 * There are only two cases: 217 * 1. We have "/string" but no newline. Add the \n. 218 * 2. We have a number but no command. Treat as #g. 219 * (This is all pretty hokey.) 220 */ 221 if (mca != A_DIGIT) 222 /* Not a number; must be search string */ 223 return('\n'); 224 else 225 /* A number; append a 'g' */ 226 return('g'); 227 } 228 return(getchr()); 229 } 230 231 /* execute a multicharacter command. */ 232 static 233 exec_mca() 234 { 235 extern int file; 236 extern char *tagfile; 237 register char *p; 238 char *glob(); 239 240 *cp = '\0'; 241 CMD_EXEC; 242 switch (mca) { 243 case A_F_SEARCH: 244 (void)search(1, cmdbuf, number, wsearch); 245 break; 246 case A_B_SEARCH: 247 (void)search(0, cmdbuf, number, wsearch); 248 break; 249 case A_EXAMINE: 250 for (p = cmdbuf; isspace(*p); ++p); 251 (void)edit(glob(p)); 252 break; 253 case A_TAGFILE: 254 for (p = cmdbuf; isspace(*p); ++p); 255 findtag(p); 256 if (tagfile == NULL) 257 break; 258 if (edit(tagfile)) 259 (void)tagsearch(); 260 break; 261 } 262 } 263 264 /* add a character to a multi-character command. */ 265 static 266 mca_char(c) 267 int c; 268 { 269 switch (mca) { 270 case 0: /* not in a multicharacter command. */ 271 case A_PREFIX: /* in the prefix of a command. */ 272 return(NO_MCA); 273 case A_DIGIT: 274 /* 275 * Entering digits of a number. 276 * Terminated by a non-digit. 277 */ 278 if (!isascii(c) || !isdigit(c) && 279 c != erase_char && c != kill_char && c != werase_char) { 280 /* 281 * Not part of the number. 282 * Treat as a normal command character. 283 */ 284 *cp = '\0'; 285 number = atoi(cmdbuf); 286 CMD_RESET; 287 mca = 0; 288 return(NO_MCA); 289 } 290 break; 291 } 292 293 /* 294 * Any other multicharacter command 295 * is terminated by a newline. 296 */ 297 if (c == '\n' || c == '\r') { 298 exec_mca(); 299 return(MCA_DONE); 300 } 301 302 /* append the char to the command buffer. */ 303 if (cmd_char(c)) 304 return(MCA_DONE); 305 306 return(MCA_MORE); 307 } 308 309 /* 310 * Main command processor. 311 * Accept and execute commands until a quit command, then return. 312 */ 313 commands() 314 { 315 register int c; 316 register int action; 317 318 last_mca = 0; 319 scroll = (sc_height + 1) / 2; 320 321 for (;;) { 322 mca = 0; 323 number = 0; 324 325 /* 326 * See if any signals need processing. 327 */ 328 if (sigs) { 329 psignals(); 330 if (quitting) 331 quit(); 332 } 333 /* 334 * Display prompt and accept a character. 335 */ 336 CMD_RESET; 337 if (!prompt()) { 338 next_file(1); 339 continue; 340 } 341 noprefix(); 342 c = getcc(); 343 344 again: if (sigs) 345 continue; 346 347 /* 348 * If we are in a multicharacter command, call mca_char. 349 * Otherwise we call cmd_decode to determine the 350 * action to be performed. 351 */ 352 if (mca) 353 switch (mca_char(c)) { 354 case MCA_MORE: 355 /* 356 * Need another character. 357 */ 358 c = getcc(); 359 goto again; 360 case MCA_DONE: 361 /* 362 * Command has been handled by mca_char. 363 * Start clean with a prompt. 364 */ 365 continue; 366 case NO_MCA: 367 /* 368 * Not a multi-char command 369 * (at least, not anymore). 370 */ 371 break; 372 } 373 374 /* decode the command character and decide what to do. */ 375 switch (action = cmd_decode(c)) { 376 case A_DIGIT: /* first digit of a number */ 377 start_mca(A_DIGIT, ":"); 378 goto again; 379 case A_F_SCREEN: /* forward one screen */ 380 CMD_EXEC; 381 if (number <= 0 && (number = sc_window) <= 0) 382 number = sc_height - 1; 383 forward(number, 1); 384 break; 385 case A_B_SCREEN: /* backward one screen */ 386 CMD_EXEC; 387 if (number <= 0 && (number = sc_window) <= 0) 388 number = sc_height - 1; 389 backward(number, 1); 390 break; 391 case A_F_LINE: /* forward N (default 1) line */ 392 CMD_EXEC; 393 forward(number <= 0 ? 1 : number, 0); 394 break; 395 case A_B_LINE: /* backward N (default 1) line */ 396 CMD_EXEC; 397 backward(number <= 0 ? 1 : number, 0); 398 break; 399 case A_F_SCROLL: /* forward N lines */ 400 CMD_EXEC; 401 if (number > 0) 402 scroll = number; 403 forward(scroll, 0); 404 break; 405 case A_B_SCROLL: /* backward N lines */ 406 CMD_EXEC; 407 if (number > 0) 408 scroll = number; 409 backward(scroll, 0); 410 break; 411 case A_FREPAINT: /* flush buffers and repaint */ 412 if (!ispipe) { 413 ch_init(0, 0); 414 clr_linenum(); 415 } 416 /* FALLTHROUGH */ 417 case A_REPAINT: /* repaint the screen */ 418 CMD_EXEC; 419 repaint(); 420 break; 421 case A_GOLINE: /* go to line N, default 1 */ 422 CMD_EXEC; 423 if (number <= 0) 424 number = 1; 425 jump_back(number); 426 break; 427 case A_PERCENT: /* go to percent of file */ 428 CMD_EXEC; 429 if (number < 0) 430 number = 0; 431 else if (number > 100) 432 number = 100; 433 jump_percent(number); 434 break; 435 case A_GOEND: /* go to line N, default end */ 436 CMD_EXEC; 437 if (number <= 0) 438 jump_forw(); 439 else 440 jump_back(number); 441 break; 442 case A_STAT: /* print file name, etc. */ 443 longprompt = 1; 444 continue; 445 case A_QUIT: /* exit */ 446 quit(); 447 case A_F_SEARCH: /* search for a pattern */ 448 case A_B_SEARCH: 449 if (number <= 0) 450 number = 1; 451 start_mca(action, (action==A_F_SEARCH) ? "/" : "?"); 452 last_mca = mca; 453 wsearch = 1; 454 c = getcc(); 455 if (c == '!') { 456 /* 457 * Invert the sense of the search; set wsearch 458 * to 0 and get a new character for the start 459 * of the pattern. 460 */ 461 start_mca(action, 462 (action == A_F_SEARCH) ? "!/" : "!?"); 463 wsearch = 0; 464 c = getcc(); 465 } 466 goto again; 467 case A_AGAIN_SEARCH: /* repeat previous search */ 468 if (number <= 0) 469 number = 1; 470 if (wsearch) 471 start_mca(last_mca, 472 (last_mca == A_F_SEARCH) ? "/" : "?"); 473 else 474 start_mca(last_mca, 475 (last_mca == A_F_SEARCH) ? "!/" : "!?"); 476 CMD_EXEC; 477 (void)search(mca == A_F_SEARCH, (char *)NULL, 478 number, wsearch); 479 break; 480 case A_HELP: /* help */ 481 lower_left(); 482 clear_eol(); 483 putstr("help"); 484 CMD_EXEC; 485 help(); 486 break; 487 case A_TAGFILE: /* tag a new file */ 488 CMD_RESET; 489 start_mca(A_TAGFILE, "Tag: "); 490 c = getcc(); 491 goto again; 492 case A_FILE_LIST: /* show list of file names */ 493 CMD_EXEC; 494 showlist(); 495 repaint(); 496 break; 497 case A_EXAMINE: /* edit a new file */ 498 CMD_RESET; 499 start_mca(A_EXAMINE, "Examine: "); 500 c = getcc(); 501 goto again; 502 case A_VISUAL: /* invoke the editor */ 503 if (ispipe) { 504 error("Cannot edit standard input"); 505 break; 506 } 507 CMD_EXEC; 508 editfile(); 509 ch_init(0, 0); 510 clr_linenum(); 511 break; 512 case A_NEXT_FILE: /* examine next file */ 513 if (number <= 0) 514 number = 1; 515 next_file(number); 516 break; 517 case A_PREV_FILE: /* examine previous file */ 518 if (number <= 0) 519 number = 1; 520 prev_file(number); 521 break; 522 case A_SETMARK: /* set a mark */ 523 lower_left(); 524 clear_eol(); 525 start_mca(A_SETMARK, "mark: "); 526 c = getcc(); 527 if (c == erase_char || c == kill_char) 528 break; 529 setmark(c); 530 break; 531 case A_GOMARK: /* go to mark */ 532 lower_left(); 533 clear_eol(); 534 start_mca(A_GOMARK, "goto mark: "); 535 c = getcc(); 536 if (c == erase_char || c == kill_char) 537 break; 538 gomark(c); 539 break; 540 case A_PREFIX: 541 /* 542 * The command is incomplete (more chars are needed). 543 * Display the current char so the user knows what's 544 * going on and get another character. 545 */ 546 if (mca != A_PREFIX) 547 start_mca(A_PREFIX, ""); 548 if (CONTROL_CHAR(c)) { 549 putchr('^'); 550 c = CARAT_CHAR(c); 551 } 552 putchr(c); 553 c = getcc(); 554 goto again; 555 default: 556 bell(); 557 break; 558 } 559 } 560 } 561 562 editfile() 563 { 564 extern char *current_file; 565 static int dolinenumber; 566 static char *editor; 567 int c; 568 char buf[MAXPATHLEN * 2 + 20], *getenv(); 569 570 if (editor == NULL) { 571 editor = getenv("EDITOR"); 572 /* pass the line number to vi */ 573 if (editor == NULL || *editor == '\0') { 574 editor = _PATH_VI; 575 dolinenumber = 1; 576 } 577 else 578 dolinenumber = 0; 579 } 580 if (dolinenumber && (c = currline(MIDDLE))) 581 (void)sprintf(buf, "%s +%d %s", editor, c, current_file); 582 else 583 (void)sprintf(buf, "%s %s", editor, current_file); 584 lsystem(buf); 585 } 586 587 showlist() 588 { 589 extern int sc_width; 590 extern char **av; 591 register int indx, width; 592 int len; 593 char *p; 594 595 if (ac <= 0) { 596 error("No files provided as arguments."); 597 return; 598 } 599 for (width = indx = 0; indx < ac;) { 600 p = strcmp(av[indx], "-") ? av[indx] : "stdin"; 601 len = strlen(p) + 1; 602 if (curr_ac == indx) 603 len += 2; 604 if (width + len + 1 >= sc_width) { 605 if (!width) { 606 if (curr_ac == indx) 607 putchr('['); 608 putstr(p); 609 if (curr_ac == indx) 610 putchr(']'); 611 ++indx; 612 } 613 width = 0; 614 putchr('\n'); 615 continue; 616 } 617 if (width) 618 putchr(' '); 619 if (curr_ac == indx) 620 putchr('['); 621 putstr(p); 622 if (curr_ac == indx) 623 putchr(']'); 624 width += len; 625 ++indx; 626 } 627 putchr('\n'); 628 error((char *)NULL); 629 } 630