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