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