xref: /openbsd-src/usr.bin/less/command.c (revision a4afd6dad3fba28f80e70208181c06c482259988)
1 /*
2  * Copyright (c) 1984,1985,1989,1994,1995  Mark Nudelman
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice in the documentation and/or other materials provided with
12  *    the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
20  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
21  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
23  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
24  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 
28 /*
29  * User-level command processor.
30  */
31 
32 #include "less.h"
33 #include "position.h"
34 #include "option.h"
35 #include "cmd.h"
36 
37 extern int erase_char, kill_char;
38 extern int sigs;
39 extern int quit_at_eof;
40 extern int hit_eof;
41 extern int sc_width;
42 extern int sc_height;
43 extern int swindow;
44 extern int jump_sline;
45 extern int quitting;
46 extern int wscroll;
47 extern int nohelp;
48 extern int top_scroll;
49 extern int ignore_eoi;
50 extern char *every_first_cmd;
51 extern char *curr_altfilename;
52 extern char version[];
53 extern struct scrpos initial_scrpos;
54 extern IFILE curr_ifile;
55 #if CMD_HISTORY
56 extern void *ml_search;
57 extern void *ml_examine;
58 #if SHELL_ESCAPE || PIPEC
59 extern void *ml_shell;
60 #endif
61 #else
62 /* No CMD_HISTORY */
63 #define	ml_search	NULL
64 #define	ml_examine	NULL
65 #define	ml_shell  	NULL
66 #endif
67 #if EDITOR
68 extern char *editor;
69 extern char *editproto;
70 #endif
71 extern int screen_trashed;	/* The screen has been overwritten */
72 extern int be_helpful;
73 
74 public int helpprompt;
75 
76 static char ungot[100];
77 static char *ungotp = NULL;
78 #if SHELL_ESCAPE
79 static char *shellcmd = NULL;	/* For holding last shell command for "!!" */
80 #endif
81 static int mca;			/* The multicharacter command (action) */
82 static int search_type;		/* The previous type of search */
83 static int number;		/* The number typed by the user */
84 static char optchar;
85 static int optflag;
86 #if PIPEC
87 static char pipec;
88 #endif
89 
90 static void multi_search();
91 
92 /*
93  * Move the cursor to lower left before executing a command.
94  * This looks nicer if the command takes a long time before
95  * updating the screen.
96  */
97 	static void
98 cmd_exec()
99 {
100 	lower_left();
101 	flush();
102 }
103 
104 /*
105  * Set up the display to start a new multi-character command.
106  */
107 	static void
108 start_mca(action, prompt, mlist)
109 	int action;
110 	char *prompt;
111 	void *mlist;
112 {
113 	mca = action;
114 	clear_bot();
115 	cmd_putstr(prompt);
116 #if CMD_HISTORY
117 	set_mlist(mlist);
118 #endif
119 }
120 
121 	public int
122 in_mca()
123 {
124 	return (mca != 0 && mca != A_PREFIX);
125 }
126 
127 /*
128  * Set up the display to start a new search command.
129  */
130 	static void
131 mca_search()
132 {
133 	if (search_type & SRCH_FORW)
134 		mca = A_F_SEARCH;
135 	else
136 		mca = A_B_SEARCH;
137 
138 	clear_bot();
139 
140 	if (search_type & SRCH_FIRST_FILE)
141 		cmd_putstr("@");
142 
143 	if (search_type & SRCH_PAST_EOF)
144 		cmd_putstr("*");
145 
146 	if (search_type & SRCH_NOMATCH)
147 		cmd_putstr("!");
148 
149 	if (search_type & SRCH_FORW)
150 		cmd_putstr("/");
151 	else
152 		cmd_putstr("?");
153 #if CMD_HISTORY
154 	set_mlist(ml_search);
155 #endif
156 }
157 
158 /*
159  * Execute a multicharacter command.
160  */
161 	static void
162 exec_mca()
163 {
164 	register char *cbuf;
165 
166 	cmd_exec();
167 	cbuf = get_cmdbuf();
168 
169 	switch (mca)
170 	{
171 	case A_F_SEARCH:
172 	case A_B_SEARCH:
173 		multi_search(cbuf, number);
174 		break;
175 	case A_FIRSTCMD:
176 		/*
177 		 * Skip leading spaces or + signs in the string.
178 		 */
179 		while (*cbuf == '+' || *cbuf == ' ')
180 			cbuf++;
181 		if (every_first_cmd != NULL)
182 			free(every_first_cmd);
183 		if (*cbuf == '\0')
184 			every_first_cmd = NULL;
185 		else
186 			every_first_cmd = save(cbuf);
187 		break;
188 	case A_OPT_TOGGLE:
189 		toggle_option(optchar, cbuf, optflag);
190 		optchar = '\0';
191 		break;
192 	case A_F_BRACKET:
193 		match_brac(cbuf[0], cbuf[1], 1, number);
194 		break;
195 	case A_B_BRACKET:
196 		match_brac(cbuf[1], cbuf[0], 0, number);
197 		break;
198 #if EXAMINE
199 	case A_EXAMINE:
200 		edit_list(cbuf);
201 		break;
202 #endif
203 #if SHELL_ESCAPE
204 	case A_SHELL:
205 		/*
206 		 * !! just uses whatever is in shellcmd.
207 		 * Otherwise, copy cmdbuf to shellcmd,
208 		 * expanding any special characters ("%" or "#").
209 		 */
210 		if (*cbuf != '!')
211 		{
212 			if (shellcmd != NULL)
213 				free(shellcmd);
214 			shellcmd = fexpand(cbuf);
215 		}
216 
217 		if (shellcmd == NULL)
218 			lsystem("");
219 		else
220 			lsystem(shellcmd);
221 		error("!done", NULL_PARG);
222 		break;
223 #endif
224 #if PIPEC
225 	case A_PIPE:
226 		(void) pipe_mark(pipec, cbuf);
227 		error("|done", NULL_PARG);
228 		break;
229 #endif
230 	}
231 }
232 
233 /*
234  * Add a character to a multi-character command.
235  */
236 	static int
237 mca_char(c)
238 	int c;
239 {
240 	char *p;
241 	int flag;
242 	char buf[3];
243 
244 	switch (mca)
245 	{
246 	case 0:
247 		/*
248 		 * Not in a multicharacter command.
249 		 */
250 		return (NO_MCA);
251 
252 	case A_PREFIX:
253 		/*
254 		 * In the prefix of a command.
255 		 * This not considered a multichar command
256 		 * (even tho it uses cmdbuf, etc.).
257 		 * It is handled in the commands() switch.
258 		 */
259 		return (NO_MCA);
260 
261 	case A_DIGIT:
262 		/*
263 		 * Entering digits of a number.
264 		 * Terminated by a non-digit.
265 		 */
266 		if ((c < '0' || c > '9') &&
267 		  editchar(c, EC_PEEK|EC_NOHISTORY|EC_NOCOMPLETE) == A_INVALID)
268 		{
269 			/*
270 			 * Not part of the number.
271 			 * Treat as a normal command character.
272 			 */
273 			number = cmd_int();
274 			mca = 0;
275 			cmd_accept();
276 			return (NO_MCA);
277 		}
278 		break;
279 
280 	case A_OPT_TOGGLE:
281 		/*
282 		 * Special case for the TOGGLE_OPTION command.
283 		 * If the option letter which was entered is a
284 		 * single-char option, execute the command immediately,
285 		 * so user doesn't have to hit RETURN.
286 		 * If the first char is + or -, this indicates
287 		 * OPT_UNSET or OPT_SET respectively, instead of OPT_TOGGLE.
288 		 */
289 		if (c == erase_char || c == kill_char)
290 			break;
291 		if (optchar != '\0' && optchar != '+' && optchar != '-')
292 			/*
293 			 * We already have the option letter.
294 			 */
295 			break;
296 		switch (c)
297 		{
298 		case '+':
299 			optflag = OPT_UNSET;
300 			break;
301 		case '-':
302 			optflag = OPT_SET;
303 			break;
304 		default:
305 			optchar = c;
306 			if (optflag != OPT_TOGGLE || single_char_option(c))
307 			{
308 				toggle_option(c, "", optflag);
309 				return (MCA_DONE);
310 			}
311 			break;
312 		}
313 		if (optchar == '+' || optchar == '-')
314 		{
315 			optchar = c;
316 			break;
317 		}
318 		/*
319 		 * Display a prompt appropriate for the option letter.
320 		 */
321 		if ((p = opt_prompt(c)) == NULL)
322 		{
323 			buf[0] = '-';
324 			buf[1] = c;
325 			buf[2] = '\0';
326 			p = buf;
327 		}
328 		start_mca(A_OPT_TOGGLE, p, (void*)NULL);
329 		return (MCA_MORE);
330 
331 	case A_F_SEARCH:
332 	case A_B_SEARCH:
333 		/*
334 		 * Special case for search commands.
335 		 * Certain characters as the first char of
336 		 * the pattern have special meaning:
337 		 *	!  Toggle the NOMATCH flag
338 		 *	*  Toggle the PAST_EOF flag
339 		 *	@  Toggle the FIRST_FILE flag
340 		 */
341 		if (len_cmdbuf() > 0)
342 			/*
343 			 * Only works for the first char of the pattern.
344 			 */
345 			break;
346 
347 		flag = 0;
348 		switch (c)
349 		{
350 		case '!':
351 			flag = SRCH_NOMATCH;
352 			break;
353 		case '@':
354 			flag = SRCH_FIRST_FILE;
355 			break;
356 		case '*':
357 			flag = SRCH_PAST_EOF;
358 			break;
359 		}
360 		if (flag != 0)
361 		{
362 			search_type ^= flag;
363 			mca_search();
364 			return (MCA_MORE);
365 		}
366 		break;
367 	}
368 
369 	/*
370 	 * Any other multicharacter command
371 	 * is terminated by a newline.
372 	 */
373 	if (c == '\n' || c == '\r')
374 	{
375 		/*
376 		 * Execute the command.
377 		 */
378 		exec_mca();
379 		return (MCA_DONE);
380 	}
381 	/*
382 	 * Append the char to the command buffer.
383 	 */
384 	if (cmd_char(c) == CC_QUIT)
385 		/*
386 		 * Abort the multi-char command.
387 		 */
388 		return (MCA_DONE);
389 
390 	if ((mca == A_F_BRACKET || mca == A_B_BRACKET) && len_cmdbuf() >= 2)
391 	{
392 		/*
393 		 * Special case for the bracket-matching commands.
394 		 * Execute the command after getting exactly two
395 		 * characters from the user.
396 		 */
397 		exec_mca();
398 		return (MCA_DONE);
399 	}
400 
401 	/*
402 	 * Need another character.
403 	 */
404 	return (MCA_MORE);
405 }
406 
407 /*
408  * Display the appropriate prompt.
409  */
410 	static void
411 prompt()
412 {
413 	register char *p;
414 
415 	if (ungotp != NULL && ungotp > ungot)
416 	{
417 		/*
418 		 * No prompt necessary if commands are from
419 		 * ungotten chars rather than from the user.
420 		 */
421 		return;
422 	}
423 
424 	/*
425 	 * If nothing is displayed yet, display starting from initial_scrpos.
426 	 */
427 	if (empty_screen())
428 	{
429 		if (initial_scrpos.pos == NULL_POSITION)
430 			/*
431 			 * {{ Maybe this should be:
432 			 *    jump_loc(ch_zero(), jump_sline);
433 			 *    but this behavior seems rather unexpected
434 			 *    on the first screen. }}
435 			 */
436 			jump_loc(ch_zero(), 1);
437 		else
438 			jump_loc(initial_scrpos.pos, initial_scrpos.ln);
439 	} else if (screen_trashed)
440 	{
441 		int save_top_scroll;
442 		save_top_scroll = top_scroll;
443 		top_scroll = 1;
444 		repaint();
445 		top_scroll = save_top_scroll;
446 	}
447 
448 	/*
449 	 * If the -E flag is set and we've hit EOF on the last file, quit.
450 	 */
451 	if (quit_at_eof == OPT_ONPLUS && hit_eof &&
452 	    next_ifile(curr_ifile) == NULL_IFILE)
453 		quit(QUIT_OK);
454 
455 	/*
456 	 * Select the proper prompt and display it.
457 	 */
458 	clear_bot();
459 	if (helpprompt) {
460 		so_enter();
461 		putstr("[Press 'h' for instructions.]");
462 		so_exit();
463 		helpprompt = 0;
464 	} else {
465 		p = pr_string();
466 		if (p == NULL)
467 			putchr(':');
468 		else
469 		{
470 			so_enter();
471 			putstr(p);
472 			if (be_helpful)
473 				putstr(" [Press space to continue, 'q' to quit.]");
474 			so_exit();
475 		}
476 	}
477 }
478 
479 	public void
480 dispversion()
481 {
482 	PARG parg;
483 
484 	parg.p_string = version;
485 	error("less  version %s", &parg);
486 }
487 
488 /*
489  * Get command character.
490  * The character normally comes from the keyboard,
491  * but may come from ungotten characters
492  * (characters previously given to ungetcc or ungetsc).
493  */
494 	public int
495 getcc()
496 {
497 	if (ungotp == NULL)
498 		/*
499 		 * Normal case: no ungotten chars, so get one from the user.
500 		 */
501 		return (getchr());
502 
503 	if (ungotp > ungot)
504 		/*
505 		 * Return the next ungotten char.
506 		 */
507 		return (*--ungotp);
508 
509 	/*
510 	 * We have just run out of ungotten chars.
511 	 */
512 	ungotp = NULL;
513 	if (len_cmdbuf() == 0 || !empty_screen())
514 		return (getchr());
515 	/*
516 	 * Command is incomplete, so try to complete it.
517 	 */
518 	switch (mca)
519 	{
520 	case A_DIGIT:
521 		/*
522 		 * We have a number but no command.  Treat as #g.
523 		 */
524 		return ('g');
525 
526 	case A_F_SEARCH:
527 	case A_B_SEARCH:
528 		/*
529 		 * We have "/string" but no newline.  Add the \n.
530 		 */
531 		return ('\n');
532 
533 	default:
534 		/*
535 		 * Some other incomplete command.  Let user complete it.
536 		 */
537 		return (getchr());
538 	}
539 }
540 
541 /*
542  * "Unget" a command character.
543  * The next getcc() will return this character.
544  */
545 	public void
546 ungetcc(c)
547 	int c;
548 {
549 	if (ungotp == NULL)
550 		ungotp = ungot;
551 	if (ungotp >= ungot + sizeof(ungot))
552 	{
553 		error("ungetcc overflow", NULL_PARG);
554 		quit(QUIT_ERROR);
555 	}
556 	*ungotp++ = c;
557 }
558 
559 /*
560  * Unget a whole string of command characters.
561  * The next sequence of getcc()'s will return this string.
562  */
563 	public void
564 ungetsc(s)
565 	char *s;
566 {
567 	register char *p;
568 
569 	for (p = s + strlen(s) - 1;  p >= s;  p--)
570 		ungetcc(*p);
571 }
572 
573 /*
574  * Search for a pattern, possibly in multiple files.
575  * If SRCH_FIRST_FILE is set, begin searching at the first file.
576  * If SRCH_PAST_EOF is set, continue the search thru multiple files.
577  */
578 	static void
579 multi_search(pattern, n)
580 	char *pattern;
581 	int n;
582 {
583 	register int nomore;
584 	IFILE save_ifile;
585 	int changed_file;
586 
587 	changed_file = 0;
588 	save_ifile = curr_ifile;
589 
590 	if (search_type & SRCH_FIRST_FILE)
591 	{
592 		/*
593 		 * Start at the first (or last) file
594 		 * in the command line list.
595 		 */
596 		if (search_type & SRCH_FORW)
597 			nomore = edit_first();
598 		else
599 			nomore = edit_last();
600 		if (nomore)
601 			return;
602 		changed_file = 1;
603 		search_type &= ~SRCH_FIRST_FILE;
604 	}
605 
606 	for (;;)
607 	{
608 		if ((n = search(search_type, pattern, n)) == 0)
609 			/*
610 			 * Found it.
611 			 */
612 			return;
613 
614 		if (n < 0)
615 			/*
616 			 * Some kind of error in the search.
617 			 * Error message has been printed by search().
618 			 */
619 			break;
620 
621 		if ((search_type & SRCH_PAST_EOF) == 0)
622 			/*
623 			 * We didn't find a match, but we're
624 			 * supposed to search only one file.
625 			 */
626 			break;
627 		/*
628 		 * Move on to the next file.
629 		 */
630 		if (search_type & SRCH_FORW)
631 			nomore = edit_next(1);
632 		else
633 			nomore = edit_prev(1);
634 		if (nomore)
635 			break;
636 		changed_file = 1;
637 	}
638 
639 	/*
640 	 * Didn't find it.
641 	 * Print an error message if we haven't already.
642 	 */
643 	if (n > 0)
644 		error("Pattern not found", NULL_PARG);
645 
646 	if (changed_file)
647 	{
648 		/*
649 		 * Restore the file we were originally viewing.
650 		 */
651 		if (edit_ifile(save_ifile))
652 			quit(QUIT_ERROR);
653 	}
654 }
655 
656 /*
657  * Main command processor.
658  * Accept and execute commands until a quit command.
659  */
660 	public void
661 commands()
662 {
663 	register int c;
664 	register int action;
665 	register char *cbuf;
666 	int save_search_type;
667 	char *s;
668 	char tbuf[2];
669 	PARG parg;
670 
671 	search_type = SRCH_FORW;
672 	wscroll = (sc_height + 1) / 2;
673 
674 	for (;;)
675 	{
676 		mca = 0;
677 		cmd_accept();
678 		number = 0;
679 		optchar = '\0';
680 
681 		/*
682 		 * See if any signals need processing.
683 		 */
684 		if (sigs)
685 		{
686 			psignals();
687 			if (quitting)
688 				quit(QUIT_SAVED_STATUS);
689 		}
690 
691 		/*
692 		 * Display prompt and accept a character.
693 		 */
694 		cmd_reset();
695 		prompt();
696 		if (sigs)
697 			continue;
698 		c = getcc();
699 
700 	again:
701 		if (sigs)
702 			continue;
703 
704 		/*
705 		 * If we are in a multicharacter command, call mca_char.
706 		 * Otherwise we call fcmd_decode to determine the
707 		 * action to be performed.
708 		 */
709 		if (mca)
710 			switch (mca_char(c))
711 			{
712 			case MCA_MORE:
713 				/*
714 				 * Need another character.
715 				 */
716 				c = getcc();
717 				goto again;
718 			case MCA_DONE:
719 				/*
720 				 * Command has been handled by mca_char.
721 				 * Start clean with a prompt.
722 				 */
723 				continue;
724 			case NO_MCA:
725 				/*
726 				 * Not a multi-char command
727 				 * (at least, not anymore).
728 				 */
729 				break;
730 			}
731 
732 		/*
733 		 * Decode the command character and decide what to do.
734 		 */
735 		if (mca)
736 		{
737 			/*
738 			 * We're in a multichar command.
739 			 * Add the character to the command buffer
740 			 * and display it on the screen.
741 			 * If the user backspaces past the start
742 			 * of the line, abort the command.
743 			 */
744 			if (cmd_char(c) == CC_QUIT || len_cmdbuf() == 0)
745 				continue;
746 			cbuf = get_cmdbuf();
747 		} else
748 		{
749 			/*
750 			 * Don't use cmd_char if we're starting fresh
751 			 * at the beginning of a command, because we
752 			 * don't want to echo the command until we know
753 			 * it is a multichar command.  We also don't
754 			 * want erase_char/kill_char to be treated
755 			 * as line editing characters.
756 			 */
757 			tbuf[0] = c;
758 			tbuf[1] = '\0';
759 			cbuf = tbuf;
760 		}
761 		s = NULL;
762 		action = fcmd_decode(cbuf, &s);
763 		/*
764 		 * If an "extra" string was returned,
765 		 * process it as a string of command characters.
766 		 */
767 		if (s != NULL)
768 			ungetsc(s);
769 		/*
770 		 * Clear the cmdbuf string.
771 		 * (But not if we're in the prefix of a command,
772 		 * because the partial command string is kept there.)
773 		 */
774 		if (action != A_PREFIX)
775 			cmd_reset();
776 
777 		switch (action)
778 		{
779 		case A_DIGIT:
780 			/*
781 			 * First digit of a number.
782 			 */
783 			start_mca(A_DIGIT, ":", (void*)NULL);
784 			goto again;
785 
786 		case A_F_WINDOW:
787 			/*
788 			 * Forward one window (and set the window size).
789 			 */
790 			if (number > 0)
791 				swindow = number;
792 			/* FALLTHRU */
793 		case A_F_SCREEN:
794 			/*
795 			 * Forward one screen.
796 			 */
797 			if (number <= 0)
798 				number = get_swindow();
799 			cmd_exec();
800 			forward(number, 0, 1);
801 			break;
802 
803 		case A_B_WINDOW:
804 			/*
805 			 * Backward one window (and set the window size).
806 			 */
807 			if (number > 0)
808 				swindow = number;
809 			/* FALLTHRU */
810 		case A_B_SCREEN:
811 			/*
812 			 * Backward one screen.
813 			 */
814 			if (number <= 0)
815 				number = get_swindow();
816 			cmd_exec();
817 			backward(number, 0, 1);
818 			break;
819 
820 		case A_F_LINE:
821 			/*
822 			 * Forward N (default 1) line.
823 			 */
824 			if (number <= 0)
825 				number = 1;
826 			cmd_exec();
827 			forward(number, 0, 0);
828 			break;
829 
830 		case A_B_LINE:
831 			/*
832 			 * Backward N (default 1) line.
833 			 */
834 			if (number <= 0)
835 				number = 1;
836 			cmd_exec();
837 			backward(number, 0, 0);
838 			break;
839 
840 		case A_FF_LINE:
841 			/*
842 			 * Force forward N (default 1) line.
843 			 */
844 			if (number <= 0)
845 				number = 1;
846 			cmd_exec();
847 			forward(number, 1, 0);
848 			break;
849 
850 		case A_BF_LINE:
851 			/*
852 			 * Force backward N (default 1) line.
853 			 */
854 			if (number <= 0)
855 				number = 1;
856 			cmd_exec();
857 			backward(number, 1, 0);
858 			break;
859 
860 		case A_F_FOREVER:
861 			/*
862 			 * Forward forever, ignoring EOF.
863 			 */
864 			cmd_exec();
865 			jump_forw();
866 			ignore_eoi = 1;
867 			hit_eof = 0;
868 			while (!ABORT_SIGS())
869 				forward(1, 0, 0);
870 			ignore_eoi = 0;
871 			break;
872 
873 		case A_F_SCROLL:
874 			/*
875 			 * Forward N lines
876 			 * (default same as last 'd' or 'u' command).
877 			 */
878 			if (number > 0)
879 				wscroll = number;
880 			cmd_exec();
881 			forward(wscroll, 0, 0);
882 			break;
883 
884 		case A_B_SCROLL:
885 			/*
886 			 * Forward N lines
887 			 * (default same as last 'd' or 'u' command).
888 			 */
889 			if (number > 0)
890 				wscroll = number;
891 			cmd_exec();
892 			backward(wscroll, 0, 0);
893 			break;
894 
895 		case A_FREPAINT:
896 			/*
897 			 * Flush buffers, then repaint screen.
898 			 * Don't flush the buffers on a pipe!
899 			 */
900 			if (ch_getflags() & CH_CANSEEK)
901 			{
902 				ch_flush();
903 				clr_linenum();
904 			}
905 			/* FALLTHRU */
906 		case A_REPAINT:
907 			/*
908 			 * Repaint screen.
909 			 */
910 			cmd_exec();
911 			repaint();
912 			break;
913 
914 		case A_GOLINE:
915 			/*
916 			 * Go to line N, default beginning of file.
917 			 */
918 			if (number <= 0)
919 				number = 1;
920 			cmd_exec();
921 			jump_back(number);
922 			break;
923 
924 		case A_PERCENT:
925 			/*
926 			 * Go to a specified percentage into the file.
927 			 */
928 			if (number < 0)
929 				number = 0;
930 			if (number > 100)
931 				number = 100;
932 			cmd_exec();
933 			jump_percent(number);
934 			break;
935 
936 		case A_GOEND:
937 			/*
938 			 * Go to line N, default end of file.
939 			 */
940 			cmd_exec();
941 			if (number <= 0)
942 				jump_forw();
943 			else
944 				jump_back(number);
945 			break;
946 
947 		case A_GOPOS:
948 			/*
949 			 * Go to a specified byte position in the file.
950 			 */
951 			cmd_exec();
952 			if (number < 0)
953 				number = 0;
954 			jump_line_loc((POSITION)number, jump_sline);
955 			break;
956 
957 		case A_STAT:
958 			/*
959 			 * Print file name, etc.
960 			 */
961 			cmd_exec();
962 			parg.p_string = eq_message();
963 			error("%s", &parg);
964 			break;
965 
966 		case A_VERSION:
967 			/*
968 			 * Print version number, without the "@(#)".
969 			 */
970 			cmd_exec();
971 			dispversion();
972 			break;
973 
974 		case A_QUIT:
975 			/*
976 			 * Exit.
977 			 */
978 			quit(QUIT_OK);
979 
980 /*
981  * Define abbreviation for a commonly used sequence below.
982  */
983 #define	DO_SEARCH()	if (number <= 0) number = 1;	\
984 			mca_search();			\
985 			cmd_exec();			\
986 			multi_search((char *)NULL, number);
987 
988 
989 		case A_F_SEARCH:
990 			/*
991 			 * Search forward for a pattern.
992 			 * Get the first char of the pattern.
993 			 */
994 			search_type = SRCH_FORW;
995 			if (number <= 0)
996 				number = 1;
997 			mca_search();
998 			c = getcc();
999 			goto again;
1000 
1001 		case A_B_SEARCH:
1002 			/*
1003 			 * Search backward for a pattern.
1004 			 * Get the first char of the pattern.
1005 			 */
1006 			search_type = SRCH_BACK;
1007 			if (number <= 0)
1008 				number = 1;
1009 			mca_search();
1010 			c = getcc();
1011 			goto again;
1012 
1013 		case A_AGAIN_SEARCH:
1014 			/*
1015 			 * Repeat previous search.
1016 			 */
1017 			DO_SEARCH();
1018 			break;
1019 
1020 		case A_T_AGAIN_SEARCH:
1021 			/*
1022 			 * Repeat previous search, multiple files.
1023 			 */
1024 			search_type |= SRCH_PAST_EOF;
1025 			DO_SEARCH();
1026 			break;
1027 
1028 		case A_REVERSE_SEARCH:
1029 			/*
1030 			 * Repeat previous search, in reverse direction.
1031 			 */
1032 			save_search_type = search_type;
1033 			search_type = SRCH_REVERSE(search_type);
1034 			DO_SEARCH();
1035 			search_type = save_search_type;
1036 			break;
1037 
1038 		case A_T_REVERSE_SEARCH:
1039 			/*
1040 			 * Repeat previous search,
1041 			 * multiple files in reverse direction.
1042 			 */
1043 			save_search_type = search_type;
1044 			search_type = SRCH_REVERSE(search_type);
1045 			search_type |= SRCH_PAST_EOF;
1046 			DO_SEARCH();
1047 			search_type = save_search_type;
1048 			break;
1049 
1050 		case A_UNDO_SEARCH:
1051 			undo_search();
1052 			break;
1053 
1054 		case A_HELP:
1055 			/*
1056 			 * Help.
1057 			 */
1058 			if (nohelp)
1059 			{
1060 				bell();
1061 				break;
1062 			}
1063 			clear_bot();
1064 			putstr(" help");
1065 			cmd_exec();
1066 			help(0);
1067 			break;
1068 
1069 		case A_EXAMINE:
1070 #if EXAMINE
1071 			/*
1072 			 * Edit a new file.  Get the filename.
1073 			 */
1074 			start_mca(A_EXAMINE, "Examine: ", ml_examine);
1075 			c = getcc();
1076 			goto again;
1077 #else
1078 			error("Command not available", NULL_PARG);
1079 			break;
1080 #endif
1081 
1082 		case A_VISUAL:
1083 			/*
1084 			 * Invoke an editor on the input file.
1085 			 */
1086 #if EDITOR
1087 			if (strcmp(get_filename(curr_ifile), "-") == 0)
1088 			{
1089 				error("Cannot edit standard input", NULL_PARG);
1090 				break;
1091 			}
1092 			if (curr_altfilename != NULL)
1093 			{
1094 				error("Cannot edit file processed with LESSOPEN",
1095 					NULL_PARG);
1096 				break;
1097 			}
1098 			/*
1099 			 * Expand the editor prototype string
1100 			 * and pass it to the system to execute.
1101 			 */
1102 			cmd_exec();
1103 			lsystem(pr_expand(editproto, 0));
1104 			/*
1105 			 * Re-edit the file, since data may have changed.
1106 			 * Some editors even recreate the file, so flushing
1107 			 * buffers is not sufficient.
1108 			 */
1109 			if (edit_ifile(curr_ifile))
1110 				quit(QUIT_ERROR);
1111 			break;
1112 #else
1113 			error("Command not available", NULL_PARG);
1114 			break;
1115 #endif
1116 
1117 		case A_NEXT_FILE:
1118 			/*
1119 			 * Examine next file.
1120 			 */
1121 			if (number <= 0)
1122 				number = 1;
1123 			if (edit_next(number))
1124 			{
1125 				if (quit_at_eof && hit_eof)
1126 					quit(QUIT_OK);
1127 				parg.p_string = (number > 1) ? "(N-th) " : "";
1128 				error("No %snext file", &parg);
1129 			}
1130 			break;
1131 
1132 		case A_PREV_FILE:
1133 			/*
1134 			 * Examine previous file.
1135 			 */
1136 			if (number <= 0)
1137 				number = 1;
1138 			if (edit_prev(number))
1139 			{
1140 				parg.p_string = (number > 1) ? "(N-th) " : "";
1141 				error("No %sprevious file", &parg);
1142 			}
1143 			break;
1144 
1145 		case A_INDEX_FILE:
1146 			/*
1147 			 * Examine a particular file.
1148 			 */
1149 			if (number <= 0)
1150 				number = 1;
1151 			if (edit_index(number))
1152 				error("No such file", NULL_PARG);
1153 			break;
1154 
1155 		case A_OPT_TOGGLE:
1156 			start_mca(A_OPT_TOGGLE, "-", (void*)NULL);
1157 			optflag = OPT_TOGGLE;
1158 			c = getcc();
1159 			goto again;
1160 
1161 		case A_DISP_OPTION:
1162 			/*
1163 			 * Report a flag setting.
1164 			 */
1165 			start_mca(A_DISP_OPTION, "_", (void*)NULL);
1166 			c = getcc();
1167 			if (c == erase_char || c == kill_char)
1168 				break;
1169 			toggle_option(c, "", OPT_NO_TOGGLE);
1170 			break;
1171 
1172 		case A_FIRSTCMD:
1173 			/*
1174 			 * Set an initial command for new files.
1175 			 */
1176 			start_mca(A_FIRSTCMD, "+", (void*)NULL);
1177 			c = getcc();
1178 			goto again;
1179 
1180 		case A_SHELL:
1181 			/*
1182 			 * Shell escape.
1183 			 */
1184 #if SHELL_ESCAPE
1185 			start_mca(A_SHELL, "!", ml_shell);
1186 			c = getcc();
1187 			goto again;
1188 #else
1189 			error("Command not available", NULL_PARG);
1190 			break;
1191 #endif
1192 
1193 		case A_SETMARK:
1194 			/*
1195 			 * Set a mark.
1196 			 */
1197 			start_mca(A_SETMARK, "mark: ", (void*)NULL);
1198 			c = getcc();
1199 			if (c == erase_char || c == kill_char ||
1200 			    c == '\n' || c == '\r')
1201 				break;
1202 			setmark(c);
1203 			break;
1204 
1205 		case A_GOMARK:
1206 			/*
1207 			 * Go to a mark.
1208 			 */
1209 			start_mca(A_GOMARK, "goto mark: ", (void*)NULL);
1210 			c = getcc();
1211 			if (c == erase_char || c == kill_char ||
1212 			    c == '\n' || c == '\r')
1213 				break;
1214 			gomark(c);
1215 			break;
1216 
1217 		case A_PIPE:
1218 #if PIPEC
1219 			start_mca(A_PIPE, "|mark: ", (void*)NULL);
1220 			c = getcc();
1221 			if (c == erase_char || c == kill_char)
1222 				break;
1223 			if (c == '\n' || c == '\r')
1224 				c = '.';
1225 			if (badmark(c))
1226 				break;
1227 			pipec = c;
1228 			start_mca(A_PIPE, "!", ml_shell);
1229 			c = getcc();
1230 			goto again;
1231 #else
1232 			error("Command not available", NULL_PARG);
1233 			break;
1234 #endif
1235 
1236 		case A_B_BRACKET:
1237 		case A_F_BRACKET:
1238 			start_mca(action, "Brackets: ", (void*)NULL);
1239 			c = getcc();
1240 			goto again;
1241 
1242 		case A_PREFIX:
1243 			/*
1244 			 * The command is incomplete (more chars are needed).
1245 			 * Display the current char, so the user knows
1246 			 * what's going on, and get another character.
1247 			 */
1248 			if (mca != A_PREFIX)
1249 			{
1250 				start_mca(A_PREFIX, " ", (void*)NULL);
1251 				cmd_reset();
1252 				(void) cmd_char(c);
1253 			}
1254 			c = getcc();
1255 			goto again;
1256 
1257 		case A_NOACTION:
1258 			break;
1259 
1260 		default:
1261 			if (be_helpful)
1262 				helpprompt = 1;
1263 			else
1264 				bell();
1265 			break;
1266 		}
1267 	}
1268 }
1269