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