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