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