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