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