1 /*
2 * Copyright (C) 1984-2023 Mark Nudelman
3 *
4 * You may distribute under the terms of either the GNU General Public
5 * License or the Less License, as specified in the README file.
6 *
7 * For more information, see the README file.
8 */
9
10
11 /*
12 * Functions which manipulate the command buffer.
13 * Used only by command() and related functions.
14 */
15
16 #include "less.h"
17 #include "cmd.h"
18 #include "charset.h"
19 #if HAVE_STAT
20 #include <sys/stat.h>
21 #endif
22
23 extern int sc_width;
24 extern int utf_mode;
25 extern int no_hist_dups;
26 extern int marks_modified;
27 extern int secure;
28
29 static char cmdbuf[CMDBUF_SIZE]; /* Buffer for holding a multi-char command */
30 static int cmd_col; /* Current column of the cursor */
31 static int prompt_col; /* Column of cursor just after prompt */
32 static char *cp; /* Pointer into cmdbuf */
33 static int cmd_offset; /* Index into cmdbuf of first displayed char */
34 static int literal; /* Next input char should not be interpreted */
35 public int updown_match = -1; /* Prefix length in up/down movement */
36
37 #if TAB_COMPLETE_FILENAME
38 static int cmd_complete(int action);
39 /*
40 * These variables are statics used by cmd_complete.
41 */
42 static int in_completion = 0;
43 static char *tk_text;
44 static char *tk_original;
45 static char *tk_ipoint;
46 static char *tk_trial = NULL;
47 static struct textlist tk_tlist;
48 #endif
49
50 static int cmd_left();
51 static int cmd_right();
52
53 #if SPACES_IN_FILENAMES
54 public char openquote = '"';
55 public char closequote = '"';
56 #endif
57
58 #if CMD_HISTORY
59
60 /* History file */
61 #define HISTFILE_FIRST_LINE ".less-history-file:"
62 #define HISTFILE_SEARCH_SECTION ".search"
63 #define HISTFILE_SHELL_SECTION ".shell"
64 #define HISTFILE_MARK_SECTION ".mark"
65
66 /*
67 * A mlist structure represents a command history.
68 */
69 struct mlist
70 {
71 struct mlist *next;
72 struct mlist *prev;
73 struct mlist *curr_mp;
74 char *string;
75 int modified;
76 };
77
78 /*
79 * These are the various command histories that exist.
80 */
81 struct mlist mlist_search =
82 { &mlist_search, &mlist_search, &mlist_search, NULL, 0 };
83 public void *ml_search = (void *) &mlist_search;
84
85 struct mlist mlist_examine =
86 { &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 };
87 public void *ml_examine = (void *) &mlist_examine;
88
89 #if SHELL_ESCAPE || PIPEC
90 struct mlist mlist_shell =
91 { &mlist_shell, &mlist_shell, &mlist_shell, NULL, 0 };
92 public void *ml_shell = (void *) &mlist_shell;
93 #endif
94
95 #else /* CMD_HISTORY */
96
97 /* If CMD_HISTORY is off, these are just flags. */
98 public void *ml_search = (void *)1;
99 public void *ml_examine = (void *)2;
100 #if SHELL_ESCAPE || PIPEC
101 public void *ml_shell = (void *)3;
102 #endif
103
104 #endif /* CMD_HISTORY */
105
106 /*
107 * History for the current command.
108 */
109 static struct mlist *curr_mlist = NULL;
110 static int curr_cmdflags;
111
112 static char cmd_mbc_buf[MAX_UTF_CHAR_LEN];
113 static int cmd_mbc_buf_len;
114 static int cmd_mbc_buf_index;
115
116
117 /*
118 * Reset command buffer (to empty).
119 */
cmd_reset(void)120 public void cmd_reset(void)
121 {
122 cp = cmdbuf;
123 *cp = '\0';
124 cmd_col = 0;
125 cmd_offset = 0;
126 literal = 0;
127 cmd_mbc_buf_len = 0;
128 updown_match = -1;
129 }
130
131 /*
132 * Clear command line.
133 */
clear_cmd(void)134 public void clear_cmd(void)
135 {
136 cmd_col = prompt_col = 0;
137 cmd_mbc_buf_len = 0;
138 updown_match = -1;
139 }
140
141 /*
142 * Display a string, usually as a prompt for input into the command buffer.
143 */
cmd_putstr(constant char * s)144 public void cmd_putstr(constant char *s)
145 {
146 LWCHAR prev_ch = 0;
147 LWCHAR ch;
148 constant char *endline = s + strlen(s);
149 while (*s != '\0')
150 {
151 char *ns = (char *) s;
152 int width;
153 ch = step_char(&ns, +1, endline);
154 while (s < ns)
155 putchr(*s++);
156 if (!utf_mode)
157 width = 1;
158 else if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
159 width = 0;
160 else
161 width = is_wide_char(ch) ? 2 : 1;
162 cmd_col += width;
163 prompt_col += width;
164 prev_ch = ch;
165 }
166 }
167
168 /*
169 * How many characters are in the command buffer?
170 */
len_cmdbuf(void)171 public int len_cmdbuf(void)
172 {
173 char *s = cmdbuf;
174 char *endline = s + strlen(s);
175 int len = 0;
176
177 while (*s != '\0')
178 {
179 step_char(&s, +1, endline);
180 len++;
181 }
182 return (len);
183 }
184
185 /*
186 * Common part of cmd_step_right() and cmd_step_left().
187 * {{ Returning pwidth and bswidth separately is a historical artifact
188 * since they're always the same. Maybe clean this up someday. }}
189 */
cmd_step_common(char * p,LWCHAR ch,int len,int * pwidth,int * bswidth)190 static char * cmd_step_common(char *p, LWCHAR ch, int len, int *pwidth, int *bswidth)
191 {
192 char *pr;
193 int width;
194
195 if (len == 1)
196 {
197 pr = prchar((int) ch);
198 width = (int) strlen(pr);
199 } else
200 {
201 pr = prutfchar(ch);
202 if (is_composing_char(ch))
203 width = 0;
204 else if (is_ubin_char(ch))
205 width = (int) strlen(pr);
206 else
207 {
208 LWCHAR prev_ch = step_char(&p, -1, cmdbuf);
209 if (is_combining_char(prev_ch, ch))
210 width = 0;
211 else
212 width = is_wide_char(ch) ? 2 : 1;
213 }
214 }
215 if (pwidth != NULL)
216 *pwidth = width;
217 if (bswidth != NULL)
218 *bswidth = width;
219 return (pr);
220 }
221
222 /*
223 * Step a pointer one character right in the command buffer.
224 */
cmd_step_right(char ** pp,int * pwidth,int * bswidth)225 static char * cmd_step_right(char **pp, int *pwidth, int *bswidth)
226 {
227 char *p = *pp;
228 LWCHAR ch = step_char(pp, +1, p + strlen(p));
229
230 return cmd_step_common(p, ch, *pp - p, pwidth, bswidth);
231 }
232
233 /*
234 * Step a pointer one character left in the command buffer.
235 */
cmd_step_left(char ** pp,int * pwidth,int * bswidth)236 static char * cmd_step_left(char **pp, int *pwidth, int *bswidth)
237 {
238 char *p = *pp;
239 LWCHAR ch = step_char(pp, -1, cmdbuf);
240
241 return cmd_step_common(*pp, ch, p - *pp, pwidth, bswidth);
242 }
243
244 /*
245 * Put the cursor at "home" (just after the prompt),
246 * and set cp to the corresponding char in cmdbuf.
247 */
cmd_home(void)248 static void cmd_home(void)
249 {
250 while (cmd_col > prompt_col)
251 {
252 int width, bswidth;
253
254 cmd_step_left(&cp, &width, &bswidth);
255 while (bswidth-- > 0)
256 putbs();
257 cmd_col -= width;
258 }
259
260 cp = &cmdbuf[cmd_offset];
261 }
262
263 /*
264 * Repaint the line from cp onwards.
265 * Then position the cursor just after the char old_cp (a pointer into cmdbuf).
266 */
cmd_repaint(constant char * old_cp)267 public void cmd_repaint(constant char *old_cp)
268 {
269 /*
270 * Repaint the line from the current position.
271 */
272 if (old_cp == NULL)
273 {
274 old_cp = cp;
275 cmd_home();
276 }
277 clear_eol();
278 while (*cp != '\0')
279 {
280 char *np = cp;
281 int width;
282 char *pr = cmd_step_right(&np, &width, NULL);
283 if (cmd_col + width >= sc_width)
284 break;
285 cp = np;
286 putstr(pr);
287 cmd_col += width;
288 }
289 while (*cp != '\0')
290 {
291 char *np = cp;
292 int width;
293 char *pr = cmd_step_right(&np, &width, NULL);
294 if (width > 0)
295 break;
296 cp = np;
297 putstr(pr);
298 }
299
300 /*
301 * Back up the cursor to the correct position.
302 */
303 while (cp > old_cp)
304 cmd_left();
305 }
306
307 /*
308 * Shift the cmdbuf display left a half-screen.
309 */
cmd_lshift(void)310 static void cmd_lshift(void)
311 {
312 char *s;
313 char *save_cp;
314 int cols;
315
316 /*
317 * Start at the first displayed char, count how far to the
318 * right we'd have to move to reach the center of the screen.
319 */
320 s = cmdbuf + cmd_offset;
321 cols = 0;
322 while (cols < (sc_width - prompt_col) / 2 && *s != '\0')
323 {
324 int width;
325 cmd_step_right(&s, &width, NULL);
326 cols += width;
327 }
328 while (*s != '\0')
329 {
330 int width;
331 char *ns = s;
332 cmd_step_right(&ns, &width, NULL);
333 if (width > 0)
334 break;
335 s = ns;
336 }
337
338 cmd_offset = (int) (s - cmdbuf);
339 save_cp = cp;
340 cmd_home();
341 cmd_repaint(save_cp);
342 }
343
344 /*
345 * Shift the cmdbuf display right a half-screen.
346 */
cmd_rshift(void)347 static void cmd_rshift(void)
348 {
349 char *s;
350 char *save_cp;
351 int cols;
352
353 /*
354 * Start at the first displayed char, count how far to the
355 * left we'd have to move to traverse a half-screen width
356 * of displayed characters.
357 */
358 s = cmdbuf + cmd_offset;
359 cols = 0;
360 while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf)
361 {
362 int width;
363 cmd_step_left(&s, &width, NULL);
364 cols += width;
365 }
366
367 cmd_offset = (int) (s - cmdbuf);
368 save_cp = cp;
369 cmd_home();
370 cmd_repaint(save_cp);
371 }
372
373 /*
374 * Move cursor right one character.
375 */
cmd_right(void)376 static int cmd_right(void)
377 {
378 char *pr;
379 char *ncp;
380 int width;
381
382 if (*cp == '\0')
383 {
384 /* Already at the end of the line. */
385 return (CC_OK);
386 }
387 ncp = cp;
388 pr = cmd_step_right(&ncp, &width, NULL);
389 if (cmd_col + width >= sc_width)
390 cmd_lshift();
391 else if (cmd_col + width == sc_width - 1 && cp[1] != '\0')
392 cmd_lshift();
393 cp = ncp;
394 cmd_col += width;
395 putstr(pr);
396 while (*cp != '\0')
397 {
398 pr = cmd_step_right(&ncp, &width, NULL);
399 if (width > 0)
400 break;
401 putstr(pr);
402 cp = ncp;
403 }
404 return (CC_OK);
405 }
406
407 /*
408 * Move cursor left one character.
409 */
cmd_left(void)410 static int cmd_left(void)
411 {
412 char *ncp;
413 int width = 0;
414 int bswidth = 0;
415
416 if (cp <= cmdbuf)
417 {
418 /* Already at the beginning of the line */
419 return (CC_OK);
420 }
421 ncp = cp;
422 while (ncp > cmdbuf)
423 {
424 cmd_step_left(&ncp, &width, &bswidth);
425 if (width > 0)
426 break;
427 }
428 if (cmd_col < prompt_col + width)
429 cmd_rshift();
430 cp = ncp;
431 cmd_col -= width;
432 while (bswidth-- > 0)
433 putbs();
434 return (CC_OK);
435 }
436
437 /*
438 * Insert a char into the command buffer, at the current position.
439 */
cmd_ichar(char * cs,int clen)440 static int cmd_ichar(char *cs, int clen)
441 {
442 char *s;
443
444 if (strlen(cmdbuf) + clen >= sizeof(cmdbuf)-1)
445 {
446 /* No room in the command buffer for another char. */
447 bell();
448 return (CC_ERROR);
449 }
450
451 /*
452 * Make room for the new character (shift the tail of the buffer right).
453 */
454 for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--)
455 s[clen] = s[0];
456 /*
457 * Insert the character into the buffer.
458 */
459 for (s = cp; s < cp + clen; s++)
460 *s = *cs++;
461 /*
462 * Reprint the tail of the line from the inserted char.
463 */
464 updown_match = -1;
465 cmd_repaint(cp);
466 cmd_right();
467 return (CC_OK);
468 }
469
470 /*
471 * Backspace in the command buffer.
472 * Delete the char to the left of the cursor.
473 */
cmd_erase(void)474 static int cmd_erase(void)
475 {
476 char *s;
477 int clen;
478
479 if (cp == cmdbuf)
480 {
481 /*
482 * Backspace past beginning of the buffer:
483 * this usually means abort the command.
484 */
485 return (CC_QUIT);
486 }
487 /*
488 * Move cursor left (to the char being erased).
489 */
490 s = cp;
491 cmd_left();
492 clen = (int) (s - cp);
493
494 /*
495 * Remove the char from the buffer (shift the buffer left).
496 */
497 for (s = cp; ; s++)
498 {
499 s[0] = s[clen];
500 if (s[0] == '\0')
501 break;
502 }
503
504 /*
505 * Repaint the buffer after the erased char.
506 */
507 updown_match = -1;
508 cmd_repaint(cp);
509
510 /*
511 * We say that erasing the entire command string causes us
512 * to abort the current command, if CF_QUIT_ON_ERASE is set.
513 */
514 if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0')
515 return (CC_QUIT);
516 return (CC_OK);
517 }
518
519 /*
520 * Delete the char under the cursor.
521 */
cmd_delete(void)522 static int cmd_delete(void)
523 {
524 if (*cp == '\0')
525 {
526 /* At end of string; there is no char under the cursor. */
527 return (CC_OK);
528 }
529 /*
530 * Move right, then use cmd_erase.
531 */
532 cmd_right();
533 cmd_erase();
534 return (CC_OK);
535 }
536
537 /*
538 * Delete the "word" to the left of the cursor.
539 */
cmd_werase(void)540 static int cmd_werase(void)
541 {
542 if (cp > cmdbuf && cp[-1] == ' ')
543 {
544 /*
545 * If the char left of cursor is a space,
546 * erase all the spaces left of cursor (to the first non-space).
547 */
548 while (cp > cmdbuf && cp[-1] == ' ')
549 (void) cmd_erase();
550 } else
551 {
552 /*
553 * If the char left of cursor is not a space,
554 * erase all the nonspaces left of cursor (the whole "word").
555 */
556 while (cp > cmdbuf && cp[-1] != ' ')
557 (void) cmd_erase();
558 }
559 return (CC_OK);
560 }
561
562 /*
563 * Delete the "word" under the cursor.
564 */
cmd_wdelete(void)565 static int cmd_wdelete(void)
566 {
567 if (*cp == ' ')
568 {
569 /*
570 * If the char under the cursor is a space,
571 * delete it and all the spaces right of cursor.
572 */
573 while (*cp == ' ')
574 (void) cmd_delete();
575 } else
576 {
577 /*
578 * If the char under the cursor is not a space,
579 * delete it and all nonspaces right of cursor (the whole word).
580 */
581 while (*cp != ' ' && *cp != '\0')
582 (void) cmd_delete();
583 }
584 return (CC_OK);
585 }
586
587 /*
588 * Delete all chars in the command buffer.
589 */
cmd_kill(void)590 static int cmd_kill(void)
591 {
592 if (cmdbuf[0] == '\0')
593 {
594 /* Buffer is already empty; abort the current command. */
595 return (CC_QUIT);
596 }
597 cmd_offset = 0;
598 cmd_home();
599 *cp = '\0';
600 updown_match = -1;
601 cmd_repaint(cp);
602
603 /*
604 * We say that erasing the entire command string causes us
605 * to abort the current command, if CF_QUIT_ON_ERASE is set.
606 */
607 if (curr_cmdflags & CF_QUIT_ON_ERASE)
608 return (CC_QUIT);
609 return (CC_OK);
610 }
611
612 /*
613 * Select an mlist structure to be the current command history.
614 */
set_mlist(void * mlist,int cmdflags)615 public void set_mlist(void *mlist, int cmdflags)
616 {
617 #if CMD_HISTORY
618 curr_mlist = (struct mlist *) mlist;
619 curr_cmdflags = cmdflags;
620
621 /* Make sure the next up-arrow moves to the last string in the mlist. */
622 if (curr_mlist != NULL)
623 curr_mlist->curr_mp = curr_mlist;
624 #endif
625 }
626
627 #if CMD_HISTORY
628 /*
629 * Move up or down in the currently selected command history list.
630 * Only consider entries whose first updown_match chars are equal to
631 * cmdbuf's corresponding chars.
632 */
cmd_updown(int action)633 static int cmd_updown(int action)
634 {
635 constant char *s;
636 struct mlist *ml;
637
638 if (curr_mlist == NULL)
639 {
640 /*
641 * The current command has no history list.
642 */
643 bell();
644 return (CC_OK);
645 }
646
647 if (updown_match < 0)
648 {
649 updown_match = (int) (cp - cmdbuf);
650 }
651
652 /*
653 * Find the next history entry which matches.
654 */
655 for (ml = curr_mlist->curr_mp;;)
656 {
657 ml = (action == EC_UP) ? ml->prev : ml->next;
658 if (ml == curr_mlist)
659 {
660 /*
661 * We reached the end (or beginning) of the list.
662 */
663 break;
664 }
665 if (strncmp(cmdbuf, ml->string, updown_match) == 0)
666 {
667 /*
668 * This entry matches; stop here.
669 * Copy the entry into cmdbuf and echo it on the screen.
670 */
671 curr_mlist->curr_mp = ml;
672 s = ml->string;
673 if (s == NULL)
674 s = "";
675 cmd_offset = 0;
676 cmd_home();
677 clear_eol();
678 strcpy(cmdbuf, s);
679 for (cp = cmdbuf; *cp != '\0'; )
680 cmd_right();
681 return (CC_OK);
682 }
683 }
684 /*
685 * We didn't find a history entry that matches.
686 */
687 bell();
688 return (CC_OK);
689 }
690 #endif
691
692 /*
693 *
694 */
ml_link(struct mlist * mlist,struct mlist * ml)695 static void ml_link(struct mlist *mlist, struct mlist *ml)
696 {
697 ml->next = mlist;
698 ml->prev = mlist->prev;
699 mlist->prev->next = ml;
700 mlist->prev = ml;
701 }
702
703 /*
704 *
705 */
ml_unlink(struct mlist * ml)706 static void ml_unlink(struct mlist *ml)
707 {
708 ml->prev->next = ml->next;
709 ml->next->prev = ml->prev;
710 }
711
712 /*
713 * Add a string to an mlist.
714 */
cmd_addhist(struct mlist * mlist,constant char * cmd,int modified)715 public void cmd_addhist(struct mlist *mlist, constant char *cmd, int modified)
716 {
717 #if CMD_HISTORY
718 struct mlist *ml;
719
720 /*
721 * Don't save a trivial command.
722 */
723 if (strlen(cmd) == 0)
724 return;
725
726 if (no_hist_dups)
727 {
728 struct mlist *next = NULL;
729 for (ml = mlist->next; ml->string != NULL; ml = next)
730 {
731 next = ml->next;
732 if (strcmp(ml->string, cmd) == 0)
733 {
734 ml_unlink(ml);
735 free(ml->string);
736 free(ml);
737 }
738 }
739 }
740
741 /*
742 * Save the command unless it's a duplicate of the
743 * last command in the history.
744 */
745 ml = mlist->prev;
746 if (ml == mlist || strcmp(ml->string, cmd) != 0)
747 {
748 /*
749 * Did not find command in history.
750 * Save the command and put it at the end of the history list.
751 */
752 ml = (struct mlist *) ecalloc(1, sizeof(struct mlist));
753 ml->string = save(cmd);
754 ml->modified = modified;
755 ml_link(mlist, ml);
756 }
757 /*
758 * Point to the cmd just after the just-accepted command.
759 * Thus, an UPARROW will always retrieve the previous command.
760 */
761 mlist->curr_mp = ml->next;
762 #endif
763 }
764
765 /*
766 * Accept the command in the command buffer.
767 * Add it to the currently selected history list.
768 */
cmd_accept(void)769 public void cmd_accept(void)
770 {
771 #if CMD_HISTORY
772 /*
773 * Nothing to do if there is no currently selected history list.
774 */
775 if (curr_mlist == NULL || curr_mlist == ml_examine)
776 return;
777 cmd_addhist(curr_mlist, cmdbuf, 1);
778 curr_mlist->modified = 1;
779 #endif
780 }
781
782 /*
783 * Try to perform a line-edit function on the command buffer,
784 * using a specified char as a line-editing command.
785 * Returns:
786 * CC_PASS The char does not invoke a line edit function.
787 * CC_OK Line edit function done.
788 * CC_QUIT The char requests the current command to be aborted.
789 */
cmd_edit(int c)790 static int cmd_edit(int c)
791 {
792 int action;
793 int flags;
794
795 #if TAB_COMPLETE_FILENAME
796 #define not_in_completion() in_completion = 0
797 #else
798 #define not_in_completion(void)
799 #endif
800
801 /*
802 * See if the char is indeed a line-editing command.
803 */
804 flags = 0;
805 #if CMD_HISTORY
806 if (curr_mlist == NULL)
807 /*
808 * No current history; don't accept history manipulation cmds.
809 */
810 flags |= ECF_NOHISTORY;
811 #endif
812 #if TAB_COMPLETE_FILENAME
813 if (curr_mlist == ml_search || curr_mlist == NULL)
814 /*
815 * Don't accept file-completion cmds in contexts
816 * such as search pattern, digits, long option name, etc.
817 */
818 flags |= ECF_NOCOMPLETE;
819 #endif
820
821 action = editchar(c, flags);
822
823 switch (action)
824 {
825 case A_NOACTION:
826 return (CC_OK);
827 case EC_RIGHT:
828 not_in_completion();
829 return (cmd_right());
830 case EC_LEFT:
831 not_in_completion();
832 return (cmd_left());
833 case EC_W_RIGHT:
834 not_in_completion();
835 while (*cp != '\0' && *cp != ' ')
836 cmd_right();
837 while (*cp == ' ')
838 cmd_right();
839 return (CC_OK);
840 case EC_W_LEFT:
841 not_in_completion();
842 while (cp > cmdbuf && cp[-1] == ' ')
843 cmd_left();
844 while (cp > cmdbuf && cp[-1] != ' ')
845 cmd_left();
846 return (CC_OK);
847 case EC_HOME:
848 not_in_completion();
849 cmd_offset = 0;
850 cmd_home();
851 cmd_repaint(cp);
852 return (CC_OK);
853 case EC_END:
854 not_in_completion();
855 while (*cp != '\0')
856 cmd_right();
857 return (CC_OK);
858 case EC_INSERT:
859 not_in_completion();
860 return (CC_OK);
861 case EC_BACKSPACE:
862 not_in_completion();
863 return (cmd_erase());
864 case EC_LINEKILL:
865 not_in_completion();
866 return (cmd_kill());
867 case EC_ABORT:
868 not_in_completion();
869 (void) cmd_kill();
870 return (CC_QUIT);
871 case EC_W_BACKSPACE:
872 not_in_completion();
873 return (cmd_werase());
874 case EC_DELETE:
875 not_in_completion();
876 return (cmd_delete());
877 case EC_W_DELETE:
878 not_in_completion();
879 return (cmd_wdelete());
880 case EC_LITERAL:
881 literal = 1;
882 return (CC_OK);
883 #if CMD_HISTORY
884 case EC_UP:
885 case EC_DOWN:
886 not_in_completion();
887 return (cmd_updown(action));
888 #endif
889 #if TAB_COMPLETE_FILENAME
890 case EC_F_COMPLETE:
891 case EC_B_COMPLETE:
892 case EC_EXPAND:
893 return (cmd_complete(action));
894 #endif
895 default:
896 not_in_completion();
897 return (CC_PASS);
898 }
899 }
900
901 #if TAB_COMPLETE_FILENAME
902 /*
903 * Insert a string into the command buffer, at the current position.
904 */
cmd_istr(char * str)905 static int cmd_istr(char *str)
906 {
907 char *s;
908 int action;
909 char *endline = str + strlen(str);
910
911 for (s = str; *s != '\0'; )
912 {
913 char *os = s;
914 step_char(&s, +1, endline);
915 action = cmd_ichar(os, s - os);
916 if (action != CC_OK)
917 return (action);
918 }
919 return (CC_OK);
920 }
921
922 /*
923 * Find the beginning and end of the "current" word.
924 * This is the word which the cursor (cp) is inside or at the end of.
925 * Return pointer to the beginning of the word and put the
926 * cursor at the end of the word.
927 */
delimit_word(void)928 static char * delimit_word(void)
929 {
930 char *word;
931 #if SPACES_IN_FILENAMES
932 char *p;
933 int delim_quoted = 0;
934 int meta_quoted = 0;
935 constant char *esc = get_meta_escape();
936 int esclen = (int) strlen(esc);
937 #endif
938
939 /*
940 * Move cursor to end of word.
941 */
942 if (*cp != ' ' && *cp != '\0')
943 {
944 /*
945 * Cursor is on a nonspace.
946 * Move cursor right to the next space.
947 */
948 while (*cp != ' ' && *cp != '\0')
949 cmd_right();
950 } else if (cp > cmdbuf && cp[-1] != ' ')
951 {
952 /*
953 * Cursor is on a space, and char to the left is a nonspace.
954 * We're already at the end of the word.
955 */
956 ;
957 #if 0
958 } else
959 {
960 /*
961 * Cursor is on a space and char to the left is a space.
962 * Huh? There's no word here.
963 */
964 return (NULL);
965 #endif
966 }
967 /*
968 * Find the beginning of the word which the cursor is in.
969 */
970 if (cp == cmdbuf)
971 return (NULL);
972 #if SPACES_IN_FILENAMES
973 /*
974 * If we have an unbalanced quote (that is, an open quote
975 * without a corresponding close quote), we return everything
976 * from the open quote, including spaces.
977 */
978 for (word = cmdbuf; word < cp; word++)
979 if (*word != ' ')
980 break;
981 if (word >= cp)
982 return (cp);
983 for (p = cmdbuf; p < cp; p++)
984 {
985 if (meta_quoted)
986 {
987 meta_quoted = 0;
988 } else if (esclen > 0 && p + esclen < cp &&
989 strncmp(p, esc, esclen) == 0)
990 {
991 meta_quoted = 1;
992 p += esclen - 1;
993 } else if (delim_quoted)
994 {
995 if (*p == closequote)
996 delim_quoted = 0;
997 } else /* (!delim_quoted) */
998 {
999 if (*p == openquote)
1000 delim_quoted = 1;
1001 else if (*p == ' ')
1002 word = p+1;
1003 }
1004 }
1005 #endif
1006 return (word);
1007 }
1008
1009 /*
1010 * Set things up to enter completion mode.
1011 * Expand the word under the cursor into a list of filenames
1012 * which start with that word, and set tk_text to that list.
1013 */
init_compl(void)1014 static void init_compl(void)
1015 {
1016 char *word;
1017 char c;
1018
1019 /*
1020 * Get rid of any previous tk_text.
1021 */
1022 if (tk_text != NULL)
1023 {
1024 free(tk_text);
1025 tk_text = NULL;
1026 }
1027 /*
1028 * Find the original (uncompleted) word in the command buffer.
1029 */
1030 word = delimit_word();
1031 if (word == NULL)
1032 return;
1033 /*
1034 * Set the insertion point to the point in the command buffer
1035 * where the original (uncompleted) word now sits.
1036 */
1037 tk_ipoint = word;
1038 /*
1039 * Save the original (uncompleted) word
1040 */
1041 if (tk_original != NULL)
1042 free(tk_original);
1043 tk_original = (char *) ecalloc(cp-word+1, sizeof(char));
1044 strncpy(tk_original, word, cp-word);
1045 /*
1046 * Get the expanded filename.
1047 * This may result in a single filename, or
1048 * a blank-separated list of filenames.
1049 */
1050 c = *cp;
1051 *cp = '\0';
1052 if (*word != openquote)
1053 {
1054 tk_text = fcomplete(word);
1055 } else
1056 {
1057 #if MSDOS_COMPILER
1058 char *qword = NULL;
1059 #else
1060 char *qword = shell_quote(word+1);
1061 #endif
1062 if (qword == NULL)
1063 tk_text = fcomplete(word+1);
1064 else
1065 {
1066 tk_text = fcomplete(qword);
1067 free(qword);
1068 }
1069 }
1070 *cp = c;
1071 }
1072
1073 /*
1074 * Return the next word in the current completion list.
1075 */
next_compl(int action,char * prev)1076 static char * next_compl(int action, char *prev)
1077 {
1078 switch (action)
1079 {
1080 case EC_F_COMPLETE:
1081 return (forw_textlist(&tk_tlist, prev));
1082 case EC_B_COMPLETE:
1083 return (back_textlist(&tk_tlist, prev));
1084 }
1085 /* Cannot happen */
1086 return ("?");
1087 }
1088
1089 /*
1090 * Complete the filename before (or under) the cursor.
1091 * cmd_complete may be called multiple times. The global in_completion
1092 * remembers whether this call is the first time (create the list),
1093 * or a subsequent time (step thru the list).
1094 */
cmd_complete(int action)1095 static int cmd_complete(int action)
1096 {
1097 char *s;
1098
1099 if (!in_completion || action == EC_EXPAND)
1100 {
1101 /*
1102 * Expand the word under the cursor and
1103 * use the first word in the expansion
1104 * (or the entire expansion if we're doing EC_EXPAND).
1105 */
1106 init_compl();
1107 if (tk_text == NULL)
1108 {
1109 bell();
1110 return (CC_OK);
1111 }
1112 if (action == EC_EXPAND)
1113 {
1114 /*
1115 * Use the whole list.
1116 */
1117 tk_trial = tk_text;
1118 } else
1119 {
1120 /*
1121 * Use the first filename in the list.
1122 */
1123 in_completion = 1;
1124 init_textlist(&tk_tlist, tk_text);
1125 tk_trial = next_compl(action, (char*)NULL);
1126 }
1127 } else
1128 {
1129 /*
1130 * We already have a completion list.
1131 * Use the next/previous filename from the list.
1132 */
1133 tk_trial = next_compl(action, tk_trial);
1134 }
1135
1136 /*
1137 * Remove the original word, or the previous trial completion.
1138 */
1139 while (cp > tk_ipoint)
1140 (void) cmd_erase();
1141
1142 if (tk_trial == NULL)
1143 {
1144 /*
1145 * There are no more trial completions.
1146 * Insert the original (uncompleted) filename.
1147 */
1148 in_completion = 0;
1149 if (cmd_istr(tk_original) != CC_OK)
1150 goto fail;
1151 } else
1152 {
1153 /*
1154 * Insert trial completion.
1155 */
1156 if (cmd_istr(tk_trial) != CC_OK)
1157 goto fail;
1158 /*
1159 * If it is a directory, append a slash.
1160 */
1161 if (is_dir(tk_trial))
1162 {
1163 if (cp > cmdbuf && cp[-1] == closequote)
1164 (void) cmd_erase();
1165 s = lgetenv("LESSSEPARATOR");
1166 if (s == NULL)
1167 s = PATHNAME_SEP;
1168 if (cmd_istr(s) != CC_OK)
1169 goto fail;
1170 }
1171 }
1172
1173 return (CC_OK);
1174
1175 fail:
1176 in_completion = 0;
1177 bell();
1178 return (CC_OK);
1179 }
1180
1181 #endif /* TAB_COMPLETE_FILENAME */
1182
1183 /*
1184 * Process a single character of a multi-character command, such as
1185 * a number, or the pattern of a search command.
1186 * Returns:
1187 * CC_OK The char was accepted.
1188 * CC_QUIT The char requests the command to be aborted.
1189 * CC_ERROR The char could not be accepted due to an error.
1190 */
cmd_char(int c)1191 public int cmd_char(int c)
1192 {
1193 int action;
1194 int len;
1195
1196 if (!utf_mode)
1197 {
1198 cmd_mbc_buf[0] = c;
1199 len = 1;
1200 } else
1201 {
1202 /* Perform strict validation in all possible cases. */
1203 if (cmd_mbc_buf_len == 0)
1204 {
1205 retry:
1206 cmd_mbc_buf_index = 1;
1207 *cmd_mbc_buf = c;
1208 if (IS_ASCII_OCTET(c))
1209 cmd_mbc_buf_len = 1;
1210 #if MSDOS_COMPILER || OS2
1211 else if (c == (unsigned char) '\340' && IS_ASCII_OCTET(peekcc()))
1212 {
1213 /* Assume a special key. */
1214 cmd_mbc_buf_len = 1;
1215 }
1216 #endif
1217 else if (IS_UTF8_LEAD(c))
1218 {
1219 cmd_mbc_buf_len = utf_len(c);
1220 return (CC_OK);
1221 } else
1222 {
1223 /* UTF8_INVALID or stray UTF8_TRAIL */
1224 bell();
1225 return (CC_ERROR);
1226 }
1227 } else if (IS_UTF8_TRAIL(c))
1228 {
1229 cmd_mbc_buf[cmd_mbc_buf_index++] = c;
1230 if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1231 return (CC_OK);
1232 if (!is_utf8_well_formed(cmd_mbc_buf, cmd_mbc_buf_index))
1233 {
1234 /* complete, but not well formed (non-shortest form), sequence */
1235 cmd_mbc_buf_len = 0;
1236 bell();
1237 return (CC_ERROR);
1238 }
1239 } else
1240 {
1241 /* Flush incomplete (truncated) sequence. */
1242 cmd_mbc_buf_len = 0;
1243 bell();
1244 /* Handle new char. */
1245 goto retry;
1246 }
1247
1248 len = cmd_mbc_buf_len;
1249 cmd_mbc_buf_len = 0;
1250 }
1251
1252 if (literal)
1253 {
1254 /*
1255 * Insert the char, even if it is a line-editing char.
1256 */
1257 literal = 0;
1258 return (cmd_ichar(cmd_mbc_buf, len));
1259 }
1260
1261 /*
1262 * See if it is a line-editing character.
1263 */
1264 if (in_mca() && len == 1)
1265 {
1266 action = cmd_edit(c);
1267 switch (action)
1268 {
1269 case CC_OK:
1270 case CC_QUIT:
1271 return (action);
1272 case CC_PASS:
1273 break;
1274 }
1275 }
1276
1277 /*
1278 * Insert the char into the command buffer.
1279 */
1280 return (cmd_ichar(cmd_mbc_buf, len));
1281 }
1282
1283 /*
1284 * Return the number currently in the command buffer.
1285 */
cmd_int(long * frac)1286 public LINENUM cmd_int(long *frac)
1287 {
1288 char *p;
1289 LINENUM n = 0;
1290 int err;
1291
1292 for (p = cmdbuf; *p >= '0' && *p <= '9'; p++)
1293 {
1294 if (ckd_mul(&n, n, 10) || ckd_add(&n, n, *p - '0'))
1295 {
1296 error("Integer is too big", NULL_PARG);
1297 return (0);
1298 }
1299 }
1300 *frac = 0;
1301 if (*p++ == '.')
1302 {
1303 *frac = getfraction(&p, NULL, &err);
1304 /* {{ do something if err is set? }} */
1305 }
1306 return (n);
1307 }
1308
1309 /*
1310 * Return a pointer to the command buffer.
1311 */
get_cmdbuf(void)1312 public char * get_cmdbuf(void)
1313 {
1314 if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1315 /* Don't return buffer containing an incomplete multibyte char. */
1316 return (NULL);
1317 return (cmdbuf);
1318 }
1319
1320 #if CMD_HISTORY
1321 /*
1322 * Return the last (most recent) string in the current command history.
1323 */
cmd_lastpattern(void)1324 public char * cmd_lastpattern(void)
1325 {
1326 if (curr_mlist == NULL)
1327 return (NULL);
1328 return (curr_mlist->curr_mp->prev->string);
1329 }
1330 #endif
1331
1332 #if CMD_HISTORY
1333 /*
1334 */
mlist_size(struct mlist * ml)1335 static int mlist_size(struct mlist *ml)
1336 {
1337 int size = 0;
1338 for (ml = ml->next; ml->string != NULL; ml = ml->next)
1339 ++size;
1340 return size;
1341 }
1342
1343 /*
1344 * Get the name of the history file.
1345 */
histfile_find(int must_exist)1346 static char * histfile_find(int must_exist)
1347 {
1348 char *home = lgetenv("HOME");
1349 char *name = NULL;
1350
1351 /* Try in $XDG_STATE_HOME, then in $HOME/.local/state, then in $XDG_DATA_HOME, then in $HOME. */
1352 #if OS2
1353 if (isnullenv(home))
1354 home = lgetenv("INIT");
1355 #endif
1356 name = dirfile(lgetenv("XDG_STATE_HOME"), &LESSHISTFILE[1], must_exist);
1357 if (name == NULL)
1358 {
1359 char *dir = dirfile(home, ".local/state", 1);
1360 if (dir != NULL)
1361 {
1362 name = dirfile(dir, &LESSHISTFILE[1], must_exist);
1363 free(dir);
1364 }
1365 }
1366 if (name == NULL)
1367 name = dirfile(lgetenv("XDG_DATA_HOME"), &LESSHISTFILE[1], must_exist);
1368 if (name == NULL)
1369 name = dirfile(home, LESSHISTFILE, must_exist);
1370 return (name);
1371 }
1372
histfile_name(int must_exist)1373 static char * histfile_name(int must_exist)
1374 {
1375 char *name;
1376
1377 /* See if filename is explicitly specified by $LESSHISTFILE. */
1378 name = lgetenv("LESSHISTFILE");
1379 if (!isnullenv(name))
1380 {
1381 if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1382 /* $LESSHISTFILE == "-" means don't use a history file. */
1383 return (NULL);
1384 return (save(name));
1385 }
1386
1387 /* See if history file is disabled in the build. */
1388 if (strcmp(LESSHISTFILE, "") == 0 || strcmp(LESSHISTFILE, "-") == 0)
1389 return (NULL);
1390
1391 name = NULL;
1392 if (!must_exist)
1393 {
1394 /* If we're writing the file and the file already exists, use it. */
1395 name = histfile_find(1);
1396 }
1397 if (name == NULL)
1398 name = histfile_find(must_exist);
1399 return (name);
1400 }
1401
1402 /*
1403 * Read a .lesshst file and call a callback for each line in the file.
1404 */
read_cmdhist2(void (* action)(void *,struct mlist *,char *),void * uparam,int skip_search,int skip_shell)1405 static void read_cmdhist2(void (*action)(void*,struct mlist*,char*), void *uparam, int skip_search, int skip_shell)
1406 {
1407 struct mlist *ml = NULL;
1408 char line[CMDBUF_SIZE];
1409 char *filename;
1410 FILE *f;
1411 char *p;
1412 int *skip = NULL;
1413
1414 filename = histfile_name(1);
1415 if (filename == NULL)
1416 return;
1417 f = fopen(filename, "r");
1418 free(filename);
1419 if (f == NULL)
1420 return;
1421 if (fgets(line, sizeof(line), f) == NULL ||
1422 strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0)
1423 {
1424 fclose(f);
1425 return;
1426 }
1427 while (fgets(line, sizeof(line), f) != NULL)
1428 {
1429 for (p = line; *p != '\0'; p++)
1430 {
1431 if (*p == '\n' || *p == '\r')
1432 {
1433 *p = '\0';
1434 break;
1435 }
1436 }
1437 if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1438 {
1439 ml = &mlist_search;
1440 skip = &skip_search;
1441 } else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0)
1442 {
1443 #if SHELL_ESCAPE || PIPEC
1444 ml = &mlist_shell;
1445 skip = &skip_shell;
1446 #else
1447 ml = NULL;
1448 skip = NULL;
1449 #endif
1450 } else if (strcmp(line, HISTFILE_MARK_SECTION) == 0)
1451 {
1452 ml = NULL;
1453 } else if (*line == '"')
1454 {
1455 if (ml != NULL)
1456 {
1457 if (skip != NULL && *skip > 0)
1458 --(*skip);
1459 else
1460 (*action)(uparam, ml, line+1);
1461 }
1462 } else if (*line == 'm')
1463 {
1464 (*action)(uparam, NULL, line);
1465 }
1466 }
1467 fclose(f);
1468 }
1469
read_cmdhist(void (* action)(void *,struct mlist *,char *),void * uparam,int skip_search,int skip_shell)1470 static void read_cmdhist(void (*action)(void*,struct mlist*,char*), void *uparam, int skip_search, int skip_shell)
1471 {
1472 if (secure)
1473 return;
1474 read_cmdhist2(action, uparam, skip_search, skip_shell);
1475 (*action)(uparam, NULL, NULL); /* signal end of file */
1476 }
1477
addhist_init(void * uparam,struct mlist * ml,char * string)1478 static void addhist_init(void *uparam, struct mlist *ml, char *string)
1479 {
1480 if (ml != NULL)
1481 cmd_addhist(ml, string, 0);
1482 else if (string != NULL)
1483 restore_mark((char*)string); /* stupid const cast */
1484 }
1485 #endif /* CMD_HISTORY */
1486
1487 /*
1488 * Initialize history from a .lesshist file.
1489 */
init_cmdhist(void)1490 public void init_cmdhist(void)
1491 {
1492 #if CMD_HISTORY
1493 read_cmdhist(&addhist_init, NULL, 0, 0);
1494 #endif /* CMD_HISTORY */
1495 }
1496
1497 /*
1498 * Write the header for a section of the history file.
1499 */
1500 #if CMD_HISTORY
write_mlist_header(struct mlist * ml,FILE * f)1501 static void write_mlist_header(struct mlist *ml, FILE *f)
1502 {
1503 if (ml == &mlist_search)
1504 fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1505 #if SHELL_ESCAPE || PIPEC
1506 else if (ml == &mlist_shell)
1507 fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1508 #endif
1509 }
1510
1511 /*
1512 * Write all modified entries in an mlist to the history file.
1513 */
write_mlist(struct mlist * ml,FILE * f)1514 static void write_mlist(struct mlist *ml, FILE *f)
1515 {
1516 for (ml = ml->next; ml->string != NULL; ml = ml->next)
1517 {
1518 if (!ml->modified)
1519 continue;
1520 fprintf(f, "\"%s\n", ml->string);
1521 ml->modified = 0;
1522 }
1523 ml->modified = 0; /* entire mlist is now unmodified */
1524 }
1525
1526 /*
1527 * Make a temp name in the same directory as filename.
1528 */
make_tempname(char * filename)1529 static char * make_tempname(char *filename)
1530 {
1531 char lastch;
1532 char *tempname = ecalloc(1, strlen(filename)+1);
1533 strcpy(tempname, filename);
1534 lastch = tempname[strlen(tempname)-1];
1535 tempname[strlen(tempname)-1] = (lastch == 'Q') ? 'Z' : 'Q';
1536 return tempname;
1537 }
1538
1539 struct save_ctx
1540 {
1541 struct mlist *mlist;
1542 FILE *fout;
1543 };
1544
1545 /*
1546 * Copy entries from the saved history file to a new file.
1547 * At the end of each mlist, append any new entries
1548 * created during this session.
1549 */
copy_hist(void * uparam,struct mlist * ml,char * string)1550 static void copy_hist(void *uparam, struct mlist *ml, char *string)
1551 {
1552 struct save_ctx *ctx = (struct save_ctx *) uparam;
1553
1554 if (ml != NULL && ml != ctx->mlist) {
1555 /* We're changing mlists. */
1556 if (ctx->mlist)
1557 /* Append any new entries to the end of the current mlist. */
1558 write_mlist(ctx->mlist, ctx->fout);
1559 /* Write the header for the new mlist. */
1560 ctx->mlist = ml;
1561 write_mlist_header(ctx->mlist, ctx->fout);
1562 }
1563
1564 if (string == NULL) /* End of file */
1565 {
1566 /* Write any sections that were not in the original file. */
1567 if (mlist_search.modified)
1568 {
1569 write_mlist_header(&mlist_search, ctx->fout);
1570 write_mlist(&mlist_search, ctx->fout);
1571 }
1572 #if SHELL_ESCAPE || PIPEC
1573 if (mlist_shell.modified)
1574 {
1575 write_mlist_header(&mlist_shell, ctx->fout);
1576 write_mlist(&mlist_shell, ctx->fout);
1577 }
1578 #endif
1579 } else if (ml != NULL)
1580 {
1581 /* Copy mlist entry. */
1582 fprintf(ctx->fout, "\"%s\n", string);
1583 }
1584 /* Skip marks */
1585 }
1586 #endif /* CMD_HISTORY */
1587
1588 /*
1589 * Make a file readable only by its owner.
1590 */
make_file_private(FILE * f)1591 static void make_file_private(FILE *f)
1592 {
1593 #if HAVE_FCHMOD
1594 int do_chmod = 1;
1595 #if HAVE_STAT
1596 struct stat statbuf;
1597 int r = fstat(fileno(f), &statbuf);
1598 if (r < 0 || !S_ISREG(statbuf.st_mode))
1599 /* Don't chmod if not a regular file. */
1600 do_chmod = 0;
1601 #endif
1602 if (do_chmod)
1603 fchmod(fileno(f), 0600);
1604 #endif
1605 }
1606
1607 /*
1608 * Does the history file need to be updated?
1609 */
1610 #if CMD_HISTORY
histfile_modified(void)1611 static int histfile_modified(void)
1612 {
1613 if (mlist_search.modified)
1614 return 1;
1615 #if SHELL_ESCAPE || PIPEC
1616 if (mlist_shell.modified)
1617 return 1;
1618 #endif
1619 if (marks_modified)
1620 return 1;
1621 return 0;
1622 }
1623 #endif
1624
1625 /*
1626 * Update the .lesshst file.
1627 */
save_cmdhist(void)1628 public void save_cmdhist(void)
1629 {
1630 #if CMD_HISTORY
1631 char *histname;
1632 char *tempname;
1633 int skip_search;
1634 int skip_shell;
1635 struct save_ctx ctx;
1636 char *s;
1637 FILE *fout = NULL;
1638 int histsize = 0;
1639
1640 if (secure || !histfile_modified())
1641 return;
1642 histname = histfile_name(0);
1643 if (histname == NULL)
1644 return;
1645 tempname = make_tempname(histname);
1646 fout = fopen(tempname, "w");
1647 if (fout != NULL)
1648 {
1649 make_file_private(fout);
1650 s = lgetenv("LESSHISTSIZE");
1651 if (s != NULL)
1652 histsize = atoi(s);
1653 if (histsize <= 0)
1654 histsize = 100;
1655 skip_search = mlist_size(&mlist_search) - histsize;
1656 #if SHELL_ESCAPE || PIPEC
1657 skip_shell = mlist_size(&mlist_shell) - histsize;
1658 #endif
1659 fprintf(fout, "%s\n", HISTFILE_FIRST_LINE);
1660 ctx.fout = fout;
1661 ctx.mlist = NULL;
1662 read_cmdhist(©_hist, &ctx, skip_search, skip_shell);
1663 save_marks(fout, HISTFILE_MARK_SECTION);
1664 fclose(fout);
1665 #if MSDOS_COMPILER==WIN32C
1666 /*
1667 * Windows rename doesn't remove an existing file,
1668 * making it useless for atomic operations. Sigh.
1669 */
1670 remove(histname);
1671 #endif
1672 rename(tempname, histname);
1673 }
1674 free(tempname);
1675 free(histname);
1676 #endif /* CMD_HISTORY */
1677 }
1678