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