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