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