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