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