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