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