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