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