1 /*
2  * Copyright (c) 1988 Mark Nudleman
3  * Portions copyright (c) 1999 T. Michael Vanderhoek
4  * Copyright (c) 1988, 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *	This product includes software developed by the University of
18  *	California, Berkeley and its contributors.
19  * 4. Neither the name of the University nor the names of its contributors
20  *    may be used to endorse or promote products derived from this software
21  *    without specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33  * SUCH DAMAGE.
34  */
35 
36 #ifndef lint
37 static char sccsid[] = "@(#)command.c	8.1 (Berkeley) 6/6/93";
38 #endif /* not lint */
39 
40 #ifndef lint
41 static const char rcsid[] =
42   "$FreeBSD: src/usr.bin/more/command.c,v 1.17 2000/01/08 18:11:05 hoek Exp $";
43 #endif /* not lint */
44 
45 /*
46  * Functions for interacting with the user directly printing hello
47  * messages or reading from the terminal.  All of these functions deal
48  * specifically with the prompt line, and only the prompt line.
49  */
50 
51 #include <sys/param.h>
52 
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <assert.h>
56 #include <ctype.h>
57 #include <stdarg.h>
58 #include <string.h>
59 #include <unistd.h>
60 
61 #include "less.h"
62 #include "pathnames.h"
63 
64 extern int erase_char, kill_char, werase_char;
65 extern int sigs;
66 extern int quit_at_eof;
67 extern int hit_eof;
68 extern int horiz_off;
69 extern int sc_width;
70 extern int bo_width;
71 extern int be_width;
72 extern int so_width;
73 extern int se_width;
74 extern int curr_ac;
75 extern int ac;
76 extern char **av;
77 extern int screen_trashed;	/* The screen has been overwritten */
78 
79 static int cmd_col;		/* Current screen column when accepting input */
80 
81 static cmd_char(), cmd_erase(), getcc();
82 void error(char *);
83 
84 
85 /*****************************************************************************
86  *
87  * Functions for reading-in user input.
88  *
89  */
90 
91 static int biggetinputhack_f;
92 
93 /* biggetinputhack()
94  *
95  * Performs as advertised.
96  */
biggetinputhack()97 biggetinputhack()
98 {
99 	biggetinputhack_f = 1;
100 }
101 
102 /*
103  * Read a line of input from the terminal.  Reads at most bufsiz - 1 characters
104  * and places them in buffer buf.  They are NUL-terminated.  Prints the
105  * temporary prompt prompt.  Returns true if the user aborted the input and
106  * returns false otherwise.
107  */
108 int
getinput(prompt,buf,bufsiz)109 getinput(prompt, buf, bufsiz)
110 	const char *prompt;
111 	char *buf;
112 	int bufsiz;
113 {
114 	extern bo_width, be_width;
115 	char *bufcur;
116 	int c;
117 
118 	prmpt(prompt);
119 
120 	bufcur = buf;
121 	for (;;) {
122 		c = getcc();
123 		if (c == '\n') {
124 			*bufcur = '\0';
125 			return 0;
126 		}
127 		if (c == READ_INTR ||
128 		    cmd_char(c, buf, &bufcur, buf + bufsiz - 1)) {
129 			/* input cancelled */
130 			if (bufsiz) *buf = '\0';
131 			return 1;
132 		}
133 		if (biggetinputhack_f) {
134 			biggetinputhack_f = 0;
135 			*bufcur = '\0';
136 			return 0;
137 		}
138 	}
139 }
140 
141 /*
142  * Process a single character of a multi-character input, such as
143  * a number, or the pattern of a search command.  Returns true if the user
144  * has cancelled the multi-character input, false otherwise and attempts
145  * to add it to buf (not exceeding bufsize).  Prints the character on the
146  * terminal output.  The bufcur should initially equal bufbeg.  After that
147  * it does not need to be touched or modified by the user, but may be expected
148  * to point at the future position of the next character.
149  */
150 static int
cmd_char(c,bufbeg,bufcur,bufend)151 cmd_char(c, bufbeg, bufcur, bufend)
152 	int c;          /* The character to process */
153 	char *bufbeg;   /* The buffer to add the character to */
154 	char **bufcur;  /* The position at which to add the character */
155 	char *bufend;   /* One after the last address available in the buffer.
156 	                 * No character will be placed into *bufend. */
157 {
158 	if (c == erase_char)
159 		return(cmd_erase(bufbeg, bufcur));
160 	/* in this order, in case werase == erase_char */
161 	if (c == werase_char) {
162 		if (*bufcur > bufbeg) {
163 			while (isspace((*bufcur)[-1]) &&
164 			    !cmd_erase(bufbeg, bufcur)) ;
165 			while (!isspace((*bufcur)[-1]) &&
166 			    !cmd_erase(bufbeg, bufcur)) ;
167 			while (isspace((*bufcur)[-1]) &&
168 			    !cmd_erase(bufbeg, bufcur)) ;
169 		}
170 		return *bufcur == bufbeg;
171 	}
172 	if (c == kill_char) {
173 		while (!cmd_erase(bufbeg, bufcur));
174 		return 1;
175 	}
176 
177 	/*
178 	 * No room in the command buffer, or no room on the screen;
179 	 * XXX If there is no room on the screen, we should just let the
180 	 * screen scroll down and set screen_trashed=1 appropriately, or
181 	 * alternatively, scroll the prompt line horizontally.
182 	 */
183 	assert (*bufcur <= bufend);
184 	if (*bufcur == bufend || cmd_col >= sc_width - 3)
185 		bell();
186 	else {
187 		*(*bufcur)++ = c;
188 		if (CONTROL_CHAR(c)) {
189 			putchr('^');
190 			cmd_col++;
191 			c &= ~0200;
192 			c = CARAT_CHAR(c);
193 		}
194 		putchr(c);
195 		cmd_col++;
196 	}
197 	return 0;
198 }
199 
200 /*
201  * Helper function to cmd_char().  Backs-up one character from bufcur in the
202  * buffer passed, and prints a backspace on the screen.  Returns true if the
203  * we backspaced past bufbegin (ie. the input is being aborted), and false
204  * otherwise.  The bufcur is expected to point to the future location of the
205  * next character in the buffer, and is modified appropriately.
206  */
207 static
cmd_erase(bufbegin,bufcur)208 cmd_erase(bufbegin, bufcur)
209 	char *bufbegin;
210 	char **bufcur;
211 {
212 	int c;
213 
214 	/*
215 	 * XXX Could add code to detect a backspace that is backing us over
216 	 * the beginning of a line and onto the previous line.  The backspace
217 	 * would not be printed for some terminals (eg. hardcopy) in that
218 	 * case.
219 	 */
220 
221 	/*
222 	 * backspace past beginning of the string: this usually means
223 	 * abort the input.
224 	 */
225 	if (*bufcur == bufbegin)
226 		return 1;
227 
228 	(*bufcur)--;
229 
230 	/* If erasing a control-char, erase an extra character for the carat. */
231 	c = **bufcur;
232 	if (CONTROL_CHAR(c)) {
233 		backspace();
234 		cmd_col--;
235 	}
236 
237 	backspace();
238 	cmd_col--;
239 
240 	return 0;
241 }
242 
243 static int ungotcc;
244 
245 /*
246  * Get command character from the terminal.
247  */
248 static
getcc()249 getcc()
250 {
251 	int ch;
252 	off_t position();
253 
254 	/* left over from error() routine. */
255 	if (ungotcc) {
256 		ch = ungotcc;
257 		ungotcc = 0;
258 		return(ch);
259 	}
260 
261 	return(getchr());
262 }
263 
264 /*
265  * Same as ungetc(), but works for people who don't like to use streams.
266  */
ungetcc(c)267 ungetcc(c)
268 	int c;
269 {
270 	ungotcc = c;
271 }
272 
273 
274 /*****************************************************************************
275  *
276  * prompts
277  *
278  */
279 
280 static int longprompt;
281 
282 /*
283  * Prints prmpt where the prompt would normally appear.  This is different
284  * from changing the current prompt --- this is more like printing a
285  * unimportant notice or error.  The prmpt line will be printed in bold (if
286  * possible).  Will in the future print only the last sc_width - 1 - bo_width
287  * characters (to prevent newline).
288  */
prmpt(prmpt)289 prmpt(prmpt)
290 	const char *prmpt;
291 {
292 	lower_left();
293 	clear_eol();
294 	bo_enter();
295 	putxstr(prmpt);
296 	bo_exit();
297 	flush();
298 	cmd_col = strlen(prmpt) + bo_width + be_width;
299 }
300 
301 /*
302  * Print the main prompt that signals we are ready for user commands.  This
303  * also magically positions the current file where it should be (either by
304  * calling repaint() if screen_trashed or by searching for a search
305  * string that was specified through option.c on the more(1) command line).
306  * Additional magic will randomly call the quit() function.
307  *
308  * This is really intended to do a lot of the work of commands().  It has
309  * little purpose outside of commands().
310  */
prompt()311 prompt()
312 {
313 	extern int linenums, short_file, ispipe;
314 	extern char *current_name, *firstsearch, *next_name;
315 	off_t len, pos, ch_length(), position(), forw_line();
316 	char pbuf[40];
317 
318 	/*
319 	 * if nothing is displayed yet, display starting from line 1;
320 	 * if search string provided, go there instead.
321 	 */
322 	if (position(TOP) == NULL_POSITION) {
323 #if 0
324 /* This code causes "more zero-byte-file /etc/termcap" to skip straight
325  * to the /etc/termcap file ... that is undesireable.  There are only a few
326  * instances where these two lines perform something useful. */
327 		if (forw_line((off_t)0) == NULL_POSITION)
328 			return 0 ;
329 #endif
330 		if (!firstsearch || !search(1, firstsearch, 1, 1))
331 			jump_back(1);
332 	}
333 	else if (screen_trashed)
334 		repaint();
335 
336 	/* if no -e flag and we've hit EOF on the last file, quit. */
337 	if (!quit_at_eof && hit_eof && curr_ac + 1 >= ac)
338 		quit();
339 
340 	/* select the proper prompt and display it. */
341 	lower_left();
342 	clear_eol();
343 	pbuf[sizeof(pbuf) - 1] = '\0';
344 	if (longprompt) {
345 		/*
346 		 * Get the current line/pos from the BOTTOM of the screen
347 		 * even though that's potentially confusing for the user
348 		 * when switching between wraplines=true and a valid horiz_off
349 		 * (with wraplines=false).  In exchange, it is sometimes
350 		 * easier for the user to tell when a file is relatively
351 		 * short vs. long.
352 		 */
353 		so_enter();
354 		putstr(current_name);
355 		putstr(":");
356 		if (!ispipe) {
357 			(void)snprintf(pbuf, sizeof(pbuf) - 1,
358 			    " file %d/%d", curr_ac + 1, ac);
359 			putstr(pbuf);
360 		}
361 		if (linenums) {
362 			(void)snprintf(pbuf, sizeof(pbuf) - 1,
363 			    " line %d", currline(BOTTOM));
364 			putstr(pbuf);
365 		}
366 		(void)snprintf(pbuf, sizeof(pbuf) - 1, " col %d", horiz_off);
367 		putstr(pbuf);
368 		if ((pos = position(BOTTOM)) != NULL_POSITION) {
369 			(void)snprintf(pbuf, sizeof(pbuf) - 1,
370 			    " byte %qd", pos);
371 			putstr(pbuf);
372 			if (!ispipe && (len = ch_length())) {
373 				(void)snprintf(pbuf, sizeof(pbuf) - 1,
374 				    "/%qd pct %qd%%", len, ((100 * pos) / len));
375 				putstr(pbuf);
376 			}
377 		}
378 		so_exit();
379 	}
380 	else {
381 		so_enter();
382 		putstr(current_name);
383 		if (hit_eof)
384 			if (next_name) {
385 				putstr(": END (next file: ");
386 				putstr(next_name);
387 				putstr(")");
388 			}
389 			else
390 				putstr(": END");
391 		else if (!ispipe &&
392 		    (pos = position(BOTTOM)) != NULL_POSITION &&
393 		    (len = ch_length())) {
394 			(void)snprintf(pbuf, sizeof(pbuf) - 1,
395 			    " (%qd%%)", ((100 * pos) / len));
396 			putstr(pbuf);
397 		}
398 		so_exit();
399 	}
400 
401 	/*
402 	 * XXX This isn't correct, but until we get around to reworking
403 	 * the whole prompt stuff the way we want it to be, this hack
404 	 * is necessary to prevent input from being blocked if getinput()
405 	 * is called and the user enters an input that fills the cmd
406 	 * buffer (or reaches the far rightside end of the screen).
407 	 */
408 	cmd_col = 0;
409 
410 	return 1;
411 }
412 
413 /*
414  * Sets the current prompt.  Currently it sets the current prompt to the
415  * long prompt.
416  */
statprompt(nostatprompt)417 statprompt(nostatprompt)
418 	int nostatprompt;  /* Turn off the stat prompt?  (off by default...) */
419 {
420 	if (nostatprompt)
421 		longprompt = 0;
422 	else
423 		longprompt = 1;
424 }
425 
426 
427 /*****************************************************************************
428  *
429  * Errors, next-of-kin to prompts.
430  *
431  */
432 
433 /*
434  * Shortcut function that may be used when setting the current erreur
435  * and erreur string at the same time.  The function name is chosen to be
436  * symetric with the SETERR() macro in less.h.  This could be written as
437  * macro, too, but we'd need to use a GNU C extension.
438  */
SETERRSTR(enum error e,const char * s,...)439 SETERRSTR(enum error e, const char *s, ...)
440 {
441 	va_list args;
442 
443 	erreur = e;
444 	if (errstr) free(errstr);
445 	errstr = NULL;
446 	va_start(args, s);
447 	vasprintf(&errstr, s, args);
448 	va_end(args);
449 }
450 
451 /*
452  * Prints an error message and clears the current error.
453  */
454 void
handle_error()455 handle_error()
456 {
457 	if (erreur == E_OK)
458 		return;
459 
460 	bell();
461 	if (errstr)
462 		error(errstr);
463 	else
464 		error(deferr[erreur]);
465 	erreur = E_OK;
466 	errstr = NULL;
467 }
468 
469 /*
470  * Clears any error messages and pretends they never occurred.
471  */
472 void
clear_error()473 clear_error()
474 {
475 	erreur = E_OK;
476 	if (errstr) free(errstr);
477 	errstr = NULL;
478 }
479 
480 int errmsgs;
481 static char return_to_continue[] = "(press RETURN)";
482 
483 /*
484  * Output a message in the lower left corner of the screen
485  * and wait for carriage return.
486  */
487 /* static */
488 void
error(s)489 error(s)
490 	char *s;
491 {
492 	extern int any_display;
493 	int ch;
494 
495 	errmsgs++;
496 	if (!any_display) {
497 		/*
498 		 * Nothing has been displayed yet.  Output this message on
499 		 * error output (file descriptor 2) and don't wait for a
500 		 * keystroke to continue.
501 		 *
502 		 * This has the desirable effect of producing all error
503 		 * messages on error output if standard output is directed
504 		 * to a file.  It also does the same if we never produce
505 		 * any real output; for example, if the input file(s) cannot
506 		 * be opened.  If we do eventually produce output, code in
507 		 * edit() makes sure these messages can be seen before they
508 		 * are overwritten or scrolled away.
509 		 */
510 		(void)write(2, s, strlen(s));
511 		(void)write(2, "\n", 1);
512 		return;
513 	}
514 
515 	lower_left();
516 	clear_eol();
517 	so_enter();
518 	if (s) {
519 		putstr(s);
520 		putstr("  ");
521 	}
522 	putstr(return_to_continue);
523 	so_exit();
524 
525 	if ((ch = getchr()) != '\n') {
526 		/* XXX hardcoded */
527 		if (ch == 'q')
528 			quit();
529 		ungotcc = ch;
530 	}
531 	lower_left();
532 
533 	if ((s==NULL)?0:(strlen(s)) + sizeof(return_to_continue) +
534 	    so_width + se_width + 1 > sc_width) {
535 		/*
536 		 * Printing the message has probably scrolled the screen.
537 		 * {{ Unless the terminal doesn't have auto margins,
538 		 *    in which case we just hammered on the right margin. }}
539 		 */
540 		/* XXX Should probably just set screen_trashed=1, but I'm
541 		 * not going to touch that until all the places that call
542 		 * error() have been checked, or until error() is staticized. */
543 		repaint();
544 	}
545 	flush();
546 }
547 
548 
549 /****************************************************************************
550  *
551  * The main command processor.
552  *
553  * (Well, it deals with things on the prompt line, doesn't it?)
554  *
555  */
556 
557 /*
558  * Main command processor.
559  *
560  * Accept and execute commands until a quit command, then return.
561  */
562 void
commands()563 commands()
564 {
565 	enum runmacro runmacro();
566 	enum runmacro rmret;
567 	long numberN;
568 	enum { NOTGOTTEN=0, GOTTEN=1, GETTING } Nstate;  /* ie. numberNstate */
569 	int c;
570 	char inbuf[20], *incur = inbuf;
571 	*inbuf = '\0';
572 
573 	Nstate = GETTING;
574 	for (;;) {
575 		/*
576 		 * See if any signals need processing.
577 		 */
578 		if (sigs)
579 			psignals();
580 
581 		/*
582 		 * Display prompt and generally get setup.  Don't display the
583 		 * prompt if we are already in the middle of accepting a
584 		 * set of characters.
585 		 */
586 		if (!*inbuf && !prompt()) {
587 			next_file(1);
588 			continue;
589 		}
590 
591 		c = getcc();
592 
593 		/* Check sigs here --- getcc() may have given us READ_INTR */
594 		if (sigs) {
595 			/* terminate any current macro */
596 			*inbuf = '\0';
597 			incur = inbuf;
598 
599 			continue;  /* process the sigs */
600 		}
601 
602 		if (Nstate == GETTING && !isdigit(c)
603 		    && c != erase_char && c != werase_char && c != kill_char) {
604 			/*
605 			 * Mark the end of an input number N, if any.
606 			 */
607 
608 			if (!*inbuf) {
609 				/* We never actually got an input number */
610 				Nstate = NOTGOTTEN;
611 			} else {
612 				numberN = atol(inbuf);
613 				Nstate = GOTTEN;
614 			}
615 			*inbuf = '\0';
616 			incur = inbuf;
617 		}
618 		(void) cmd_char(c, inbuf, &incur, inbuf + sizeof(inbuf) - 1);
619 		*incur = '\0';
620 		if (*inbuf)
621 			prmpt(inbuf);
622 		else
623 			Nstate = GETTING;  /* abort command */
624 
625 		if (Nstate == GETTING) {
626 			/* Still reading in the number N ... don't want to
627 			 * try running the macro expander. */
628 			continue;
629 		} else {
630 			/* Try expanding the macro */
631 			switch (runmacro(inbuf, numberN, Nstate)) {
632 			case TOOMACRO:
633 				break;
634 			case BADMACRO: case NOMACRO: case BADCOMMAND:
635 				handle_error();
636 				/* fallthrough */
637 			case OK:
638 				/* recock */
639 				*inbuf = '\0';
640 				incur = inbuf;
641 				Nstate = GETTING;
642 				break;
643 			}
644 		}
645 	}  /* for (;;) */
646 }
647 
648 
649 /*****************************************************************************
650  *
651  * Misc functions that belong in ncommand.c but are here for historical
652  * and for copyright reasons.
653  *
654  */
655 
656 void
editfile()657 editfile()
658 {
659 	off_t position();
660 	extern char *current_file;
661 	static int dolinenumber;
662 	static char *editor;
663 	char *base;
664 	int linenumber;
665 	char buf[MAXPATHLEN * 2 + 20], *getenv();
666 
667 	if (editor == NULL) {
668 		editor = getenv("EDITOR");
669 
670 		/* default editor is vi */
671 		if (editor == NULL || *editor == '\0')
672 			editor = _PATH_VI;
673 
674 		/* check last component in case of full path */
675 		base = strrchr(editor, '/');
676 		if (!base)
677 			base = editor;
678 		else
679 			base++;
680 
681 		/* emacs also accepts vi-style +nnnn */
682 		if (strncmp(base, "vi", 2) == 0 || strcmp(base, "emacs") == 0)
683 			dolinenumber = 1;
684 		else
685 			dolinenumber = 0;
686 	}
687 	/*
688 	 * XXX Can't just use currline(MIDDLE) since that might be NULL_POSITION
689 	 * if we are editting a short file or some kind of search positioned
690 	 * us near the last line.  It's not clear what currline() should do
691 	 * in those circumstances, but as of this writing, it doesn't do
692 	 * anything reasonable from our perspective.  The currline(MIDDLE)
693 	 * never had the desired results for an editfile() after a search()
694 	 * anyways.  Note, though, that when vi(1) starts its editting, it
695 	 * positions the focus line in the middle of the screen, not the top.
696 	 *
697 	 * I think what is needed is some kind of setfocus() and getfocus()
698 	 * function.  This could put the focussed line in the middle, top,
699 	 * or wherever as per the user's wishes, and allow things like us
700 	 * to getfocus() the correct file-position/line-number.  A search would
701 	 * then search forward (or backward) from the current focus position,
702 	 * etc.
703 	 *
704 	 * currline() doesn't belong.
705 	 */
706 	if (position(MIDDLE) == NULL_POSITION)
707 		linenumber = currline(TOP);
708 	else
709 		linenumber = currline(MIDDLE);
710 	if (dolinenumber && linenumber)
711 		(void)snprintf(buf, sizeof(buf),
712 		    "%s +%d %s", editor, linenumber, current_file);
713 	else
714 		(void)snprintf(buf, sizeof(buf), "%s %s", editor, current_file);
715 	lsystem(buf);
716 }
717 
718 void
showlist()719 showlist()
720 {
721 	extern int sc_width;
722 	register int indx, width;
723 	int len;
724 	char *p;
725 
726 	if (ac <= 0) {
727 		error("No files provided as arguments.");
728 		return;
729 	}
730 	for (width = indx = 0; indx < ac;) {
731 		p = strcmp(av[indx], "-") ? av[indx] : "stdin";
732 		len = strlen(p) + 1;
733 		if (curr_ac == indx)
734 			len += 2;
735 		if (width + len + 1 >= sc_width) {
736 			if (!width) {
737 				if (curr_ac == indx)
738 					putchr('[');
739 				putstr(p);
740 				if (curr_ac == indx)
741 					putchr(']');
742 				++indx;
743 			}
744 			width = 0;
745 			putchr('\n');
746 			continue;
747 		}
748 		if (width)
749 			putchr(' ');
750 		if (curr_ac == indx)
751 			putchr('[');
752 		putstr(p);
753 		if (curr_ac == indx)
754 			putchr(']');
755 		width += len;
756 		++indx;
757 	}
758 	putchr('\n');
759 	error((char *)NULL);
760 }
761