cmdbuf.c revision 367516
197049Speter/*
2166124Srafan * Copyright (C) 1984-2020  Mark Nudelman
397049Speter *
497049Speter * You may distribute under the terms of either the GNU General Public
597049Speter * License or the Less License, as specified in the README file.
697049Speter *
797049Speter * For more information, see the README file.
897049Speter */
997049Speter
1097049Speter
1197049Speter/*
1297049Speter * Functions which manipulate the command buffer.
1397049Speter * Used only by command() and related functions.
1497049Speter */
1597049Speter
1697049Speter#include "less.h"
1797049Speter#include "cmd.h"
1897049Speter#include "charset.h"
1997049Speter#if HAVE_STAT
2097049Speter#include <sys/stat.h>
2197049Speter#endif
2297049Speter
2397049Speterextern int sc_width;
2497049Speterextern int utf_mode;
2597049Speterextern int no_hist_dups;
2697049Speterextern int marks_modified;
2797049Speter
2897049Speterstatic char cmdbuf[CMDBUF_SIZE]; /* Buffer for holding a multi-char command */
2997049Speterstatic int cmd_col;		/* Current column of the cursor */
30166124Srafanstatic int prompt_col;		/* Column of cursor just after prompt */
3197049Speterstatic char *cp;		/* Pointer into cmdbuf */
3297049Speterstatic int cmd_offset;		/* Index into cmdbuf of first displayed char */
3397049Speterstatic int literal;		/* Next input char should not be interpreted */
3497049Speterstatic int updown_match = -1;	/* Prefix length in up/down movement */
3597049Speter
3697049Speter#if TAB_COMPLETE_FILENAME
3797049Speterstatic int cmd_complete LESSPARAMS((int action));
3897049Speter/*
3997049Speter * These variables are statics used by cmd_complete.
4097049Speter */
4197049Speterstatic int in_completion = 0;
42166124Srafanstatic char *tk_text;
4397049Speterstatic char *tk_original;
4497049Speterstatic char *tk_ipoint;
45166124Srafanstatic char *tk_trial = NULL;
4697049Speterstatic struct textlist tk_tlist;
4797049Speter#endif
4897049Speter
4997049Speterstatic int cmd_left();
5097049Speterstatic int cmd_right();
5197049Speter
5297049Speter#if SPACES_IN_FILENAMES
53166124Srafanpublic char openquote = '"';
5497049Speterpublic char closequote = '"';
5597049Speter#endif
5697049Speter
5797049Speter#if CMD_HISTORY
5897049Speter
5997049Speter/* History file */
60166124Srafan#define HISTFILE_FIRST_LINE      ".less-history-file:"
61166124Srafan#define HISTFILE_SEARCH_SECTION  ".search"
62166124Srafan#define HISTFILE_SHELL_SECTION   ".shell"
63166124Srafan#define HISTFILE_MARK_SECTION    ".mark"
64166124Srafan
65166124Srafan/*
66166124Srafan * A mlist structure represents a command history.
67166124Srafan */
68166124Srafanstruct mlist
6997049Speter{
70166124Srafan	struct mlist *next;
7197049Speter	struct mlist *prev;
7297049Speter	struct mlist *curr_mp;
7397049Speter	char *string;
7497049Speter	int modified;
7597049Speter};
7697049Speter
7797049Speter/*
7897049Speter * These are the various command histories that exist.
79166124Srafan */
8097049Speterstruct mlist mlist_search =
81166124Srafan	{ &mlist_search,  &mlist_search,  &mlist_search,  NULL, 0 };
82166124Srafanpublic void *ml_search = (void *) &mlist_search;
8397049Speter
8497049Speterstruct mlist mlist_examine =
8597049Speter	{ &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 };
8697049Speterpublic void *ml_examine = (void *) &mlist_examine;
8797049Speter
8897049Speter#if SHELL_ESCAPE || PIPEC
8997049Speterstruct mlist mlist_shell =
9097049Speter	{ &mlist_shell,   &mlist_shell,   &mlist_shell,   NULL, 0 };
9197049Speterpublic void *ml_shell = (void *) &mlist_shell;
9297049Speter#endif
93166124Srafan
9497049Speter#else /* CMD_HISTORY */
9597049Speter
96166124Srafan/* If CMD_HISTORY is off, these are just flags. */
9797049Speterpublic void *ml_search = (void *)1;
9897049Speterpublic void *ml_examine = (void *)2;
9997049Speter#if SHELL_ESCAPE || PIPEC
10097049Speterpublic 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			return (action);
964	}
965	return (CC_OK);
966}
967
968/*
969 * Find the beginning and end of the "current" word.
970 * This is the word which the cursor (cp) is inside or at the end of.
971 * Return pointer to the beginning of the word and put the
972 * cursor at the end of the word.
973 */
974	static char *
975delimit_word(VOID_PARAM)
976{
977	char *word;
978#if SPACES_IN_FILENAMES
979	char *p;
980	int delim_quoted = 0;
981	int meta_quoted = 0;
982	constant char *esc = get_meta_escape();
983	int esclen = (int) strlen(esc);
984#endif
985
986	/*
987	 * Move cursor to end of word.
988	 */
989	if (*cp != ' ' && *cp != '\0')
990	{
991		/*
992		 * Cursor is on a nonspace.
993		 * Move cursor right to the next space.
994		 */
995		while (*cp != ' ' && *cp != '\0')
996			cmd_right();
997	} else if (cp > cmdbuf && cp[-1] != ' ')
998	{
999		/*
1000		 * Cursor is on a space, and char to the left is a nonspace.
1001		 * We're already at the end of the word.
1002		 */
1003		;
1004#if 0
1005	} else
1006	{
1007		/*
1008		 * Cursor is on a space and char to the left is a space.
1009		 * Huh? There's no word here.
1010		 */
1011		return (NULL);
1012#endif
1013	}
1014	/*
1015	 * Find the beginning of the word which the cursor is in.
1016	 */
1017	if (cp == cmdbuf)
1018		return (NULL);
1019#if SPACES_IN_FILENAMES
1020	/*
1021	 * If we have an unbalanced quote (that is, an open quote
1022	 * without a corresponding close quote), we return everything
1023	 * from the open quote, including spaces.
1024	 */
1025	for (word = cmdbuf;  word < cp;  word++)
1026		if (*word != ' ')
1027			break;
1028	if (word >= cp)
1029		return (cp);
1030	for (p = cmdbuf;  p < cp;  p++)
1031	{
1032		if (meta_quoted)
1033		{
1034			meta_quoted = 0;
1035		} else if (esclen > 0 && p + esclen < cp &&
1036		           strncmp(p, esc, esclen) == 0)
1037		{
1038			meta_quoted = 1;
1039			p += esclen - 1;
1040		} else if (delim_quoted)
1041		{
1042			if (*p == closequote)
1043				delim_quoted = 0;
1044		} else /* (!delim_quoted) */
1045		{
1046			if (*p == openquote)
1047				delim_quoted = 1;
1048			else if (*p == ' ')
1049				word = p+1;
1050		}
1051	}
1052#endif
1053	return (word);
1054}
1055
1056/*
1057 * Set things up to enter completion mode.
1058 * Expand the word under the cursor into a list of filenames
1059 * which start with that word, and set tk_text to that list.
1060 */
1061	static void
1062init_compl(VOID_PARAM)
1063{
1064	char *word;
1065	char c;
1066
1067	/*
1068	 * Get rid of any previous tk_text.
1069	 */
1070	if (tk_text != NULL)
1071	{
1072		free(tk_text);
1073		tk_text = NULL;
1074	}
1075	/*
1076	 * Find the original (uncompleted) word in the command buffer.
1077	 */
1078	word = delimit_word();
1079	if (word == NULL)
1080		return;
1081	/*
1082	 * Set the insertion point to the point in the command buffer
1083	 * where the original (uncompleted) word now sits.
1084	 */
1085	tk_ipoint = word;
1086	/*
1087	 * Save the original (uncompleted) word
1088	 */
1089	if (tk_original != NULL)
1090		free(tk_original);
1091	tk_original = (char *) ecalloc(cp-word+1, sizeof(char));
1092	strncpy(tk_original, word, cp-word);
1093	/*
1094	 * Get the expanded filename.
1095	 * This may result in a single filename, or
1096	 * a blank-separated list of filenames.
1097	 */
1098	c = *cp;
1099	*cp = '\0';
1100	if (*word != openquote)
1101	{
1102		tk_text = fcomplete(word);
1103	} else
1104	{
1105#if MSDOS_COMPILER
1106		char *qword = NULL;
1107#else
1108		char *qword = shell_quote(word+1);
1109#endif
1110		if (qword == NULL)
1111			tk_text = fcomplete(word+1);
1112		else
1113		{
1114			tk_text = fcomplete(qword);
1115			free(qword);
1116		}
1117	}
1118	*cp = c;
1119}
1120
1121/*
1122 * Return the next word in the current completion list.
1123 */
1124	static char *
1125next_compl(action, prev)
1126	int action;
1127	char *prev;
1128{
1129	switch (action)
1130	{
1131	case EC_F_COMPLETE:
1132		return (forw_textlist(&tk_tlist, prev));
1133	case EC_B_COMPLETE:
1134		return (back_textlist(&tk_tlist, prev));
1135	}
1136	/* Cannot happen */
1137	return ("?");
1138}
1139
1140/*
1141 * Complete the filename before (or under) the cursor.
1142 * cmd_complete may be called multiple times.  The global in_completion
1143 * remembers whether this call is the first time (create the list),
1144 * or a subsequent time (step thru the list).
1145 */
1146	static int
1147cmd_complete(action)
1148	int action;
1149{
1150	char *s;
1151
1152	if (!in_completion || action == EC_EXPAND)
1153	{
1154		/*
1155		 * Expand the word under the cursor and
1156		 * use the first word in the expansion
1157		 * (or the entire expansion if we're doing EC_EXPAND).
1158		 */
1159		init_compl();
1160		if (tk_text == NULL)
1161		{
1162			bell();
1163			return (CC_OK);
1164		}
1165		if (action == EC_EXPAND)
1166		{
1167			/*
1168			 * Use the whole list.
1169			 */
1170			tk_trial = tk_text;
1171		} else
1172		{
1173			/*
1174			 * Use the first filename in the list.
1175			 */
1176			in_completion = 1;
1177			init_textlist(&tk_tlist, tk_text);
1178			tk_trial = next_compl(action, (char*)NULL);
1179		}
1180	} else
1181	{
1182		/*
1183		 * We already have a completion list.
1184		 * Use the next/previous filename from the list.
1185		 */
1186		tk_trial = next_compl(action, tk_trial);
1187	}
1188
1189	/*
1190	 * Remove the original word, or the previous trial completion.
1191	 */
1192	while (cp > tk_ipoint)
1193		(void) cmd_erase();
1194
1195	if (tk_trial == NULL)
1196	{
1197		/*
1198		 * There are no more trial completions.
1199		 * Insert the original (uncompleted) filename.
1200		 */
1201		in_completion = 0;
1202		if (cmd_istr(tk_original) != CC_OK)
1203			goto fail;
1204	} else
1205	{
1206		/*
1207		 * Insert trial completion.
1208		 */
1209		if (cmd_istr(tk_trial) != CC_OK)
1210			goto fail;
1211		/*
1212		 * If it is a directory, append a slash.
1213		 */
1214		if (is_dir(tk_trial))
1215		{
1216			if (cp > cmdbuf && cp[-1] == closequote)
1217				(void) cmd_erase();
1218			s = lgetenv("LESSSEPARATOR");
1219			if (s == NULL)
1220				s = PATHNAME_SEP;
1221			if (cmd_istr(s) != CC_OK)
1222				goto fail;
1223		}
1224	}
1225
1226	return (CC_OK);
1227
1228fail:
1229	in_completion = 0;
1230	bell();
1231	return (CC_OK);
1232}
1233
1234#endif /* TAB_COMPLETE_FILENAME */
1235
1236/*
1237 * Process a single character of a multi-character command, such as
1238 * a number, or the pattern of a search command.
1239 * Returns:
1240 *	CC_OK		The char was accepted.
1241 *	CC_QUIT		The char requests the command to be aborted.
1242 *	CC_ERROR	The char could not be accepted due to an error.
1243 */
1244	public int
1245cmd_char(c)
1246	int c;
1247{
1248	int action;
1249	int len;
1250
1251	if (!utf_mode)
1252	{
1253		cmd_mbc_buf[0] = c;
1254		len = 1;
1255	} else
1256	{
1257		/* Perform strict validation in all possible cases.  */
1258		if (cmd_mbc_buf_len == 0)
1259		{
1260		 retry:
1261			cmd_mbc_buf_index = 1;
1262			*cmd_mbc_buf = c;
1263			if (IS_ASCII_OCTET(c))
1264				cmd_mbc_buf_len = 1;
1265#if MSDOS_COMPILER || OS2
1266			else if (c == (unsigned char) '\340' && IS_ASCII_OCTET(peekcc()))
1267			{
1268				/* Assume a special key. */
1269				cmd_mbc_buf_len = 1;
1270			}
1271#endif
1272			else if (IS_UTF8_LEAD(c))
1273			{
1274				cmd_mbc_buf_len = utf_len(c);
1275				return (CC_OK);
1276			} else
1277			{
1278				/* UTF8_INVALID or stray UTF8_TRAIL */
1279				bell();
1280				return (CC_ERROR);
1281			}
1282		} else if (IS_UTF8_TRAIL(c))
1283		{
1284			cmd_mbc_buf[cmd_mbc_buf_index++] = c;
1285			if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1286				return (CC_OK);
1287			if (!is_utf8_well_formed(cmd_mbc_buf, cmd_mbc_buf_index))
1288			{
1289				/* complete, but not well formed (non-shortest form), sequence */
1290				cmd_mbc_buf_len = 0;
1291				bell();
1292				return (CC_ERROR);
1293			}
1294		} else
1295		{
1296			/* Flush incomplete (truncated) sequence.  */
1297			cmd_mbc_buf_len = 0;
1298			bell();
1299			/* Handle new char.  */
1300			goto retry;
1301		}
1302
1303		len = cmd_mbc_buf_len;
1304		cmd_mbc_buf_len = 0;
1305	}
1306
1307	if (literal)
1308	{
1309		/*
1310		 * Insert the char, even if it is a line-editing char.
1311		 */
1312		literal = 0;
1313		return (cmd_ichar(cmd_mbc_buf, len));
1314	}
1315
1316	/*
1317	 * See if it is a line-editing character.
1318	 */
1319	if (in_mca() && len == 1)
1320	{
1321		action = cmd_edit(c);
1322		switch (action)
1323		{
1324		case CC_OK:
1325		case CC_QUIT:
1326			return (action);
1327		case CC_PASS:
1328			break;
1329		}
1330	}
1331
1332	/*
1333	 * Insert the char into the command buffer.
1334	 */
1335	return (cmd_ichar(cmd_mbc_buf, len));
1336}
1337
1338/*
1339 * Return the number currently in the command buffer.
1340 */
1341	public LINENUM
1342cmd_int(frac)
1343	long *frac;
1344{
1345	char *p;
1346	LINENUM n = 0;
1347	int err;
1348
1349	for (p = cmdbuf;  *p >= '0' && *p <= '9';  p++)
1350		n = (n * 10) + (*p - '0');
1351	*frac = 0;
1352	if (*p++ == '.')
1353	{
1354		*frac = getfraction(&p, NULL, &err);
1355		/* {{ do something if err is set? }} */
1356	}
1357	return (n);
1358}
1359
1360/*
1361 * Return a pointer to the command buffer.
1362 */
1363	public char *
1364get_cmdbuf(VOID_PARAM)
1365{
1366	return (cmdbuf);
1367}
1368
1369#if CMD_HISTORY
1370/*
1371 * Return the last (most recent) string in the current command history.
1372 */
1373	public char *
1374cmd_lastpattern(VOID_PARAM)
1375{
1376	if (curr_mlist == NULL)
1377		return (NULL);
1378	return (curr_mlist->curr_mp->prev->string);
1379}
1380#endif
1381
1382#if CMD_HISTORY
1383/*
1384 */
1385	static int
1386mlist_size(ml)
1387	struct mlist *ml;
1388{
1389	int size = 0;
1390	for (ml = ml->next;  ml->string != NULL;  ml = ml->next)
1391		++size;
1392	return size;
1393}
1394
1395/*
1396 * Get the name of the history file.
1397 */
1398	static char *
1399histfile_name(VOID_PARAM)
1400{
1401	char *home;
1402	char *name;
1403	int len;
1404
1405	/* See if filename is explicitly specified by $LESSHISTFILE. */
1406	name = lgetenv("LESSHISTFILE");
1407	if (!isnullenv(name))
1408	{
1409		if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1410			/* $LESSHISTFILE == "-" means don't use a history file. */
1411			return (NULL);
1412		return (save(name));
1413	}
1414
1415	/* See if history file is disabled in the build. */
1416	if (strcmp(LESSHISTFILE, "") == 0 || strcmp(LESSHISTFILE, "-") == 0)
1417		return (NULL);
1418
1419	/* Otherwise, file is in $HOME. */
1420	home = lgetenv("HOME");
1421	if (isnullenv(home))
1422	{
1423#if OS2
1424		home = lgetenv("INIT");
1425		if (isnullenv(home))
1426#endif
1427			return (NULL);
1428	}
1429	len = (int) (strlen(home) + strlen(LESSHISTFILE) + 2);
1430	name = (char *) ecalloc(len, sizeof(char));
1431	SNPRINTF2(name, len, "%s/%s", home, LESSHISTFILE);
1432	return (name);
1433}
1434
1435/*
1436 * Read a .lesshst file and call a callback for each line in the file.
1437 */
1438	static void
1439read_cmdhist2(action, uparam, skip_search, skip_shell)
1440	void (*action)(void*,struct mlist*,char*);
1441	void *uparam;
1442	int skip_search;
1443	int skip_shell;
1444{
1445	struct mlist *ml = NULL;
1446	char line[CMDBUF_SIZE];
1447	char *filename;
1448	FILE *f;
1449	char *p;
1450	int *skip = NULL;
1451
1452	filename = histfile_name();
1453	if (filename == NULL)
1454		return;
1455	f = fopen(filename, "r");
1456	free(filename);
1457	if (f == NULL)
1458		return;
1459	if (fgets(line, sizeof(line), f) == NULL ||
1460	    strncmp(line, HISTFILE_FIRST_LINE, strlen(HISTFILE_FIRST_LINE)) != 0)
1461	{
1462		fclose(f);
1463		return;
1464	}
1465	while (fgets(line, sizeof(line), f) != NULL)
1466	{
1467		for (p = line;  *p != '\0';  p++)
1468		{
1469			if (*p == '\n' || *p == '\r')
1470			{
1471				*p = '\0';
1472				break;
1473			}
1474		}
1475		if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1476		{
1477			ml = &mlist_search;
1478			skip = &skip_search;
1479		} else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0)
1480		{
1481#if SHELL_ESCAPE || PIPEC
1482			ml = &mlist_shell;
1483			skip = &skip_shell;
1484#else
1485			ml = NULL;
1486			skip = NULL;
1487#endif
1488		} else if (strcmp(line, HISTFILE_MARK_SECTION) == 0)
1489		{
1490			ml = NULL;
1491		} else if (*line == '"')
1492		{
1493			if (ml != NULL)
1494			{
1495				if (skip != NULL && *skip > 0)
1496					--(*skip);
1497				else
1498					(*action)(uparam, ml, line+1);
1499			}
1500		} else if (*line == 'm')
1501		{
1502			(*action)(uparam, NULL, line);
1503		}
1504	}
1505	fclose(f);
1506}
1507
1508	static void
1509read_cmdhist(action, uparam, skip_search, skip_shell)
1510	void (*action)(void*,struct mlist*,char*);
1511	void *uparam;
1512	int skip_search;
1513	int skip_shell;
1514{
1515	read_cmdhist2(action, uparam, skip_search, skip_shell);
1516	(*action)(uparam, NULL, NULL); /* signal end of file */
1517}
1518
1519	static void
1520addhist_init(void *uparam, struct mlist *ml, char *string)
1521{
1522	if (ml != NULL)
1523		cmd_addhist(ml, string, 0);
1524	else if (string != NULL)
1525		restore_mark(string);
1526}
1527#endif /* CMD_HISTORY */
1528
1529/*
1530 * Initialize history from a .lesshist file.
1531 */
1532	public void
1533init_cmdhist(VOID_PARAM)
1534{
1535#if CMD_HISTORY
1536	read_cmdhist(&addhist_init, NULL, 0, 0);
1537#endif /* CMD_HISTORY */
1538}
1539
1540/*
1541 * Write the header for a section of the history file.
1542 */
1543#if CMD_HISTORY
1544	static void
1545write_mlist_header(ml, f)
1546	struct mlist *ml;
1547	FILE *f;
1548{
1549	if (ml == &mlist_search)
1550		fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1551#if SHELL_ESCAPE || PIPEC
1552	else if (ml == &mlist_shell)
1553		fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1554#endif
1555}
1556
1557/*
1558 * Write all modified entries in an mlist to the history file.
1559 */
1560	static void
1561write_mlist(ml, f)
1562	struct mlist *ml;
1563	FILE *f;
1564{
1565	for (ml = ml->next;  ml->string != NULL;  ml = ml->next)
1566	{
1567		if (!ml->modified)
1568			continue;
1569		fprintf(f, "\"%s\n", ml->string);
1570		ml->modified = 0;
1571	}
1572	ml->modified = 0; /* entire mlist is now unmodified */
1573}
1574
1575/*
1576 * Make a temp name in the same directory as filename.
1577 */
1578	static char *
1579make_tempname(filename)
1580	char *filename;
1581{
1582	char lastch;
1583	char *tempname = ecalloc(1, strlen(filename)+1);
1584	strcpy(tempname, filename);
1585	lastch = tempname[strlen(tempname)-1];
1586	tempname[strlen(tempname)-1] = (lastch == 'Q') ? 'Z' : 'Q';
1587	return tempname;
1588}
1589
1590struct save_ctx
1591{
1592	struct mlist *mlist;
1593	FILE *fout;
1594};
1595
1596/*
1597 * Copy entries from the saved history file to a new file.
1598 * At the end of each mlist, append any new entries
1599 * created during this session.
1600 */
1601	static void
1602copy_hist(void *uparam, struct mlist *ml, char *string)
1603{
1604	struct save_ctx *ctx = (struct save_ctx *) uparam;
1605
1606	if (ml != NULL && ml != ctx->mlist) {
1607		/* We're changing mlists. */
1608		if (ctx->mlist)
1609			/* Append any new entries to the end of the current mlist. */
1610			write_mlist(ctx->mlist, ctx->fout);
1611		/* Write the header for the new mlist. */
1612		ctx->mlist = ml;
1613		write_mlist_header(ctx->mlist, ctx->fout);
1614	}
1615
1616	if (string == NULL) /* End of file */
1617	{
1618		/* Write any sections that were not in the original file. */
1619		if (mlist_search.modified)
1620		{
1621			write_mlist_header(&mlist_search, ctx->fout);
1622			write_mlist(&mlist_search, ctx->fout);
1623		}
1624#if SHELL_ESCAPE || PIPEC
1625		if (mlist_shell.modified)
1626		{
1627			write_mlist_header(&mlist_shell, ctx->fout);
1628			write_mlist(&mlist_shell, ctx->fout);
1629		}
1630#endif
1631	} else if (ml != NULL)
1632	{
1633		/* Copy mlist entry. */
1634		fprintf(ctx->fout, "\"%s\n", string);
1635	}
1636	/* Skip marks */
1637}
1638#endif /* CMD_HISTORY */
1639
1640/*
1641 * Make a file readable only by its owner.
1642 */
1643	static void
1644make_file_private(f)
1645	FILE *f;
1646{
1647#if HAVE_FCHMOD
1648	int do_chmod = 1;
1649#if HAVE_STAT
1650	struct stat statbuf;
1651	int r = fstat(fileno(f), &statbuf);
1652	if (r < 0 || !S_ISREG(statbuf.st_mode))
1653		/* Don't chmod if not a regular file. */
1654		do_chmod = 0;
1655#endif
1656	if (do_chmod)
1657		fchmod(fileno(f), 0600);
1658#endif
1659}
1660
1661/*
1662 * Does the history file need to be updated?
1663 */
1664	static int
1665histfile_modified(VOID_PARAM)
1666{
1667	if (mlist_search.modified)
1668		return 1;
1669#if SHELL_ESCAPE || PIPEC
1670	if (mlist_shell.modified)
1671		return 1;
1672#endif
1673#if CMD_HISTORY
1674	if (marks_modified)
1675		return 1;
1676#endif
1677	return 0;
1678}
1679
1680/*
1681 * Update the .lesshst file.
1682 */
1683	public void
1684save_cmdhist(VOID_PARAM)
1685{
1686#if CMD_HISTORY
1687	char *histname;
1688	char *tempname;
1689	int skip_search;
1690	int skip_shell;
1691	struct save_ctx ctx;
1692	char *s;
1693	FILE *fout = NULL;
1694	int histsize = 0;
1695
1696	if (!histfile_modified())
1697		return;
1698	histname = histfile_name();
1699	if (histname == NULL)
1700		return;
1701	tempname = make_tempname(histname);
1702	fout = fopen(tempname, "w");
1703	if (fout != NULL)
1704	{
1705		make_file_private(fout);
1706		s = lgetenv("LESSHISTSIZE");
1707		if (s != NULL)
1708			histsize = atoi(s);
1709		if (histsize <= 0)
1710			histsize = 100;
1711		skip_search = mlist_size(&mlist_search) - histsize;
1712#if SHELL_ESCAPE || PIPEC
1713		skip_shell = mlist_size(&mlist_shell) - histsize;
1714#endif
1715		fprintf(fout, "%s\n", HISTFILE_FIRST_LINE);
1716		ctx.fout = fout;
1717		ctx.mlist = NULL;
1718		read_cmdhist(&copy_hist, &ctx, skip_search, skip_shell);
1719		save_marks(fout, HISTFILE_MARK_SECTION);
1720		fclose(fout);
1721#if MSDOS_COMPILER==WIN32C
1722		/*
1723		 * Windows rename doesn't remove an existing file,
1724		 * making it useless for atomic operations. Sigh.
1725		 */
1726		remove(histname);
1727#endif
1728		rename(tempname, histname);
1729	}
1730	free(tempname);
1731	free(histname);
1732#endif /* CMD_HISTORY */
1733}
1734