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