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