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