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