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