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