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