Merge branch 'vendor/LIBPCAP'
[dragonfly.git] / contrib / less / cmdbuf.c
1 /*
2  * Copyright (C) 1984-2012  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;
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 * constant ml_search = (void *) &mlist_search;
80
81 struct mlist mlist_examine = 
82         { &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 };
83 public void * constant 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 * constant 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 * constant ml_search = (void *)1;
95 public void * constant ml_examine = (void *)2;
96 #if SHELL_ESCAPE || PIPEC
97 public void * constant 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
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
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
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         updown_match = -1;
510         cmd_repaint(cp);
511         cmd_right();
512         return (CC_OK);
513 }
514
515 /*
516  * Backspace in the command buffer.
517  * Delete the char to the left of the cursor.
518  */
519         static int
520 cmd_erase()
521 {
522         register char *s;
523         int clen;
524
525         if (cp == cmdbuf)
526         {
527                 /*
528                  * Backspace past beginning of the buffer:
529                  * this usually means abort the command.
530                  */
531                 return (CC_QUIT);
532         }
533         /*
534          * Move cursor left (to the char being erased).
535          */
536         s = cp;
537         cmd_left();
538         clen = s - cp;
539
540         /*
541          * Remove the char from the buffer (shift the buffer left).
542          */
543         for (s = cp;  ;  s++)
544         {
545                 s[0] = s[clen];
546                 if (s[0] == '\0')
547                         break;
548         }
549
550         /*
551          * Repaint the buffer after the erased char.
552          */
553         updown_match = -1;
554         cmd_repaint(cp);
555         
556         /*
557          * We say that erasing the entire command string causes us
558          * to abort the current command, if CF_QUIT_ON_ERASE is set.
559          */
560         if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0')
561                 return (CC_QUIT);
562         return (CC_OK);
563 }
564
565 /*
566  * Delete the char under the cursor.
567  */
568         static int
569 cmd_delete()
570 {
571         if (*cp == '\0')
572         {
573                 /* At end of string; there is no char under the cursor. */
574                 return (CC_OK);
575         }
576         /*
577          * Move right, then use cmd_erase.
578          */
579         cmd_right();
580         cmd_erase();
581         return (CC_OK);
582 }
583
584 /*
585  * Delete the "word" to the left of the cursor.
586  */
587         static int
588 cmd_werase()
589 {
590         if (cp > cmdbuf && cp[-1] == ' ')
591         {
592                 /*
593                  * If the char left of cursor is a space,
594                  * erase all the spaces left of cursor (to the first non-space).
595                  */
596                 while (cp > cmdbuf && cp[-1] == ' ')
597                         (void) cmd_erase();
598         } else
599         {
600                 /*
601                  * If the char left of cursor is not a space,
602                  * erase all the nonspaces left of cursor (the whole "word").
603                  */
604                 while (cp > cmdbuf && cp[-1] != ' ')
605                         (void) cmd_erase();
606         }
607         return (CC_OK);
608 }
609
610 /*
611  * Delete the "word" under the cursor.
612  */
613         static int
614 cmd_wdelete()
615 {
616         if (*cp == ' ')
617         {
618                 /*
619                  * If the char under the cursor is a space,
620                  * delete it and all the spaces right of cursor.
621                  */
622                 while (*cp == ' ')
623                         (void) cmd_delete();
624         } else
625         {
626                 /*
627                  * If the char under the cursor is not a space,
628                  * delete it and all nonspaces right of cursor (the whole word).
629                  */
630                 while (*cp != ' ' && *cp != '\0')
631                         (void) cmd_delete();
632         }
633         return (CC_OK);
634 }
635
636 /*
637  * Delete all chars in the command buffer.
638  */
639         static int
640 cmd_kill()
641 {
642         if (cmdbuf[0] == '\0')
643         {
644                 /* Buffer is already empty; abort the current command. */
645                 return (CC_QUIT);
646         }
647         cmd_offset = 0;
648         cmd_home();
649         *cp = '\0';
650         updown_match = -1;
651         cmd_repaint(cp);
652
653         /*
654          * We say that erasing the entire command string causes us
655          * to abort the current command, if CF_QUIT_ON_ERASE is set.
656          */
657         if (curr_cmdflags & CF_QUIT_ON_ERASE)
658                 return (CC_QUIT);
659         return (CC_OK);
660 }
661
662 /*
663  * Select an mlist structure to be the current command history.
664  */
665         public void
666 set_mlist(mlist, cmdflags)
667         void *mlist;
668         int cmdflags;
669 {
670 #if CMD_HISTORY
671         curr_mlist = (struct mlist *) mlist;
672         curr_cmdflags = cmdflags;
673
674         /* Make sure the next up-arrow moves to the last string in the mlist. */
675         if (curr_mlist != NULL)
676                 curr_mlist->curr_mp = curr_mlist;
677 #endif
678 }
679
680 #if CMD_HISTORY
681 /*
682  * Move up or down in the currently selected command history list.
683  * Only consider entries whose first updown_match chars are equal to
684  * cmdbuf's corresponding chars.
685  */
686         static int
687 cmd_updown(action)
688         int action;
689 {
690         char *s;
691         struct mlist *ml;
692         
693         if (curr_mlist == NULL)
694         {
695                 /*
696                  * The current command has no history list.
697                  */
698                 bell();
699                 return (CC_OK);
700         }
701
702         if (updown_match < 0)
703         {
704                 updown_match = cp - cmdbuf;
705         }
706
707         /*
708          * Find the next history entry which matches.
709          */
710         for (ml = curr_mlist->curr_mp;;)
711         {
712                 ml = (action == EC_UP) ? ml->prev : ml->next;
713                 if (ml == curr_mlist)
714                 {
715                         /*
716                          * We reached the end (or beginning) of the list.
717                          */
718                         break;
719                 }
720                 if (strncmp(cmdbuf, ml->string, updown_match) == 0)
721                 {
722                         /*
723                          * This entry matches; stop here.
724                          * Copy the entry into cmdbuf and echo it on the screen.
725                          */
726                         curr_mlist->curr_mp = ml;
727                         s = ml->string;
728                         if (s == NULL)
729                                 s = "";
730                         cmd_home();
731                         clear_eol();
732                         strcpy(cmdbuf, s);
733                         for (cp = cmdbuf;  *cp != '\0';  )
734                                 cmd_right();
735                         return (CC_OK);
736                 }
737         }
738         /*
739          * We didn't find a history entry that matches.
740          */
741         bell();
742         return (CC_OK);
743 }
744 #endif
745
746 /*
747  * Add a string to a history list.
748  */
749         public void
750 cmd_addhist(mlist, cmd)
751         struct mlist *mlist;
752         char *cmd;
753 {
754 #if CMD_HISTORY
755         struct mlist *ml;
756         
757         /*
758          * Don't save a trivial command.
759          */
760         if (strlen(cmd) == 0)
761                 return;
762
763         /*
764          * Save the command unless it's a duplicate of the
765          * last command in the history.
766          */
767         ml = mlist->prev;
768         if (ml == mlist || strcmp(ml->string, cmd) != 0)
769         {
770                 /*
771                  * Did not find command in history.
772                  * Save the command and put it at the end of the history list.
773                  */
774                 ml = (struct mlist *) ecalloc(1, sizeof(struct mlist));
775                 ml->string = save(cmd);
776                 ml->next = mlist;
777                 ml->prev = mlist->prev;
778                 mlist->prev->next = ml;
779                 mlist->prev = ml;
780         }
781         /*
782          * Point to the cmd just after the just-accepted command.
783          * Thus, an UPARROW will always retrieve the previous command.
784          */
785         mlist->curr_mp = ml->next;
786 #endif
787 }
788
789 /*
790  * Accept the command in the command buffer.
791  * Add it to the currently selected history list.
792  */
793         public void
794 cmd_accept()
795 {
796 #if CMD_HISTORY
797         /*
798          * Nothing to do if there is no currently selected history list.
799          */
800         if (curr_mlist == NULL)
801                 return;
802         cmd_addhist(curr_mlist, cmdbuf);
803         curr_mlist->modified = 1;
804 #endif
805 }
806
807 /*
808  * Try to perform a line-edit function on the command buffer,
809  * using a specified char as a line-editing command.
810  * Returns:
811  *      CC_PASS The char does not invoke a line edit function.
812  *      CC_OK   Line edit function done.
813  *      CC_QUIT The char requests the current command to be aborted.
814  */
815         static int
816 cmd_edit(c)
817         int c;
818 {
819         int action;
820         int flags;
821
822 #if TAB_COMPLETE_FILENAME
823 #define not_in_completion()     in_completion = 0
824 #else
825 #define not_in_completion()
826 #endif
827         
828         /*
829          * See if the char is indeed a line-editing command.
830          */
831         flags = 0;
832 #if CMD_HISTORY
833         if (curr_mlist == NULL)
834                 /*
835                  * No current history; don't accept history manipulation cmds.
836                  */
837                 flags |= EC_NOHISTORY;
838 #endif
839 #if TAB_COMPLETE_FILENAME
840         if (curr_mlist == ml_search)
841                 /*
842                  * In a search command; don't accept file-completion cmds.
843                  */
844                 flags |= EC_NOCOMPLETE;
845 #endif
846
847         action = editchar(c, flags);
848
849         switch (action)
850         {
851         case EC_RIGHT:
852                 not_in_completion();
853                 return (cmd_right());
854         case EC_LEFT:
855                 not_in_completion();
856                 return (cmd_left());
857         case EC_W_RIGHT:
858                 not_in_completion();
859                 while (*cp != '\0' && *cp != ' ')
860                         cmd_right();
861                 while (*cp == ' ')
862                         cmd_right();
863                 return (CC_OK);
864         case EC_W_LEFT:
865                 not_in_completion();
866                 while (cp > cmdbuf && cp[-1] == ' ')
867                         cmd_left();
868                 while (cp > cmdbuf && cp[-1] != ' ')
869                         cmd_left();
870                 return (CC_OK);
871         case EC_HOME:
872                 not_in_completion();
873                 cmd_offset = 0;
874                 cmd_home();
875                 cmd_repaint(cp);
876                 return (CC_OK);
877         case EC_END:
878                 not_in_completion();
879                 while (*cp != '\0')
880                         cmd_right();
881                 return (CC_OK);
882         case EC_INSERT:
883                 not_in_completion();
884                 return (CC_OK);
885         case EC_BACKSPACE:
886                 not_in_completion();
887                 return (cmd_erase());
888         case EC_LINEKILL:
889                 not_in_completion();
890                 return (cmd_kill());
891         case EC_ABORT:
892                 not_in_completion();
893                 (void) cmd_kill();
894                 return (CC_QUIT);
895         case EC_W_BACKSPACE:
896                 not_in_completion();
897                 return (cmd_werase());
898         case EC_DELETE:
899                 not_in_completion();
900                 return (cmd_delete());
901         case EC_W_DELETE:
902                 not_in_completion();
903                 return (cmd_wdelete());
904         case EC_LITERAL:
905                 literal = 1;
906                 return (CC_OK);
907 #if CMD_HISTORY
908         case EC_UP:
909         case EC_DOWN:
910                 not_in_completion();
911                 return (cmd_updown(action));
912 #endif
913 #if TAB_COMPLETE_FILENAME
914         case EC_F_COMPLETE:
915         case EC_B_COMPLETE:
916         case EC_EXPAND:
917                 return (cmd_complete(action));
918 #endif
919         case EC_NOACTION:
920                 return (CC_OK);
921         default:
922                 not_in_completion();
923                 return (CC_PASS);
924         }
925 }
926
927 #if TAB_COMPLETE_FILENAME
928 /*
929  * Insert a string into the command buffer, at the current position.
930  */
931         static int
932 cmd_istr(str)
933         char *str;
934 {
935         char *s;
936         int action;
937         char *endline = str + strlen(str);
938         
939         for (s = str;  *s != '\0';  )
940         {
941                 char *os = s;
942                 step_char(&s, +1, endline);
943                 action = cmd_ichar(os, s - os);
944                 if (action != CC_OK)
945                 {
946                         bell();
947                         return (action);
948                 }
949         }
950         return (CC_OK);
951 }
952
953 /*
954  * Find the beginning and end of the "current" word.
955  * This is the word which the cursor (cp) is inside or at the end of.
956  * Return pointer to the beginning of the word and put the
957  * cursor at the end of the word.
958  */
959         static char *
960 delimit_word()
961 {
962         char *word;
963 #if SPACES_IN_FILENAMES
964         char *p;
965         int delim_quoted = 0;
966         int meta_quoted = 0;
967         char *esc = get_meta_escape();
968         int esclen = strlen(esc);
969 #endif
970         
971         /*
972          * Move cursor to end of word.
973          */
974         if (*cp != ' ' && *cp != '\0')
975         {
976                 /*
977                  * Cursor is on a nonspace.
978                  * Move cursor right to the next space.
979                  */
980                 while (*cp != ' ' && *cp != '\0')
981                         cmd_right();
982         } else if (cp > cmdbuf && cp[-1] != ' ')
983         {
984                 /*
985                  * Cursor is on a space, and char to the left is a nonspace.
986                  * We're already at the end of the word.
987                  */
988                 ;
989 #if 0
990         } else
991         {
992                 /*
993                  * Cursor is on a space and char to the left is a space.
994                  * Huh? There's no word here.
995                  */
996                 return (NULL);
997 #endif
998         }
999         /*
1000          * Find the beginning of the word which the cursor is in.
1001          */
1002         if (cp == cmdbuf)
1003                 return (NULL);
1004 #if SPACES_IN_FILENAMES
1005         /*
1006          * If we have an unbalanced quote (that is, an open quote
1007          * without a corresponding close quote), we return everything
1008          * from the open quote, including spaces.
1009          */
1010         for (word = cmdbuf;  word < cp;  word++)
1011                 if (*word != ' ')
1012                         break;
1013         if (word >= cp)
1014                 return (cp);
1015         for (p = cmdbuf;  p < cp;  p++)
1016         {
1017                 if (meta_quoted)
1018                 {
1019                         meta_quoted = 0;
1020                 } else if (esclen > 0 && p + esclen < cp &&
1021                            strncmp(p, esc, esclen) == 0)
1022                 {
1023                         meta_quoted = 1;
1024                         p += esclen - 1;
1025                 } else if (delim_quoted)
1026                 {
1027                         if (*p == closequote)
1028                                 delim_quoted = 0;
1029                 } else /* (!delim_quoted) */
1030                 {
1031                         if (*p == openquote)
1032                                 delim_quoted = 1;
1033                         else if (*p == ' ')
1034                                 word = p+1;
1035                 }
1036         }
1037 #endif
1038         return (word);
1039 }
1040
1041 /*
1042  * Set things up to enter completion mode.
1043  * Expand the word under the cursor into a list of filenames 
1044  * which start with that word, and set tk_text to that list.
1045  */
1046         static void
1047 init_compl()
1048 {
1049         char *word;
1050         char c;
1051         
1052         /*
1053          * Get rid of any previous tk_text.
1054          */
1055         if (tk_text != NULL)
1056         {
1057                 free(tk_text);
1058                 tk_text = NULL;
1059         }
1060         /*
1061          * Find the original (uncompleted) word in the command buffer.
1062          */
1063         word = delimit_word();
1064         if (word == NULL)
1065                 return;
1066         /*
1067          * Set the insertion point to the point in the command buffer
1068          * where the original (uncompleted) word now sits.
1069          */
1070         tk_ipoint = word;
1071         /*
1072          * Save the original (uncompleted) word
1073          */
1074         if (tk_original != NULL)
1075                 free(tk_original);
1076         tk_original = (char *) ecalloc(cp-word+1, sizeof(char));
1077         strncpy(tk_original, word, cp-word);
1078         /*
1079          * Get the expanded filename.
1080          * This may result in a single filename, or
1081          * a blank-separated list of filenames.
1082          */
1083         c = *cp;
1084         *cp = '\0';
1085         if (*word != openquote)
1086         {
1087                 tk_text = fcomplete(word);
1088         } else
1089         {
1090 #if MSDOS_COMPILER
1091                 char *qword = NULL;
1092 #else
1093                 char *qword = shell_quote(word+1);
1094 #endif
1095                 if (qword == NULL)
1096                         tk_text = fcomplete(word+1);
1097                 else
1098                 {
1099                         tk_text = fcomplete(qword);
1100                         free(qword);
1101                 }
1102         }
1103         *cp = c;
1104 }
1105
1106 /*
1107  * Return the next word in the current completion list.
1108  */
1109         static char *
1110 next_compl(action, prev)
1111         int action;
1112         char *prev;
1113 {
1114         switch (action)
1115         {
1116         case EC_F_COMPLETE:
1117                 return (forw_textlist(&tk_tlist, prev));
1118         case EC_B_COMPLETE:
1119                 return (back_textlist(&tk_tlist, prev));
1120         }
1121         /* Cannot happen */
1122         return ("?");
1123 }
1124
1125 /*
1126  * Complete the filename before (or under) the cursor.
1127  * cmd_complete may be called multiple times.  The global in_completion
1128  * remembers whether this call is the first time (create the list),
1129  * or a subsequent time (step thru the list).
1130  */
1131         static int
1132 cmd_complete(action)
1133         int action;
1134 {
1135         char *s;
1136
1137         if (!in_completion || action == EC_EXPAND)
1138         {
1139                 /*
1140                  * Expand the word under the cursor and 
1141                  * use the first word in the expansion 
1142                  * (or the entire expansion if we're doing EC_EXPAND).
1143                  */
1144                 init_compl();
1145                 if (tk_text == NULL)
1146                 {
1147                         bell();
1148                         return (CC_OK);
1149                 }
1150                 if (action == EC_EXPAND)
1151                 {
1152                         /*
1153                          * Use the whole list.
1154                          */
1155                         tk_trial = tk_text;
1156                 } else
1157                 {
1158                         /*
1159                          * Use the first filename in the list.
1160                          */
1161                         in_completion = 1;
1162                         init_textlist(&tk_tlist, tk_text);
1163                         tk_trial = next_compl(action, (char*)NULL);
1164                 }
1165         } else
1166         {
1167                 /*
1168                  * We already have a completion list.
1169                  * Use the next/previous filename from the list.
1170                  */
1171                 tk_trial = next_compl(action, tk_trial);
1172         }
1173         
1174         /*
1175          * Remove the original word, or the previous trial completion.
1176          */
1177         while (cp > tk_ipoint)
1178                 (void) cmd_erase();
1179         
1180         if (tk_trial == NULL)
1181         {
1182                 /*
1183                  * There are no more trial completions.
1184                  * Insert the original (uncompleted) filename.
1185                  */
1186                 in_completion = 0;
1187                 if (cmd_istr(tk_original) != CC_OK)
1188                         goto fail;
1189         } else
1190         {
1191                 /*
1192                  * Insert trial completion.
1193                  */
1194                 if (cmd_istr(tk_trial) != CC_OK)
1195                         goto fail;
1196                 /*
1197                  * If it is a directory, append a slash.
1198                  */
1199                 if (is_dir(tk_trial))
1200                 {
1201                         if (cp > cmdbuf && cp[-1] == closequote)
1202                                 (void) cmd_erase();
1203                         s = lgetenv("LESSSEPARATOR");
1204                         if (s == NULL)
1205                                 s = PATHNAME_SEP;
1206                         if (cmd_istr(s) != CC_OK)
1207                                 goto fail;
1208                 }
1209         }
1210         
1211         return (CC_OK);
1212         
1213 fail:
1214         in_completion = 0;
1215         bell();
1216         return (CC_OK);
1217 }
1218
1219 #endif /* TAB_COMPLETE_FILENAME */
1220
1221 /*
1222  * Process a single character of a multi-character command, such as
1223  * a number, or the pattern of a search command.
1224  * Returns:
1225  *      CC_OK           The char was accepted.
1226  *      CC_QUIT         The char requests the command to be aborted.
1227  *      CC_ERROR        The char could not be accepted due to an error.
1228  */
1229         public int
1230 cmd_char(c)
1231         int c;
1232 {
1233         int action;
1234         int len;
1235
1236         if (!utf_mode)
1237         {
1238                 cmd_mbc_buf[0] = c;
1239                 len = 1;
1240         } else
1241         {
1242                 /* Perform strict validation in all possible cases.  */
1243                 if (cmd_mbc_buf_len == 0)
1244                 {
1245                  retry:
1246                         cmd_mbc_buf_index = 1;
1247                         *cmd_mbc_buf = c;
1248                         if (IS_ASCII_OCTET(c))
1249                                 cmd_mbc_buf_len = 1;
1250                         else if (IS_UTF8_LEAD(c))
1251                         {
1252                                 cmd_mbc_buf_len = utf_len(c);
1253                                 return (CC_OK);
1254                         } else
1255                         {
1256                                 /* UTF8_INVALID or stray UTF8_TRAIL */
1257                                 bell();
1258                                 return (CC_ERROR);
1259                         }
1260                 } else if (IS_UTF8_TRAIL(c))
1261                 {
1262                         cmd_mbc_buf[cmd_mbc_buf_index++] = c;
1263                         if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1264                                 return (CC_OK);
1265                         if (!is_utf8_well_formed(cmd_mbc_buf))
1266                         {
1267                                 /* complete, but not well formed (non-shortest form), sequence */
1268                                 cmd_mbc_buf_len = 0;
1269                                 bell();
1270                                 return (CC_ERROR);
1271                         }
1272                 } else
1273                 {
1274                         /* Flush incomplete (truncated) sequence.  */
1275                         cmd_mbc_buf_len = 0;
1276                         bell();
1277                         /* Handle new char.  */
1278                         goto retry;
1279                 }
1280
1281                 len = cmd_mbc_buf_len;
1282                 cmd_mbc_buf_len = 0;
1283         }
1284
1285         if (literal)
1286         {
1287                 /*
1288                  * Insert the char, even if it is a line-editing char.
1289                  */
1290                 literal = 0;
1291                 return (cmd_ichar(cmd_mbc_buf, len));
1292         }
1293                 
1294         /*
1295          * See if it is a line-editing character.
1296          */
1297         if (in_mca() && len == 1)
1298         {
1299                 action = cmd_edit(c);
1300                 switch (action)
1301                 {
1302                 case CC_OK:
1303                 case CC_QUIT:
1304                         return (action);
1305                 case CC_PASS:
1306                         break;
1307                 }
1308         }
1309         
1310         /*
1311          * Insert the char into the command buffer.
1312          */
1313         return (cmd_ichar(cmd_mbc_buf, len));
1314 }
1315
1316 /*
1317  * Return the number currently in the command buffer.
1318  */
1319         public LINENUM
1320 cmd_int(frac)
1321         long *frac;
1322 {
1323         char *p;
1324         LINENUM n = 0;
1325         int err;
1326
1327         for (p = cmdbuf;  *p >= '0' && *p <= '9';  p++)
1328                 n = (n * 10) + (*p - '0');
1329         *frac = 0;
1330         if (*p++ == '.')
1331         {
1332                 *frac = getfraction(&p, NULL, &err);
1333                 /* {{ do something if err is set? }} */
1334         }
1335         return (n);
1336 }
1337
1338 /*
1339  * Return a pointer to the command buffer.
1340  */
1341         public char *
1342 get_cmdbuf()
1343 {
1344         return (cmdbuf);
1345 }
1346
1347 #if CMD_HISTORY
1348 /*
1349  * Return the last (most recent) string in the current command history.
1350  */
1351         public char *
1352 cmd_lastpattern()
1353 {
1354         if (curr_mlist == NULL)
1355                 return (NULL);
1356         return (curr_mlist->curr_mp->prev->string);
1357 }
1358 #endif
1359
1360 #if CMD_HISTORY
1361 /*
1362  * Get the name of the history file.
1363  */
1364         static char *
1365 histfile_name()
1366 {
1367         char *home;
1368         char *name;
1369         int len;
1370         
1371         /* See if filename is explicitly specified by $LESSHISTFILE. */
1372         name = lgetenv("LESSHISTFILE");
1373         if (name != NULL && *name != '\0')
1374         {
1375                 if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1376                         /* $LESSHISTFILE == "-" means don't use a history file. */
1377                         return (NULL);
1378                 return (save(name));
1379         }
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 = 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 #endif /* CMD_HISTORY */
1397
1398 /*
1399  * Initialize history from a .lesshist file.
1400  */
1401         public void
1402 init_cmdhist()
1403 {
1404 #if CMD_HISTORY
1405         struct mlist *ml = NULL;
1406         char line[CMDBUF_SIZE];
1407         char *filename;
1408         FILE *f;
1409         char *p;
1410
1411         filename = histfile_name();
1412         if (filename == NULL)
1413                 return;
1414         f = fopen(filename, "r");
1415         free(filename);
1416         if (f == NULL)
1417                 return;
1418         if (fgets(line, sizeof(line), f) == NULL ||
1419             strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0)
1420         {
1421                 fclose(f);
1422                 return;
1423         }
1424         while (fgets(line, sizeof(line), f) != NULL)
1425         {
1426                 for (p = line;  *p != '\0';  p++)
1427                 {
1428                         if (*p == '\n' || *p == '\r')
1429                         {
1430                                 *p = '\0';
1431                                 break;
1432                         }
1433                 }
1434                 if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1435                         ml = &mlist_search;
1436                 else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0)
1437                 {
1438 #if SHELL_ESCAPE || PIPEC
1439                         ml = &mlist_shell;
1440 #else
1441                         ml = NULL;
1442 #endif
1443                 } else if (*line == '"')
1444                 {
1445                         if (ml != NULL)
1446                                 cmd_addhist(ml, line+1);
1447                 }
1448         }
1449         fclose(f);
1450 #endif /* CMD_HISTORY */
1451 }
1452
1453 /*
1454  *
1455  */
1456 #if CMD_HISTORY
1457         static void
1458 save_mlist(ml, f)
1459         struct mlist *ml;
1460         FILE *f;
1461 {
1462         int histsize = 0;
1463         int n;
1464         char *s;
1465
1466         s = lgetenv("LESSHISTSIZE");
1467         if (s != NULL)
1468                 histsize = atoi(s);
1469         if (histsize == 0)
1470                 histsize = 100;
1471
1472         ml = ml->prev;
1473         for (n = 0;  n < histsize;  n++)
1474         {
1475                 if (ml->string == NULL)
1476                         break;
1477                 ml = ml->prev;
1478         }
1479         for (ml = ml->next;  ml->string != NULL;  ml = ml->next)
1480                 fprintf(f, "\"%s\n", ml->string);
1481 }
1482 #endif /* CMD_HISTORY */
1483
1484 /*
1485  *
1486  */
1487         public void
1488 save_cmdhist()
1489 {
1490 #if CMD_HISTORY
1491         char *filename;
1492         FILE *f;
1493         int modified = 0;
1494
1495         if (mlist_search.modified)
1496                 modified = 1;
1497 #if SHELL_ESCAPE || PIPEC
1498         if (mlist_shell.modified)
1499                 modified = 1;
1500 #endif
1501         if (!modified)
1502                 return;
1503         filename = histfile_name();
1504         if (filename == NULL)
1505                 return;
1506         f = fopen(filename, "w");
1507         free(filename);
1508         if (f == NULL)
1509                 return;
1510 #if HAVE_FCHMOD
1511 {
1512         /* Make history file readable only by owner. */
1513         int do_chmod = 1;
1514 #if HAVE_STAT
1515         struct stat statbuf;
1516         int r = fstat(fileno(f), &statbuf);
1517         if (r < 0 || !S_ISREG(statbuf.st_mode))
1518                 /* Don't chmod if not a regular file. */
1519                 do_chmod = 0;
1520 #endif
1521         if (do_chmod)
1522                 fchmod(fileno(f), 0600);
1523 }
1524 #endif
1525
1526         fprintf(f, "%s\n", HISTFILE_FIRST_LINE);
1527
1528         fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1529         save_mlist(&mlist_search, f);
1530
1531 #if SHELL_ESCAPE || PIPEC
1532         fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1533         save_mlist(&mlist_shell, f);
1534 #endif
1535
1536         fclose(f);
1537 #endif /* CMD_HISTORY */
1538 }