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