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