xref: /netbsd/distrib/utils/more/command.c (revision bf9ec67e)
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