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