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