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