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