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