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