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