command.c revision 60786
1/*
2 * Copyright (C) 1984-2000  Mark Nudelman
3 *
4 * You may distribute under the terms of either the GNU General Public
5 * License or the Less License, as specified in the README file.
6 *
7 * For more information about less, or for information on how to
8 * contact the author, see the README file.
9 */
10
11
12/*
13 * User-level command processor.
14 */
15
16#include "less.h"
17#include "position.h"
18#include "option.h"
19#include "cmd.h"
20
21extern int erase_char, kill_char;
22extern int sigs;
23extern int quit_at_eof;
24extern int quit_if_one_screen;
25extern int squished;
26extern int hit_eof;
27extern int sc_width;
28extern int sc_height;
29extern int swindow;
30extern int jump_sline;
31extern int quitting;
32extern int wscroll;
33extern int top_scroll;
34extern int ignore_eoi;
35extern int secure;
36extern int hshift;
37extern int show_attn;
38extern char *every_first_cmd;
39extern char *curr_altfilename;
40extern char version[];
41extern struct scrpos initial_scrpos;
42extern IFILE curr_ifile;
43extern void constant *ml_search;
44extern void constant *ml_examine;
45#if SHELL_ESCAPE || PIPEC
46extern void constant *ml_shell;
47#endif
48#if EDITOR
49extern char *editor;
50extern char *editproto;
51#endif
52extern int screen_trashed;	/* The screen has been overwritten */
53
54static char ungot[UNGOT_SIZE];
55static char *ungotp = NULL;
56#if SHELL_ESCAPE
57static char *shellcmd = NULL;	/* For holding last shell command for "!!" */
58#endif
59static int mca;			/* The multicharacter command (action) */
60static int search_type;		/* The previous type of search */
61static int number;		/* The number typed by the user */
62static char optchar;
63static int optflag;
64static int optgetname;
65static POSITION bottompos;
66#if PIPEC
67static char pipec;
68#endif
69
70static void multi_search();
71
72/*
73 * Move the cursor to lower left before executing a command.
74 * This looks nicer if the command takes a long time before
75 * updating the screen.
76 */
77	static void
78cmd_exec()
79{
80	clear_attn();
81	lower_left();
82	flush();
83}
84
85/*
86 * Set up the display to start a new multi-character command.
87 */
88	static void
89start_mca(action, prompt, mlist, cmdflags)
90	int action;
91	char *prompt;
92	void *mlist;
93	int cmdflags;
94{
95	mca = action;
96	clear_cmd();
97	cmd_putstr(prompt);
98	set_mlist(mlist, cmdflags);
99}
100
101	public int
102in_mca()
103{
104	return (mca != 0 && mca != A_PREFIX);
105}
106
107/*
108 * Set up the display to start a new search command.
109 */
110	static void
111mca_search()
112{
113	if (search_type & SRCH_FORW)
114		mca = A_F_SEARCH;
115	else
116		mca = A_B_SEARCH;
117
118	clear_cmd();
119
120	if (search_type & SRCH_NO_MATCH)
121		cmd_putstr("Non-match ");
122	if (search_type & SRCH_FIRST_FILE)
123		cmd_putstr("First-file ");
124	if (search_type & SRCH_PAST_EOF)
125		cmd_putstr("EOF-ignore ");
126	if (search_type & SRCH_NO_MOVE)
127		cmd_putstr("Keep-pos ");
128	if (search_type & SRCH_NO_REGEX)
129		cmd_putstr("Regex-off ");
130
131	if (search_type & SRCH_FORW)
132		cmd_putstr("/");
133	else
134		cmd_putstr("?");
135	set_mlist(ml_search, 0);
136}
137
138/*
139 * Set up the display to start a new toggle-option command.
140 */
141	static void
142mca_opt_toggle()
143{
144	int no_prompt;
145	int flag;
146	char *dash;
147
148	no_prompt = (optflag & OPT_NO_PROMPT);
149	flag = (optflag & ~OPT_NO_PROMPT);
150	dash = (flag == OPT_NO_TOGGLE) ? "_" : "-";
151
152	mca = A_OPT_TOGGLE;
153	clear_cmd();
154	cmd_putstr(dash);
155	if (optgetname)
156		cmd_putstr(dash);
157	if (no_prompt)
158		cmd_putstr("(P)");
159	switch (flag)
160	{
161	case OPT_UNSET:
162		cmd_putstr("+");
163		break;
164	case OPT_SET:
165		cmd_putstr("!");
166		break;
167	}
168	set_mlist(NULL, 0);
169}
170
171/*
172 * Execute a multicharacter command.
173 */
174	static void
175exec_mca()
176{
177	register char *cbuf;
178
179	cmd_exec();
180	cbuf = get_cmdbuf();
181
182	switch (mca)
183	{
184	case A_F_SEARCH:
185	case A_B_SEARCH:
186		multi_search(cbuf, number);
187		break;
188	case A_FIRSTCMD:
189		/*
190		 * Skip leading spaces or + signs in the string.
191		 */
192		while (*cbuf == '+' || *cbuf == ' ')
193			cbuf++;
194		if (every_first_cmd != NULL)
195			free(every_first_cmd);
196		if (*cbuf == '\0')
197			every_first_cmd = NULL;
198		else
199			every_first_cmd = save(cbuf);
200		break;
201	case A_OPT_TOGGLE:
202		toggle_option(optchar, cbuf, optflag);
203		optchar = '\0';
204		break;
205	case A_F_BRACKET:
206		match_brac(cbuf[0], cbuf[1], 1, number);
207		break;
208	case A_B_BRACKET:
209		match_brac(cbuf[1], cbuf[0], 0, number);
210		break;
211#if EXAMINE
212	case A_EXAMINE:
213		if (secure)
214			break;
215		edit_list(cbuf);
216		break;
217#endif
218#if SHELL_ESCAPE
219	case A_SHELL:
220		/*
221		 * !! just uses whatever is in shellcmd.
222		 * Otherwise, copy cmdbuf to shellcmd,
223		 * expanding any special characters ("%" or "#").
224		 */
225		if (*cbuf != '!')
226		{
227			if (shellcmd != NULL)
228				free(shellcmd);
229			shellcmd = fexpand(cbuf);
230		}
231
232		if (secure)
233			break;
234		if (shellcmd == NULL)
235			lsystem("", "!done");
236		else
237			lsystem(shellcmd, "!done");
238		break;
239#endif
240#if PIPEC
241	case A_PIPE:
242		if (secure)
243			break;
244		(void) pipe_mark(pipec, cbuf);
245		error("|done", NULL_PARG);
246		break;
247#endif
248	}
249}
250
251/*
252 * Add a character to a multi-character command.
253 */
254	static int
255mca_char(c)
256	int c;
257{
258	char *p;
259	int flag;
260	char buf[3];
261	PARG parg;
262
263	switch (mca)
264	{
265	case 0:
266		/*
267		 * Not in a multicharacter command.
268		 */
269		return (NO_MCA);
270
271	case A_PREFIX:
272		/*
273		 * In the prefix of a command.
274		 * This not considered a multichar command
275		 * (even tho it uses cmdbuf, etc.).
276		 * It is handled in the commands() switch.
277		 */
278		return (NO_MCA);
279
280	case A_DIGIT:
281		/*
282		 * Entering digits of a number.
283		 * Terminated by a non-digit.
284		 */
285		if ((c < '0' || c > '9') &&
286		  editchar(c, EC_PEEK|EC_NOHISTORY|EC_NOCOMPLETE) == A_INVALID)
287		{
288			/*
289			 * Not part of the number.
290			 * Treat as a normal command character.
291			 */
292			number = cmd_int();
293			mca = 0;
294			cmd_accept();
295			return (NO_MCA);
296		}
297		break;
298
299	case A_OPT_TOGGLE:
300		/*
301		 * Special case for the TOGGLE_OPTION command.
302		 * If the option letter which was entered is a
303		 * single-char option, execute the command immediately,
304		 * so user doesn't have to hit RETURN.
305		 * If the first char is + or -, this indicates
306		 * OPT_UNSET or OPT_SET respectively, instead of OPT_TOGGLE.
307		 * "--" begins inputting a long option name.
308		 */
309		if (optchar == '\0' && len_cmdbuf() == 0)
310		{
311			flag = (optflag & ~OPT_NO_PROMPT);
312			if (flag == OPT_NO_TOGGLE)
313			{
314				switch (c)
315				{
316				case '_':
317					/* "__" = long option name. */
318					optgetname = TRUE;
319					mca_opt_toggle();
320					return (MCA_MORE);
321				}
322			} else
323			{
324				switch (c)
325				{
326				case '+':
327					/* "-+" = UNSET. */
328					optflag = (flag == OPT_UNSET) ?
329						OPT_TOGGLE : OPT_UNSET;
330					mca_opt_toggle();
331					return (MCA_MORE);
332				case '!':
333					/* "-!" = SET */
334					optflag = (flag == OPT_SET) ?
335						OPT_TOGGLE : OPT_SET;
336					mca_opt_toggle();
337					return (MCA_MORE);
338				case CONTROL('P'):
339					optflag ^= OPT_NO_PROMPT;
340					mca_opt_toggle();
341					return (MCA_MORE);
342				case '-':
343					/* "--" = long option name. */
344					optgetname = TRUE;
345					mca_opt_toggle();
346					return (MCA_MORE);
347				}
348			}
349		}
350		if (optgetname)
351		{
352			/*
353			 * We're getting a long option name.
354			 * See if we've matched an option name yet.
355			 * If so, display the complete name and stop
356			 * accepting chars until user hits RETURN.
357			 */
358			struct option *o;
359			char *oname;
360			int lc;
361
362			if (c == '\n' || c == '\r')
363			{
364				/*
365				 * When the user hits RETURN, make sure
366				 * we've matched an option name, then
367				 * pretend he just entered the equivalent
368				 * option letter.
369				 */
370				if (optchar == '\0')
371				{
372					parg.p_string = get_cmdbuf();
373					error("There is no --%s option", &parg);
374					return (MCA_DONE);
375				}
376				optgetname = FALSE;
377				cmd_reset();
378				c = optchar;
379			} else
380			{
381				if (optchar != '\0')
382				{
383					/*
384					 * Already have a match for the name.
385					 * Don't accept anything but erase/kill.
386					 */
387					if (c == erase_char || c == kill_char)
388						return (MCA_DONE);
389					return (MCA_MORE);
390				}
391				/*
392				 * Add char to cmd buffer and try to match
393				 * the option name.
394				 */
395				if (cmd_char(c) == CC_QUIT)
396					return (MCA_DONE);
397				p = get_cmdbuf();
398				lc = islower(p[0]);
399				o = findopt_name(&p, &oname, NULL);
400				if (o != NULL)
401				{
402					/*
403					 * Got a match.
404					 * Remember the option letter and
405					 * display the full option name.
406					 */
407					optchar = o->oletter;
408					if (!lc && islower(optchar))
409						optchar = toupper(optchar);
410					cmd_reset();
411					mca_opt_toggle();
412					for (p = oname;  *p != '\0';  p++)
413					{
414						c = *p;
415						if (!lc && islower(c))
416							c = toupper(c);
417						if (cmd_char(c) != CC_OK)
418							return (MCA_DONE);
419					}
420				}
421				return (MCA_MORE);
422			}
423		} else
424		{
425			if (c == erase_char || c == kill_char)
426				break;
427			if (optchar != '\0')
428				/* We already have the option letter. */
429				break;
430		}
431
432		optchar = c;
433		if ((optflag & ~OPT_NO_PROMPT) != OPT_TOGGLE ||
434		    single_char_option(c))
435		{
436			toggle_option(c, "", optflag);
437			return (MCA_DONE);
438		}
439		/*
440		 * Display a prompt appropriate for the option letter.
441		 */
442		if ((p = opt_prompt(c)) == NULL)
443		{
444			buf[0] = '-';
445			buf[1] = c;
446			buf[2] = '\0';
447			p = buf;
448		}
449		start_mca(A_OPT_TOGGLE, p, (void*)NULL, 0);
450		return (MCA_MORE);
451
452	case A_F_SEARCH:
453	case A_B_SEARCH:
454		/*
455		 * Special case for search commands.
456		 * Certain characters as the first char of
457		 * the pattern have special meaning:
458		 *	!  Toggle the NO_MATCH flag
459		 *	*  Toggle the PAST_EOF flag
460		 *	@  Toggle the FIRST_FILE flag
461		 */
462		if (len_cmdbuf() > 0)
463			/*
464			 * Only works for the first char of the pattern.
465			 */
466			break;
467
468		flag = 0;
469		switch (c)
470		{
471		case CONTROL('E'): /* ignore END of file */
472		case '*':
473			flag = SRCH_PAST_EOF;
474			break;
475		case CONTROL('F'): /* FIRST file */
476		case '@':
477			flag = SRCH_FIRST_FILE;
478			break;
479		case CONTROL('K'): /* KEEP position */
480			flag = SRCH_NO_MOVE;
481			break;
482		case CONTROL('R'): /* Don't use REGULAR EXPRESSIONS */
483			flag = SRCH_NO_REGEX;
484			break;
485		case CONTROL('N'): /* NOT match */
486		case '!':
487			flag = SRCH_NO_MATCH;
488			break;
489		}
490		if (flag != 0)
491		{
492			search_type ^= flag;
493			mca_search();
494			return (MCA_MORE);
495		}
496		break;
497	}
498
499	/*
500	 * Any other multicharacter command
501	 * is terminated by a newline.
502	 */
503	if (c == '\n' || c == '\r')
504	{
505		/*
506		 * Execute the command.
507		 */
508		exec_mca();
509		return (MCA_DONE);
510	}
511
512	/*
513	 * Append the char to the command buffer.
514	 */
515	if (cmd_char(c) == CC_QUIT)
516		/*
517		 * Abort the multi-char command.
518		 */
519		return (MCA_DONE);
520
521	if ((mca == A_F_BRACKET || mca == A_B_BRACKET) && len_cmdbuf() >= 2)
522	{
523		/*
524		 * Special case for the bracket-matching commands.
525		 * Execute the command after getting exactly two
526		 * characters from the user.
527		 */
528		exec_mca();
529		return (MCA_DONE);
530	}
531
532	/*
533	 * Need another character.
534	 */
535	return (MCA_MORE);
536}
537
538/*
539 * Make sure the screen is displayed.
540 */
541	static void
542make_display()
543{
544	/*
545	 * If nothing is displayed yet, display starting from initial_scrpos.
546	 */
547	if (empty_screen())
548	{
549		if (initial_scrpos.pos == NULL_POSITION)
550			/*
551			 * {{ Maybe this should be:
552			 *    jump_loc(ch_zero(), jump_sline);
553			 *    but this behavior seems rather unexpected
554			 *    on the first screen. }}
555			 */
556			jump_loc(ch_zero(), 1);
557		else
558			jump_loc(initial_scrpos.pos, initial_scrpos.ln);
559	} else if (screen_trashed)
560	{
561		int save_top_scroll;
562		save_top_scroll = top_scroll;
563		top_scroll = 1;
564		repaint();
565		top_scroll = save_top_scroll;
566	}
567}
568
569/*
570 * Display the appropriate prompt.
571 */
572	static void
573prompt()
574{
575	register char *p;
576
577	if (ungotp != NULL && ungotp > ungot)
578	{
579		/*
580		 * No prompt necessary if commands are from
581		 * ungotten chars rather than from the user.
582		 */
583		return;
584	}
585
586	/*
587	 * Make sure the screen is displayed.
588	 */
589	make_display();
590	bottompos = position(BOTTOM_PLUS_ONE);
591
592	/*
593	 * If the -E flag is set and we've hit EOF on the last file, quit.
594	 */
595	if ((quit_at_eof == OPT_ONPLUS || quit_if_one_screen) &&
596	    hit_eof && !(ch_getflags() & CH_HELPFILE) &&
597	    next_ifile(curr_ifile) == NULL_IFILE)
598		quit(QUIT_OK);
599	quit_if_one_screen = FALSE;
600#if 0 /* This doesn't work well because some "te"s clear the screen. */
601	/*
602	 * If the -e flag is set and we've hit EOF on the last file,
603	 * and the file is squished (shorter than the screen), quit.
604	 */
605	if (quit_at_eof && squished &&
606	    next_ifile(curr_ifile) == NULL_IFILE)
607		quit(QUIT_OK);
608#endif
609
610	/*
611	 * Select the proper prompt and display it.
612	 */
613	clear_cmd();
614	p = pr_string();
615	if (p == NULL)
616		putchr(':');
617	else
618	{
619		so_enter();
620		putstr(p);
621		so_exit();
622	}
623}
624
625/*
626 * Display the less version message.
627 */
628	public void
629dispversion()
630{
631	PARG parg;
632
633	parg.p_string = version;
634	error("less %s", &parg);
635}
636
637/*
638 * Get command character.
639 * The character normally comes from the keyboard,
640 * but may come from ungotten characters
641 * (characters previously given to ungetcc or ungetsc).
642 */
643	public int
644getcc()
645{
646	if (ungotp == NULL)
647		/*
648		 * Normal case: no ungotten chars, so get one from the user.
649		 */
650		return (getchr());
651
652	if (ungotp > ungot)
653		/*
654		 * Return the next ungotten char.
655		 */
656		return (*--ungotp);
657
658	/*
659	 * We have just run out of ungotten chars.
660	 */
661	ungotp = NULL;
662	if (len_cmdbuf() == 0 || !empty_screen())
663		return (getchr());
664	/*
665	 * Command is incomplete, so try to complete it.
666	 */
667	switch (mca)
668	{
669	case A_DIGIT:
670		/*
671		 * We have a number but no command.  Treat as #g.
672		 */
673		return ('g');
674
675	case A_F_SEARCH:
676	case A_B_SEARCH:
677		/*
678		 * We have "/string" but no newline.  Add the \n.
679		 */
680		return ('\n');
681
682	default:
683		/*
684		 * Some other incomplete command.  Let user complete it.
685		 */
686		return (getchr());
687	}
688}
689
690/*
691 * "Unget" a command character.
692 * The next getcc() will return this character.
693 */
694	public void
695ungetcc(c)
696	int c;
697{
698	if (ungotp == NULL)
699		ungotp = ungot;
700	if (ungotp >= ungot + sizeof(ungot))
701	{
702		error("ungetcc overflow", NULL_PARG);
703		quit(QUIT_ERROR);
704	}
705	*ungotp++ = c;
706}
707
708/*
709 * Unget a whole string of command characters.
710 * The next sequence of getcc()'s will return this string.
711 */
712	public void
713ungetsc(s)
714	char *s;
715{
716	register char *p;
717
718	for (p = s + strlen(s) - 1;  p >= s;  p--)
719		ungetcc(*p);
720}
721
722/*
723 * Search for a pattern, possibly in multiple files.
724 * If SRCH_FIRST_FILE is set, begin searching at the first file.
725 * If SRCH_PAST_EOF is set, continue the search thru multiple files.
726 */
727	static void
728multi_search(pattern, n)
729	char *pattern;
730	int n;
731{
732	register int nomore;
733	IFILE save_ifile;
734	int changed_file;
735
736	changed_file = 0;
737	save_ifile = save_curr_ifile();
738
739	if (search_type & SRCH_FIRST_FILE)
740	{
741		/*
742		 * Start at the first (or last) file
743		 * in the command line list.
744		 */
745		if (search_type & SRCH_FORW)
746			nomore = edit_first();
747		else
748			nomore = edit_last();
749		if (nomore)
750		{
751			unsave_ifile(save_ifile);
752			return;
753		}
754		changed_file = 1;
755		search_type &= ~SRCH_FIRST_FILE;
756	}
757
758	for (;;)
759	{
760		n = search(search_type, pattern, n);
761		/*
762		 * The SRCH_NO_MOVE flag doesn't "stick": it gets cleared
763		 * after being used once.  This allows "n" to work after
764		 * using a /@@ search.
765		 */
766		search_type &= ~SRCH_NO_MOVE;
767		if (n == 0)
768		{
769			/*
770			 * Found it.
771			 */
772			unsave_ifile(save_ifile);
773			return;
774		}
775
776		if (n < 0)
777			/*
778			 * Some kind of error in the search.
779			 * Error message has been printed by search().
780			 */
781			break;
782
783		if ((search_type & SRCH_PAST_EOF) == 0)
784			/*
785			 * We didn't find a match, but we're
786			 * supposed to search only one file.
787			 */
788			break;
789		/*
790		 * Move on to the next file.
791		 */
792		if (search_type & SRCH_FORW)
793			nomore = edit_next(1);
794		else
795			nomore = edit_prev(1);
796		if (nomore)
797			break;
798		changed_file = 1;
799	}
800
801	/*
802	 * Didn't find it.
803	 * Print an error message if we haven't already.
804	 */
805	if (n > 0)
806		error("Pattern not found", NULL_PARG);
807
808	if (changed_file)
809	{
810		/*
811		 * Restore the file we were originally viewing.
812		 */
813		reedit_ifile(save_ifile);
814	}
815}
816
817/*
818 * Main command processor.
819 * Accept and execute commands until a quit command.
820 */
821	public void
822commands()
823{
824	register int c;
825	register int action;
826	register char *cbuf;
827	int newaction;
828	int save_search_type;
829	char *extra;
830	char tbuf[2];
831	PARG parg;
832	IFILE old_ifile;
833	IFILE new_ifile;
834
835	search_type = SRCH_FORW;
836	wscroll = (sc_height + 1) / 2;
837	newaction = A_NOACTION;
838
839	for (;;)
840	{
841		mca = 0;
842		cmd_accept();
843		number = 0;
844		optchar = '\0';
845
846		/*
847		 * See if any signals need processing.
848		 */
849		if (sigs)
850		{
851			psignals();
852			if (quitting)
853				quit(QUIT_SAVED_STATUS);
854		}
855
856		/*
857		 * See if window size changed, for systems that don't
858		 * generate SIGWINCH.
859		 */
860		check_winch();
861
862		/*
863		 * Display prompt and accept a character.
864		 */
865		cmd_reset();
866		prompt();
867		if (sigs)
868			continue;
869		if (newaction == A_NOACTION)
870			c = getcc();
871
872	again:
873		if (sigs)
874			continue;
875
876		if (newaction != A_NOACTION)
877		{
878			action = newaction;
879			newaction = A_NOACTION;
880		} else
881		{
882			/*
883			 * If we are in a multicharacter command, call mca_char.
884			 * Otherwise we call fcmd_decode to determine the
885			 * action to be performed.
886			 */
887			if (mca)
888				switch (mca_char(c))
889				{
890				case MCA_MORE:
891					/*
892					 * Need another character.
893					 */
894					c = getcc();
895					goto again;
896				case MCA_DONE:
897					/*
898					 * Command has been handled by mca_char.
899					 * Start clean with a prompt.
900					 */
901					continue;
902				case NO_MCA:
903					/*
904					 * Not a multi-char command
905					 * (at least, not anymore).
906					 */
907					break;
908				}
909
910			/*
911			 * Decode the command character and decide what to do.
912			 */
913			if (mca)
914			{
915				/*
916				 * We're in a multichar command.
917				 * Add the character to the command buffer
918				 * and display it on the screen.
919				 * If the user backspaces past the start
920				 * of the line, abort the command.
921				 */
922				if (cmd_char(c) == CC_QUIT || len_cmdbuf() == 0)
923					continue;
924				cbuf = get_cmdbuf();
925			} else
926			{
927				/*
928				 * Don't use cmd_char if we're starting fresh
929				 * at the beginning of a command, because we
930				 * don't want to echo the command until we know
931				 * it is a multichar command.  We also don't
932				 * want erase_char/kill_char to be treated
933				 * as line editing characters.
934				 */
935				tbuf[0] = c;
936				tbuf[1] = '\0';
937				cbuf = tbuf;
938			}
939			extra = NULL;
940			action = fcmd_decode(cbuf, &extra);
941			/*
942			 * If an "extra" string was returned,
943			 * process it as a string of command characters.
944			 */
945			if (extra != NULL)
946				ungetsc(extra);
947		}
948		/*
949		 * Clear the cmdbuf string.
950		 * (But not if we're in the prefix of a command,
951		 * because the partial command string is kept there.)
952		 */
953		if (action != A_PREFIX)
954			cmd_reset();
955
956		switch (action)
957		{
958		case A_DIGIT:
959			/*
960			 * First digit of a number.
961			 */
962			start_mca(A_DIGIT, ":", (void*)NULL, CF_QUIT_ON_ERASE);
963			goto again;
964
965		case A_F_WINDOW:
966			/*
967			 * Forward one window (and set the window size).
968			 */
969			if (number > 0)
970				swindow = number;
971			/* FALLTHRU */
972		case A_F_SCREEN:
973			/*
974			 * Forward one screen.
975			 */
976			if (number <= 0)
977				number = get_swindow();
978			cmd_exec();
979			if (show_attn)
980				set_attnpos(bottompos);
981			forward(number, 0, 1);
982			break;
983
984		case A_B_WINDOW:
985			/*
986			 * Backward one window (and set the window size).
987			 */
988			if (number > 0)
989				swindow = number;
990			/* FALLTHRU */
991		case A_B_SCREEN:
992			/*
993			 * Backward one screen.
994			 */
995			if (number <= 0)
996				number = get_swindow();
997			cmd_exec();
998			backward(number, 0, 1);
999			break;
1000
1001		case A_F_LINE:
1002			/*
1003			 * Forward N (default 1) line.
1004			 */
1005			if (number <= 0)
1006				number = 1;
1007			cmd_exec();
1008			if (show_attn == OPT_ONPLUS && number > 1)
1009				set_attnpos(bottompos);
1010			forward(number, 0, 0);
1011			break;
1012
1013		case A_B_LINE:
1014			/*
1015			 * Backward N (default 1) line.
1016			 */
1017			if (number <= 0)
1018				number = 1;
1019			cmd_exec();
1020			backward(number, 0, 0);
1021			break;
1022
1023		case A_FF_LINE:
1024			/*
1025			 * Force forward N (default 1) line.
1026			 */
1027			if (number <= 0)
1028				number = 1;
1029			cmd_exec();
1030			if (show_attn == OPT_ONPLUS && number > 1)
1031				set_attnpos(bottompos);
1032			forward(number, 1, 0);
1033			break;
1034
1035		case A_BF_LINE:
1036			/*
1037			 * Force backward N (default 1) line.
1038			 */
1039			if (number <= 0)
1040				number = 1;
1041			cmd_exec();
1042			backward(number, 1, 0);
1043			break;
1044
1045		case A_FF_SCREEN:
1046			/*
1047			 * Force forward one screen.
1048			 */
1049			if (number <= 0)
1050				number = get_swindow();
1051			cmd_exec();
1052			if (show_attn == OPT_ONPLUS)
1053				set_attnpos(bottompos);
1054			forward(number, 1, 0);
1055			break;
1056
1057		case A_F_FOREVER:
1058			/*
1059			 * Forward forever, ignoring EOF.
1060			 */
1061			if (ch_getflags() & CH_HELPFILE)
1062				break;
1063			cmd_exec();
1064			jump_forw();
1065			ignore_eoi = 1;
1066			hit_eof = 0;
1067			while (!sigs)
1068				forward(1, 0, 0);
1069			ignore_eoi = 0;
1070			/*
1071			 * This gets us back in "F mode" after processing
1072			 * a non-abort signal (e.g. window-change).
1073			 */
1074			if (sigs && !ABORT_SIGS())
1075				newaction = A_F_FOREVER;
1076			break;
1077
1078		case A_F_SCROLL:
1079			/*
1080			 * Forward N lines
1081			 * (default same as last 'd' or 'u' command).
1082			 */
1083			if (number > 0)
1084				wscroll = number;
1085			cmd_exec();
1086			if (show_attn == OPT_ONPLUS)
1087				set_attnpos(bottompos);
1088			forward(wscroll, 0, 0);
1089			break;
1090
1091		case A_B_SCROLL:
1092			/*
1093			 * Forward N lines
1094			 * (default same as last 'd' or 'u' command).
1095			 */
1096			if (number > 0)
1097				wscroll = number;
1098			cmd_exec();
1099			backward(wscroll, 0, 0);
1100			break;
1101
1102		case A_FREPAINT:
1103			/*
1104			 * Flush buffers, then repaint screen.
1105			 * Don't flush the buffers on a pipe!
1106			 */
1107			if (ch_getflags() & CH_CANSEEK)
1108			{
1109				ch_flush();
1110				clr_linenum();
1111#if HILITE_SEARCH
1112				clr_hilite();
1113#endif
1114			}
1115			/* FALLTHRU */
1116		case A_REPAINT:
1117			/*
1118			 * Repaint screen.
1119			 */
1120			cmd_exec();
1121			repaint();
1122			break;
1123
1124		case A_GOLINE:
1125			/*
1126			 * Go to line N, default beginning of file.
1127			 */
1128			if (number <= 0)
1129				number = 1;
1130			cmd_exec();
1131			jump_back(number);
1132			break;
1133
1134		case A_PERCENT:
1135			/*
1136			 * Go to a specified percentage into the file.
1137			 */
1138			if (number < 0)
1139				number = 0;
1140			if (number > 100)
1141				number = 100;
1142			cmd_exec();
1143			jump_percent(number);
1144			break;
1145
1146		case A_GOEND:
1147			/*
1148			 * Go to line N, default end of file.
1149			 */
1150			cmd_exec();
1151			if (number <= 0)
1152				jump_forw();
1153			else
1154				jump_back(number);
1155			break;
1156
1157		case A_GOPOS:
1158			/*
1159			 * Go to a specified byte position in the file.
1160			 */
1161			cmd_exec();
1162			if (number < 0)
1163				number = 0;
1164			jump_line_loc((POSITION)number, jump_sline);
1165			break;
1166
1167		case A_STAT:
1168			/*
1169			 * Print file name, etc.
1170			 */
1171			if (ch_getflags() & CH_HELPFILE)
1172				break;
1173			cmd_exec();
1174			parg.p_string = eq_message();
1175			error("%s", &parg);
1176			break;
1177
1178		case A_VERSION:
1179			/*
1180			 * Print version number, without the "@(#)".
1181			 */
1182			cmd_exec();
1183			dispversion();
1184			break;
1185
1186		case A_QUIT:
1187			/*
1188			 * Exit.
1189			 */
1190			if (curr_ifile != NULL_IFILE &&
1191			    ch_getflags() & CH_HELPFILE)
1192			{
1193				/*
1194				 * Quit while viewing the help file
1195				 * just means return to viewing the
1196				 * previous file.
1197				 */
1198				if (edit_prev(1) == 0)
1199					break;
1200			}
1201			if (extra != NULL)
1202				quit(*extra);
1203			quit(QUIT_OK);
1204			break;
1205
1206/*
1207 * Define abbreviation for a commonly used sequence below.
1208 */
1209#define	DO_SEARCH()	if (number <= 0) number = 1;	\
1210			mca_search();			\
1211			cmd_exec();			\
1212			multi_search((char *)NULL, number);
1213
1214
1215		case A_F_SEARCH:
1216			/*
1217			 * Search forward for a pattern.
1218			 * Get the first char of the pattern.
1219			 */
1220			search_type = SRCH_FORW;
1221			if (number <= 0)
1222				number = 1;
1223			mca_search();
1224			c = getcc();
1225			goto again;
1226
1227		case A_B_SEARCH:
1228			/*
1229			 * Search backward for a pattern.
1230			 * Get the first char of the pattern.
1231			 */
1232			search_type = SRCH_BACK;
1233			if (number <= 0)
1234				number = 1;
1235			mca_search();
1236			c = getcc();
1237			goto again;
1238
1239		case A_AGAIN_SEARCH:
1240			/*
1241			 * Repeat previous search.
1242			 */
1243			DO_SEARCH();
1244			break;
1245
1246		case A_T_AGAIN_SEARCH:
1247			/*
1248			 * Repeat previous search, multiple files.
1249			 */
1250			search_type |= SRCH_PAST_EOF;
1251			DO_SEARCH();
1252			break;
1253
1254		case A_REVERSE_SEARCH:
1255			/*
1256			 * Repeat previous search, in reverse direction.
1257			 */
1258			save_search_type = search_type;
1259			search_type = SRCH_REVERSE(search_type);
1260			DO_SEARCH();
1261			search_type = save_search_type;
1262			break;
1263
1264		case A_T_REVERSE_SEARCH:
1265			/*
1266			 * Repeat previous search,
1267			 * multiple files in reverse direction.
1268			 */
1269			save_search_type = search_type;
1270			search_type = SRCH_REVERSE(search_type);
1271			search_type |= SRCH_PAST_EOF;
1272			DO_SEARCH();
1273			search_type = save_search_type;
1274			break;
1275
1276		case A_UNDO_SEARCH:
1277			undo_search();
1278			break;
1279
1280		case A_HELP:
1281			/*
1282			 * Help.
1283			 */
1284			if (ch_getflags() & CH_HELPFILE)
1285				break;
1286			cmd_exec();
1287			(void) edit(FAKE_HELPFILE);
1288			break;
1289
1290		case A_EXAMINE:
1291#if EXAMINE
1292			/*
1293			 * Edit a new file.  Get the filename.
1294			 */
1295			if (secure)
1296			{
1297				error("Command not available", NULL_PARG);
1298				break;
1299			}
1300			start_mca(A_EXAMINE, "Examine: ", ml_examine, 0);
1301			c = getcc();
1302			goto again;
1303#else
1304			error("Command not available", NULL_PARG);
1305			break;
1306#endif
1307
1308		case A_VISUAL:
1309			/*
1310			 * Invoke an editor on the input file.
1311			 */
1312#if EDITOR
1313			if (secure)
1314			{
1315				error("Command not available", NULL_PARG);
1316				break;
1317			}
1318			if (ch_getflags() & CH_HELPFILE)
1319				break;
1320			if (strcmp(get_filename(curr_ifile), "-") == 0)
1321			{
1322				error("Cannot edit standard input", NULL_PARG);
1323				break;
1324			}
1325			if (curr_altfilename != NULL)
1326			{
1327				error("Cannot edit file processed with LESSOPEN",
1328					NULL_PARG);
1329				break;
1330			}
1331			start_mca(A_SHELL, "!", ml_shell, 0);
1332			/*
1333			 * Expand the editor prototype string
1334			 * and pass it to the system to execute.
1335			 * (Make sure the screen is displayed so the
1336			 * expansion of "+%lm" works.)
1337			 */
1338			make_display();
1339			cmd_exec();
1340			lsystem(pr_expand(editproto, 0), (char*)NULL);
1341			break;
1342#else
1343			error("Command not available", NULL_PARG);
1344			break;
1345#endif
1346
1347		case A_NEXT_FILE:
1348			/*
1349			 * Examine next file.
1350			 */
1351			if (number <= 0)
1352				number = 1;
1353			if (edit_next(number))
1354			{
1355				if (quit_at_eof && hit_eof &&
1356				    !(ch_getflags() & CH_HELPFILE))
1357					quit(QUIT_OK);
1358				parg.p_string = (number > 1) ? "(N-th) " : "";
1359				error("No %snext file", &parg);
1360			}
1361			break;
1362
1363		case A_PREV_FILE:
1364			/*
1365			 * Examine previous file.
1366			 */
1367			if (number <= 0)
1368				number = 1;
1369			if (edit_prev(number))
1370			{
1371				parg.p_string = (number > 1) ? "(N-th) " : "";
1372				error("No %sprevious file", &parg);
1373			}
1374			break;
1375
1376		case A_INDEX_FILE:
1377			/*
1378			 * Examine a particular file.
1379			 */
1380			if (number <= 0)
1381				number = 1;
1382			if (edit_index(number))
1383				error("No such file", NULL_PARG);
1384			break;
1385
1386		case A_REMOVE_FILE:
1387			if (ch_getflags() & CH_HELPFILE)
1388				break;
1389			old_ifile = curr_ifile;
1390			new_ifile = getoff_ifile(curr_ifile);
1391			if (new_ifile == NULL_IFILE)
1392			{
1393				bell();
1394				break;
1395			}
1396			if (edit_ifile(new_ifile) != 0)
1397			{
1398				reedit_ifile(old_ifile);
1399				break;
1400			}
1401			del_ifile(old_ifile);
1402			break;
1403
1404		case A_OPT_TOGGLE:
1405			optflag = OPT_TOGGLE;
1406			optgetname = FALSE;
1407			mca_opt_toggle();
1408			c = getcc();
1409			goto again;
1410
1411		case A_DISP_OPTION:
1412			/*
1413			 * Report a flag setting.
1414			 */
1415			optflag = OPT_NO_TOGGLE;
1416			optgetname = FALSE;
1417			mca_opt_toggle();
1418			c = getcc();
1419			goto again;
1420
1421		case A_FIRSTCMD:
1422			/*
1423			 * Set an initial command for new files.
1424			 */
1425			start_mca(A_FIRSTCMD, "+", (void*)NULL, 0);
1426			c = getcc();
1427			goto again;
1428
1429		case A_SHELL:
1430			/*
1431			 * Shell escape.
1432			 */
1433#if SHELL_ESCAPE
1434			if (secure)
1435			{
1436				error("Command not available", NULL_PARG);
1437				break;
1438			}
1439			start_mca(A_SHELL, "!", ml_shell, 0);
1440			c = getcc();
1441			goto again;
1442#else
1443			error("Command not available", NULL_PARG);
1444			break;
1445#endif
1446
1447		case A_SETMARK:
1448			/*
1449			 * Set a mark.
1450			 */
1451			if (ch_getflags() & CH_HELPFILE)
1452				break;
1453			start_mca(A_SETMARK, "mark: ", (void*)NULL, 0);
1454			c = getcc();
1455			if (c == erase_char || c == kill_char ||
1456			    c == '\n' || c == '\r')
1457				break;
1458			setmark(c);
1459			break;
1460
1461		case A_GOMARK:
1462			/*
1463			 * Go to a mark.
1464			 */
1465			start_mca(A_GOMARK, "goto mark: ", (void*)NULL, 0);
1466			c = getcc();
1467			if (c == erase_char || c == kill_char ||
1468			    c == '\n' || c == '\r')
1469				break;
1470			gomark(c);
1471			break;
1472
1473		case A_PIPE:
1474#if PIPEC
1475			if (secure)
1476			{
1477				error("Command not available", NULL_PARG);
1478				break;
1479			}
1480			start_mca(A_PIPE, "|mark: ", (void*)NULL, 0);
1481			c = getcc();
1482			if (c == erase_char || c == kill_char)
1483				break;
1484			if (c == '\n' || c == '\r')
1485				c = '.';
1486			if (badmark(c))
1487				break;
1488			pipec = c;
1489			start_mca(A_PIPE, "!", ml_shell, 0);
1490			c = getcc();
1491			goto again;
1492#else
1493			error("Command not available", NULL_PARG);
1494			break;
1495#endif
1496
1497		case A_B_BRACKET:
1498		case A_F_BRACKET:
1499			start_mca(action, "Brackets: ", (void*)NULL, 0);
1500			c = getcc();
1501			goto again;
1502
1503		case A_LSHIFT:
1504			if (number <= 0)
1505				number = 8;
1506			if (number > hshift)
1507				number = hshift;
1508			hshift -= number;
1509			screen_trashed = 1;
1510			break;
1511
1512		case A_RSHIFT:
1513			if (number <= 0)
1514				number = 8;
1515			hshift += number;
1516			screen_trashed = 1;
1517			break;
1518
1519		case A_PREFIX:
1520			/*
1521			 * The command is incomplete (more chars are needed).
1522			 * Display the current char, so the user knows
1523			 * what's going on, and get another character.
1524			 */
1525			if (mca != A_PREFIX)
1526			{
1527				cmd_reset();
1528				start_mca(A_PREFIX, " ", (void*)NULL,
1529					CF_QUIT_ON_ERASE);
1530				(void) cmd_char(c);
1531			}
1532			c = getcc();
1533			goto again;
1534
1535		case A_NOACTION:
1536			break;
1537
1538		default:
1539			bell();
1540			break;
1541		}
1542	}
1543}
1544