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