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