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