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(©_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