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