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