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