1238730Sdelphij/*
2330571Sdelphij * Copyright (C) 1984-2017  Mark Nudelman
3238730Sdelphij *
4238730Sdelphij * You may distribute under the terms of either the GNU General Public
5238730Sdelphij * License or the Less License, as specified in the README file.
6238730Sdelphij *
7238730Sdelphij * For more information, see the README file.
8238730Sdelphij */
960786Sps
1060786Sps
1160786Sps/*
1260786Sps * Functions which manipulate the command buffer.
1360786Sps * Used only by command() and related functions.
1460786Sps */
1560786Sps
1660786Sps#include "less.h"
1760786Sps#include "cmd.h"
18161475Sdelphij#include "charset.h"
19161475Sdelphij#if HAVE_STAT
20161475Sdelphij#include <sys/stat.h>
21161475Sdelphij#endif
2260786Sps
2360786Spsextern int sc_width;
24161475Sdelphijextern int utf_mode;
2560786Sps
2660786Spsstatic char cmdbuf[CMDBUF_SIZE]; /* Buffer for holding a multi-char command */
2760786Spsstatic int cmd_col;		/* Current column of the cursor */
2860786Spsstatic int prompt_col;		/* Column of cursor just after prompt */
2960786Spsstatic char *cp;		/* Pointer into cmdbuf */
3060786Spsstatic int cmd_offset;		/* Index into cmdbuf of first displayed char */
3160786Spsstatic int literal;		/* Next input char should not be interpreted */
32237613Sdelphijstatic int updown_match = -1;	/* Prefix length in up/down movement */
3360786Sps
3460786Sps#if TAB_COMPLETE_FILENAME
3560786Spsstatic int cmd_complete();
3660786Sps/*
3760786Sps * These variables are statics used by cmd_complete.
3860786Sps */
3960786Spsstatic int in_completion = 0;
4060786Spsstatic char *tk_text;
4160786Spsstatic char *tk_original;
4260786Spsstatic char *tk_ipoint;
43330571Sdelphijstatic char *tk_trial = NULL;
4460786Spsstatic struct textlist tk_tlist;
4560786Sps#endif
4660786Sps
4760786Spsstatic int cmd_left();
4860786Spsstatic int cmd_right();
4960786Sps
5060786Sps#if SPACES_IN_FILENAMES
5160786Spspublic char openquote = '"';
5260786Spspublic char closequote = '"';
5360786Sps#endif
5460786Sps
5560786Sps#if CMD_HISTORY
56161475Sdelphij
57161475Sdelphij/* History file */
58161475Sdelphij#define HISTFILE_FIRST_LINE      ".less-history-file:"
59161475Sdelphij#define HISTFILE_SEARCH_SECTION  ".search"
60161475Sdelphij#define HISTFILE_SHELL_SECTION   ".shell"
61161475Sdelphij
6260786Sps/*
6360786Sps * A mlist structure represents a command history.
6460786Sps */
6560786Spsstruct mlist
6660786Sps{
6760786Sps	struct mlist *next;
6860786Sps	struct mlist *prev;
6960786Sps	struct mlist *curr_mp;
7060786Sps	char *string;
71170256Sdelphij	int modified;
7260786Sps};
7360786Sps
7460786Sps/*
7560786Sps * These are the various command histories that exist.
7660786Sps */
7760786Spsstruct mlist mlist_search =
78170256Sdelphij	{ &mlist_search,  &mlist_search,  &mlist_search,  NULL, 0 };
79330571Sdelphijpublic void *ml_search = (void *) &mlist_search;
8060786Sps
8160786Spsstruct mlist mlist_examine =
82170256Sdelphij	{ &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 };
83330571Sdelphijpublic void *ml_examine = (void *) &mlist_examine;
8460786Sps
8560786Sps#if SHELL_ESCAPE || PIPEC
8660786Spsstruct mlist mlist_shell =
87170256Sdelphij	{ &mlist_shell,   &mlist_shell,   &mlist_shell,   NULL, 0 };
88330571Sdelphijpublic void *ml_shell = (void *) &mlist_shell;
8960786Sps#endif
9060786Sps
9160786Sps#else /* CMD_HISTORY */
9260786Sps
9360786Sps/* If CMD_HISTORY is off, these are just flags. */
94330571Sdelphijpublic void *ml_search = (void *)1;
95330571Sdelphijpublic void *ml_examine = (void *)2;
9660786Sps#if SHELL_ESCAPE || PIPEC
97330571Sdelphijpublic void *ml_shell = (void *)3;
9860786Sps#endif
9960786Sps
10060786Sps#endif /* CMD_HISTORY */
10160786Sps
10260786Sps/*
10360786Sps * History for the current command.
10460786Sps */
10560786Spsstatic struct mlist *curr_mlist = NULL;
10660786Spsstatic int curr_cmdflags;
10760786Sps
108161475Sdelphijstatic char cmd_mbc_buf[MAX_UTF_CHAR_LEN];
109161475Sdelphijstatic int cmd_mbc_buf_len;
110161475Sdelphijstatic int cmd_mbc_buf_index;
11160786Sps
112161475Sdelphij
11360786Sps/*
11460786Sps * Reset command buffer (to empty).
11560786Sps */
11660786Sps	public void
11760786Spscmd_reset()
11860786Sps{
11960786Sps	cp = cmdbuf;
12060786Sps	*cp = '\0';
12160786Sps	cmd_col = 0;
12260786Sps	cmd_offset = 0;
12360786Sps	literal = 0;
124161475Sdelphij	cmd_mbc_buf_len = 0;
125237613Sdelphij	updown_match = -1;
12660786Sps}
12760786Sps
12860786Sps/*
129170256Sdelphij * Clear command line.
13060786Sps */
13160786Sps	public void
13260786Spsclear_cmd()
13360786Sps{
13460786Sps	cmd_col = prompt_col = 0;
135161475Sdelphij	cmd_mbc_buf_len = 0;
136237613Sdelphij	updown_match = -1;
13760786Sps}
13860786Sps
13960786Sps/*
14060786Sps * Display a string, usually as a prompt for input into the command buffer.
14160786Sps */
14260786Sps	public void
14360786Spscmd_putstr(s)
144330571Sdelphij	constant char *s;
14560786Sps{
146161475Sdelphij	LWCHAR prev_ch = 0;
147161475Sdelphij	LWCHAR ch;
148330571Sdelphij	constant char *endline = s + strlen(s);
149161475Sdelphij	while (*s != '\0')
150161475Sdelphij	{
151330571Sdelphij		char *ns = (char *) s;
152330571Sdelphij		int width;
153161475Sdelphij		ch = step_char(&ns, +1, endline);
154161475Sdelphij		while (s < ns)
155161475Sdelphij			putchr(*s++);
156161475Sdelphij		if (!utf_mode)
157330571Sdelphij			width = 1;
158330571Sdelphij		else if (is_composing_char(ch) || is_combining_char(prev_ch, ch))
159330571Sdelphij			width = 0;
160330571Sdelphij		else
161330571Sdelphij			width = is_wide_char(ch) ? 2 : 1;
162330571Sdelphij		cmd_col += width;
163330571Sdelphij		prompt_col += width;
164161475Sdelphij		prev_ch = ch;
165161475Sdelphij	}
16660786Sps}
16760786Sps
16860786Sps/*
16960786Sps * How many characters are in the command buffer?
17060786Sps */
17160786Sps	public int
17260786Spslen_cmdbuf()
17360786Sps{
174161475Sdelphij	char *s = cmdbuf;
175161475Sdelphij	char *endline = s + strlen(s);
176161475Sdelphij	int len = 0;
177161475Sdelphij
178161475Sdelphij	while (*s != '\0')
179161475Sdelphij	{
180161475Sdelphij		step_char(&s, +1, endline);
181161475Sdelphij		len++;
182161475Sdelphij	}
183161475Sdelphij	return (len);
18460786Sps}
18560786Sps
18660786Sps/*
187161475Sdelphij * Common part of cmd_step_right() and cmd_step_left().
188330571Sdelphij * {{ Returning pwidth and bswidth separately is a historical artifact
189330571Sdelphij *    since they're always the same. Maybe clean this up someday. }}
190161475Sdelphij */
191161475Sdelphij	static char *
192161475Sdelphijcmd_step_common(p, ch, len, pwidth, bswidth)
193161475Sdelphij	char *p;
194161475Sdelphij	LWCHAR ch;
195161475Sdelphij	int len;
196161475Sdelphij	int *pwidth;
197161475Sdelphij	int *bswidth;
198161475Sdelphij{
199161475Sdelphij	char *pr;
200330571Sdelphij	int width;
201161475Sdelphij
202161475Sdelphij	if (len == 1)
203161475Sdelphij	{
204161475Sdelphij		pr = prchar((int) ch);
205330571Sdelphij		width = (int) strlen(pr);
206161475Sdelphij	} else
207161475Sdelphij	{
208161475Sdelphij		pr = prutfchar(ch);
209330571Sdelphij		if (is_composing_char(ch))
210330571Sdelphij			width = 0;
211330571Sdelphij		else if (is_ubin_char(ch))
212330571Sdelphij			width = (int) strlen(pr);
213330571Sdelphij		else
214161475Sdelphij		{
215330571Sdelphij			LWCHAR prev_ch = step_char(&p, -1, cmdbuf);
216330571Sdelphij			if (is_combining_char(prev_ch, ch))
217330571Sdelphij				width = 0;
218330571Sdelphij			else
219330571Sdelphij				width = is_wide_char(ch) ? 2 : 1;
220161475Sdelphij		}
221161475Sdelphij	}
222330571Sdelphij	if (pwidth != NULL)
223330571Sdelphij		*pwidth	= width;
224330571Sdelphij	if (bswidth != NULL)
225330571Sdelphij		*bswidth = width;
226161475Sdelphij	return (pr);
227161475Sdelphij}
228161475Sdelphij
229161475Sdelphij/*
230161475Sdelphij * Step a pointer one character right in the command buffer.
231161475Sdelphij */
232161475Sdelphij	static char *
233161475Sdelphijcmd_step_right(pp, pwidth, bswidth)
234161475Sdelphij	char **pp;
235161475Sdelphij	int *pwidth;
236161475Sdelphij	int *bswidth;
237161475Sdelphij{
238161475Sdelphij	char *p = *pp;
239161475Sdelphij	LWCHAR ch = step_char(pp, +1, p + strlen(p));
240161475Sdelphij
241161475Sdelphij	return cmd_step_common(p, ch, *pp - p, pwidth, bswidth);
242161475Sdelphij}
243161475Sdelphij
244161475Sdelphij/*
245161475Sdelphij * Step a pointer one character left in the command buffer.
246161475Sdelphij */
247161475Sdelphij	static char *
248161475Sdelphijcmd_step_left(pp, pwidth, bswidth)
249161475Sdelphij	char **pp;
250161475Sdelphij	int *pwidth;
251161475Sdelphij	int *bswidth;
252161475Sdelphij{
253161475Sdelphij	char *p = *pp;
254161475Sdelphij	LWCHAR ch = step_char(pp, -1, cmdbuf);
255161475Sdelphij
256161475Sdelphij	return cmd_step_common(*pp, ch, p - *pp, pwidth, bswidth);
257161475Sdelphij}
258161475Sdelphij
259161475Sdelphij/*
26060786Sps * Repaint the line from cp onwards.
26160786Sps * Then position the cursor just after the char old_cp (a pointer into cmdbuf).
26260786Sps */
26360786Sps	static void
26460786Spscmd_repaint(old_cp)
265330571Sdelphij	constant char *old_cp;
26660786Sps{
26760786Sps	/*
26860786Sps	 * Repaint the line from the current position.
26960786Sps	 */
27060786Sps	clear_eol();
271161475Sdelphij	while (*cp != '\0')
27260786Sps	{
273161475Sdelphij		char *np = cp;
274161475Sdelphij		int width;
275161475Sdelphij		char *pr = cmd_step_right(&np, &width, NULL);
276161475Sdelphij		if (cmd_col + width >= sc_width)
27760786Sps			break;
278161475Sdelphij		cp = np;
279161475Sdelphij		putstr(pr);
280161475Sdelphij		cmd_col += width;
28160786Sps	}
282161475Sdelphij	while (*cp != '\0')
283161475Sdelphij	{
284161475Sdelphij		char *np = cp;
285161475Sdelphij		int width;
286161475Sdelphij		char *pr = cmd_step_right(&np, &width, NULL);
287161475Sdelphij		if (width > 0)
288161475Sdelphij			break;
289161475Sdelphij		cp = np;
290161475Sdelphij		putstr(pr);
291161475Sdelphij	}
29260786Sps
29360786Sps	/*
29460786Sps	 * Back up the cursor to the correct position.
29560786Sps	 */
29660786Sps	while (cp > old_cp)
29760786Sps		cmd_left();
29860786Sps}
29960786Sps
30060786Sps/*
30160786Sps * Put the cursor at "home" (just after the prompt),
30260786Sps * and set cp to the corresponding char in cmdbuf.
30360786Sps */
30460786Sps	static void
30560786Spscmd_home()
30660786Sps{
30760786Sps	while (cmd_col > prompt_col)
30860786Sps	{
309161475Sdelphij		int width, bswidth;
310161475Sdelphij
311161475Sdelphij		cmd_step_left(&cp, &width, &bswidth);
312161475Sdelphij		while (bswidth-- > 0)
313161475Sdelphij			putbs();
314161475Sdelphij		cmd_col -= width;
31560786Sps	}
31660786Sps
31760786Sps	cp = &cmdbuf[cmd_offset];
31860786Sps}
31960786Sps
32060786Sps/*
32160786Sps * Shift the cmdbuf display left a half-screen.
32260786Sps */
32360786Sps	static void
32460786Spscmd_lshift()
32560786Sps{
32660786Sps	char *s;
32760786Sps	char *save_cp;
32860786Sps	int cols;
32960786Sps
33060786Sps	/*
33160786Sps	 * Start at the first displayed char, count how far to the
33260786Sps	 * right we'd have to move to reach the center of the screen.
33360786Sps	 */
33460786Sps	s = cmdbuf + cmd_offset;
33560786Sps	cols = 0;
33660786Sps	while (cols < (sc_width - prompt_col) / 2 && *s != '\0')
337161475Sdelphij	{
338161475Sdelphij		int width;
339161475Sdelphij		cmd_step_right(&s, &width, NULL);
340161475Sdelphij		cols += width;
341161475Sdelphij	}
342161475Sdelphij	while (*s != '\0')
343161475Sdelphij	{
344161475Sdelphij		int width;
345161475Sdelphij		char *ns = s;
346161475Sdelphij		cmd_step_right(&ns, &width, NULL);
347161475Sdelphij		if (width > 0)
348161475Sdelphij			break;
349161475Sdelphij		s = ns;
350161475Sdelphij	}
35160786Sps
352294286Sdelphij	cmd_offset = (int) (s - cmdbuf);
35360786Sps	save_cp = cp;
35460786Sps	cmd_home();
35560786Sps	cmd_repaint(save_cp);
35660786Sps}
35760786Sps
35860786Sps/*
35960786Sps * Shift the cmdbuf display right a half-screen.
36060786Sps */
36160786Sps	static void
36260786Spscmd_rshift()
36360786Sps{
36460786Sps	char *s;
36560786Sps	char *save_cp;
36660786Sps	int cols;
36760786Sps
36860786Sps	/*
36960786Sps	 * Start at the first displayed char, count how far to the
37060786Sps	 * left we'd have to move to traverse a half-screen width
37160786Sps	 * of displayed characters.
37260786Sps	 */
37360786Sps	s = cmdbuf + cmd_offset;
37460786Sps	cols = 0;
37560786Sps	while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf)
37660786Sps	{
377161475Sdelphij		int width;
378161475Sdelphij		cmd_step_left(&s, &width, NULL);
379161475Sdelphij		cols += width;
38060786Sps	}
38160786Sps
382294286Sdelphij	cmd_offset = (int) (s - cmdbuf);
38360786Sps	save_cp = cp;
38460786Sps	cmd_home();
38560786Sps	cmd_repaint(save_cp);
38660786Sps}
38760786Sps
38860786Sps/*
38960786Sps * Move cursor right one character.
39060786Sps */
39160786Sps	static int
39260786Spscmd_right()
39360786Sps{
394161475Sdelphij	char *pr;
395161475Sdelphij	char *ncp;
396161475Sdelphij	int width;
39760786Sps
39860786Sps	if (*cp == '\0')
39960786Sps	{
400161475Sdelphij		/* Already at the end of the line. */
40160786Sps		return (CC_OK);
40260786Sps	}
403161475Sdelphij	ncp = cp;
404161475Sdelphij	pr = cmd_step_right(&ncp, &width, NULL);
405161475Sdelphij	if (cmd_col + width >= sc_width)
40660786Sps		cmd_lshift();
407161475Sdelphij	else if (cmd_col + width == sc_width - 1 && cp[1] != '\0')
40860786Sps		cmd_lshift();
409161475Sdelphij	cp = ncp;
410161475Sdelphij	cmd_col += width;
411161475Sdelphij	putstr(pr);
412161475Sdelphij	while (*cp != '\0')
413161475Sdelphij	{
414161475Sdelphij		pr = cmd_step_right(&ncp, &width, NULL);
415161475Sdelphij		if (width > 0)
416161475Sdelphij			break;
417161475Sdelphij		putstr(pr);
418161475Sdelphij		cp = ncp;
419161475Sdelphij	}
42060786Sps	return (CC_OK);
42160786Sps}
42260786Sps
42360786Sps/*
42460786Sps * Move cursor left one character.
42560786Sps */
42660786Sps	static int
42760786Spscmd_left()
42860786Sps{
429161475Sdelphij	char *ncp;
430330571Sdelphij	int width = 0;
431330571Sdelphij	int bswidth = 0;
432330571Sdelphij
43360786Sps	if (cp <= cmdbuf)
43460786Sps	{
43560786Sps		/* Already at the beginning of the line */
43660786Sps		return (CC_OK);
43760786Sps	}
438161475Sdelphij	ncp = cp;
439161475Sdelphij	while (ncp > cmdbuf)
440161475Sdelphij	{
441161475Sdelphij		cmd_step_left(&ncp, &width, &bswidth);
442161475Sdelphij		if (width > 0)
443161475Sdelphij			break;
444161475Sdelphij	}
445161475Sdelphij	if (cmd_col < prompt_col + width)
44660786Sps		cmd_rshift();
447161475Sdelphij	cp = ncp;
448161475Sdelphij	cmd_col -= width;
449161475Sdelphij	while (bswidth-- > 0)
45060786Sps		putbs();
45160786Sps	return (CC_OK);
45260786Sps}
45360786Sps
45460786Sps/*
45560786Sps * Insert a char into the command buffer, at the current position.
45660786Sps */
45760786Sps	static int
458161475Sdelphijcmd_ichar(cs, clen)
459161475Sdelphij	char *cs;
460161475Sdelphij	int clen;
46160786Sps{
46260786Sps	char *s;
46360786Sps
464161475Sdelphij	if (strlen(cmdbuf) + clen >= sizeof(cmdbuf)-1)
46560786Sps	{
466161475Sdelphij		/* No room in the command buffer for another char. */
46760786Sps		bell();
46860786Sps		return (CC_ERROR);
46960786Sps	}
47060786Sps
47160786Sps	/*
472161475Sdelphij	 * Make room for the new character (shift the tail of the buffer right).
47360786Sps	 */
47460786Sps	for (s = &cmdbuf[strlen(cmdbuf)];  s >= cp;  s--)
475161475Sdelphij		s[clen] = s[0];
47660786Sps	/*
477161475Sdelphij	 * Insert the character into the buffer.
478161475Sdelphij	 */
479161475Sdelphij	for (s = cp;  s < cp + clen;  s++)
480161475Sdelphij		*s = *cs++;
481161475Sdelphij	/*
48260786Sps	 * Reprint the tail of the line from the inserted char.
48360786Sps	 */
484237613Sdelphij	updown_match = -1;
48560786Sps	cmd_repaint(cp);
48660786Sps	cmd_right();
48760786Sps	return (CC_OK);
48860786Sps}
48960786Sps
49060786Sps/*
49160786Sps * Backspace in the command buffer.
49260786Sps * Delete the char to the left of the cursor.
49360786Sps */
49460786Sps	static int
49560786Spscmd_erase()
49660786Sps{
497330571Sdelphij	char *s;
498161475Sdelphij	int clen;
49960786Sps
50060786Sps	if (cp == cmdbuf)
50160786Sps	{
50260786Sps		/*
50360786Sps		 * Backspace past beginning of the buffer:
50460786Sps		 * this usually means abort the command.
50560786Sps		 */
50660786Sps		return (CC_QUIT);
50760786Sps	}
50860786Sps	/*
50960786Sps	 * Move cursor left (to the char being erased).
51060786Sps	 */
511161475Sdelphij	s = cp;
51260786Sps	cmd_left();
513294286Sdelphij	clen = (int) (s - cp);
514161475Sdelphij
51560786Sps	/*
51660786Sps	 * Remove the char from the buffer (shift the buffer left).
51760786Sps	 */
518161475Sdelphij	for (s = cp;  ;  s++)
519161475Sdelphij	{
520161475Sdelphij		s[0] = s[clen];
521161475Sdelphij		if (s[0] == '\0')
522161475Sdelphij			break;
523161475Sdelphij	}
524161475Sdelphij
52560786Sps	/*
52660786Sps	 * Repaint the buffer after the erased char.
52760786Sps	 */
528237613Sdelphij	updown_match = -1;
52960786Sps	cmd_repaint(cp);
53060786Sps
53160786Sps	/*
53260786Sps	 * We say that erasing the entire command string causes us
53360786Sps	 * to abort the current command, if CF_QUIT_ON_ERASE is set.
53460786Sps	 */
53560786Sps	if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0')
53660786Sps		return (CC_QUIT);
53760786Sps	return (CC_OK);
53860786Sps}
53960786Sps
54060786Sps/*
54160786Sps * Delete the char under the cursor.
54260786Sps */
54360786Sps	static int
54460786Spscmd_delete()
54560786Sps{
54660786Sps	if (*cp == '\0')
54760786Sps	{
548161475Sdelphij		/* At end of string; there is no char under the cursor. */
54960786Sps		return (CC_OK);
55060786Sps	}
55160786Sps	/*
55260786Sps	 * Move right, then use cmd_erase.
55360786Sps	 */
55460786Sps	cmd_right();
55560786Sps	cmd_erase();
55660786Sps	return (CC_OK);
55760786Sps}
55860786Sps
55960786Sps/*
56060786Sps * Delete the "word" to the left of the cursor.
56160786Sps */
56260786Sps	static int
56360786Spscmd_werase()
56460786Sps{
56560786Sps	if (cp > cmdbuf && cp[-1] == ' ')
56660786Sps	{
56760786Sps		/*
56860786Sps		 * If the char left of cursor is a space,
56960786Sps		 * erase all the spaces left of cursor (to the first non-space).
57060786Sps		 */
57160786Sps		while (cp > cmdbuf && cp[-1] == ' ')
57260786Sps			(void) cmd_erase();
57360786Sps	} else
57460786Sps	{
57560786Sps		/*
57660786Sps		 * If the char left of cursor is not a space,
57760786Sps		 * erase all the nonspaces left of cursor (the whole "word").
57860786Sps		 */
57960786Sps		while (cp > cmdbuf && cp[-1] != ' ')
58060786Sps			(void) cmd_erase();
58160786Sps	}
58260786Sps	return (CC_OK);
58360786Sps}
58460786Sps
58560786Sps/*
58660786Sps * Delete the "word" under the cursor.
58760786Sps */
58860786Sps	static int
58960786Spscmd_wdelete()
59060786Sps{
59160786Sps	if (*cp == ' ')
59260786Sps	{
59360786Sps		/*
59460786Sps		 * If the char under the cursor is a space,
59560786Sps		 * delete it and all the spaces right of cursor.
59660786Sps		 */
59760786Sps		while (*cp == ' ')
59860786Sps			(void) cmd_delete();
59960786Sps	} else
60060786Sps	{
60160786Sps		/*
60260786Sps		 * If the char under the cursor is not a space,
60360786Sps		 * delete it and all nonspaces right of cursor (the whole word).
60460786Sps		 */
60560786Sps		while (*cp != ' ' && *cp != '\0')
60660786Sps			(void) cmd_delete();
60760786Sps	}
60860786Sps	return (CC_OK);
60960786Sps}
61060786Sps
61160786Sps/*
61260786Sps * Delete all chars in the command buffer.
61360786Sps */
61460786Sps	static int
61560786Spscmd_kill()
61660786Sps{
61760786Sps	if (cmdbuf[0] == '\0')
61860786Sps	{
619161475Sdelphij		/* Buffer is already empty; abort the current command. */
62060786Sps		return (CC_QUIT);
62160786Sps	}
62260786Sps	cmd_offset = 0;
62360786Sps	cmd_home();
62460786Sps	*cp = '\0';
625237613Sdelphij	updown_match = -1;
62660786Sps	cmd_repaint(cp);
62760786Sps
62860786Sps	/*
62960786Sps	 * We say that erasing the entire command string causes us
63060786Sps	 * to abort the current command, if CF_QUIT_ON_ERASE is set.
63160786Sps	 */
63260786Sps	if (curr_cmdflags & CF_QUIT_ON_ERASE)
63360786Sps		return (CC_QUIT);
63460786Sps	return (CC_OK);
63560786Sps}
63660786Sps
63760786Sps/*
63860786Sps * Select an mlist structure to be the current command history.
63960786Sps */
64060786Sps	public void
64160786Spsset_mlist(mlist, cmdflags)
64260786Sps	void *mlist;
64360786Sps	int cmdflags;
64460786Sps{
645191930Sdelphij#if CMD_HISTORY
64660786Sps	curr_mlist = (struct mlist *) mlist;
64760786Sps	curr_cmdflags = cmdflags;
648161475Sdelphij
649161475Sdelphij	/* Make sure the next up-arrow moves to the last string in the mlist. */
650161475Sdelphij	if (curr_mlist != NULL)
651161475Sdelphij		curr_mlist->curr_mp = curr_mlist;
652191930Sdelphij#endif
65360786Sps}
65460786Sps
65560786Sps#if CMD_HISTORY
65660786Sps/*
65760786Sps * Move up or down in the currently selected command history list.
658237613Sdelphij * Only consider entries whose first updown_match chars are equal to
659237613Sdelphij * cmdbuf's corresponding chars.
66060786Sps */
66160786Sps	static int
66260786Spscmd_updown(action)
66360786Sps	int action;
66460786Sps{
665330571Sdelphij	constant char *s;
666237613Sdelphij	struct mlist *ml;
66760786Sps
66860786Sps	if (curr_mlist == NULL)
66960786Sps	{
67060786Sps		/*
67160786Sps		 * The current command has no history list.
67260786Sps		 */
67360786Sps		bell();
67460786Sps		return (CC_OK);
67560786Sps	}
676237613Sdelphij
677237613Sdelphij	if (updown_match < 0)
678237613Sdelphij	{
679294286Sdelphij		updown_match = (int) (cp - cmdbuf);
680237613Sdelphij	}
681237613Sdelphij
68260786Sps	/*
683237613Sdelphij	 * Find the next history entry which matches.
68460786Sps	 */
685237613Sdelphij	for (ml = curr_mlist->curr_mp;;)
686237613Sdelphij	{
687237613Sdelphij		ml = (action == EC_UP) ? ml->prev : ml->next;
688237613Sdelphij		if (ml == curr_mlist)
689237613Sdelphij		{
690237613Sdelphij			/*
691237613Sdelphij			 * We reached the end (or beginning) of the list.
692237613Sdelphij			 */
693237613Sdelphij			break;
694237613Sdelphij		}
695237613Sdelphij		if (strncmp(cmdbuf, ml->string, updown_match) == 0)
696237613Sdelphij		{
697237613Sdelphij			/*
698237613Sdelphij			 * This entry matches; stop here.
699237613Sdelphij			 * Copy the entry into cmdbuf and echo it on the screen.
700237613Sdelphij			 */
701237613Sdelphij			curr_mlist->curr_mp = ml;
702237613Sdelphij			s = ml->string;
703237613Sdelphij			if (s == NULL)
704237613Sdelphij				s = "";
705237613Sdelphij			cmd_home();
706237613Sdelphij			clear_eol();
707250592Sdelphij			strcpy(cmdbuf, s);
708237613Sdelphij			for (cp = cmdbuf;  *cp != '\0';  )
709237613Sdelphij				cmd_right();
710237613Sdelphij			return (CC_OK);
711237613Sdelphij		}
712237613Sdelphij	}
71360786Sps	/*
714237613Sdelphij	 * We didn't find a history entry that matches.
71560786Sps	 */
716237613Sdelphij	bell();
71760786Sps	return (CC_OK);
71860786Sps}
71960786Sps#endif
72060786Sps
72160786Sps/*
722294286Sdelphij * Add a string to an mlist.
72360786Sps */
72460786Sps	public void
725294286Sdelphijcmd_addhist(mlist, cmd, modified)
72660786Sps	struct mlist *mlist;
727330571Sdelphij	constant char *cmd;
728294286Sdelphij	int modified;
72960786Sps{
73060786Sps#if CMD_HISTORY
73160786Sps	struct mlist *ml;
73260786Sps
73360786Sps	/*
73460786Sps	 * Don't save a trivial command.
73560786Sps	 */
73660786Sps	if (strlen(cmd) == 0)
73760786Sps		return;
738161475Sdelphij
73960786Sps	/*
740161475Sdelphij	 * Save the command unless it's a duplicate of the
741161475Sdelphij	 * last command in the history.
74260786Sps	 */
743161475Sdelphij	ml = mlist->prev;
744161475Sdelphij	if (ml == mlist || strcmp(ml->string, cmd) != 0)
74560786Sps	{
74660786Sps		/*
74760786Sps		 * Did not find command in history.
74860786Sps		 * Save the command and put it at the end of the history list.
74960786Sps		 */
75060786Sps		ml = (struct mlist *) ecalloc(1, sizeof(struct mlist));
75160786Sps		ml->string = save(cmd);
752294286Sdelphij		ml->modified = modified;
75360786Sps		ml->next = mlist;
75460786Sps		ml->prev = mlist->prev;
75560786Sps		mlist->prev->next = ml;
75660786Sps		mlist->prev = ml;
75760786Sps	}
75860786Sps	/*
75960786Sps	 * Point to the cmd just after the just-accepted command.
76060786Sps	 * Thus, an UPARROW will always retrieve the previous command.
76160786Sps	 */
76260786Sps	mlist->curr_mp = ml->next;
76360786Sps#endif
76460786Sps}
76560786Sps
76660786Sps/*
76760786Sps * Accept the command in the command buffer.
76860786Sps * Add it to the currently selected history list.
76960786Sps */
77060786Sps	public void
77160786Spscmd_accept()
77260786Sps{
77360786Sps#if CMD_HISTORY
77460786Sps	/*
77560786Sps	 * Nothing to do if there is no currently selected history list.
77660786Sps	 */
77760786Sps	if (curr_mlist == NULL)
77860786Sps		return;
779294286Sdelphij	cmd_addhist(curr_mlist, cmdbuf, 1);
780170256Sdelphij	curr_mlist->modified = 1;
78160786Sps#endif
78260786Sps}
78360786Sps
78460786Sps/*
78560786Sps * Try to perform a line-edit function on the command buffer,
78660786Sps * using a specified char as a line-editing command.
78760786Sps * Returns:
78860786Sps *	CC_PASS	The char does not invoke a line edit function.
78960786Sps *	CC_OK	Line edit function done.
79060786Sps *	CC_QUIT	The char requests the current command to be aborted.
79160786Sps */
79260786Sps	static int
79360786Spscmd_edit(c)
79460786Sps	int c;
79560786Sps{
79660786Sps	int action;
79760786Sps	int flags;
79860786Sps
79960786Sps#if TAB_COMPLETE_FILENAME
80060786Sps#define	not_in_completion()	in_completion = 0
80160786Sps#else
80260786Sps#define	not_in_completion()
80360786Sps#endif
80460786Sps
80560786Sps	/*
80660786Sps	 * See if the char is indeed a line-editing command.
80760786Sps	 */
80860786Sps	flags = 0;
80960786Sps#if CMD_HISTORY
81060786Sps	if (curr_mlist == NULL)
81160786Sps		/*
81260786Sps		 * No current history; don't accept history manipulation cmds.
81360786Sps		 */
81460786Sps		flags |= EC_NOHISTORY;
81560786Sps#endif
81660786Sps#if TAB_COMPLETE_FILENAME
81760786Sps	if (curr_mlist == ml_search)
81860786Sps		/*
81960786Sps		 * In a search command; don't accept file-completion cmds.
82060786Sps		 */
82160786Sps		flags |= EC_NOCOMPLETE;
82260786Sps#endif
82360786Sps
82460786Sps	action = editchar(c, flags);
82560786Sps
82660786Sps	switch (action)
82760786Sps	{
82860786Sps	case EC_RIGHT:
82960786Sps		not_in_completion();
83060786Sps		return (cmd_right());
83160786Sps	case EC_LEFT:
83260786Sps		not_in_completion();
83360786Sps		return (cmd_left());
83460786Sps	case EC_W_RIGHT:
83560786Sps		not_in_completion();
83660786Sps		while (*cp != '\0' && *cp != ' ')
83760786Sps			cmd_right();
83860786Sps		while (*cp == ' ')
83960786Sps			cmd_right();
84060786Sps		return (CC_OK);
84160786Sps	case EC_W_LEFT:
84260786Sps		not_in_completion();
84360786Sps		while (cp > cmdbuf && cp[-1] == ' ')
84460786Sps			cmd_left();
84560786Sps		while (cp > cmdbuf && cp[-1] != ' ')
84660786Sps			cmd_left();
84760786Sps		return (CC_OK);
84860786Sps	case EC_HOME:
84960786Sps		not_in_completion();
85060786Sps		cmd_offset = 0;
85160786Sps		cmd_home();
85260786Sps		cmd_repaint(cp);
85360786Sps		return (CC_OK);
85460786Sps	case EC_END:
85560786Sps		not_in_completion();
85660786Sps		while (*cp != '\0')
85760786Sps			cmd_right();
85860786Sps		return (CC_OK);
85960786Sps	case EC_INSERT:
86060786Sps		not_in_completion();
86160786Sps		return (CC_OK);
86260786Sps	case EC_BACKSPACE:
86360786Sps		not_in_completion();
86460786Sps		return (cmd_erase());
86560786Sps	case EC_LINEKILL:
86660786Sps		not_in_completion();
86760786Sps		return (cmd_kill());
868221715Sdelphij	case EC_ABORT:
869221715Sdelphij		not_in_completion();
870221715Sdelphij		(void) cmd_kill();
871221715Sdelphij		return (CC_QUIT);
87260786Sps	case EC_W_BACKSPACE:
87360786Sps		not_in_completion();
87460786Sps		return (cmd_werase());
87560786Sps	case EC_DELETE:
87660786Sps		not_in_completion();
87760786Sps		return (cmd_delete());
87860786Sps	case EC_W_DELETE:
87960786Sps		not_in_completion();
88060786Sps		return (cmd_wdelete());
88160786Sps	case EC_LITERAL:
88260786Sps		literal = 1;
88360786Sps		return (CC_OK);
88460786Sps#if CMD_HISTORY
88560786Sps	case EC_UP:
88660786Sps	case EC_DOWN:
88760786Sps		not_in_completion();
88860786Sps		return (cmd_updown(action));
88960786Sps#endif
89060786Sps#if TAB_COMPLETE_FILENAME
89160786Sps	case EC_F_COMPLETE:
89260786Sps	case EC_B_COMPLETE:
89360786Sps	case EC_EXPAND:
89460786Sps		return (cmd_complete(action));
89560786Sps#endif
89660786Sps	case EC_NOACTION:
89760786Sps		return (CC_OK);
89860786Sps	default:
89960786Sps		not_in_completion();
90060786Sps		return (CC_PASS);
90160786Sps	}
90260786Sps}
90360786Sps
90460786Sps#if TAB_COMPLETE_FILENAME
90560786Sps/*
90660786Sps * Insert a string into the command buffer, at the current position.
90760786Sps */
90860786Sps	static int
90960786Spscmd_istr(str)
91060786Sps	char *str;
91160786Sps{
91260786Sps	char *s;
91360786Sps	int action;
914161475Sdelphij	char *endline = str + strlen(str);
91560786Sps
916161475Sdelphij	for (s = str;  *s != '\0';  )
91760786Sps	{
918161475Sdelphij		char *os = s;
919161475Sdelphij		step_char(&s, +1, endline);
920161475Sdelphij		action = cmd_ichar(os, s - os);
92160786Sps		if (action != CC_OK)
92260786Sps		{
92360786Sps			bell();
92460786Sps			return (action);
92560786Sps		}
92660786Sps	}
92760786Sps	return (CC_OK);
92860786Sps}
92960786Sps
93060786Sps/*
93160786Sps * Find the beginning and end of the "current" word.
93260786Sps * This is the word which the cursor (cp) is inside or at the end of.
93360786Sps * Return pointer to the beginning of the word and put the
93460786Sps * cursor at the end of the word.
93560786Sps */
93660786Sps	static char *
93760786Spsdelimit_word()
93860786Sps{
93960786Sps	char *word;
94060786Sps#if SPACES_IN_FILENAMES
94160786Sps	char *p;
942128345Stjr	int delim_quoted = 0;
943128345Stjr	int meta_quoted = 0;
944330571Sdelphij	constant char *esc = get_meta_escape();
945294286Sdelphij	int esclen = (int) strlen(esc);
94660786Sps#endif
94760786Sps
94860786Sps	/*
94960786Sps	 * Move cursor to end of word.
95060786Sps	 */
95160786Sps	if (*cp != ' ' && *cp != '\0')
95260786Sps	{
95360786Sps		/*
95460786Sps		 * Cursor is on a nonspace.
95560786Sps		 * Move cursor right to the next space.
95660786Sps		 */
95760786Sps		while (*cp != ' ' && *cp != '\0')
95860786Sps			cmd_right();
95960786Sps	} else if (cp > cmdbuf && cp[-1] != ' ')
96060786Sps	{
96160786Sps		/*
96260786Sps		 * Cursor is on a space, and char to the left is a nonspace.
96360786Sps		 * We're already at the end of the word.
96460786Sps		 */
96560786Sps		;
966128345Stjr#if 0
96760786Sps	} else
96860786Sps	{
96960786Sps		/*
97060786Sps		 * Cursor is on a space and char to the left is a space.
97160786Sps		 * Huh? There's no word here.
97260786Sps		 */
97360786Sps		return (NULL);
974128345Stjr#endif
97560786Sps	}
97660786Sps	/*
977128345Stjr	 * Find the beginning of the word which the cursor is in.
97860786Sps	 */
97960786Sps	if (cp == cmdbuf)
98060786Sps		return (NULL);
98160786Sps#if SPACES_IN_FILENAMES
98260786Sps	/*
98360786Sps	 * If we have an unbalanced quote (that is, an open quote
98460786Sps	 * without a corresponding close quote), we return everything
98560786Sps	 * from the open quote, including spaces.
98660786Sps	 */
987128345Stjr	for (word = cmdbuf;  word < cp;  word++)
988128345Stjr		if (*word != ' ')
989128345Stjr			break;
990128345Stjr	if (word >= cp)
991128345Stjr		return (cp);
99260786Sps	for (p = cmdbuf;  p < cp;  p++)
99360786Sps	{
994128345Stjr		if (meta_quoted)
99560786Sps		{
996128345Stjr			meta_quoted = 0;
997128345Stjr		} else if (esclen > 0 && p + esclen < cp &&
998128345Stjr		           strncmp(p, esc, esclen) == 0)
99960786Sps		{
1000128345Stjr			meta_quoted = 1;
1001128345Stjr			p += esclen - 1;
1002128345Stjr		} else if (delim_quoted)
1003128345Stjr		{
1004128345Stjr			if (*p == closequote)
1005128345Stjr				delim_quoted = 0;
1006128345Stjr		} else /* (!delim_quoted) */
1007128345Stjr		{
1008128345Stjr			if (*p == openquote)
1009128345Stjr				delim_quoted = 1;
1010128345Stjr			else if (*p == ' ')
1011128345Stjr				word = p+1;
101260786Sps		}
101360786Sps	}
101460786Sps#endif
101560786Sps	return (word);
101660786Sps}
101760786Sps
101860786Sps/*
101960786Sps * Set things up to enter completion mode.
102060786Sps * Expand the word under the cursor into a list of filenames
102160786Sps * which start with that word, and set tk_text to that list.
102260786Sps */
102360786Sps	static void
102460786Spsinit_compl()
102560786Sps{
102660786Sps	char *word;
102760786Sps	char c;
102860786Sps
102960786Sps	/*
103060786Sps	 * Get rid of any previous tk_text.
103160786Sps	 */
103260786Sps	if (tk_text != NULL)
103360786Sps	{
103460786Sps		free(tk_text);
103560786Sps		tk_text = NULL;
103660786Sps	}
103760786Sps	/*
103860786Sps	 * Find the original (uncompleted) word in the command buffer.
103960786Sps	 */
104060786Sps	word = delimit_word();
104160786Sps	if (word == NULL)
104260786Sps		return;
104360786Sps	/*
104460786Sps	 * Set the insertion point to the point in the command buffer
104560786Sps	 * where the original (uncompleted) word now sits.
104660786Sps	 */
104760786Sps	tk_ipoint = word;
104860786Sps	/*
104960786Sps	 * Save the original (uncompleted) word
105060786Sps	 */
105160786Sps	if (tk_original != NULL)
105260786Sps		free(tk_original);
105360786Sps	tk_original = (char *) ecalloc(cp-word+1, sizeof(char));
105460786Sps	strncpy(tk_original, word, cp-word);
105560786Sps	/*
105660786Sps	 * Get the expanded filename.
105760786Sps	 * This may result in a single filename, or
105860786Sps	 * a blank-separated list of filenames.
105960786Sps	 */
106060786Sps	c = *cp;
106160786Sps	*cp = '\0';
1062128345Stjr	if (*word != openquote)
1063128345Stjr	{
1064128345Stjr		tk_text = fcomplete(word);
1065128345Stjr	} else
1066128345Stjr	{
1067238730Sdelphij#if MSDOS_COMPILER
1068238730Sdelphij		char *qword = NULL;
1069238730Sdelphij#else
1070128345Stjr		char *qword = shell_quote(word+1);
1071238730Sdelphij#endif
1072128345Stjr		if (qword == NULL)
1073128345Stjr			tk_text = fcomplete(word+1);
1074128345Stjr		else
1075128345Stjr		{
1076128345Stjr			tk_text = fcomplete(qword);
1077128345Stjr			free(qword);
1078128345Stjr		}
1079128345Stjr	}
108060786Sps	*cp = c;
108160786Sps}
108260786Sps
108360786Sps/*
108460786Sps * Return the next word in the current completion list.
108560786Sps */
108660786Sps	static char *
108760786Spsnext_compl(action, prev)
108860786Sps	int action;
108960786Sps	char *prev;
109060786Sps{
109160786Sps	switch (action)
109260786Sps	{
109360786Sps	case EC_F_COMPLETE:
109460786Sps		return (forw_textlist(&tk_tlist, prev));
109560786Sps	case EC_B_COMPLETE:
109660786Sps		return (back_textlist(&tk_tlist, prev));
109760786Sps	}
109860786Sps	/* Cannot happen */
109960786Sps	return ("?");
110060786Sps}
110160786Sps
110260786Sps/*
110360786Sps * Complete the filename before (or under) the cursor.
110460786Sps * cmd_complete may be called multiple times.  The global in_completion
110560786Sps * remembers whether this call is the first time (create the list),
110660786Sps * or a subsequent time (step thru the list).
110760786Sps */
110860786Sps	static int
110960786Spscmd_complete(action)
111060786Sps	int action;
111160786Sps{
111260786Sps	char *s;
111360786Sps
111460786Sps	if (!in_completion || action == EC_EXPAND)
111560786Sps	{
111660786Sps		/*
111760786Sps		 * Expand the word under the cursor and
111860786Sps		 * use the first word in the expansion
111960786Sps		 * (or the entire expansion if we're doing EC_EXPAND).
112060786Sps		 */
112160786Sps		init_compl();
112260786Sps		if (tk_text == NULL)
112360786Sps		{
112460786Sps			bell();
112560786Sps			return (CC_OK);
112660786Sps		}
112760786Sps		if (action == EC_EXPAND)
112860786Sps		{
112960786Sps			/*
113060786Sps			 * Use the whole list.
113160786Sps			 */
113260786Sps			tk_trial = tk_text;
113360786Sps		} else
113460786Sps		{
113560786Sps			/*
113660786Sps			 * Use the first filename in the list.
113760786Sps			 */
113860786Sps			in_completion = 1;
113960786Sps			init_textlist(&tk_tlist, tk_text);
114060786Sps			tk_trial = next_compl(action, (char*)NULL);
114160786Sps		}
114260786Sps	} else
114360786Sps	{
114460786Sps		/*
114560786Sps		 * We already have a completion list.
114660786Sps		 * Use the next/previous filename from the list.
114760786Sps		 */
114860786Sps		tk_trial = next_compl(action, tk_trial);
114960786Sps	}
115060786Sps
115160786Sps  	/*
115260786Sps  	 * Remove the original word, or the previous trial completion.
115360786Sps  	 */
115460786Sps	while (cp > tk_ipoint)
115560786Sps		(void) cmd_erase();
115660786Sps
115760786Sps	if (tk_trial == NULL)
115860786Sps	{
115960786Sps		/*
116060786Sps		 * There are no more trial completions.
116160786Sps		 * Insert the original (uncompleted) filename.
116260786Sps		 */
116360786Sps		in_completion = 0;
116460786Sps		if (cmd_istr(tk_original) != CC_OK)
116560786Sps			goto fail;
116660786Sps	} else
116760786Sps	{
116860786Sps		/*
116960786Sps		 * Insert trial completion.
117060786Sps		 */
117160786Sps		if (cmd_istr(tk_trial) != CC_OK)
117260786Sps			goto fail;
117360786Sps		/*
117460786Sps		 * If it is a directory, append a slash.
117560786Sps		 */
117660786Sps		if (is_dir(tk_trial))
117760786Sps		{
117860786Sps			if (cp > cmdbuf && cp[-1] == closequote)
117960786Sps				(void) cmd_erase();
118060786Sps			s = lgetenv("LESSSEPARATOR");
118160786Sps			if (s == NULL)
118260786Sps				s = PATHNAME_SEP;
118360786Sps			if (cmd_istr(s) != CC_OK)
118460786Sps				goto fail;
118560786Sps		}
118660786Sps	}
118760786Sps
118860786Sps	return (CC_OK);
118960786Sps
119060786Spsfail:
119160786Sps	in_completion = 0;
119260786Sps	bell();
119360786Sps	return (CC_OK);
119460786Sps}
119560786Sps
119660786Sps#endif /* TAB_COMPLETE_FILENAME */
119760786Sps
119860786Sps/*
119960786Sps * Process a single character of a multi-character command, such as
120060786Sps * a number, or the pattern of a search command.
120160786Sps * Returns:
120260786Sps *	CC_OK		The char was accepted.
120360786Sps *	CC_QUIT		The char requests the command to be aborted.
120460786Sps *	CC_ERROR	The char could not be accepted due to an error.
120560786Sps */
120660786Sps	public int
120760786Spscmd_char(c)
120860786Sps	int c;
120960786Sps{
121060786Sps	int action;
1211161475Sdelphij	int len;
121260786Sps
1213161475Sdelphij	if (!utf_mode)
1214161475Sdelphij	{
1215161475Sdelphij		cmd_mbc_buf[0] = c;
1216161475Sdelphij		len = 1;
1217161475Sdelphij	} else
1218161475Sdelphij	{
1219161475Sdelphij		/* Perform strict validation in all possible cases.  */
1220161475Sdelphij		if (cmd_mbc_buf_len == 0)
1221161475Sdelphij		{
1222161475Sdelphij		 retry:
1223161475Sdelphij			cmd_mbc_buf_index = 1;
1224161475Sdelphij			*cmd_mbc_buf = c;
1225161475Sdelphij			if (IS_ASCII_OCTET(c))
1226161475Sdelphij				cmd_mbc_buf_len = 1;
1227330571Sdelphij#if MSDOS_COMPILER || OS2
1228330571Sdelphij			else if (c == (unsigned char) '\340' && IS_ASCII_OCTET(peekcc()))
1229330571Sdelphij			{
1230330571Sdelphij				/* Assume a special key. */
1231330571Sdelphij				cmd_mbc_buf_len = 1;
1232330571Sdelphij			}
1233330571Sdelphij#endif
1234161475Sdelphij			else if (IS_UTF8_LEAD(c))
1235161475Sdelphij			{
1236161475Sdelphij				cmd_mbc_buf_len = utf_len(c);
1237161475Sdelphij				return (CC_OK);
1238161475Sdelphij			} else
1239161475Sdelphij			{
1240161475Sdelphij				/* UTF8_INVALID or stray UTF8_TRAIL */
1241161475Sdelphij				bell();
1242161475Sdelphij				return (CC_ERROR);
1243161475Sdelphij			}
1244161475Sdelphij		} else if (IS_UTF8_TRAIL(c))
1245161475Sdelphij		{
1246161475Sdelphij			cmd_mbc_buf[cmd_mbc_buf_index++] = c;
1247161475Sdelphij			if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1248161475Sdelphij				return (CC_OK);
1249294286Sdelphij			if (!is_utf8_well_formed(cmd_mbc_buf, cmd_mbc_buf_index))
1250161475Sdelphij			{
1251161475Sdelphij				/* complete, but not well formed (non-shortest form), sequence */
1252161475Sdelphij				cmd_mbc_buf_len = 0;
1253161475Sdelphij				bell();
1254161475Sdelphij				return (CC_ERROR);
1255161475Sdelphij			}
1256161475Sdelphij		} else
1257161475Sdelphij		{
1258161475Sdelphij			/* Flush incomplete (truncated) sequence.  */
1259161475Sdelphij			cmd_mbc_buf_len = 0;
1260161475Sdelphij			bell();
1261161475Sdelphij			/* Handle new char.  */
1262161475Sdelphij			goto retry;
1263161475Sdelphij		}
1264161475Sdelphij
1265161475Sdelphij		len = cmd_mbc_buf_len;
1266161475Sdelphij		cmd_mbc_buf_len = 0;
1267161475Sdelphij	}
1268161475Sdelphij
126960786Sps	if (literal)
127060786Sps	{
127160786Sps		/*
127260786Sps		 * Insert the char, even if it is a line-editing char.
127360786Sps		 */
127460786Sps		literal = 0;
1275161475Sdelphij		return (cmd_ichar(cmd_mbc_buf, len));
127660786Sps	}
127760786Sps
127860786Sps	/*
1279161475Sdelphij	 * See if it is a line-editing character.
128060786Sps	 */
1281161475Sdelphij	if (in_mca() && len == 1)
128260786Sps	{
128360786Sps		action = cmd_edit(c);
128460786Sps		switch (action)
128560786Sps		{
128660786Sps		case CC_OK:
128760786Sps		case CC_QUIT:
128860786Sps			return (action);
128960786Sps		case CC_PASS:
129060786Sps			break;
129160786Sps		}
129260786Sps	}
129360786Sps
129460786Sps	/*
129560786Sps	 * Insert the char into the command buffer.
129660786Sps	 */
1297161475Sdelphij	return (cmd_ichar(cmd_mbc_buf, len));
129860786Sps}
129960786Sps
130060786Sps/*
130160786Sps * Return the number currently in the command buffer.
130260786Sps */
1303128345Stjr	public LINENUM
1304170256Sdelphijcmd_int(frac)
1305170256Sdelphij	long *frac;
130660786Sps{
1307170256Sdelphij	char *p;
1308128345Stjr	LINENUM n = 0;
1309170256Sdelphij	int err;
1310128345Stjr
1311170256Sdelphij	for (p = cmdbuf;  *p >= '0' && *p <= '9';  p++)
1312170256Sdelphij		n = (n * 10) + (*p - '0');
1313170256Sdelphij	*frac = 0;
1314170256Sdelphij	if (*p++ == '.')
1315170256Sdelphij	{
1316170256Sdelphij		*frac = getfraction(&p, NULL, &err);
1317170256Sdelphij		/* {{ do something if err is set? }} */
1318170256Sdelphij	}
1319128345Stjr	return (n);
132060786Sps}
132160786Sps
132260786Sps/*
132360786Sps * Return a pointer to the command buffer.
132460786Sps */
132560786Sps	public char *
132660786Spsget_cmdbuf()
132760786Sps{
132860786Sps	return (cmdbuf);
132960786Sps}
1330161475Sdelphij
1331191930Sdelphij#if CMD_HISTORY
1332170256Sdelphij/*
1333170256Sdelphij * Return the last (most recent) string in the current command history.
1334170256Sdelphij */
1335170256Sdelphij	public char *
1336170256Sdelphijcmd_lastpattern()
1337170256Sdelphij{
1338170256Sdelphij	if (curr_mlist == NULL)
1339170256Sdelphij		return (NULL);
1340170256Sdelphij	return (curr_mlist->curr_mp->prev->string);
1341170256Sdelphij}
1342191930Sdelphij#endif
1343170256Sdelphij
1344161475Sdelphij#if CMD_HISTORY
1345161475Sdelphij/*
1346294286Sdelphij */
1347294286Sdelphij	static int
1348294286Sdelphijmlist_size(ml)
1349294286Sdelphij	struct mlist *ml;
1350294286Sdelphij{
1351294286Sdelphij	int size = 0;
1352294286Sdelphij	for (ml = ml->next;  ml->string != NULL;  ml = ml->next)
1353294286Sdelphij		++size;
1354294286Sdelphij	return size;
1355294286Sdelphij}
1356294286Sdelphij
1357294286Sdelphij/*
1358161475Sdelphij * Get the name of the history file.
1359161475Sdelphij */
1360161475Sdelphij	static char *
1361161475Sdelphijhistfile_name()
1362161475Sdelphij{
1363161475Sdelphij	char *home;
1364161475Sdelphij	char *name;
1365161475Sdelphij	int len;
1366161475Sdelphij
1367161475Sdelphij	/* See if filename is explicitly specified by $LESSHISTFILE. */
1368161475Sdelphij	name = lgetenv("LESSHISTFILE");
1369161475Sdelphij	if (name != NULL && *name != '\0')
1370161475Sdelphij	{
1371170256Sdelphij		if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1372161475Sdelphij			/* $LESSHISTFILE == "-" means don't use a history file. */
1373161475Sdelphij			return (NULL);
1374161475Sdelphij		return (save(name));
1375161475Sdelphij	}
1376161475Sdelphij
1377294286Sdelphij	/* See if history file is disabled in the build. */
1378294286Sdelphij	if (strcmp(LESSHISTFILE, "") == 0 || strcmp(LESSHISTFILE, "-") == 0)
1379294286Sdelphij		return (NULL);
1380294286Sdelphij
1381161475Sdelphij	/* Otherwise, file is in $HOME. */
1382161475Sdelphij	home = lgetenv("HOME");
1383161475Sdelphij	if (home == NULL || *home == '\0')
1384161475Sdelphij	{
1385161475Sdelphij#if OS2
1386161475Sdelphij		home = lgetenv("INIT");
1387161475Sdelphij		if (home == NULL || *home == '\0')
1388161475Sdelphij#endif
1389161475Sdelphij			return (NULL);
1390161475Sdelphij	}
1391294286Sdelphij	len = (int) (strlen(home) + strlen(LESSHISTFILE) + 2);
1392161475Sdelphij	name = (char *) ecalloc(len, sizeof(char));
1393161475Sdelphij	SNPRINTF2(name, len, "%s/%s", home, LESSHISTFILE);
1394161475Sdelphij	return (name);
1395161475Sdelphij}
1396161475Sdelphij
1397161475Sdelphij/*
1398294286Sdelphij * Read a .lesshst file and call a callback for each line in the file.
1399161475Sdelphij */
1400294286Sdelphij	static void
1401294286Sdelphijread_cmdhist2(action, uparam, skip_search, skip_shell)
1402294286Sdelphij	void (*action)(void*,struct mlist*,char*);
1403294286Sdelphij	void *uparam;
1404294286Sdelphij	int skip_search;
1405294286Sdelphij	int skip_shell;
1406161475Sdelphij{
1407161475Sdelphij	struct mlist *ml = NULL;
1408161475Sdelphij	char line[CMDBUF_SIZE];
1409161475Sdelphij	char *filename;
1410161475Sdelphij	FILE *f;
1411161475Sdelphij	char *p;
1412294286Sdelphij	int *skip = NULL;
1413161475Sdelphij
1414161475Sdelphij	filename = histfile_name();
1415161475Sdelphij	if (filename == NULL)
1416161475Sdelphij		return;
1417161475Sdelphij	f = fopen(filename, "r");
1418161475Sdelphij	free(filename);
1419161475Sdelphij	if (f == NULL)
1420161475Sdelphij		return;
1421161475Sdelphij	if (fgets(line, sizeof(line), f) == NULL ||
1422161475Sdelphij	    strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0)
1423161475Sdelphij	{
1424161475Sdelphij		fclose(f);
1425161475Sdelphij		return;
1426161475Sdelphij	}
1427161475Sdelphij	while (fgets(line, sizeof(line), f) != NULL)
1428161475Sdelphij	{
1429161475Sdelphij		for (p = line;  *p != '\0';  p++)
1430161475Sdelphij		{
1431161475Sdelphij			if (*p == '\n' || *p == '\r')
1432161475Sdelphij			{
1433161475Sdelphij				*p = '\0';
1434161475Sdelphij				break;
1435161475Sdelphij			}
1436161475Sdelphij		}
1437161475Sdelphij		if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1438294286Sdelphij		{
1439161475Sdelphij			ml = &mlist_search;
1440294286Sdelphij			skip = &skip_search;
1441294286Sdelphij		} else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0)
1442170964Sdelphij		{
1443161475Sdelphij#if SHELL_ESCAPE || PIPEC
1444161475Sdelphij			ml = &mlist_shell;
1445294286Sdelphij			skip = &skip_shell;
1446170964Sdelphij#else
1447170964Sdelphij			ml = NULL;
1448294286Sdelphij			skip = NULL;
1449161475Sdelphij#endif
1450170964Sdelphij		} else if (*line == '"')
1451161475Sdelphij		{
1452161475Sdelphij			if (ml != NULL)
1453294286Sdelphij			{
1454294286Sdelphij				if (skip != NULL && *skip > 0)
1455294286Sdelphij					--(*skip);
1456294286Sdelphij				else
1457294286Sdelphij					(*action)(uparam, ml, line+1);
1458294286Sdelphij			}
1459161475Sdelphij		}
1460161475Sdelphij	}
1461161475Sdelphij	fclose(f);
1462294286Sdelphij}
1463294286Sdelphij
1464294286Sdelphij	static void
1465294286Sdelphijread_cmdhist(action, uparam, skip_search, skip_shell)
1466294286Sdelphij	void (*action)(void*,struct mlist*,char*);
1467294286Sdelphij	void *uparam;
1468294286Sdelphij	int skip_search;
1469294286Sdelphij	int skip_shell;
1470294286Sdelphij{
1471294286Sdelphij	read_cmdhist2(action, uparam, skip_search, skip_shell);
1472294286Sdelphij	(*action)(uparam, NULL, NULL); /* signal end of file */
1473294286Sdelphij}
1474294286Sdelphij
1475294286Sdelphij	static void
1476294286Sdelphijaddhist_init(void *uparam, struct mlist *ml, char *string)
1477294286Sdelphij{
1478294286Sdelphij	if (ml == NULL || string == NULL)
1479294286Sdelphij		return;
1480294286Sdelphij	cmd_addhist(ml, string, 0);
1481294286Sdelphij}
1482161475Sdelphij#endif /* CMD_HISTORY */
1483294286Sdelphij
1484294286Sdelphij/*
1485294286Sdelphij * Initialize history from a .lesshist file.
1486294286Sdelphij */
1487294286Sdelphij	public void
1488294286Sdelphijinit_cmdhist()
1489294286Sdelphij{
1490294286Sdelphij#if CMD_HISTORY
1491294286Sdelphij	read_cmdhist(&addhist_init, NULL, 0, 0);
1492294286Sdelphij#endif /* CMD_HISTORY */
1493161475Sdelphij}
1494161475Sdelphij
1495161475Sdelphij/*
1496294286Sdelphij * Write the header for a section of the history file.
1497161475Sdelphij */
1498161475Sdelphij#if CMD_HISTORY
1499161475Sdelphij	static void
1500294286Sdelphijwrite_mlist_header(ml, f)
1501161475Sdelphij	struct mlist *ml;
1502161475Sdelphij	FILE *f;
1503161475Sdelphij{
1504294286Sdelphij	if (ml == &mlist_search)
1505294286Sdelphij		fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1506294286Sdelphij#if SHELL_ESCAPE || PIPEC
1507294286Sdelphij	else if (ml == &mlist_shell)
1508294286Sdelphij		fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1509294286Sdelphij#endif
1510294286Sdelphij}
1511161475Sdelphij
1512294286Sdelphij/*
1513294286Sdelphij * Write all modified entries in an mlist to the history file.
1514294286Sdelphij */
1515294286Sdelphij	static void
1516294286Sdelphijwrite_mlist(ml, f)
1517294286Sdelphij	struct mlist *ml;
1518294286Sdelphij	FILE *f;
1519294286Sdelphij{
1520294286Sdelphij	for (ml = ml->next;  ml->string != NULL;  ml = ml->next)
1521161475Sdelphij	{
1522294286Sdelphij		if (!ml->modified)
1523294286Sdelphij			continue;
1524294286Sdelphij		fprintf(f, "\"%s\n", ml->string);
1525294286Sdelphij		ml->modified = 0;
1526161475Sdelphij	}
1527294286Sdelphij	ml->modified = 0; /* entire mlist is now unmodified */
1528161475Sdelphij}
1529161475Sdelphij
1530161475Sdelphij/*
1531294286Sdelphij * Make a temp name in the same directory as filename.
1532161475Sdelphij */
1533294286Sdelphij	static char *
1534294286Sdelphijmake_tempname(filename)
1535294286Sdelphij	char *filename;
1536161475Sdelphij{
1537294286Sdelphij	char lastch;
1538294286Sdelphij	char *tempname = ecalloc(1, strlen(filename)+1);
1539294286Sdelphij	strcpy(tempname, filename);
1540294286Sdelphij	lastch = tempname[strlen(tempname)-1];
1541294286Sdelphij	tempname[strlen(tempname)-1] = (lastch == 'Q') ? 'Z' : 'Q';
1542294286Sdelphij	return tempname;
1543294286Sdelphij}
1544161475Sdelphij
1545294286Sdelphijstruct save_ctx
1546294286Sdelphij{
1547294286Sdelphij	struct mlist *mlist;
1548294286Sdelphij	FILE *fout;
1549294286Sdelphij};
1550294286Sdelphij
1551294286Sdelphij/*
1552294286Sdelphij * Copy entries from the saved history file to a new file.
1553294286Sdelphij * At the end of each mlist, append any new entries
1554294286Sdelphij * created during this session.
1555294286Sdelphij */
1556294286Sdelphij	static void
1557294286Sdelphijcopy_hist(void *uparam, struct mlist *ml, char *string)
1558294286Sdelphij{
1559294286Sdelphij	struct save_ctx *ctx = (struct save_ctx *) uparam;
1560294286Sdelphij
1561294286Sdelphij	if (ml != ctx->mlist) {
1562294286Sdelphij		/* We're changing mlists. */
1563294286Sdelphij		if (ctx->mlist)
1564294286Sdelphij			/* Append any new entries to the end of the current mlist. */
1565294286Sdelphij			write_mlist(ctx->mlist, ctx->fout);
1566294286Sdelphij		/* Write the header for the new mlist. */
1567294286Sdelphij		ctx->mlist = ml;
1568294286Sdelphij		write_mlist_header(ctx->mlist, ctx->fout);
1569294286Sdelphij	}
1570294286Sdelphij	if (string != NULL)
1571294286Sdelphij	{
1572294286Sdelphij		/* Copy the entry. */
1573294286Sdelphij		fprintf(ctx->fout, "\"%s\n", string);
1574294286Sdelphij	}
1575294286Sdelphij	if (ml == NULL) /* End of file */
1576294286Sdelphij	{
1577294286Sdelphij		/* Write any sections that were not in the original file. */
1578294286Sdelphij		if (mlist_search.modified)
1579294286Sdelphij		{
1580294286Sdelphij			write_mlist_header(&mlist_search, ctx->fout);
1581294286Sdelphij			write_mlist(&mlist_search, ctx->fout);
1582294286Sdelphij		}
1583170964Sdelphij#if SHELL_ESCAPE || PIPEC
1584294286Sdelphij		if (mlist_shell.modified)
1585294286Sdelphij		{
1586294286Sdelphij			write_mlist_header(&mlist_shell, ctx->fout);
1587294286Sdelphij			write_mlist(&mlist_shell, ctx->fout);
1588294286Sdelphij		}
1589170964Sdelphij#endif
1590294286Sdelphij	}
1591294286Sdelphij}
1592294286Sdelphij#endif /* CMD_HISTORY */
1593294286Sdelphij
1594294286Sdelphij/*
1595294286Sdelphij * Make a file readable only by its owner.
1596294286Sdelphij */
1597294286Sdelphij	static void
1598294286Sdelphijmake_file_private(f)
1599294286Sdelphij	FILE *f;
1600294286Sdelphij{
1601161475Sdelphij#if HAVE_FCHMOD
1602191930Sdelphij	int do_chmod = 1;
1603191930Sdelphij#if HAVE_STAT
1604191930Sdelphij	struct stat statbuf;
1605191930Sdelphij	int r = fstat(fileno(f), &statbuf);
1606191930Sdelphij	if (r < 0 || !S_ISREG(statbuf.st_mode))
1607191930Sdelphij		/* Don't chmod if not a regular file. */
1608191930Sdelphij		do_chmod = 0;
1609161475Sdelphij#endif
1610191930Sdelphij	if (do_chmod)
1611191930Sdelphij		fchmod(fileno(f), 0600);
1612294286Sdelphij#endif
1613191930Sdelphij}
1614294286Sdelphij
1615294286Sdelphij/*
1616294286Sdelphij * Does the history file need to be updated?
1617294286Sdelphij */
1618294286Sdelphij	static int
1619294286Sdelphijhistfile_modified()
1620294286Sdelphij{
1621294286Sdelphij	if (mlist_search.modified)
1622294286Sdelphij		return 1;
1623294286Sdelphij#if SHELL_ESCAPE || PIPEC
1624294286Sdelphij	if (mlist_shell.modified)
1625294286Sdelphij		return 1;
1626191930Sdelphij#endif
1627294286Sdelphij	return 0;
1628294286Sdelphij}
1629161475Sdelphij
1630294286Sdelphij/*
1631294286Sdelphij * Update the .lesshst file.
1632294286Sdelphij */
1633294286Sdelphij	public void
1634294286Sdelphijsave_cmdhist()
1635294286Sdelphij{
1636294286Sdelphij#if CMD_HISTORY
1637294286Sdelphij	char *histname;
1638294286Sdelphij	char *tempname;
1639294286Sdelphij	int skip_search;
1640294286Sdelphij	int skip_shell;
1641294286Sdelphij	struct save_ctx ctx;
1642294286Sdelphij	char *s;
1643294286Sdelphij	FILE *fout = NULL;
1644294286Sdelphij	int histsize = 0;
1645161475Sdelphij
1646294286Sdelphij	if (!histfile_modified())
1647294286Sdelphij		return;
1648294286Sdelphij	histname = histfile_name();
1649294286Sdelphij	if (histname == NULL)
1650294286Sdelphij		return;
1651294286Sdelphij	tempname = make_tempname(histname);
1652294286Sdelphij	fout = fopen(tempname, "w");
1653294286Sdelphij	if (fout != NULL)
1654294286Sdelphij	{
1655294286Sdelphij		make_file_private(fout);
1656294286Sdelphij		s = lgetenv("LESSHISTSIZE");
1657294286Sdelphij		if (s != NULL)
1658294286Sdelphij			histsize = atoi(s);
1659294286Sdelphij		if (histsize <= 0)
1660294286Sdelphij			histsize = 100;
1661294286Sdelphij		skip_search = mlist_size(&mlist_search) - histsize;
1662161475Sdelphij#if SHELL_ESCAPE || PIPEC
1663294286Sdelphij		skip_shell = mlist_size(&mlist_shell) - histsize;
1664161475Sdelphij#endif
1665294286Sdelphij		fprintf(fout, "%s\n", HISTFILE_FIRST_LINE);
1666294286Sdelphij		ctx.fout = fout;
1667294286Sdelphij		ctx.mlist = NULL;
1668294286Sdelphij		read_cmdhist(copy_hist, &ctx, skip_search, skip_shell);
1669294286Sdelphij		fclose(fout);
1670294286Sdelphij#if MSDOS_COMPILER==WIN32C
1671294286Sdelphij		/*
1672294286Sdelphij		 * Windows rename doesn't remove an existing file,
1673294286Sdelphij		 * making it useless for atomic operations. Sigh.
1674294286Sdelphij		 */
1675294286Sdelphij		remove(histname);
1676294286Sdelphij#endif
1677294286Sdelphij		rename(tempname, histname);
1678294286Sdelphij	}
1679294286Sdelphij	free(tempname);
1680294286Sdelphij	free(histname);
1681161475Sdelphij#endif /* CMD_HISTORY */
1682161475Sdelphij}
1683