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