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