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