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