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