1/* $FreeBSD$ */
2/*
3 * Copyright (C) 1984-2021  Mark Nudelman
4 *
5 * You may distribute under the terms of either the GNU General Public
6 * License or the Less License, as specified in the README file.
7 *
8 * For more information, see the README file.
9 */
10
11
12/*
13 * User-level command processor.
14 */
15
16#include "less.h"
17#if MSDOS_COMPILER==WIN32C
18#include <windows.h>
19#endif
20#include "position.h"
21#include "option.h"
22#include "cmd.h"
23
24extern int erase_char, erase2_char, kill_char;
25extern int sigs;
26extern int quit_if_one_screen;
27extern int squished;
28extern int sc_width;
29extern int sc_height;
30extern char *kent;
31extern int swindow;
32extern int jump_sline;
33extern int quitting;
34extern int wscroll;
35extern int top_scroll;
36extern int ignore_eoi;
37extern int secure;
38extern int hshift;
39extern int bs_mode;
40extern int show_attn;
41extern int less_is_more;
42extern int status_col;
43extern POSITION highest_hilite;
44extern POSITION start_attnpos;
45extern POSITION end_attnpos;
46extern char *every_first_cmd;
47extern char version[];
48extern struct scrpos initial_scrpos;
49extern IFILE curr_ifile;
50extern void *ml_search;
51extern void *ml_examine;
52extern int wheel_lines;
53#if SHELL_ESCAPE || PIPEC
54extern void *ml_shell;
55#endif
56#if EDITOR
57extern char *editor;
58extern char *editproto;
59#endif
60extern int screen_trashed;      /* The screen has been overwritten */
61extern int shift_count;
62extern int oldbot;
63extern int forw_prompt;
64extern int incr_search;
65#if MSDOS_COMPILER==WIN32C
66extern int utf_mode;
67#endif
68
69#if SHELL_ESCAPE
70static char *shellcmd = NULL;   /* For holding last shell command for "!!" */
71#endif
72static int mca;                 /* The multicharacter command (action) */
73static int search_type;         /* The previous type of search */
74static LINENUM number;          /* The number typed by the user */
75static long fraction;           /* The fractional part of the number */
76static struct loption *curropt;
77static int opt_lower;
78static int optflag;
79static int optgetname;
80static POSITION bottompos;
81static int save_hshift;
82static int save_bs_mode;
83#if PIPEC
84static char pipec;
85#endif
86
87/* Stack of ungotten chars (via ungetcc) */
88struct ungot {
89	struct ungot *ug_next;
90	LWCHAR ug_char;
91};
92static struct ungot* ungot = NULL;
93
94static void multi_search LESSPARAMS((char *pattern, int n, int silent));
95
96/*
97 * Move the cursor to start of prompt line before executing a command.
98 * This looks nicer if the command takes a long time before
99 * updating the screen.
100 */
101	static void
102cmd_exec(VOID_PARAM)
103{
104	clear_attn();
105	clear_bot();
106	flush();
107}
108
109/*
110 * Indicate we are reading a multi-character command.
111 */
112	static void
113set_mca(action)
114	int action;
115{
116	mca = action;
117	clear_bot();
118	clear_cmd();
119}
120
121/*
122 * Indicate we are not reading a multi-character command.
123 */
124	static void
125clear_mca(VOID_PARAM)
126{
127	if (mca == 0)
128		return;
129	mca = 0;
130}
131
132/*
133 * Set up the display to start a new multi-character command.
134 */
135	static void
136start_mca(action, prompt, mlist, cmdflags)
137	int action;
138	constant char *prompt;
139	void *mlist;
140	int cmdflags;
141{
142	set_mca(action);
143	cmd_putstr(prompt);
144	set_mlist(mlist, cmdflags);
145}
146
147	public int
148in_mca(VOID_PARAM)
149{
150	return (mca != 0 && mca != A_PREFIX);
151}
152
153/*
154 * Set up the display to start a new search command.
155 */
156	static void
157mca_search(VOID_PARAM)
158{
159#if HILITE_SEARCH
160	if (search_type & SRCH_FILTER)
161		set_mca(A_FILTER);
162	else
163#endif
164	if (search_type & SRCH_FORW)
165		set_mca(A_F_SEARCH);
166	else
167		set_mca(A_B_SEARCH);
168
169	if (search_type & SRCH_NO_MATCH)
170		cmd_putstr("Non-match ");
171	if (search_type & SRCH_FIRST_FILE)
172		cmd_putstr("First-file ");
173	if (search_type & SRCH_PAST_EOF)
174		cmd_putstr("EOF-ignore ");
175	if (search_type & SRCH_NO_MOVE)
176		cmd_putstr("Keep-pos ");
177	if (search_type & SRCH_NO_REGEX)
178		cmd_putstr("Regex-off ");
179	if (search_type & SRCH_WRAP)
180		cmd_putstr("Wrap ");
181
182#if HILITE_SEARCH
183	if (search_type & SRCH_FILTER)
184		cmd_putstr("&/");
185	else
186#endif
187	if (search_type & SRCH_FORW)
188		cmd_putstr("/");
189	else
190		cmd_putstr("?");
191	forw_prompt = 0;
192	set_mlist(ml_search, 0);
193}
194
195/*
196 * Set up the display to start a new toggle-option command.
197 */
198	static void
199mca_opt_toggle(VOID_PARAM)
200{
201	int no_prompt;
202	int flag;
203	char *dash;
204
205	no_prompt = (optflag & OPT_NO_PROMPT);
206	flag = (optflag & ~OPT_NO_PROMPT);
207	dash = (flag == OPT_NO_TOGGLE) ? "_" : "-";
208
209	set_mca(A_OPT_TOGGLE);
210	cmd_putstr(dash);
211	if (optgetname)
212		cmd_putstr(dash);
213	if (no_prompt)
214		cmd_putstr("(P)");
215	switch (flag)
216	{
217	case OPT_UNSET:
218		cmd_putstr("+");
219		break;
220	case OPT_SET:
221		cmd_putstr("!");
222		break;
223	}
224	forw_prompt = 0;
225	set_mlist(NULL, 0);
226}
227
228/*
229 * Execute a multicharacter command.
230 */
231	static void
232exec_mca(VOID_PARAM)
233{
234	char *cbuf;
235
236	cmd_exec();
237	cbuf = get_cmdbuf();
238
239	switch (mca)
240	{
241	case A_F_SEARCH:
242	case A_B_SEARCH:
243		multi_search(cbuf, (int) number, 0);
244		break;
245#if HILITE_SEARCH
246	case A_FILTER:
247		search_type ^= SRCH_NO_MATCH;
248		set_filter_pattern(cbuf, search_type);
249		break;
250#endif
251	case A_FIRSTCMD:
252		/*
253		 * Skip leading spaces or + signs in the string.
254		 */
255		while (*cbuf == '+' || *cbuf == ' ')
256			cbuf++;
257		if (every_first_cmd != NULL)
258			free(every_first_cmd);
259		if (*cbuf == '\0')
260			every_first_cmd = NULL;
261		else
262			every_first_cmd = save(cbuf);
263		break;
264	case A_OPT_TOGGLE:
265		toggle_option(curropt, opt_lower, cbuf, optflag);
266		curropt = NULL;
267		break;
268	case A_F_BRACKET:
269		match_brac(cbuf[0], cbuf[1], 1, (int) number);
270		break;
271	case A_B_BRACKET:
272		match_brac(cbuf[1], cbuf[0], 0, (int) number);
273		break;
274#if EXAMINE
275	case A_EXAMINE:
276		if (secure)
277			break;
278		edit_list(cbuf);
279#if TAGS
280		/* If tag structure is loaded then clean it up. */
281		cleantags();
282#endif
283		break;
284#endif
285#if SHELL_ESCAPE
286	case A_SHELL:
287		/*
288		 * !! just uses whatever is in shellcmd.
289		 * Otherwise, copy cmdbuf to shellcmd,
290		 * expanding any special characters ("%" or "#").
291		 */
292		if (*cbuf != '!')
293		{
294			if (shellcmd != NULL)
295				free(shellcmd);
296			shellcmd = fexpand(cbuf);
297		}
298
299		if (secure)
300			break;
301		if (shellcmd == NULL)
302			lsystem("", "!done");
303		else
304			lsystem(shellcmd, "!done");
305		break;
306#endif
307#if PIPEC
308	case A_PIPE:
309		if (secure)
310			break;
311		(void) pipe_mark(pipec, cbuf);
312		error("|done", NULL_PARG);
313		break;
314#endif
315	}
316}
317
318/*
319 * Is a character an erase or kill char?
320 */
321	static int
322is_erase_char(c)
323	int c;
324{
325	return (c == erase_char || c == erase2_char || c == kill_char);
326}
327
328/*
329 * Is a character a carriage return or newline?
330 */
331	static int
332is_newline_char(c)
333	int c;
334{
335	return (c == '\n' || c == '\r');
336}
337
338/*
339 * Handle the first char of an option (after the initial dash).
340 */
341	static int
342mca_opt_first_char(c)
343	int c;
344{
345	int flag = (optflag & ~OPT_NO_PROMPT);
346	if (flag == OPT_NO_TOGGLE)
347	{
348		switch (c)
349		{
350		case '_':
351			/* "__" = long option name. */
352			optgetname = TRUE;
353			mca_opt_toggle();
354			return (MCA_MORE);
355		}
356	} else
357	{
358		switch (c)
359		{
360		case '+':
361			/* "-+" = UNSET. */
362			optflag = (flag == OPT_UNSET) ?
363				OPT_TOGGLE : OPT_UNSET;
364			mca_opt_toggle();
365			return (MCA_MORE);
366		case '!':
367			/* "-!" = SET */
368			optflag = (flag == OPT_SET) ?
369				OPT_TOGGLE : OPT_SET;
370			mca_opt_toggle();
371			return (MCA_MORE);
372		case CONTROL('P'):
373			optflag ^= OPT_NO_PROMPT;
374			mca_opt_toggle();
375			return (MCA_MORE);
376		case '-':
377			/* "--" = long option name. */
378			optgetname = TRUE;
379			mca_opt_toggle();
380			return (MCA_MORE);
381		}
382	}
383	/* Char was not handled here. */
384	return (NO_MCA);
385}
386
387/*
388 * Add a char to a long option name.
389 * See if we've got a match for an option name yet.
390 * If so, display the complete name and stop
391 * accepting chars until user hits RETURN.
392 */
393	static int
394mca_opt_nonfirst_char(c)
395	int c;
396{
397	char *p;
398	char *oname;
399	int err;
400
401	if (curropt != NULL)
402	{
403		/*
404		 * Already have a match for the name.
405		 * Don't accept anything but erase/kill.
406		 */
407		if (is_erase_char(c))
408			return (MCA_DONE);
409		return (MCA_MORE);
410	}
411	/*
412	 * Add char to cmd buffer and try to match
413	 * the option name.
414	 */
415	if (cmd_char(c) == CC_QUIT)
416		return (MCA_DONE);
417	p = get_cmdbuf();
418	opt_lower = ASCII_IS_LOWER(p[0]);
419	err = 0;
420	curropt = findopt_name(&p, &oname, &err);
421	if (curropt != NULL)
422	{
423		/*
424		 * Got a match.
425		 * Remember the option and
426		 * display the full option name.
427		 */
428		cmd_reset();
429		mca_opt_toggle();
430		for (p = oname;  *p != '\0';  p++)
431		{
432			c = *p;
433			if (!opt_lower && ASCII_IS_LOWER(c))
434				c = ASCII_TO_UPPER(c);
435			if (cmd_char(c) != CC_OK)
436				return (MCA_DONE);
437		}
438	} else if (err != OPT_AMBIG)
439	{
440		bell();
441	}
442	return (MCA_MORE);
443}
444
445/*
446 * Handle a char of an option toggle command.
447 */
448	static int
449mca_opt_char(c)
450	int c;
451{
452	PARG parg;
453
454	/*
455	 * This may be a short option (single char),
456	 * or one char of a long option name,
457	 * or one char of the option parameter.
458	 */
459	if (curropt == NULL && len_cmdbuf() == 0)
460	{
461		int ret = mca_opt_first_char(c);
462		if (ret != NO_MCA)
463			return (ret);
464	}
465	if (optgetname)
466	{
467		/* We're getting a long option name.  */
468		if (!is_newline_char(c))
469			return (mca_opt_nonfirst_char(c));
470		if (curropt == NULL)
471		{
472			parg.p_string = get_cmdbuf();
473			error("There is no --%s option", &parg);
474			return (MCA_DONE);
475		}
476		optgetname = FALSE;
477		cmd_reset();
478	} else
479	{
480		if (is_erase_char(c))
481			return (NO_MCA);
482		if (curropt != NULL)
483			/* We're getting the option parameter. */
484			return (NO_MCA);
485		curropt = findopt(c);
486		if (curropt == NULL)
487		{
488			parg.p_string = propt(c);
489			error("There is no %s option", &parg);
490			return (MCA_DONE);
491		}
492		opt_lower = ASCII_IS_LOWER(c);
493	}
494	/*
495	 * If the option which was entered does not take a
496	 * parameter, toggle the option immediately,
497	 * so user doesn't have to hit RETURN.
498	 */
499	if ((optflag & ~OPT_NO_PROMPT) != OPT_TOGGLE ||
500	    !opt_has_param(curropt))
501	{
502		toggle_option(curropt, opt_lower, "", optflag);
503		return (MCA_DONE);
504	}
505	/*
506	 * Display a prompt appropriate for the option parameter.
507	 */
508	start_mca(A_OPT_TOGGLE, opt_prompt(curropt), (void*)NULL, 0);
509	return (MCA_MORE);
510}
511
512/*
513 * Handle a char of a search command.
514 */
515	static int
516mca_search_char(c)
517	int c;
518{
519	int flag = 0;
520
521	/*
522	 * Certain characters as the first char of
523	 * the pattern have special meaning:
524	 *      !  Toggle the NO_MATCH flag
525	 *      *  Toggle the PAST_EOF flag
526	 *      @  Toggle the FIRST_FILE flag
527	 */
528	if (len_cmdbuf() > 0)
529		return (NO_MCA);
530
531	switch (c)
532	{
533	case '*':
534		if (less_is_more)
535			break;
536	case CONTROL('E'): /* ignore END of file */
537		if (mca != A_FILTER)
538			flag = SRCH_PAST_EOF;
539		break;
540	case '@':
541		if (less_is_more)
542			break;
543	case CONTROL('F'): /* FIRST file */
544		if (mca != A_FILTER)
545			flag = SRCH_FIRST_FILE;
546		break;
547	case CONTROL('K'): /* KEEP position */
548		if (mca != A_FILTER)
549			flag = SRCH_NO_MOVE;
550		break;
551	case CONTROL('W'): /* WRAP around */
552		if (mca != A_FILTER)
553			flag = SRCH_WRAP;
554		break;
555	case CONTROL('R'): /* Don't use REGULAR EXPRESSIONS */
556		flag = SRCH_NO_REGEX;
557		break;
558	case CONTROL('N'): /* NOT match */
559	case '!':
560		flag = SRCH_NO_MATCH;
561		break;
562	}
563
564	if (flag != 0)
565	{
566		/* Toggle flag, but keep PAST_EOF and WRAP mutually exclusive. */
567		search_type ^= flag | (search_type & (SRCH_PAST_EOF|SRCH_WRAP));
568		mca_search();
569		return (MCA_MORE);
570	}
571	return (NO_MCA);
572}
573
574/*
575 * Handle a character of a multi-character command.
576 */
577	static int
578mca_char(c)
579	int c;
580{
581	int ret;
582
583	switch (mca)
584	{
585	case 0:
586		/*
587		 * We're not in a multicharacter command.
588		 */
589		return (NO_MCA);
590
591	case A_PREFIX:
592		/*
593		 * In the prefix of a command.
594		 * This not considered a multichar command
595		 * (even tho it uses cmdbuf, etc.).
596		 * It is handled in the commands() switch.
597		 */
598		return (NO_MCA);
599
600	case A_DIGIT:
601		/*
602		 * Entering digits of a number.
603		 * Terminated by a non-digit.
604		 */
605		if ((c >= '0' && c <= '9') || c == '.')
606			break;
607		switch (editchar(c, ECF_PEEK|ECF_NOHISTORY|ECF_NOCOMPLETE|ECF_NORIGHTLEFT))
608		{
609		case A_NOACTION:
610			/*
611			 * Ignore this char and get another one.
612			 */
613			return (MCA_MORE);
614		case A_INVALID:
615			/*
616			 * Not part of the number.
617			 * End the number and treat this char
618			 * as a normal command character.
619			 */
620			number = cmd_int(&fraction);
621			clear_mca();
622			cmd_accept();
623			return (NO_MCA);
624		}
625		break;
626
627	case A_OPT_TOGGLE:
628		ret = mca_opt_char(c);
629		if (ret != NO_MCA)
630			return (ret);
631		break;
632
633	case A_F_SEARCH:
634	case A_B_SEARCH:
635	case A_FILTER:
636		ret = mca_search_char(c);
637		if (ret != NO_MCA)
638			return (ret);
639		break;
640
641	default:
642		/* Other multicharacter command. */
643		break;
644	}
645
646	/*
647	 * The multichar command is terminated by a newline.
648	 */
649	if (is_newline_char(c))
650	{
651		/*
652		 * Execute the command.
653		 */
654		exec_mca();
655		return (MCA_DONE);
656	}
657
658	/*
659	 * Append the char to the command buffer.
660	 */
661	if (cmd_char(c) == CC_QUIT)
662		/*
663		 * Abort the multi-char command.
664		 */
665		return (MCA_DONE);
666
667	switch (mca)
668	{
669	case A_F_BRACKET:
670	case A_B_BRACKET:
671		if (len_cmdbuf() >= 2)
672		{
673			/*
674			 * Special case for the bracket-matching commands.
675			 * Execute the command after getting exactly two
676			 * characters from the user.
677			 */
678			exec_mca();
679			return (MCA_DONE);
680		}
681		break;
682	case A_F_SEARCH:
683	case A_B_SEARCH:
684		if (incr_search)
685		{
686			/* Incremental search: do a search after every input char. */
687			int st = (search_type & (SRCH_FORW|SRCH_BACK|SRCH_NO_MATCH|SRCH_NO_REGEX|SRCH_NO_MOVE|SRCH_WRAP));
688			char *pattern = get_cmdbuf();
689			cmd_exec();
690			if (*pattern == '\0')
691			{
692				/* User has backspaced to an empty pattern. */
693				undo_search(1);
694			} else
695			{
696				if (search(st | SRCH_INCR, pattern, 1) != 0)
697					/* No match, invalid pattern, etc. */
698					undo_search(1);
699			}
700			/* Redraw the search prompt and search string. */
701			mca_search();
702			cmd_repaint(NULL);
703		}
704		break;
705	}
706
707	/*
708	 * Need another character.
709	 */
710	return (MCA_MORE);
711}
712
713/*
714 * Discard any buffered file data.
715 */
716	static void
717clear_buffers(VOID_PARAM)
718{
719	if (!(ch_getflags() & CH_CANSEEK))
720		return;
721	ch_flush();
722	clr_linenum();
723#if HILITE_SEARCH
724	clr_hilite();
725#endif
726}
727
728/*
729 * Make sure the screen is displayed.
730 */
731	static void
732make_display(VOID_PARAM)
733{
734	/*
735	 * If nothing is displayed yet, display starting from initial_scrpos.
736	 */
737	if (empty_screen())
738	{
739		if (initial_scrpos.pos == NULL_POSITION)
740			jump_loc(ch_zero(), 1);
741		else
742			jump_loc(initial_scrpos.pos, initial_scrpos.ln);
743	} else if (screen_trashed)
744	{
745		int save_top_scroll = top_scroll;
746		int save_ignore_eoi = ignore_eoi;
747		top_scroll = 1;
748		ignore_eoi = 0;
749		if (screen_trashed == 2)
750		{
751			/* Special case used by ignore_eoi: re-open the input file
752			 * and jump to the end of the file. */
753			reopen_curr_ifile();
754			jump_forw();
755		}
756		repaint();
757		top_scroll = save_top_scroll;
758		ignore_eoi = save_ignore_eoi;
759	}
760}
761
762/*
763 * Display the appropriate prompt.
764 */
765	static void
766prompt(VOID_PARAM)
767{
768	constant char *p;
769
770	if (ungot != NULL && ungot->ug_char != CHAR_END_COMMAND)
771	{
772		/*
773		 * No prompt necessary if commands are from
774		 * ungotten chars rather than from the user.
775		 */
776		return;
777	}
778
779	/*
780	 * Make sure the screen is displayed.
781	 */
782	make_display();
783	bottompos = position(BOTTOM_PLUS_ONE);
784
785	/*
786	 * If we've hit EOF on the last file and the -E flag is set, quit.
787	 */
788	if (get_quit_at_eof() == OPT_ONPLUS &&
789	    eof_displayed() && !(ch_getflags() & CH_HELPFILE) &&
790	    next_ifile(curr_ifile) == NULL_IFILE)
791		quit(QUIT_OK);
792
793	/*
794	 * If the entire file is displayed and the -F flag is set, quit.
795	 */
796	if (quit_if_one_screen &&
797	    entire_file_displayed() && !(ch_getflags() & CH_HELPFILE) &&
798	    next_ifile(curr_ifile) == NULL_IFILE)
799		quit(QUIT_OK);
800
801#if MSDOS_COMPILER==WIN32C
802	/*
803	 * In Win32, display the file name in the window title.
804	 */
805	if (!(ch_getflags() & CH_HELPFILE))
806	{
807		WCHAR w[MAX_PATH+16];
808		p = pr_expand("Less?f - %f.", 0);
809		MultiByteToWideChar(CP_ACP, 0, p, -1, w, sizeof(w)/sizeof(*w));
810		SetConsoleTitleW(w);
811	}
812#endif
813
814	/*
815	 * Select the proper prompt and display it.
816	 */
817	/*
818	 * If the previous action was a forward movement,
819	 * don't clear the bottom line of the display;
820	 * just print the prompt since the forward movement guarantees
821	 * that we're in the right position to display the prompt.
822	 * Clearing the line could cause a problem: for example, if the last
823	 * line displayed ended at the right screen edge without a newline,
824	 * then clearing would clear the last displayed line rather than
825	 * the prompt line.
826	 */
827	if (!forw_prompt)
828		clear_bot();
829	clear_cmd();
830	forw_prompt = 0;
831	p = pr_string();
832#if HILITE_SEARCH
833	if (is_filtering())
834		putstr("& ");
835#endif
836	if (p == NULL || *p == '\0')
837	{
838		at_enter(AT_NORMAL|AT_COLOR_PROMPT);
839		putchr(':');
840		at_exit();
841	} else
842	{
843#if MSDOS_COMPILER==WIN32C
844		WCHAR w[MAX_PATH*2];
845		char  a[MAX_PATH*2];
846		MultiByteToWideChar(CP_ACP, 0, p, -1, w, sizeof(w)/sizeof(*w));
847		WideCharToMultiByte(utf_mode ? CP_UTF8 : GetConsoleOutputCP(),
848		                    0, w, -1, a, sizeof(a), NULL, NULL);
849		p = a;
850#endif
851		at_enter(AT_STANDOUT|AT_COLOR_PROMPT);
852		putstr(p);
853		at_exit();
854	}
855	clear_eol();
856}
857
858/*
859 * Display the less version message.
860 */
861	public void
862dispversion(VOID_PARAM)
863{
864	PARG parg;
865
866	parg.p_string = version;
867	error("less %s", &parg);
868}
869
870/*
871 * Return a character to complete a partial command, if possible.
872 */
873	static LWCHAR
874getcc_end_command(VOID_PARAM)
875{
876	switch (mca)
877	{
878	case A_DIGIT:
879		/* We have a number but no command.  Treat as #g. */
880		return ('g');
881	case A_F_SEARCH:
882	case A_B_SEARCH:
883		/* We have "/string" but no newline.  Add the \n. */
884		return ('\n');
885	default:
886		/* Some other incomplete command.  Let user complete it. */
887		return ((ungot == NULL) ? getchr() : 0);
888	}
889}
890
891/*
892 * Get command character.
893 * The character normally comes from the keyboard,
894 * but may come from ungotten characters
895 * (characters previously given to ungetcc or ungetsc).
896 */
897	static LWCHAR
898getccu(VOID_PARAM)
899{
900	LWCHAR c = 0;
901	while (c == 0)
902	{
903		if (ungot == NULL)
904		{
905			/* Normal case: no ungotten chars.
906			 * Get char from the user. */
907			c = getchr();
908		} else
909		{
910			/* Ungotten chars available:
911			 * Take the top of stack (most recent). */
912			struct ungot *ug = ungot;
913			c = ug->ug_char;
914			ungot = ug->ug_next;
915			free(ug);
916
917			if (c == CHAR_END_COMMAND)
918				c = getcc_end_command();
919		}
920	}
921	return (c);
922}
923
924/*
925 * Get a command character, but if we receive the orig sequence,
926 * convert it to the repl sequence.
927 */
928	static LWCHAR
929getcc_repl(orig, repl, gr_getc, gr_ungetc)
930	char const* orig;
931	char const* repl;
932	LWCHAR (*gr_getc)(VOID_PARAM);
933	void (*gr_ungetc)(LWCHAR);
934{
935	LWCHAR c;
936	LWCHAR keys[16];
937	int ki = 0;
938
939	c = (*gr_getc)();
940	if (orig == NULL || orig[0] == '\0')
941		return c;
942	for (;;)
943	{
944		keys[ki] = c;
945		if (c != orig[ki] || ki >= sizeof(keys)-1)
946		{
947			/* This is not orig we have been receiving.
948			 * If we have stashed chars in keys[],
949			 * unget them and return the first one. */
950			while (ki > 0)
951				(*gr_ungetc)(keys[ki--]);
952			return keys[0];
953		}
954		if (orig[++ki] == '\0')
955		{
956			/* We've received the full orig sequence.
957			 * Return the repl sequence. */
958			ki = strlen(repl)-1;
959			while (ki > 0)
960				(*gr_ungetc)(repl[ki--]);
961			return repl[0];
962		}
963		/* We've received a partial orig sequence (ki chars of it).
964		 * Get next char and see if it continues to match orig. */
965		c = (*gr_getc)();
966	}
967}
968
969/*
970 * Get command character.
971 */
972	public int
973getcc(VOID_PARAM)
974{
975	/* Replace kent (keypad Enter) with a newline. */
976	return getcc_repl(kent, "\n", getccu, ungetcc);
977}
978
979/*
980 * "Unget" a command character.
981 * The next getcc() will return this character.
982 */
983	public void
984ungetcc(c)
985	LWCHAR c;
986{
987	struct ungot *ug = (struct ungot *) ecalloc(1, sizeof(struct ungot));
988
989	ug->ug_char = c;
990	ug->ug_next = ungot;
991	ungot = ug;
992}
993
994/*
995 * "Unget" a command character.
996 * If any other chars are already ungotten, put this one after those.
997 */
998	public void
999ungetcc_back(c)
1000	LWCHAR c;
1001{
1002	struct ungot *ug = (struct ungot *) ecalloc(1, sizeof(struct ungot));
1003	ug->ug_char = c;
1004	ug->ug_next = NULL;
1005	if (ungot == NULL)
1006		ungot = ug;
1007	else
1008	{
1009		struct ungot *pu;
1010		for (pu = ungot; pu->ug_next != NULL; pu = pu->ug_next)
1011			continue;
1012		pu->ug_next = ug;
1013	}
1014}
1015
1016/*
1017 * Unget a whole string of command characters.
1018 * The next sequence of getcc()'s will return this string.
1019 */
1020	public void
1021ungetsc(s)
1022	char *s;
1023{
1024	while (*s != '\0')
1025		ungetcc_back(*s++);
1026}
1027
1028/*
1029 * Peek the next command character, without consuming it.
1030 */
1031	public LWCHAR
1032peekcc(VOID_PARAM)
1033{
1034	LWCHAR c = getcc();
1035	ungetcc(c);
1036	return c;
1037}
1038
1039/*
1040 * Search for a pattern, possibly in multiple files.
1041 * If SRCH_FIRST_FILE is set, begin searching at the first file.
1042 * If SRCH_PAST_EOF is set, continue the search thru multiple files.
1043 */
1044	static void
1045multi_search(pattern, n, silent)
1046	char *pattern;
1047	int n;
1048	int silent;
1049{
1050	int nomore;
1051	IFILE save_ifile;
1052	int changed_file;
1053
1054	changed_file = 0;
1055	save_ifile = save_curr_ifile();
1056
1057	if (search_type & SRCH_FIRST_FILE)
1058	{
1059		/*
1060		 * Start at the first (or last) file
1061		 * in the command line list.
1062		 */
1063		if (search_type & SRCH_FORW)
1064			nomore = edit_first();
1065		else
1066			nomore = edit_last();
1067		if (nomore)
1068		{
1069			unsave_ifile(save_ifile);
1070			return;
1071		}
1072		changed_file = 1;
1073		search_type &= ~SRCH_FIRST_FILE;
1074	}
1075
1076	for (;;)
1077	{
1078		n = search(search_type, pattern, n);
1079		/*
1080		 * The SRCH_NO_MOVE flag doesn't "stick": it gets cleared
1081		 * after being used once.  This allows "n" to work after
1082		 * using a /@@ search.
1083		 */
1084		search_type &= ~SRCH_NO_MOVE;
1085		if (n == 0)
1086		{
1087			/*
1088			 * Found it.
1089			 */
1090			unsave_ifile(save_ifile);
1091			return;
1092		}
1093
1094		if (n < 0)
1095			/*
1096			 * Some kind of error in the search.
1097			 * Error message has been printed by search().
1098			 */
1099			break;
1100
1101		if ((search_type & SRCH_PAST_EOF) == 0)
1102			/*
1103			 * We didn't find a match, but we're
1104			 * supposed to search only one file.
1105			 */
1106			break;
1107		/*
1108		 * Move on to the next file.
1109		 */
1110		if (search_type & SRCH_FORW)
1111			nomore = edit_next(1);
1112		else
1113			nomore = edit_prev(1);
1114		if (nomore)
1115			break;
1116		changed_file = 1;
1117	}
1118
1119	/*
1120	 * Didn't find it.
1121	 * Print an error message if we haven't already.
1122	 */
1123	if (n > 0 && !silent)
1124		error("Pattern not found", NULL_PARG);
1125
1126	if (changed_file)
1127	{
1128		/*
1129		 * Restore the file we were originally viewing.
1130		 */
1131		reedit_ifile(save_ifile);
1132	} else
1133	{
1134		unsave_ifile(save_ifile);
1135	}
1136}
1137
1138/*
1139 * Forward forever, or until a highlighted line appears.
1140 */
1141	static int
1142forw_loop(until_hilite)
1143	int until_hilite;
1144{
1145	POSITION curr_len;
1146
1147	if (ch_getflags() & CH_HELPFILE)
1148		return (A_NOACTION);
1149
1150	cmd_exec();
1151	jump_forw_buffered();
1152	curr_len = ch_length();
1153	highest_hilite = until_hilite ? curr_len : NULL_POSITION;
1154	ignore_eoi = 1;
1155	while (!sigs)
1156	{
1157		if (until_hilite && highest_hilite > curr_len)
1158		{
1159			bell();
1160			break;
1161		}
1162		make_display();
1163		forward(1, 0, 0);
1164	}
1165	ignore_eoi = 0;
1166	ch_set_eof();
1167
1168	/*
1169	 * This gets us back in "F mode" after processing
1170	 * a non-abort signal (e.g. window-change).
1171	 */
1172	if (sigs && !ABORT_SIGS())
1173		return (until_hilite ? A_F_UNTIL_HILITE : A_F_FOREVER);
1174
1175	return (A_NOACTION);
1176}
1177
1178/*
1179 * Main command processor.
1180 * Accept and execute commands until a quit command.
1181 */
1182	public void
1183commands(VOID_PARAM)
1184{
1185	int c;
1186	int action;
1187	char *cbuf;
1188	int newaction;
1189	int save_jump_sline;
1190	int save_search_type;
1191	char *extra;
1192	char tbuf[2];
1193	PARG parg;
1194	IFILE old_ifile;
1195	IFILE new_ifile;
1196	char *tagfile;
1197
1198	search_type = SRCH_FORW;
1199	wscroll = (sc_height + 1) / 2;
1200	newaction = A_NOACTION;
1201
1202	for (;;)
1203	{
1204		clear_mca();
1205		cmd_accept();
1206		number = 0;
1207		curropt = NULL;
1208
1209		/*
1210		 * See if any signals need processing.
1211		 */
1212		if (sigs)
1213		{
1214			psignals();
1215			if (quitting)
1216				quit(QUIT_SAVED_STATUS);
1217		}
1218
1219		/*
1220		 * See if window size changed, for systems that don't
1221		 * generate SIGWINCH.
1222		 */
1223		check_winch();
1224
1225		/*
1226		 * Display prompt and accept a character.
1227		 */
1228		cmd_reset();
1229		prompt();
1230		if (sigs)
1231			continue;
1232		if (newaction == A_NOACTION)
1233			c = getcc();
1234
1235	again:
1236		if (sigs)
1237			continue;
1238
1239		if (newaction != A_NOACTION)
1240		{
1241			action = newaction;
1242			newaction = A_NOACTION;
1243		} else
1244		{
1245			/*
1246			 * If we are in a multicharacter command, call mca_char.
1247			 * Otherwise we call fcmd_decode to determine the
1248			 * action to be performed.
1249			 */
1250			if (mca)
1251				switch (mca_char(c))
1252				{
1253				case MCA_MORE:
1254					/*
1255					 * Need another character.
1256					 */
1257					c = getcc();
1258					goto again;
1259				case MCA_DONE:
1260					/*
1261					 * Command has been handled by mca_char.
1262					 * Start clean with a prompt.
1263					 */
1264					continue;
1265				case NO_MCA:
1266					/*
1267					 * Not a multi-char command
1268					 * (at least, not anymore).
1269					 */
1270					break;
1271				}
1272
1273			/*
1274			 * Decode the command character and decide what to do.
1275			 */
1276			if (mca)
1277			{
1278				/*
1279				 * We're in a multichar command.
1280				 * Add the character to the command buffer
1281				 * and display it on the screen.
1282				 * If the user backspaces past the start
1283				 * of the line, abort the command.
1284				 */
1285				if (cmd_char(c) == CC_QUIT || len_cmdbuf() == 0)
1286					continue;
1287				cbuf = get_cmdbuf();
1288			} else
1289			{
1290				/*
1291				 * Don't use cmd_char if we're starting fresh
1292				 * at the beginning of a command, because we
1293				 * don't want to echo the command until we know
1294				 * it is a multichar command.  We also don't
1295				 * want erase_char/kill_char to be treated
1296				 * as line editing characters.
1297				 */
1298				tbuf[0] = c;
1299				tbuf[1] = '\0';
1300				cbuf = tbuf;
1301			}
1302			extra = NULL;
1303			action = fcmd_decode(cbuf, &extra);
1304			/*
1305			 * If an "extra" string was returned,
1306			 * process it as a string of command characters.
1307			 */
1308			if (extra != NULL)
1309				ungetsc(extra);
1310		}
1311		/*
1312		 * Clear the cmdbuf string.
1313		 * (But not if we're in the prefix of a command,
1314		 * because the partial command string is kept there.)
1315		 */
1316		if (action != A_PREFIX)
1317			cmd_reset();
1318
1319		switch (action)
1320		{
1321		case A_DIGIT:
1322			/*
1323			 * First digit of a number.
1324			 */
1325			start_mca(A_DIGIT, ":", (void*)NULL, CF_QUIT_ON_ERASE);
1326			goto again;
1327
1328		case A_F_WINDOW:
1329			/*
1330			 * Forward one window (and set the window size).
1331			 */
1332			if (number > 0)
1333				swindow = (int) number;
1334			/* FALLTHRU */
1335		case A_F_SCREEN:
1336			/*
1337			 * Forward one screen.
1338			 */
1339			if (number <= 0)
1340				number = get_swindow();
1341			cmd_exec();
1342			if (show_attn)
1343				set_attnpos(bottompos);
1344			forward((int) number, 0, 1);
1345			break;
1346
1347		case A_B_WINDOW:
1348			/*
1349			 * Backward one window (and set the window size).
1350			 */
1351			if (number > 0)
1352				swindow = (int) number;
1353			/* FALLTHRU */
1354		case A_B_SCREEN:
1355			/*
1356			 * Backward one screen.
1357			 */
1358			if (number <= 0)
1359				number = get_swindow();
1360			cmd_exec();
1361			backward((int) number, 0, 1);
1362			break;
1363
1364		case A_F_LINE:
1365			/*
1366			 * Forward N (default 1) line.
1367			 */
1368			if (number <= 0)
1369				number = 1;
1370			cmd_exec();
1371			if (show_attn == OPT_ONPLUS && number > 1)
1372				set_attnpos(bottompos);
1373			forward((int) number, 0, 0);
1374			break;
1375
1376		case A_B_LINE:
1377			/*
1378			 * Backward N (default 1) line.
1379			 */
1380			if (number <= 0)
1381				number = 1;
1382			cmd_exec();
1383			backward((int) number, 0, 0);
1384			break;
1385
1386		case A_F_MOUSE:
1387			/*
1388			 * Forward wheel_lines lines.
1389			 */
1390			cmd_exec();
1391			forward(wheel_lines, 0, 0);
1392			break;
1393
1394		case A_B_MOUSE:
1395			/*
1396			 * Backward wheel_lines lines.
1397			 */
1398			cmd_exec();
1399			backward(wheel_lines, 0, 0);
1400			break;
1401
1402		case A_FF_LINE:
1403			/*
1404			 * Force forward N (default 1) line.
1405			 */
1406			if (number <= 0)
1407				number = 1;
1408			cmd_exec();
1409			if (show_attn == OPT_ONPLUS && number > 1)
1410				set_attnpos(bottompos);
1411			forward((int) number, 1, 0);
1412			break;
1413
1414		case A_BF_LINE:
1415			/*
1416			 * Force backward N (default 1) line.
1417			 */
1418			if (number <= 0)
1419				number = 1;
1420			cmd_exec();
1421			backward((int) number, 1, 0);
1422			break;
1423
1424		case A_FF_SCREEN:
1425			/*
1426			 * Force forward one screen.
1427			 */
1428			if (number <= 0)
1429				number = get_swindow();
1430			cmd_exec();
1431			if (show_attn == OPT_ONPLUS)
1432				set_attnpos(bottompos);
1433			forward((int) number, 1, 0);
1434			break;
1435
1436		case A_F_FOREVER:
1437			/*
1438			 * Forward forever, ignoring EOF.
1439			 */
1440			if (show_attn)
1441				set_attnpos(bottompos);
1442			newaction = forw_loop(0);
1443			break;
1444
1445		case A_F_UNTIL_HILITE:
1446			newaction = forw_loop(1);
1447			break;
1448
1449		case A_F_SCROLL:
1450			/*
1451			 * Forward N lines
1452			 * (default same as last 'd' or 'u' command).
1453			 */
1454			if (number > 0)
1455				wscroll = (int) number;
1456			cmd_exec();
1457			if (show_attn == OPT_ONPLUS)
1458				set_attnpos(bottompos);
1459			forward(wscroll, 0, 0);
1460			break;
1461
1462		case A_B_SCROLL:
1463			/*
1464			 * Forward N lines
1465			 * (default same as last 'd' or 'u' command).
1466			 */
1467			if (number > 0)
1468				wscroll = (int) number;
1469			cmd_exec();
1470			backward(wscroll, 0, 0);
1471			break;
1472
1473		case A_FREPAINT:
1474			/*
1475			 * Flush buffers, then repaint screen.
1476			 * Don't flush the buffers on a pipe!
1477			 */
1478			clear_buffers();
1479			/* FALLTHRU */
1480		case A_REPAINT:
1481			/*
1482			 * Repaint screen.
1483			 */
1484			cmd_exec();
1485			repaint();
1486			break;
1487
1488		case A_GOLINE:
1489			/*
1490			 * Go to line N, default beginning of file.
1491			 * If N <= 0, ignore jump_sline in order to avoid
1492			 * empty lines before the beginning of the file.
1493			 */
1494			save_jump_sline = jump_sline;
1495			if (number <= 0)
1496			{
1497				number = 1;
1498				jump_sline = 0;
1499			}
1500			cmd_exec();
1501			jump_back(number);
1502			jump_sline = save_jump_sline;
1503			break;
1504
1505		case A_PERCENT:
1506			/*
1507			 * Go to a specified percentage into the file.
1508			 */
1509			if (number < 0)
1510			{
1511				number = 0;
1512				fraction = 0;
1513			}
1514			if (number > 100 || (number == 100 && fraction != 0))
1515			{
1516				number = 100;
1517				fraction = 0;
1518			}
1519			cmd_exec();
1520			jump_percent((int) number, fraction);
1521			break;
1522
1523		case A_GOEND:
1524			/*
1525			 * Go to line N, default end of file.
1526			 */
1527			cmd_exec();
1528			if (number <= 0)
1529				jump_forw();
1530			else
1531				jump_back(number);
1532			break;
1533
1534		case A_GOEND_BUF:
1535			/*
1536			 * Go to line N, default last buffered byte.
1537			 */
1538			cmd_exec();
1539			if (number <= 0)
1540				jump_forw_buffered();
1541			else
1542				jump_back(number);
1543			break;
1544
1545		case A_GOPOS:
1546			/*
1547			 * Go to a specified byte position in the file.
1548			 */
1549			cmd_exec();
1550			if (number < 0)
1551				number = 0;
1552			jump_line_loc((POSITION) number, jump_sline);
1553			break;
1554
1555		case A_STAT:
1556			/*
1557			 * Print file name, etc.
1558			 */
1559			if (ch_getflags() & CH_HELPFILE)
1560				break;
1561			cmd_exec();
1562			parg.p_string = eq_message();
1563			error("%s", &parg);
1564			break;
1565
1566		case A_VERSION:
1567			/*
1568			 * Print version number.
1569			 */
1570			cmd_exec();
1571			dispversion();
1572			break;
1573
1574		case A_QUIT:
1575			/*
1576			 * Exit.
1577			 */
1578			if (curr_ifile != NULL_IFILE &&
1579			    ch_getflags() & CH_HELPFILE)
1580			{
1581				/*
1582				 * Quit while viewing the help file
1583				 * just means return to viewing the
1584				 * previous file.
1585				 */
1586				hshift = save_hshift;
1587				bs_mode = save_bs_mode;
1588				if (edit_prev(1) == 0)
1589					break;
1590			}
1591			if (extra != NULL)
1592				quit(*extra);
1593			quit(QUIT_OK);
1594			break;
1595
1596/*
1597 * Define abbreviation for a commonly used sequence below.
1598 */
1599#define DO_SEARCH() \
1600			if (number <= 0) number = 1;    \
1601			mca_search();                   \
1602			cmd_exec();                     \
1603			multi_search((char *)NULL, (int) number, 0);
1604
1605
1606		case A_F_SEARCH:
1607			/*
1608			 * Search forward for a pattern.
1609			 * Get the first char of the pattern.
1610			 */
1611			search_type = SRCH_FORW;
1612			if (number <= 0)
1613				number = 1;
1614			mca_search();
1615			c = getcc();
1616			goto again;
1617
1618		case A_B_SEARCH:
1619			/*
1620			 * Search backward for a pattern.
1621			 * Get the first char of the pattern.
1622			 */
1623			search_type = SRCH_BACK;
1624			if (number <= 0)
1625				number = 1;
1626			mca_search();
1627			c = getcc();
1628			goto again;
1629
1630		case A_FILTER:
1631#if HILITE_SEARCH
1632			search_type = SRCH_FORW | SRCH_FILTER;
1633			mca_search();
1634			c = getcc();
1635			goto again;
1636#else
1637			error("Command not available", NULL_PARG);
1638			break;
1639#endif
1640
1641		case A_AGAIN_SEARCH:
1642			/*
1643			 * Repeat previous search.
1644			 */
1645			DO_SEARCH();
1646			break;
1647
1648		case A_T_AGAIN_SEARCH:
1649			/*
1650			 * Repeat previous search, multiple files.
1651			 */
1652			search_type |= SRCH_PAST_EOF;
1653			DO_SEARCH();
1654			break;
1655
1656		case A_REVERSE_SEARCH:
1657			/*
1658			 * Repeat previous search, in reverse direction.
1659			 */
1660			save_search_type = search_type;
1661			search_type = SRCH_REVERSE(search_type);
1662			DO_SEARCH();
1663			search_type = save_search_type;
1664			break;
1665
1666		case A_T_REVERSE_SEARCH:
1667			/*
1668			 * Repeat previous search,
1669			 * multiple files in reverse direction.
1670			 */
1671			save_search_type = search_type;
1672			search_type = SRCH_REVERSE(search_type);
1673			search_type |= SRCH_PAST_EOF;
1674			DO_SEARCH();
1675			search_type = save_search_type;
1676			break;
1677
1678		case A_UNDO_SEARCH:
1679		case A_CLR_SEARCH:
1680			/*
1681			 * Clear search string highlighting.
1682			 */
1683			undo_search(action == A_CLR_SEARCH);
1684			break;
1685
1686		case A_HELP:
1687			/*
1688			 * Help.
1689			 */
1690			if (ch_getflags() & CH_HELPFILE)
1691				break;
1692			cmd_exec();
1693			save_hshift = hshift;
1694			hshift = 0;
1695			save_bs_mode = bs_mode;
1696			bs_mode = BS_SPECIAL;
1697			(void) edit(FAKE_HELPFILE);
1698			break;
1699
1700		case A_EXAMINE:
1701			/*
1702			 * Edit a new file.  Get the filename.
1703			 */
1704#if EXAMINE
1705			if (!secure)
1706			{
1707				start_mca(A_EXAMINE, "Examine: ", ml_examine, 0);
1708				c = getcc();
1709				goto again;
1710			}
1711#endif
1712			error("Command not available", NULL_PARG);
1713			break;
1714
1715		case A_VISUAL:
1716			/*
1717			 * Invoke an editor on the input file.
1718			 */
1719#if EDITOR
1720			if (!secure)
1721			{
1722				if (ch_getflags() & CH_HELPFILE)
1723					break;
1724				if (strcmp(get_filename(curr_ifile), "-") == 0)
1725				{
1726					error("Cannot edit standard input", NULL_PARG);
1727					break;
1728				}
1729				if (get_altfilename(curr_ifile) != NULL)
1730				{
1731					error("WARNING: This file was viewed via LESSOPEN",
1732						NULL_PARG);
1733				}
1734				start_mca(A_SHELL, "!", ml_shell, 0);
1735				/*
1736				 * Expand the editor prototype string
1737				 * and pass it to the system to execute.
1738				 * (Make sure the screen is displayed so the
1739				 * expansion of "+%lm" works.)
1740				 */
1741				make_display();
1742				cmd_exec();
1743				lsystem(pr_expand(editproto, 0), (char*)NULL);
1744				break;
1745			}
1746#endif
1747			error("Command not available", NULL_PARG);
1748			break;
1749
1750		case A_NEXT_FILE:
1751			/*
1752			 * Examine next file.
1753			 */
1754#if TAGS
1755			if (ntags())
1756			{
1757				error("No next file", NULL_PARG);
1758				break;
1759			}
1760#endif
1761			if (number <= 0)
1762				number = 1;
1763			if (edit_next((int) number))
1764			{
1765				if (get_quit_at_eof() && eof_displayed() &&
1766				    !(ch_getflags() & CH_HELPFILE))
1767					quit(QUIT_OK);
1768				parg.p_string = (number > 1) ? "(N-th) " : "";
1769				error("No %snext file", &parg);
1770			}
1771			break;
1772
1773		case A_PREV_FILE:
1774			/*
1775			 * Examine previous file.
1776			 */
1777#if TAGS
1778			if (ntags())
1779			{
1780				error("No previous file", NULL_PARG);
1781				break;
1782			}
1783#endif
1784			if (number <= 0)
1785				number = 1;
1786			if (edit_prev((int) number))
1787			{
1788				parg.p_string = (number > 1) ? "(N-th) " : "";
1789				error("No %sprevious file", &parg);
1790			}
1791			break;
1792
1793		case A_NEXT_TAG:
1794			/*
1795			 * Jump to the next tag in the current tag list.
1796			 */
1797#if TAGS
1798			if (number <= 0)
1799				number = 1;
1800			tagfile = nexttag((int) number);
1801			if (tagfile == NULL)
1802			{
1803				error("No next tag", NULL_PARG);
1804				break;
1805			}
1806			cmd_exec();
1807			if (edit(tagfile) == 0)
1808			{
1809				POSITION pos = tagsearch();
1810				if (pos != NULL_POSITION)
1811					jump_loc(pos, jump_sline);
1812			}
1813#else
1814			error("Command not available", NULL_PARG);
1815#endif
1816			break;
1817
1818		case A_PREV_TAG:
1819			/*
1820			 * Jump to the previous tag in the current tag list.
1821			 */
1822#if TAGS
1823			if (number <= 0)
1824				number = 1;
1825			tagfile = prevtag((int) number);
1826			if (tagfile == NULL)
1827			{
1828				error("No previous tag", NULL_PARG);
1829				break;
1830			}
1831			cmd_exec();
1832			if (edit(tagfile) == 0)
1833			{
1834				POSITION pos = tagsearch();
1835				if (pos != NULL_POSITION)
1836					jump_loc(pos, jump_sline);
1837			}
1838#else
1839			error("Command not available", NULL_PARG);
1840#endif
1841			break;
1842
1843		case A_INDEX_FILE:
1844			/*
1845			 * Examine a particular file.
1846			 */
1847			if (number <= 0)
1848				number = 1;
1849			if (edit_index((int) number))
1850				error("No such file", NULL_PARG);
1851			break;
1852
1853		case A_REMOVE_FILE:
1854			/*
1855			 * Remove a file from the input file list.
1856			 */
1857			if (ch_getflags() & CH_HELPFILE)
1858				break;
1859			old_ifile = curr_ifile;
1860			new_ifile = getoff_ifile(curr_ifile);
1861			if (new_ifile == NULL_IFILE)
1862			{
1863				bell();
1864				break;
1865			}
1866			if (edit_ifile(new_ifile) != 0)
1867			{
1868				reedit_ifile(old_ifile);
1869				break;
1870			}
1871			del_ifile(old_ifile);
1872			break;
1873
1874		case A_OPT_TOGGLE:
1875			/*
1876			 * Change the setting of an  option.
1877			 */
1878			optflag = OPT_TOGGLE;
1879			optgetname = FALSE;
1880			mca_opt_toggle();
1881			c = getcc();
1882			cbuf = opt_toggle_disallowed(c);
1883			if (cbuf != NULL)
1884			{
1885				error(cbuf, NULL_PARG);
1886				break;
1887			}
1888			goto again;
1889
1890		case A_DISP_OPTION:
1891			/*
1892			 * Report the setting of an option.
1893			 */
1894			optflag = OPT_NO_TOGGLE;
1895			optgetname = FALSE;
1896			mca_opt_toggle();
1897			c = getcc();
1898			goto again;
1899
1900		case A_FIRSTCMD:
1901			/*
1902			 * Set an initial command for new files.
1903			 */
1904			start_mca(A_FIRSTCMD, "+", (void*)NULL, 0);
1905			c = getcc();
1906			goto again;
1907
1908		case A_SHELL:
1909			/*
1910			 * Shell escape.
1911			 */
1912#if SHELL_ESCAPE
1913			if (!secure)
1914			{
1915				start_mca(A_SHELL, "!", ml_shell, 0);
1916				c = getcc();
1917				goto again;
1918			}
1919#endif
1920			error("Command not available", NULL_PARG);
1921			break;
1922
1923		case A_SETMARK:
1924		case A_SETMARKBOT:
1925			/*
1926			 * Set a mark.
1927			 */
1928			if (ch_getflags() & CH_HELPFILE)
1929				break;
1930			start_mca(A_SETMARK, "set mark: ", (void*)NULL, 0);
1931			c = getcc();
1932			if (is_erase_char(c) || is_newline_char(c))
1933				break;
1934			setmark(c, action == A_SETMARKBOT ? BOTTOM : TOP);
1935			repaint();
1936			break;
1937
1938		case A_CLRMARK:
1939			/*
1940			 * Clear a mark.
1941			 */
1942			start_mca(A_CLRMARK, "clear mark: ", (void*)NULL, 0);
1943			c = getcc();
1944			if (is_erase_char(c) || is_newline_char(c))
1945				break;
1946			clrmark(c);
1947			repaint();
1948			break;
1949
1950		case A_GOMARK:
1951			/*
1952			 * Jump to a marked position.
1953			 */
1954			start_mca(A_GOMARK, "goto mark: ", (void*)NULL, 0);
1955			c = getcc();
1956			if (is_erase_char(c) || is_newline_char(c))
1957				break;
1958			cmd_exec();
1959			gomark(c);
1960			break;
1961
1962		case A_PIPE:
1963			/*
1964			 * Write part of the input to a pipe to a shell command.
1965			 */
1966#if PIPEC
1967			if (!secure)
1968			{
1969				start_mca(A_PIPE, "|mark: ", (void*)NULL, 0);
1970				c = getcc();
1971				if (is_erase_char(c))
1972					break;
1973				if (is_newline_char(c))
1974					c = '.';
1975				if (badmark(c))
1976					break;
1977				pipec = c;
1978				start_mca(A_PIPE, "!", ml_shell, 0);
1979				c = getcc();
1980				goto again;
1981			}
1982#endif
1983			error("Command not available", NULL_PARG);
1984			break;
1985
1986		case A_B_BRACKET:
1987		case A_F_BRACKET:
1988			start_mca(action, "Brackets: ", (void*)NULL, 0);
1989			c = getcc();
1990			goto again;
1991
1992		case A_LSHIFT:
1993			/*
1994			 * Shift view left.
1995			 */
1996			if (number > 0)
1997				shift_count = number;
1998			else
1999				number = (shift_count > 0) ?
2000					shift_count : sc_width / 2;
2001			if (number > hshift)
2002				number = hshift;
2003			hshift -= number;
2004			screen_trashed = 1;
2005			break;
2006
2007		case A_RSHIFT:
2008			/*
2009			 * Shift view right.
2010			 */
2011			if (number > 0)
2012				shift_count = number;
2013			else
2014				number = (shift_count > 0) ?
2015					shift_count : sc_width / 2;
2016			hshift += number;
2017			screen_trashed = 1;
2018			break;
2019
2020		case A_LLSHIFT:
2021			/*
2022			 * Shift view left to margin.
2023			 */
2024			hshift = 0;
2025			screen_trashed = 1;
2026			break;
2027
2028		case A_RRSHIFT:
2029			/*
2030			 * Shift view right to view rightmost char on screen.
2031			 */
2032			hshift = rrshift();
2033			screen_trashed = 1;
2034			break;
2035
2036		case A_PREFIX:
2037			/*
2038			 * The command is incomplete (more chars are needed).
2039			 * Display the current char, so the user knows
2040			 * what's going on, and get another character.
2041			 */
2042			if (mca != A_PREFIX)
2043			{
2044				cmd_reset();
2045				start_mca(A_PREFIX, " ", (void*)NULL,
2046					CF_QUIT_ON_ERASE);
2047				(void) cmd_char(c);
2048			}
2049			c = getcc();
2050			goto again;
2051
2052		case A_NOACTION:
2053			break;
2054
2055		default:
2056			bell();
2057			break;
2058		}
2059	}
2060}
2061