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