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