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