pax: avoid implicit function declaration warnings
[unleashed.git] / bin / less / cmdbuf.c
blob233ab24609e309d41f01c36ec219c0fe52699fee
1 /*
2 * Copyright (C) 1984-2012 Mark Nudelman
3 * Modified for use with illumos by Garrett D'Amore.
4 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
6 * You may distribute under the terms of either the GNU General Public
7 * License or the Less License, as specified in the README file.
9 * For more information, see the README file.
13 * Functions which manipulate the command buffer.
14 * Used only by command() and related functions.
17 #include <sys/stat.h>
19 #include "charset.h"
20 #include "cmd.h"
21 #include "less.h"
23 extern int sc_width;
24 extern int utf_mode;
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 */
34 static int cmd_complete(int);
36 * These variables are statics used by cmd_complete.
38 static int in_completion = 0;
39 static char *tk_text;
40 static char *tk_original;
41 static char *tk_ipoint;
42 static char *tk_trial;
43 static struct textlist tk_tlist;
45 static int cmd_left(void);
46 static int cmd_right(void);
48 char openquote = '"';
49 char closequote = '"';
51 /* History file */
52 #define HISTFILE_FIRST_LINE ".less-history-file:"
53 #define HISTFILE_SEARCH_SECTION ".search"
54 #define HISTFILE_SHELL_SECTION ".shell"
57 * A mlist structure represents a command history.
59 struct mlist {
60 struct mlist *next;
61 struct mlist *prev;
62 struct mlist *curr_mp;
63 char *string;
64 int modified;
68 * These are the various command histories that exist.
70 struct mlist mlist_search =
71 { &mlist_search, &mlist_search, &mlist_search, NULL, 0 };
72 void * const ml_search = (void *) &mlist_search;
74 struct mlist mlist_examine =
75 { &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 };
76 void * const ml_examine = (void *) &mlist_examine;
78 struct mlist mlist_shell =
79 { &mlist_shell, &mlist_shell, &mlist_shell, NULL, 0 };
80 void * const ml_shell = (void *) &mlist_shell;
83 * History for the current command.
85 static struct mlist *curr_mlist = NULL;
86 static int curr_cmdflags;
88 static char cmd_mbc_buf[MAX_UTF_CHAR_LEN];
89 static int cmd_mbc_buf_len;
90 static int cmd_mbc_buf_index;
94 * Reset command buffer (to empty).
96 void
97 cmd_reset(void)
99 cp = cmdbuf;
100 *cp = '\0';
101 cmd_col = 0;
102 cmd_offset = 0;
103 literal = 0;
104 cmd_mbc_buf_len = 0;
105 updown_match = -1;
109 * Clear command line.
111 void
112 clear_cmd(void)
114 cmd_col = prompt_col = 0;
115 cmd_mbc_buf_len = 0;
116 updown_match = -1;
120 * Display a string, usually as a prompt for input into the command buffer.
122 void
123 cmd_putstr(char *s)
125 LWCHAR prev_ch = 0;
126 LWCHAR ch;
127 char *endline = s + strlen(s);
128 while (*s != '\0') {
129 char *ns = s;
130 ch = step_char(&ns, +1, endline);
131 while (s < ns)
132 putchr(*s++);
133 if (!utf_mode) {
134 cmd_col++;
135 prompt_col++;
136 } else if (!is_composing_char(ch) &&
137 !is_combining_char(prev_ch, ch)) {
138 int width = is_wide_char(ch) ? 2 : 1;
139 cmd_col += width;
140 prompt_col += width;
142 prev_ch = ch;
147 * How many characters are in the command buffer?
150 len_cmdbuf(void)
152 char *s = cmdbuf;
153 char *endline = s + strlen(s);
154 int len = 0;
156 while (*s != '\0') {
157 step_char(&s, +1, endline);
158 len++;
160 return (len);
164 * Common part of cmd_step_right() and cmd_step_left().
166 static char *
167 cmd_step_common(char *p, LWCHAR ch, int len, int *pwidth, int *bswidth)
169 char *pr;
171 if (len == 1) {
172 pr = prchar((int)ch);
173 if (pwidth != NULL || bswidth != NULL) {
174 int prlen = strlen(pr);
175 if (pwidth != NULL)
176 *pwidth = prlen;
177 if (bswidth != NULL)
178 *bswidth = prlen;
180 } else {
181 pr = prutfchar(ch);
182 if (pwidth != NULL || bswidth != NULL) {
183 if (is_composing_char(ch)) {
184 if (pwidth != NULL)
185 *pwidth = 0;
186 if (bswidth != NULL)
187 *bswidth = 0;
188 } else if (is_ubin_char(ch)) {
189 int prlen = strlen(pr);
190 if (pwidth != NULL)
191 *pwidth = prlen;
192 if (bswidth != NULL)
193 *bswidth = prlen;
194 } else {
195 LWCHAR prev_ch = step_char(&p, -1, cmdbuf);
196 if (is_combining_char(prev_ch, ch)) {
197 if (pwidth != NULL)
198 *pwidth = 0;
199 if (bswidth != NULL)
200 *bswidth = 0;
201 } else {
202 if (pwidth != NULL)
203 *pwidth = is_wide_char(ch)
204 ? 2 : 1;
205 if (bswidth != NULL)
206 *bswidth = 1;
212 return (pr);
216 * Step a pointer one character right in the command buffer.
218 static char *
219 cmd_step_right(char **pp, int *pwidth, int *bswidth)
221 char *p = *pp;
222 LWCHAR ch = step_char(pp, +1, p + strlen(p));
224 return (cmd_step_common(p, ch, *pp - p, pwidth, bswidth));
228 * Step a pointer one character left in the command buffer.
230 static char *
231 cmd_step_left(char **pp, int *pwidth, int *bswidth)
233 char *p = *pp;
234 LWCHAR ch = step_char(pp, -1, cmdbuf);
236 return (cmd_step_common(*pp, ch, p - *pp, pwidth, bswidth));
240 * Repaint the line from cp onwards.
241 * Then position the cursor just after the char old_cp (a pointer into cmdbuf).
243 static void
244 cmd_repaint(char *old_cp)
247 * Repaint the line from the current position.
249 clear_eol();
250 while (*cp != '\0') {
251 char *np = cp;
252 int width;
253 char *pr = cmd_step_right(&np, &width, NULL);
254 if (cmd_col + width >= sc_width)
255 break;
256 cp = np;
257 putstr(pr);
258 cmd_col += width;
260 while (*cp != '\0') {
261 char *np = cp;
262 int width;
263 char *pr = cmd_step_right(&np, &width, NULL);
264 if (width > 0)
265 break;
266 cp = np;
267 putstr(pr);
271 * Back up the cursor to the correct position.
273 while (cp > old_cp)
274 cmd_left();
278 * Put the cursor at "home" (just after the prompt),
279 * and set cp to the corresponding char in cmdbuf.
281 static void
282 cmd_home(void)
284 while (cmd_col > prompt_col) {
285 int width, bswidth;
287 cmd_step_left(&cp, &width, &bswidth);
288 while (bswidth-- > 0)
289 putbs();
290 cmd_col -= width;
293 cp = &cmdbuf[cmd_offset];
297 * Shift the cmdbuf display left a half-screen.
299 static void
300 cmd_lshift(void)
302 char *s;
303 char *save_cp;
304 int cols;
307 * Start at the first displayed char, count how far to the
308 * right we'd have to move to reach the center of the screen.
310 s = cmdbuf + cmd_offset;
311 cols = 0;
312 while (cols < (sc_width - prompt_col) / 2 && *s != '\0') {
313 int width;
314 cmd_step_right(&s, &width, NULL);
315 cols += width;
317 while (*s != '\0') {
318 int width;
319 char *ns = s;
320 cmd_step_right(&ns, &width, NULL);
321 if (width > 0)
322 break;
323 s = ns;
326 cmd_offset = s - cmdbuf;
327 save_cp = cp;
328 cmd_home();
329 cmd_repaint(save_cp);
333 * Shift the cmdbuf display right a half-screen.
335 static void
336 cmd_rshift(void)
338 char *s;
339 char *save_cp;
340 int cols;
343 * Start at the first displayed char, count how far to the
344 * left we'd have to move to traverse a half-screen width
345 * of displayed characters.
347 s = cmdbuf + cmd_offset;
348 cols = 0;
349 while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf) {
350 int width;
351 cmd_step_left(&s, &width, NULL);
352 cols += width;
355 cmd_offset = s - cmdbuf;
356 save_cp = cp;
357 cmd_home();
358 cmd_repaint(save_cp);
362 * Move cursor right one character.
364 static int
365 cmd_right(void)
367 char *pr;
368 char *ncp;
369 int width;
371 if (*cp == '\0') {
372 /* Already at the end of the line. */
373 return (CC_OK);
375 ncp = cp;
376 pr = cmd_step_right(&ncp, &width, NULL);
377 if (cmd_col + width >= sc_width)
378 cmd_lshift();
379 else if (cmd_col + width == sc_width - 1 && cp[1] != '\0')
380 cmd_lshift();
381 cp = ncp;
382 cmd_col += width;
383 putstr(pr);
384 while (*cp != '\0') {
385 pr = cmd_step_right(&ncp, &width, NULL);
386 if (width > 0)
387 break;
388 putstr(pr);
389 cp = ncp;
391 return (CC_OK);
395 * Move cursor left one character.
397 static int
398 cmd_left(void)
400 char *ncp;
401 int width, bswidth;
403 if (cp <= cmdbuf) {
404 /* Already at the beginning of the line */
405 return (CC_OK);
407 ncp = cp;
408 while (ncp > cmdbuf) {
409 cmd_step_left(&ncp, &width, &bswidth);
410 if (width > 0)
411 break;
413 if (cmd_col < prompt_col + width)
414 cmd_rshift();
415 cp = ncp;
416 cmd_col -= width;
417 while (bswidth-- > 0)
418 putbs();
419 return (CC_OK);
423 * Insert a char into the command buffer, at the current position.
425 static int
426 cmd_ichar(char *cs, int clen)
428 char *s;
430 if (strlen(cmdbuf) + clen >= sizeof (cmdbuf)-1) {
431 /* No room in the command buffer for another char. */
432 ring_bell();
433 return (CC_ERROR);
437 * Make room for the new character (shift the tail of the buffer right).
439 for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--)
440 s[clen] = s[0];
442 * Insert the character into the buffer.
444 for (s = cp; s < cp + clen; s++)
445 *s = *cs++;
447 * Reprint the tail of the line from the inserted char.
449 updown_match = -1;
450 cmd_repaint(cp);
451 cmd_right();
452 return (CC_OK);
456 * Backspace in the command buffer.
457 * Delete the char to the left of the cursor.
459 static int
460 cmd_erase(void)
462 char *s;
463 int clen;
465 if (cp == cmdbuf) {
467 * Backspace past beginning of the buffer:
468 * this usually means abort the command.
470 return (CC_QUIT);
473 * Move cursor left (to the char being erased).
475 s = cp;
476 cmd_left();
477 clen = s - cp;
480 * Remove the char from the buffer (shift the buffer left).
482 for (s = cp; ; s++) {
483 s[0] = s[clen];
484 if (s[0] == '\0')
485 break;
489 * Repaint the buffer after the erased char.
491 updown_match = -1;
492 cmd_repaint(cp);
495 * We say that erasing the entire command string causes us
496 * to abort the current command, if CF_QUIT_ON_ERASE is set.
498 if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0')
499 return (CC_QUIT);
500 return (CC_OK);
504 * Delete the char under the cursor.
506 static int
507 cmd_delete(void)
509 if (*cp == '\0') {
510 /* At end of string; there is no char under the cursor. */
511 return (CC_OK);
514 * Move right, then use cmd_erase.
516 cmd_right();
517 cmd_erase();
518 return (CC_OK);
522 * Delete the "word" to the left of the cursor.
524 static int
525 cmd_werase(void)
527 if (cp > cmdbuf && cp[-1] == ' ') {
529 * If the char left of cursor is a space,
530 * erase all the spaces left of cursor (to the first non-space).
532 while (cp > cmdbuf && cp[-1] == ' ')
533 (void) cmd_erase();
534 } else {
536 * If the char left of cursor is not a space,
537 * erase all the nonspaces left of cursor (the whole "word").
539 while (cp > cmdbuf && cp[-1] != ' ')
540 (void) cmd_erase();
542 return (CC_OK);
546 * Delete the "word" under the cursor.
548 static int
549 cmd_wdelete(void)
551 if (*cp == ' ') {
553 * If the char under the cursor is a space,
554 * delete it and all the spaces right of cursor.
556 while (*cp == ' ')
557 (void) cmd_delete();
558 } else {
560 * If the char under the cursor is not a space,
561 * delete it and all nonspaces right of cursor (the whole word).
563 while (*cp != ' ' && *cp != '\0')
564 (void) cmd_delete();
566 return (CC_OK);
570 * Delete all chars in the command buffer.
572 static int
573 cmd_kill(void)
575 if (cmdbuf[0] == '\0') {
576 /* Buffer is already empty; abort the current command. */
577 return (CC_QUIT);
579 cmd_offset = 0;
580 cmd_home();
581 *cp = '\0';
582 updown_match = -1;
583 cmd_repaint(cp);
586 * We say that erasing the entire command string causes us
587 * to abort the current command, if CF_QUIT_ON_ERASE is set.
589 if (curr_cmdflags & CF_QUIT_ON_ERASE)
590 return (CC_QUIT);
591 return (CC_OK);
595 * Select an mlist structure to be the current command history.
597 void
598 set_mlist(void *mlist, int cmdflags)
600 curr_mlist = (struct mlist *)mlist;
601 curr_cmdflags = cmdflags;
603 /* Make sure the next up-arrow moves to the last string in the mlist. */
604 if (curr_mlist != NULL)
605 curr_mlist->curr_mp = curr_mlist;
609 * Move up or down in the currently selected command history list.
610 * Only consider entries whose first updown_match chars are equal to
611 * cmdbuf's corresponding chars.
613 static int
614 cmd_updown(int action)
616 char *s;
617 struct mlist *ml;
619 if (curr_mlist == NULL) {
621 * The current command has no history list.
623 ring_bell();
624 return (CC_OK);
627 if (updown_match < 0) {
628 updown_match = cp - cmdbuf;
632 * Find the next history entry which matches.
634 for (ml = curr_mlist->curr_mp; ; ) {
635 ml = (action == EC_UP) ? ml->prev : ml->next;
636 if (ml == curr_mlist) {
638 * We reached the end (or beginning) of the list.
640 break;
642 if (strncmp(cmdbuf, ml->string, updown_match) == 0) {
644 * This entry matches; stop here.
645 * Copy the entry into cmdbuf and echo it on the screen.
647 curr_mlist->curr_mp = ml;
648 s = ml->string;
649 if (s == NULL)
650 s = "";
651 cmd_home();
652 clear_eol();
653 strlcpy(cmdbuf, s, sizeof (cmdbuf));
654 for (cp = cmdbuf; *cp != '\0'; )
655 cmd_right();
656 return (CC_OK);
660 * We didn't find a history entry that matches.
662 ring_bell();
663 return (CC_OK);
667 * Add a string to a history list.
669 void
670 cmd_addhist(struct mlist *mlist, const char *cmd)
672 struct mlist *ml;
675 * Don't save a trivial command.
677 if (strlen(cmd) == 0)
678 return;
681 * Save the command unless it's a duplicate of the
682 * last command in the history.
684 ml = mlist->prev;
685 if (ml == mlist || strcmp(ml->string, cmd) != 0) {
687 * Did not find command in history.
688 * Save the command and put it at the end of the history list.
690 ml = ecalloc(1, sizeof (struct mlist));
691 ml->string = estrdup(cmd);
692 ml->next = mlist;
693 ml->prev = mlist->prev;
694 mlist->prev->next = ml;
695 mlist->prev = ml;
698 * Point to the cmd just after the just-accepted command.
699 * Thus, an UPARROW will always retrieve the previous command.
701 mlist->curr_mp = ml->next;
705 * Accept the command in the command buffer.
706 * Add it to the currently selected history list.
708 void
709 cmd_accept(void)
712 * Nothing to do if there is no currently selected history list.
714 if (curr_mlist == NULL)
715 return;
716 cmd_addhist(curr_mlist, cmdbuf);
717 curr_mlist->modified = 1;
721 * Try to perform a line-edit function on the command buffer,
722 * using a specified char as a line-editing command.
723 * Returns:
724 * CC_PASS The char does not invoke a line edit function.
725 * CC_OK Line edit function done.
726 * CC_QUIT The char requests the current command to be aborted.
728 static int
729 cmd_edit(int c)
731 int action;
732 int flags;
734 #define not_in_completion() in_completion = 0
737 * See if the char is indeed a line-editing command.
739 flags = 0;
740 if (curr_mlist == NULL)
742 * No current history; don't accept history manipulation cmds.
744 flags |= EC_NOHISTORY;
745 if (curr_mlist == ml_search)
747 * In a search command; don't accept file-completion cmds.
749 flags |= EC_NOCOMPLETE;
751 action = editchar(c, flags);
753 switch (action) {
754 case EC_RIGHT:
755 not_in_completion();
756 return (cmd_right());
757 case EC_LEFT:
758 not_in_completion();
759 return (cmd_left());
760 case EC_W_RIGHT:
761 not_in_completion();
762 while (*cp != '\0' && *cp != ' ')
763 cmd_right();
764 while (*cp == ' ')
765 cmd_right();
766 return (CC_OK);
767 case EC_W_LEFT:
768 not_in_completion();
769 while (cp > cmdbuf && cp[-1] == ' ')
770 cmd_left();
771 while (cp > cmdbuf && cp[-1] != ' ')
772 cmd_left();
773 return (CC_OK);
774 case EC_HOME:
775 not_in_completion();
776 cmd_offset = 0;
777 cmd_home();
778 cmd_repaint(cp);
779 return (CC_OK);
780 case EC_END:
781 not_in_completion();
782 while (*cp != '\0')
783 cmd_right();
784 return (CC_OK);
785 case EC_INSERT:
786 not_in_completion();
787 return (CC_OK);
788 case EC_BACKSPACE:
789 not_in_completion();
790 return (cmd_erase());
791 case EC_LINEKILL:
792 not_in_completion();
793 return (cmd_kill());
794 case EC_ABORT:
795 not_in_completion();
796 (void) cmd_kill();
797 return (CC_QUIT);
798 case EC_W_BACKSPACE:
799 not_in_completion();
800 return (cmd_werase());
801 case EC_DELETE:
802 not_in_completion();
803 return (cmd_delete());
804 case EC_W_DELETE:
805 not_in_completion();
806 return (cmd_wdelete());
807 case EC_LITERAL:
808 literal = 1;
809 return (CC_OK);
810 case EC_UP:
811 case EC_DOWN:
812 not_in_completion();
813 return (cmd_updown(action));
814 case EC_F_COMPLETE:
815 case EC_B_COMPLETE:
816 case EC_EXPAND:
817 return (cmd_complete(action));
818 case EC_NOACTION:
819 return (CC_OK);
820 default:
821 not_in_completion();
822 return (CC_PASS);
827 * Insert a string into the command buffer, at the current position.
829 static int
830 cmd_istr(char *str)
832 char *s;
833 int action;
834 char *endline = str + strlen(str);
836 for (s = str; *s != '\0'; ) {
837 char *os = s;
838 step_char(&s, +1, endline);
839 action = cmd_ichar(os, s - os);
840 if (action != CC_OK) {
841 ring_bell();
842 return (action);
845 return (CC_OK);
849 * Find the beginning and end of the "current" word.
850 * This is the word which the cursor (cp) is inside or at the end of.
851 * Return pointer to the beginning of the word and put the
852 * cursor at the end of the word.
854 static char *
855 delimit_word(void)
857 char *word;
858 char *p;
859 int delim_quoted = 0;
860 int meta_quoted = 0;
861 char *esc = get_meta_escape();
862 int esclen = strlen(esc);
865 * Move cursor to end of word.
867 if (*cp != ' ' && *cp != '\0') {
869 * Cursor is on a nonspace.
870 * Move cursor right to the next space.
872 while (*cp != ' ' && *cp != '\0')
873 cmd_right();
877 * Find the beginning of the word which the cursor is in.
879 if (cp == cmdbuf)
880 return (NULL);
882 * If we have an unbalanced quote (that is, an open quote
883 * without a corresponding close quote), we return everything
884 * from the open quote, including spaces.
886 for (word = cmdbuf; word < cp; word++)
887 if (*word != ' ')
888 break;
889 if (word >= cp)
890 return (cp);
891 for (p = cmdbuf; p < cp; p++) {
892 if (meta_quoted) {
893 meta_quoted = 0;
894 } else if (esclen > 0 && p + esclen < cp &&
895 strncmp(p, esc, esclen) == 0) {
896 meta_quoted = 1;
897 p += esclen - 1;
898 } else if (delim_quoted) {
899 if (*p == closequote)
900 delim_quoted = 0;
901 } else { /* (!delim_quoted) */
902 if (*p == openquote)
903 delim_quoted = 1;
904 else if (*p == ' ')
905 word = p+1;
908 return (word);
912 * Set things up to enter completion mode.
913 * Expand the word under the cursor into a list of filenames
914 * which start with that word, and set tk_text to that list.
916 static void
917 init_compl(void)
919 char *word;
920 char c;
922 free(tk_text);
923 tk_text = NULL;
925 * Find the original (uncompleted) word in the command buffer.
927 word = delimit_word();
928 if (word == NULL)
929 return;
931 * Set the insertion point to the point in the command buffer
932 * where the original (uncompleted) word now sits.
934 tk_ipoint = word;
936 * Save the original (uncompleted) word
938 free(tk_original);
939 tk_original = ecalloc(cp-word+1, sizeof (char));
940 (void) strncpy(tk_original, word, cp-word);
942 * Get the expanded filename.
943 * This may result in a single filename, or
944 * a blank-separated list of filenames.
946 c = *cp;
947 *cp = '\0';
948 if (*word != openquote) {
949 tk_text = fcomplete(word);
950 } else {
951 char *qword = shell_quote(word+1);
952 if (qword == NULL)
953 tk_text = fcomplete(word+1);
954 else
955 tk_text = fcomplete(qword);
956 free(qword);
958 *cp = c;
962 * Return the next word in the current completion list.
964 static char *
965 next_compl(int action, char *prev)
967 switch (action) {
968 case EC_F_COMPLETE:
969 return (forw_textlist(&tk_tlist, prev));
970 case EC_B_COMPLETE:
971 return (back_textlist(&tk_tlist, prev));
973 /* Cannot happen */
974 return ("?");
978 * Complete the filename before (or under) the cursor.
979 * cmd_complete may be called multiple times. The global in_completion
980 * remembers whether this call is the first time (create the list),
981 * or a subsequent time (step thru the list).
983 static int
984 cmd_complete(int action)
986 char *s;
988 if (!in_completion || action == EC_EXPAND) {
990 * Expand the word under the cursor and
991 * use the first word in the expansion
992 * (or the entire expansion if we're doing EC_EXPAND).
994 init_compl();
995 if (tk_text == NULL) {
996 ring_bell();
997 return (CC_OK);
999 if (action == EC_EXPAND) {
1001 * Use the whole list.
1003 tk_trial = tk_text;
1004 } else {
1006 * Use the first filename in the list.
1008 in_completion = 1;
1009 init_textlist(&tk_tlist, tk_text);
1010 tk_trial = next_compl(action, NULL);
1012 } else {
1014 * We already have a completion list.
1015 * Use the next/previous filename from the list.
1017 tk_trial = next_compl(action, tk_trial);
1021 * Remove the original word, or the previous trial completion.
1023 while (cp > tk_ipoint)
1024 (void) cmd_erase();
1026 if (tk_trial == NULL) {
1028 * There are no more trial completions.
1029 * Insert the original (uncompleted) filename.
1031 in_completion = 0;
1032 if (cmd_istr(tk_original) != CC_OK)
1033 goto fail;
1034 } else {
1036 * Insert trial completion.
1038 if (cmd_istr(tk_trial) != CC_OK)
1039 goto fail;
1041 * If it is a directory, append a slash.
1043 if (is_dir(tk_trial)) {
1044 if (cp > cmdbuf && cp[-1] == closequote)
1045 (void) cmd_erase();
1046 s = lgetenv("LESSSEPARATOR");
1047 if (s == NULL)
1048 s = "/";
1049 if (cmd_istr(s) != CC_OK)
1050 goto fail;
1054 return (CC_OK);
1056 fail:
1057 in_completion = 0;
1058 ring_bell();
1059 return (CC_OK);
1063 * Process a single character of a multi-character command, such as
1064 * a number, or the pattern of a search command.
1065 * Returns:
1066 * CC_OK The char was accepted.
1067 * CC_QUIT The char requests the command to be aborted.
1068 * CC_ERROR The char could not be accepted due to an error.
1071 cmd_char(int c)
1073 int action;
1074 int len;
1076 if (!utf_mode) {
1077 cmd_mbc_buf[0] = c & 0xff;
1078 len = 1;
1079 } else {
1080 /* Perform strict validation in all possible cases. */
1081 if (cmd_mbc_buf_len == 0) {
1082 retry:
1083 cmd_mbc_buf_index = 1;
1084 *cmd_mbc_buf = c & 0xff;
1085 if (IS_ASCII_OCTET(c))
1086 cmd_mbc_buf_len = 1;
1087 else if (IS_UTF8_LEAD(c)) {
1088 cmd_mbc_buf_len = utf_len(c);
1089 return (CC_OK);
1090 } else {
1091 /* UTF8_INVALID or stray UTF8_TRAIL */
1092 ring_bell();
1093 return (CC_ERROR);
1095 } else if (IS_UTF8_TRAIL(c)) {
1096 cmd_mbc_buf[cmd_mbc_buf_index++] = c & 0xff;
1097 if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1098 return (CC_OK);
1099 if (!is_utf8_well_formed(cmd_mbc_buf)) {
1101 * complete, but not well formed
1102 * (non-shortest form), sequence
1104 cmd_mbc_buf_len = 0;
1105 ring_bell();
1106 return (CC_ERROR);
1108 } else {
1109 /* Flush incomplete (truncated) sequence. */
1110 cmd_mbc_buf_len = 0;
1111 ring_bell();
1112 /* Handle new char. */
1113 goto retry;
1116 len = cmd_mbc_buf_len;
1117 cmd_mbc_buf_len = 0;
1120 if (literal) {
1122 * Insert the char, even if it is a line-editing char.
1124 literal = 0;
1125 return (cmd_ichar(cmd_mbc_buf, len));
1129 * See if it is a line-editing character.
1131 if (in_mca() && len == 1) {
1132 action = cmd_edit(c);
1133 switch (action) {
1134 case CC_OK:
1135 case CC_QUIT:
1136 return (action);
1137 case CC_PASS:
1138 break;
1143 * Insert the char into the command buffer.
1145 return (cmd_ichar(cmd_mbc_buf, len));
1149 * Return the number currently in the command buffer.
1151 off_t
1152 cmd_int(long *frac)
1154 char *p;
1155 off_t n = 0;
1156 int err;
1158 for (p = cmdbuf; *p >= '0' && *p <= '9'; p++)
1159 n = (n * 10) + (*p - '0');
1160 *frac = 0;
1161 if (*p++ == '.') {
1162 *frac = getfraction(&p, NULL, &err);
1163 /* {{ do something if err is set? }} */
1165 return (n);
1169 * Return a pointer to the command buffer.
1171 char *
1172 get_cmdbuf(void)
1174 return (cmdbuf);
1178 * Return the last (most recent) string in the current command history.
1180 char *
1181 cmd_lastpattern(void)
1183 if (curr_mlist == NULL)
1184 return (NULL);
1185 return (curr_mlist->curr_mp->prev->string);
1189 * Get the name of the history file.
1191 static char *
1192 histfile_name(void)
1194 char *home;
1195 char *name;
1197 /* See if filename is explicitly specified by $LESSHISTFILE. */
1198 name = lgetenv("LESSHISTFILE");
1199 if (name != NULL && *name != '\0') {
1200 if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1201 /* $LESSHISTFILE == "-" means don't use history file */
1202 return (NULL);
1203 return (estrdup(name));
1206 /* Otherwise, file is in $HOME if enabled. */
1207 if (strcmp(LESSHISTFILE, "-") == 0)
1208 return (NULL);
1209 home = lgetenv("HOME");
1210 if (home == NULL || *home == '\0') {
1211 return (NULL);
1213 return (easprintf("%s/%s", home, LESSHISTFILE));
1217 * Initialize history from a .lesshist file.
1219 void
1220 init_cmdhist(void)
1222 struct mlist *ml = NULL;
1223 char line[CMDBUF_SIZE];
1224 char *filename;
1225 FILE *f;
1226 char *p;
1228 filename = histfile_name();
1229 if (filename == NULL)
1230 return;
1231 f = fopen(filename, "r");
1232 free(filename);
1233 if (f == NULL)
1234 return;
1235 if (fgets(line, sizeof (line), f) == NULL ||
1236 strncmp(line, HISTFILE_FIRST_LINE,
1237 strlen(HISTFILE_FIRST_LINE)) != 0) {
1238 (void) fclose(f);
1239 return;
1241 while (fgets(line, sizeof (line), f) != NULL) {
1242 for (p = line; *p != '\0'; p++) {
1243 if (*p == '\n' || *p == '\r') {
1244 *p = '\0';
1245 break;
1248 if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1249 ml = &mlist_search;
1250 else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0) {
1251 ml = &mlist_shell;
1252 } else if (*line == '"') {
1253 if (ml != NULL)
1254 cmd_addhist(ml, line+1);
1257 (void) fclose(f);
1263 static void
1264 save_mlist(struct mlist *ml, FILE *f)
1266 int histsize = 0;
1267 int n;
1268 char *s;
1270 s = lgetenv("LESSHISTSIZE");
1271 if (s != NULL)
1272 histsize = atoi(s);
1273 if (histsize == 0)
1274 histsize = 100;
1276 ml = ml->prev;
1277 for (n = 0; n < histsize; n++) {
1278 if (ml->string == NULL)
1279 break;
1280 ml = ml->prev;
1282 for (ml = ml->next; ml->string != NULL; ml = ml->next)
1283 (void) fprintf(f, "\"%s\n", ml->string);
1289 void
1290 save_cmdhist(void)
1292 char *filename;
1293 FILE *f;
1294 int modified = 0;
1295 int do_chmod = 1;
1296 struct stat statbuf;
1297 int r;
1299 if (mlist_search.modified)
1300 modified = 1;
1301 if (mlist_shell.modified)
1302 modified = 1;
1303 if (!modified)
1304 return;
1305 filename = histfile_name();
1306 if (filename == NULL)
1307 return;
1308 f = fopen(filename, "w");
1309 free(filename);
1310 if (f == NULL)
1311 return;
1313 /* Make history file readable only by owner. */
1314 r = fstat(fileno(f), &statbuf);
1315 if (r < 0 || !S_ISREG(statbuf.st_mode))
1316 /* Don't chmod if not a regular file. */
1317 do_chmod = 0;
1318 if (do_chmod)
1319 (void) fchmod(fileno(f), 0600);
1321 (void) fprintf(f, "%s\n", HISTFILE_FIRST_LINE);
1323 (void) fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1324 save_mlist(&mlist_search, f);
1326 (void) fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1327 save_mlist(&mlist_shell, f);
1329 (void) fclose(f);