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