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