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