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