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