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