GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/less/less/../cmdbuf.c Lines: 192 524 36.6 %
Date: 2017-11-07 Branches: 72 362 19.9 %

Line Branch Exec Source
1
/*
2
 * Copyright (C) 1984-2012  Mark Nudelman
3
 * Modified for use with illumos by Garrett D'Amore.
4
 * Copyright 2014 Garrett D'Amore <garrett@damore.org>
5
 *
6
 * You may distribute under the terms of either the GNU General Public
7
 * License or the Less License, as specified in the README file.
8
 *
9
 * For more information, see the README file.
10
 */
11
12
/*
13
 * Functions which manipulate the command buffer.
14
 * Used only by command() and related functions.
15
 */
16
17
#include <sys/stat.h>
18
19
#include "charset.h"
20
#include "cmd.h"
21
#include "less.h"
22
23
extern int sc_width;
24
extern int utf_mode;
25
26
static char cmdbuf[CMDBUF_SIZE]; /* Buffer for holding a multi-char command */
27
static int cmd_col;		/* Current column of the cursor */
28
static int prompt_col;		/* Column of cursor just after prompt */
29
static char *cp;		/* Pointer into cmdbuf */
30
static int cmd_offset;		/* Index into cmdbuf of first displayed char */
31
static int literal;		/* Next input char should not be interpreted */
32
static int updown_match = -1;	/* Prefix length in up/down movement */
33
34
static int cmd_complete(int);
35
/*
36
 * These variables are statics used by cmd_complete.
37
 */
38
static int in_completion = 0;
39
static char *tk_text;
40
static char *tk_original;
41
static char *tk_ipoint;
42
static char *tk_trial;
43
static struct textlist tk_tlist;
44
45
static int cmd_left(void);
46
static int cmd_right(void);
47
48
char openquote = '"';
49
char closequote = '"';
50
51
/* History file */
52
#define	HISTFILE_FIRST_LINE	".less-history-file:"
53
#define	HISTFILE_SEARCH_SECTION	".search"
54
#define	HISTFILE_SHELL_SECTION	".shell"
55
56
/*
57
 * A mlist structure represents a command history.
58
 */
59
struct mlist {
60
	struct mlist *next;
61
	struct mlist *prev;
62
	struct mlist *curr_mp;
63
	char *string;
64
	int modified;
65
};
66
67
/*
68
 * These are the various command histories that exist.
69
 */
70
struct mlist mlist_search =
71
	{ &mlist_search,  &mlist_search,  &mlist_search,  NULL, 0 };
72
void * const ml_search = (void *) &mlist_search;
73
74
struct mlist mlist_examine =
75
	{ &mlist_examine, &mlist_examine, &mlist_examine, NULL, 0 };
76
void * const ml_examine = (void *) &mlist_examine;
77
78
struct mlist mlist_shell =
79
	{ &mlist_shell,   &mlist_shell,   &mlist_shell,   NULL, 0 };
80
void * const ml_shell = (void *) &mlist_shell;
81
82
/*
83
 * History for the current command.
84
 */
85
static struct mlist *curr_mlist = NULL;
86
static int curr_cmdflags;
87
88
static char cmd_mbc_buf[MAX_UTF_CHAR_LEN];
89
static int cmd_mbc_buf_len;
90
static int cmd_mbc_buf_index;
91
92
93
/*
94
 * Reset command buffer (to empty).
95
 */
96
void
97
cmd_reset(void)
98
{
99
5374
	cp = cmdbuf;
100
2687
	*cp = '\0';
101
2687
	cmd_col = 0;
102
2687
	cmd_offset = 0;
103
2687
	literal = 0;
104
2687
	cmd_mbc_buf_len = 0;
105
2687
	updown_match = -1;
106
2687
}
107
108
/*
109
 * Clear command line.
110
 */
111
void
112
clear_cmd(void)
113
{
114
3568
	cmd_col = prompt_col = 0;
115
1784
	cmd_mbc_buf_len = 0;
116
1784
	updown_match = -1;
117
1784
}
118
119
/*
120
 * Display a string, usually as a prompt for input into the command buffer.
121
 */
122
void
123
cmd_putstr(char *s)
124
{
125
	LWCHAR prev_ch = 0;
126
	LWCHAR ch;
127
1736
	char *endline = s + strlen(s);
128
3500
	while (*s != '\0') {
129
882
		char *ns = s;
130
882
		ch = step_char(&ns, +1, endline);
131
3528
		while (s < ns)
132
882
			putchr(*s++);
133
882
		if (!utf_mode) {
134
882
			cmd_col++;
135
882
			prompt_col++;
136

882
		} else if (!is_composing_char(ch) &&
137
		    !is_combining_char(prev_ch, ch)) {
138
			int width = is_wide_char(ch) ? 2 : 1;
139
			cmd_col += width;
140
			prompt_col += width;
141
		}
142
		prev_ch = ch;
143
882
	}
144
868
}
145
146
/*
147
 * How many characters are in the command buffer?
148
 */
149
int
150
len_cmdbuf(void)
151
{
152
4990
	char *s = cmdbuf;
153
2495
	char *endline = s + strlen(s);
154
	int len = 0;
155
156
19720
	while (*s != '\0') {
157
7365
		step_char(&s, +1, endline);
158
7365
		len++;
159
	}
160
2495
	return (len);
161
2495
}
162
163
/*
164
 * Common part of cmd_step_right() and cmd_step_left().
165
 */
166
static char *
167
cmd_step_common(char *p, LWCHAR ch, int len, int *pwidth, int *bswidth)
168
{
169
	char *pr;
170
171
10029
	if (len == 1) {
172
10029
		pr = prchar((int)ch);
173
10029
		if (pwidth != NULL || bswidth != NULL) {
174
10029
			int prlen = strlen(pr);
175
10029
			if (pwidth != NULL)
176
10029
				*pwidth = prlen;
177
10029
			if (bswidth != NULL)
178
3345
				*bswidth = prlen;
179
10029
		}
180
	} else {
181
		pr = prutfchar(ch);
182
		if (pwidth != NULL || bswidth != NULL) {
183
			if (is_composing_char(ch)) {
184
				if (pwidth != NULL)
185
					*pwidth = 0;
186
				if (bswidth != NULL)
187
					*bswidth = 0;
188
			} else if (is_ubin_char(ch)) {
189
				int prlen = strlen(pr);
190
				if (pwidth != NULL)
191
					*pwidth = prlen;
192
				if (bswidth != NULL)
193
					*bswidth = prlen;
194
			} else {
195
				LWCHAR prev_ch = step_char(&p, -1, cmdbuf);
196
				if (is_combining_char(prev_ch, ch)) {
197
					if (pwidth != NULL)
198
						*pwidth = 0;
199
					if (bswidth != NULL)
200
						*bswidth = 0;
201
				} else {
202
					if (pwidth != NULL)
203
						*pwidth	= is_wide_char(ch)
204
						    ? 2 : 1;
205
					if (bswidth != NULL)
206
						*bswidth = 1;
207
				}
208
			}
209
		}
210
	}
211
212
10029
	return (pr);
213
}
214
215
/*
216
 * Step a pointer one character right in the command buffer.
217
 */
218
static char *
219
cmd_step_right(char **pp, int *pwidth, int *bswidth)
220
{
221
13368
	char *p = *pp;
222
6684
	LWCHAR ch = step_char(pp, +1, p + strlen(p));
223
224
6684
	return (cmd_step_common(p, ch, *pp - p, pwidth, bswidth));
225
}
226
227
/*
228
 * Step a pointer one character left in the command buffer.
229
 */
230
static char *
231
cmd_step_left(char **pp, int *pwidth, int *bswidth)
232
{
233
6690
	char *p = *pp;
234
3345
	LWCHAR ch = step_char(pp, -1, cmdbuf);
235
236
3345
	return (cmd_step_common(*pp, ch, p - *pp, pwidth, bswidth));
237
}
238
239
/*
240
 * Repaint the line from cp onwards.
241
 * Then position the cursor just after the char old_cp (a pointer into cmdbuf).
242
 */
243
static void
244
cmd_repaint(char *old_cp)
245
{
246
	/*
247
	 * Repaint the line from the current position.
248
	 */
249
6690
	clear_eol();
250
10032
	while (*cp != '\0') {
251
3342
		char *np = cp;
252
3342
		int width;
253
3342
		char *pr = cmd_step_right(&np, &width, NULL);
254
3342
		if (cmd_col + width >= sc_width)
255
			break;
256
3342
		cp = np;
257
3342
		putstr(pr);
258
3342
		cmd_col += width;
259
6684
	}
260
3345
	while (*cp != '\0') {
261
		char *np = cp;
262
		int width;
263
		char *pr = cmd_step_right(&np, &width, NULL);
264
		if (width > 0)
265
			break;
266
		cp = np;
267
		putstr(pr);
268
	}
269
270
	/*
271
	 * Back up the cursor to the correct position.
272
	 */
273
10029
	while (cp > old_cp)
274
3342
		cmd_left();
275
3345
}
276
277
/*
278
 * Put the cursor at "home" (just after the prompt),
279
 * and set cp to the corresponding char in cmdbuf.
280
 */
281
static void
282
cmd_home(void)
283
{
284
	while (cmd_col > prompt_col) {
285
		int width, bswidth;
286
287
		cmd_step_left(&cp, &width, &bswidth);
288
		while (bswidth-- > 0)
289
			putbs();
290
		cmd_col -= width;
291
	}
292
293
	cp = &cmdbuf[cmd_offset];
294
}
295
296
/*
297
 * Shift the cmdbuf display left a half-screen.
298
 */
299
static void
300
cmd_lshift(void)
301
{
302
	char *s;
303
	char *save_cp;
304
	int cols;
305
306
	/*
307
	 * Start at the first displayed char, count how far to the
308
	 * right we'd have to move to reach the center of the screen.
309
	 */
310
	s = cmdbuf + cmd_offset;
311
	cols = 0;
312
	while (cols < (sc_width - prompt_col) / 2 && *s != '\0') {
313
		int width;
314
		cmd_step_right(&s, &width, NULL);
315
		cols += width;
316
	}
317
	while (*s != '\0') {
318
		int width;
319
		char *ns = s;
320
		cmd_step_right(&ns, &width, NULL);
321
		if (width > 0)
322
			break;
323
		s = ns;
324
	}
325
326
	cmd_offset = s - cmdbuf;
327
	save_cp = cp;
328
	cmd_home();
329
	cmd_repaint(save_cp);
330
}
331
332
/*
333
 * Shift the cmdbuf display right a half-screen.
334
 */
335
static void
336
cmd_rshift(void)
337
{
338
	char *s;
339
	char *save_cp;
340
	int cols;
341
342
	/*
343
	 * Start at the first displayed char, count how far to the
344
	 * left we'd have to move to traverse a half-screen width
345
	 * of displayed characters.
346
	 */
347
	s = cmdbuf + cmd_offset;
348
	cols = 0;
349
	while (cols < (sc_width - prompt_col) / 2 && s > cmdbuf) {
350
		int width;
351
		cmd_step_left(&s, &width, NULL);
352
		cols += width;
353
	}
354
355
	cmd_offset = s - cmdbuf;
356
	save_cp = cp;
357
	cmd_home();
358
	cmd_repaint(save_cp);
359
}
360
361
/*
362
 * Move cursor right one character.
363
 */
364
static int
365
cmd_right(void)
366
{
367
	char *pr;
368
6684
	char *ncp;
369
3342
	int width;
370
371
3342
	if (*cp == '\0') {
372
		/* Already at the end of the line. */
373
		return (CC_OK);
374
	}
375
3342
	ncp = cp;
376
3342
	pr = cmd_step_right(&ncp, &width, NULL);
377
3342
	if (cmd_col + width >= sc_width)
378
		cmd_lshift();
379

3342
	else if (cmd_col + width == sc_width - 1 && cp[1] != '\0')
380
		cmd_lshift();
381
3342
	cp = ncp;
382
3342
	cmd_col += width;
383
3342
	putstr(pr);
384
6684
	while (*cp != '\0') {
385
		pr = cmd_step_right(&ncp, &width, NULL);
386
		if (width > 0)
387
			break;
388
		putstr(pr);
389
		cp = ncp;
390
	}
391
3342
	return (CC_OK);
392
3342
}
393
394
/*
395
 * Move cursor left one character.
396
 */
397
static int
398
cmd_left(void)
399
{
400
6690
	char *ncp;
401
3345
	int width, bswidth;
402
403
3345
	if (cp <= cmdbuf) {
404
		/* Already at the beginning of the line */
405
		return (CC_OK);
406
	}
407
3345
	ncp = cp;
408
6690
	while (ncp > cmdbuf) {
409
3345
		cmd_step_left(&ncp, &width, &bswidth);
410
6690
		if (width > 0)
411
			break;
412
	}
413
3345
	if (cmd_col < prompt_col + width)
414
		cmd_rshift();
415
3345
	cp = ncp;
416
3345
	cmd_col -= width;
417
16796
	while (bswidth-- > 0)
418
5053
		putbs();
419
3345
	return (CC_OK);
420
3345
}
421
422
/*
423
 * Insert a char into the command buffer, at the current position.
424
 */
425
static int
426
cmd_ichar(char *cs, int clen)
427
{
428
	char *s;
429
430
6684
	if (strlen(cmdbuf) + clen >= sizeof (cmdbuf)-1) {
431
		/* No room in the command buffer for another char. */
432
		ring_bell();
433
		return (CC_ERROR);
434
	}
435
436
	/*
437
	 * Make room for the new character (shift the tail of the buffer right).
438
	 */
439
13368
	for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--)
440
3342
		s[clen] = s[0];
441
	/*
442
	 * Insert the character into the buffer.
443
	 */
444
13368
	for (s = cp; s < cp + clen; s++)
445
3342
		*s = *cs++;
446
	/*
447
	 * Reprint the tail of the line from the inserted char.
448
	 */
449
3342
	updown_match = -1;
450
3342
	cmd_repaint(cp);
451
3342
	cmd_right();
452
3342
	return (CC_OK);
453
3342
}
454
455
/*
456
 * Backspace in the command buffer.
457
 * Delete the char to the left of the cursor.
458
 */
459
static int
460
cmd_erase(void)
461
{
462
	char *s;
463
	int clen;
464
465
6
	if (cp == cmdbuf) {
466
		/*
467
		 * Backspace past beginning of the buffer:
468
		 * this usually means abort the command.
469
		 */
470
		return (CC_QUIT);
471
	}
472
	/*
473
	 * Move cursor left (to the char being erased).
474
	 */
475
	s = cp;
476
3
	cmd_left();
477
3
	clen = s - cp;
478
479
	/*
480
	 * Remove the char from the buffer (shift the buffer left).
481
	 */
482
3
	for (s = cp; ; s++) {
483
3
		s[0] = s[clen];
484
3
		if (s[0] == '\0')
485
			break;
486
	}
487
488
	/*
489
	 * Repaint the buffer after the erased char.
490
	 */
491
3
	updown_match = -1;
492
3
	cmd_repaint(cp);
493
494
	/*
495
	 * We say that erasing the entire command string causes us
496
	 * to abort the current command, if CF_QUIT_ON_ERASE is set.
497
	 */
498

3
	if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0')
499
		return (CC_QUIT);
500
3
	return (CC_OK);
501
3
}
502
503
/*
504
 * Delete the char under the cursor.
505
 */
506
static int
507
cmd_delete(void)
508
{
509
	if (*cp == '\0') {
510
		/* At end of string; there is no char under the cursor. */
511
		return (CC_OK);
512
	}
513
	/*
514
	 * Move right, then use cmd_erase.
515
	 */
516
	cmd_right();
517
	cmd_erase();
518
	return (CC_OK);
519
}
520
521
/*
522
 * Delete the "word" to the left of the cursor.
523
 */
524
static int
525
cmd_werase(void)
526
{
527
	if (cp > cmdbuf && cp[-1] == ' ') {
528
		/*
529
		 * If the char left of cursor is a space,
530
		 * erase all the spaces left of cursor (to the first non-space).
531
		 */
532
		while (cp > cmdbuf && cp[-1] == ' ')
533
			(void) cmd_erase();
534
	} else {
535
		/*
536
		 * If the char left of cursor is not a space,
537
		 * erase all the nonspaces left of cursor (the whole "word").
538
		 */
539
		while (cp > cmdbuf && cp[-1] != ' ')
540
			(void) cmd_erase();
541
	}
542
	return (CC_OK);
543
}
544
545
/*
546
 * Delete the "word" under the cursor.
547
 */
548
static int
549
cmd_wdelete(void)
550
{
551
	if (*cp == ' ') {
552
		/*
553
		 * If the char under the cursor is a space,
554
		 * delete it and all the spaces right of cursor.
555
		 */
556
		while (*cp == ' ')
557
			(void) cmd_delete();
558
	} else {
559
		/*
560
		 * If the char under the cursor is not a space,
561
		 * delete it and all nonspaces right of cursor (the whole word).
562
		 */
563
		while (*cp != ' ' && *cp != '\0')
564
			(void) cmd_delete();
565
	}
566
	return (CC_OK);
567
}
568
569
/*
570
 * Delete all chars in the command buffer.
571
 */
572
static int
573
cmd_kill(void)
574
{
575
	if (cmdbuf[0] == '\0') {
576
		/* Buffer is already empty; abort the current command. */
577
		return (CC_QUIT);
578
	}
579
	cmd_offset = 0;
580
	cmd_home();
581
	*cp = '\0';
582
	updown_match = -1;
583
	cmd_repaint(cp);
584
585
	/*
586
	 * We say that erasing the entire command string causes us
587
	 * to abort the current command, if CF_QUIT_ON_ERASE is set.
588
	 */
589
	if (curr_cmdflags & CF_QUIT_ON_ERASE)
590
		return (CC_QUIT);
591
	return (CC_OK);
592
}
593
594
/*
595
 * Select an mlist structure to be the current command history.
596
 */
597
void
598
set_mlist(void *mlist, int cmdflags)
599
{
600
1736
	curr_mlist = (struct mlist *)mlist;
601
868
	curr_cmdflags = cmdflags;
602
603
	/* Make sure the next up-arrow moves to the last string in the mlist. */
604
868
	if (curr_mlist != NULL)
605
11
		curr_mlist->curr_mp = curr_mlist;
606
868
}
607
608
/*
609
 * Move up or down in the currently selected command history list.
610
 * Only consider entries whose first updown_match chars are equal to
611
 * cmdbuf's corresponding chars.
612
 */
613
static int
614
cmd_updown(int action)
615
{
616
	char *s;
617
	struct mlist *ml;
618
619
	if (curr_mlist == NULL) {
620
		/*
621
		 * The current command has no history list.
622
		 */
623
		ring_bell();
624
		return (CC_OK);
625
	}
626
627
	if (updown_match < 0) {
628
		updown_match = cp - cmdbuf;
629
	}
630
631
	/*
632
	 * Find the next history entry which matches.
633
	 */
634
	for (ml = curr_mlist->curr_mp; ; ) {
635
		ml = (action == EC_UP) ? ml->prev : ml->next;
636
		if (ml == curr_mlist) {
637
			/*
638
			 * We reached the end (or beginning) of the list.
639
			 */
640
			break;
641
		}
642
		if (strncmp(cmdbuf, ml->string, updown_match) == 0) {
643
			/*
644
			 * This entry matches; stop here.
645
			 * Copy the entry into cmdbuf and echo it on the screen.
646
			 */
647
			curr_mlist->curr_mp = ml;
648
			s = ml->string;
649
			if (s == NULL)
650
				s = "";
651
			cmd_home();
652
			clear_eol();
653
			strlcpy(cmdbuf, s, sizeof (cmdbuf));
654
			for (cp = cmdbuf; *cp != '\0'; )
655
				cmd_right();
656
			return (CC_OK);
657
		}
658
	}
659
	/*
660
	 * We didn't find a history entry that matches.
661
	 */
662
	ring_bell();
663
	return (CC_OK);
664
}
665
666
/*
667
 * Add a string to a history list.
668
 */
669
void
670
cmd_addhist(struct mlist *mlist, const char *cmd)
671
{
672
	struct mlist *ml;
673
674
	/*
675
	 * Don't save a trivial command.
676
	 */
677
96
	if (strlen(cmd) == 0)
678
7
		return;
679
680
	/*
681
	 * Save the command unless it's a duplicate of the
682
	 * last command in the history.
683
	 */
684
41
	ml = mlist->prev;
685

41
	if (ml == mlist || strcmp(ml->string, cmd) != 0) {
686
		/*
687
		 * Did not find command in history.
688
		 * Save the command and put it at the end of the history list.
689
		 */
690
41
		ml = ecalloc(1, sizeof (struct mlist));
691
41
		ml->string = estrdup(cmd);
692
41
		ml->next = mlist;
693
41
		ml->prev = mlist->prev;
694
41
		mlist->prev->next = ml;
695
41
		mlist->prev = ml;
696
41
	}
697
	/*
698
	 * Point to the cmd just after the just-accepted command.
699
	 * Thus, an UPARROW will always retrieve the previous command.
700
	 */
701
41
	mlist->curr_mp = ml->next;
702
89
}
703
704
/*
705
 * Accept the command in the command buffer.
706
 * Add it to the currently selected history list.
707
 */
708
void
709
cmd_accept(void)
710
{
711
	/*
712
	 * Nothing to do if there is no currently selected history list.
713
	 */
714
1834
	if (curr_mlist == NULL)
715
		return;
716
11
	cmd_addhist(curr_mlist, cmdbuf);
717
11
	curr_mlist->modified = 1;
718
928
}
719
720
/*
721
 * Try to perform a line-edit function on the command buffer,
722
 * using a specified char as a line-editing command.
723
 * Returns:
724
 *	CC_PASS	The char does not invoke a line edit function.
725
 *	CC_OK	Line edit function done.
726
 *	CC_QUIT	The char requests the current command to be aborted.
727
 */
728
static int
729
cmd_edit(int c)
730
{
731
	int action;
732
	int flags;
733
734
#define	not_in_completion()	in_completion = 0
735
736
	/*
737
	 * See if the char is indeed a line-editing command.
738
	 */
739
	flags = 0;
740
50
	if (curr_mlist == NULL)
741
		/*
742
		 * No current history; don't accept history manipulation cmds.
743
		 */
744
3
		flags |= EC_NOHISTORY;
745
25
	if (curr_mlist == ml_search)
746
		/*
747
		 * In a search command; don't accept file-completion cmds.
748
		 */
749
22
		flags |= EC_NOCOMPLETE;
750
751
25
	action = editchar(c, flags);
752
753





25
	switch (action) {
754
	case EC_RIGHT:
755
		not_in_completion();
756
		return (cmd_right());
757
	case EC_LEFT:
758
		not_in_completion();
759
		return (cmd_left());
760
	case EC_W_RIGHT:
761
		not_in_completion();
762
		while (*cp != '\0' && *cp != ' ')
763
			cmd_right();
764
		while (*cp == ' ')
765
			cmd_right();
766
		return (CC_OK);
767
	case EC_W_LEFT:
768
		not_in_completion();
769
		while (cp > cmdbuf && cp[-1] == ' ')
770
			cmd_left();
771
		while (cp > cmdbuf && cp[-1] != ' ')
772
			cmd_left();
773
		return (CC_OK);
774
	case EC_HOME:
775
		not_in_completion();
776
		cmd_offset = 0;
777
		cmd_home();
778
		cmd_repaint(cp);
779
		return (CC_OK);
780
	case EC_END:
781
		not_in_completion();
782
		while (*cp != '\0')
783
			cmd_right();
784
		return (CC_OK);
785
	case EC_INSERT:
786
		not_in_completion();
787
		return (CC_OK);
788
	case EC_BACKSPACE:
789
3
		not_in_completion();
790
3
		return (cmd_erase());
791
	case EC_LINEKILL:
792
		not_in_completion();
793
		return (cmd_kill());
794
	case EC_ABORT:
795
		not_in_completion();
796
		(void) cmd_kill();
797
		return (CC_QUIT);
798
	case EC_W_BACKSPACE:
799
		not_in_completion();
800
		return (cmd_werase());
801
	case EC_DELETE:
802
		not_in_completion();
803
		return (cmd_delete());
804
	case EC_W_DELETE:
805
		not_in_completion();
806
		return (cmd_wdelete());
807
	case EC_LITERAL:
808
		literal = 1;
809
		return (CC_OK);
810
	case EC_UP:
811
	case EC_DOWN:
812
		not_in_completion();
813
		return (cmd_updown(action));
814
	case EC_F_COMPLETE:
815
	case EC_B_COMPLETE:
816
	case EC_EXPAND:
817
1
		return (cmd_complete(action));
818
	case EC_NOACTION:
819
		return (CC_OK);
820
	default:
821
21
		not_in_completion();
822
21
		return (CC_PASS);
823
	}
824
25
}
825
826
/*
827
 * Insert a string into the command buffer, at the current position.
828
 */
829
static int
830
cmd_istr(char *str)
831
{
832
	char *s;
833
	int action;
834
	char *endline = str + strlen(str);
835
836
	for (s = str; *s != '\0'; ) {
837
		char *os = s;
838
		step_char(&s, +1, endline);
839
		action = cmd_ichar(os, s - os);
840
		if (action != CC_OK) {
841
			ring_bell();
842
			return (action);
843
		}
844
	}
845
	return (CC_OK);
846
}
847
848
/*
849
 * Find the beginning and end of the "current" word.
850
 * This is the word which the cursor (cp) is inside or at the end of.
851
 * Return pointer to the beginning of the word and put the
852
 * cursor at the end of the word.
853
 */
854
static char *
855
delimit_word(void)
856
{
857
	char *word;
858
	char *p;
859
	int delim_quoted = 0;
860
	int meta_quoted = 0;
861
2
	char *esc = get_meta_escape();
862
1
	int esclen = strlen(esc);
863
864
	/*
865
	 * Move cursor to end of word.
866
	 */
867

2
	if (*cp != ' ' && *cp != '\0') {
868
		/*
869
		 * Cursor is on a nonspace.
870
		 * Move cursor right to the next space.
871
		 */
872
		while (*cp != ' ' && *cp != '\0')
873
			cmd_right();
874
	}
875
876
	/*
877
	 * Find the beginning of the word which the cursor is in.
878
	 */
879
1
	if (cp == cmdbuf)
880
1
		return (NULL);
881
	/*
882
	 * If we have an unbalanced quote (that is, an open quote
883
	 * without a corresponding close quote), we return everything
884
	 * from the open quote, including spaces.
885
	 */
886
	for (word = cmdbuf; word < cp; word++)
887
		if (*word != ' ')
888
			break;
889
	if (word >= cp)
890
		return (cp);
891
	for (p = cmdbuf; p < cp; p++) {
892
		if (meta_quoted) {
893
			meta_quoted = 0;
894
		} else if (esclen > 0 && p + esclen < cp &&
895
		    strncmp(p, esc, esclen) == 0) {
896
			meta_quoted = 1;
897
			p += esclen - 1;
898
		} else if (delim_quoted) {
899
			if (*p == closequote)
900
				delim_quoted = 0;
901
		} else { /* (!delim_quoted) */
902
			if (*p == openquote)
903
				delim_quoted = 1;
904
			else if (*p == ' ')
905
				word = p+1;
906
		}
907
	}
908
	return (word);
909
1
}
910
911
/*
912
 * Set things up to enter completion mode.
913
 * Expand the word under the cursor into a list of filenames
914
 * which start with that word, and set tk_text to that list.
915
 */
916
static void
917
init_compl(void)
918
{
919
	char *word;
920
	char c;
921
922
2
	free(tk_text);
923
1
	tk_text = NULL;
924
	/*
925
	 * Find the original (uncompleted) word in the command buffer.
926
	 */
927
1
	word = delimit_word();
928
1
	if (word == NULL)
929
1
		return;
930
	/*
931
	 * Set the insertion point to the point in the command buffer
932
	 * where the original (uncompleted) word now sits.
933
	 */
934
	tk_ipoint = word;
935
	/*
936
	 * Save the original (uncompleted) word
937
	 */
938
	free(tk_original);
939
	tk_original = ecalloc(cp-word+1, sizeof (char));
940
	(void) strncpy(tk_original, word, cp-word);
941
	/*
942
	 * Get the expanded filename.
943
	 * This may result in a single filename, or
944
	 * a blank-separated list of filenames.
945
	 */
946
	c = *cp;
947
	*cp = '\0';
948
	if (*word != openquote) {
949
		tk_text = fcomplete(word);
950
	} else {
951
		char *qword = shell_quote(word+1);
952
		if (qword == NULL)
953
			tk_text = fcomplete(word+1);
954
		else
955
			tk_text = fcomplete(qword);
956
		free(qword);
957
	}
958
	*cp = c;
959
1
}
960
961
/*
962
 * Return the next word in the current completion list.
963
 */
964
static char *
965
next_compl(int action, char *prev)
966
{
967
	switch (action) {
968
	case EC_F_COMPLETE:
969
		return (forw_textlist(&tk_tlist, prev));
970
	case EC_B_COMPLETE:
971
		return (back_textlist(&tk_tlist, prev));
972
	}
973
	/* Cannot happen */
974
	return ("?");
975
}
976
977
/*
978
 * Complete the filename before (or under) the cursor.
979
 * cmd_complete may be called multiple times.  The global in_completion
980
 * remembers whether this call is the first time (create the list),
981
 * or a subsequent time (step thru the list).
982
 */
983
static int
984
cmd_complete(int action)
985
{
986
	char *s;
987
988
2
	if (!in_completion || action == EC_EXPAND) {
989
		/*
990
		 * Expand the word under the cursor and
991
		 * use the first word in the expansion
992
		 * (or the entire expansion if we're doing EC_EXPAND).
993
		 */
994
1
		init_compl();
995
1
		if (tk_text == NULL) {
996
1
			ring_bell();
997
1
			return (CC_OK);
998
		}
999
		if (action == EC_EXPAND) {
1000
			/*
1001
			 * Use the whole list.
1002
			 */
1003
			tk_trial = tk_text;
1004
		} else {
1005
			/*
1006
			 * Use the first filename in the list.
1007
			 */
1008
			in_completion = 1;
1009
			init_textlist(&tk_tlist, tk_text);
1010
			tk_trial = next_compl(action, NULL);
1011
		}
1012
	} else {
1013
		/*
1014
		 * We already have a completion list.
1015
		 * Use the next/previous filename from the list.
1016
		 */
1017
		tk_trial = next_compl(action, tk_trial);
1018
	}
1019
1020
	/*
1021
	 * Remove the original word, or the previous trial completion.
1022
	 */
1023
	while (cp > tk_ipoint)
1024
		(void) cmd_erase();
1025
1026
	if (tk_trial == NULL) {
1027
		/*
1028
		 * There are no more trial completions.
1029
		 * Insert the original (uncompleted) filename.
1030
		 */
1031
		in_completion = 0;
1032
		if (cmd_istr(tk_original) != CC_OK)
1033
			goto fail;
1034
	} else {
1035
		/*
1036
		 * Insert trial completion.
1037
		 */
1038
		if (cmd_istr(tk_trial) != CC_OK)
1039
			goto fail;
1040
		/*
1041
		 * If it is a directory, append a slash.
1042
		 */
1043
		if (is_dir(tk_trial)) {
1044
			if (cp > cmdbuf && cp[-1] == closequote)
1045
				(void) cmd_erase();
1046
			s = lgetenv("LESSSEPARATOR");
1047
			if (s == NULL)
1048
				s = "/";
1049
			if (cmd_istr(s) != CC_OK)
1050
				goto fail;
1051
		}
1052
	}
1053
1054
	return (CC_OK);
1055
1056
fail:
1057
	in_completion = 0;
1058
	ring_bell();
1059
	return (CC_OK);
1060
1
}
1061
1062
/*
1063
 * Process a single character of a multi-character command, such as
1064
 * a number, or the pattern of a search command.
1065
 * Returns:
1066
 *	CC_OK		The char was accepted.
1067
 *	CC_QUIT		The char requests the command to be aborted.
1068
 *	CC_ERROR	The char could not be accepted due to an error.
1069
 */
1070
int
1071
cmd_char(int c)
1072
{
1073
	int action;
1074
	int len;
1075
1076
6692
	if (!utf_mode) {
1077
3346
		cmd_mbc_buf[0] = c & 0xff;
1078
		len = 1;
1079
3346
	} else {
1080
		/* Perform strict validation in all possible cases.  */
1081
		if (cmd_mbc_buf_len == 0) {
1082
retry:
1083
			cmd_mbc_buf_index = 1;
1084
			*cmd_mbc_buf = c & 0xff;
1085
			if (IS_ASCII_OCTET(c))
1086
				cmd_mbc_buf_len = 1;
1087
			else if (IS_UTF8_LEAD(c)) {
1088
				cmd_mbc_buf_len = utf_len(c);
1089
				return (CC_OK);
1090
			} else {
1091
				/* UTF8_INVALID or stray UTF8_TRAIL */
1092
				ring_bell();
1093
				return (CC_ERROR);
1094
			}
1095
		} else if (IS_UTF8_TRAIL(c)) {
1096
			cmd_mbc_buf[cmd_mbc_buf_index++] = c & 0xff;
1097
			if (cmd_mbc_buf_index < cmd_mbc_buf_len)
1098
				return (CC_OK);
1099
			if (!is_utf8_well_formed(cmd_mbc_buf)) {
1100
				/*
1101
				 * complete, but not well formed
1102
				 * (non-shortest form), sequence
1103
				 */
1104
				cmd_mbc_buf_len = 0;
1105
				ring_bell();
1106
				return (CC_ERROR);
1107
			}
1108
		} else {
1109
			/* Flush incomplete (truncated) sequence.  */
1110
			cmd_mbc_buf_len = 0;
1111
			ring_bell();
1112
			/* Handle new char.  */
1113
			goto retry;
1114
		}
1115
1116
		len = cmd_mbc_buf_len;
1117
		cmd_mbc_buf_len = 0;
1118
	}
1119
1120
3346
	if (literal) {
1121
		/*
1122
		 * Insert the char, even if it is a line-editing char.
1123
		 */
1124
		literal = 0;
1125
		return (cmd_ichar(cmd_mbc_buf, len));
1126
	}
1127
1128
	/*
1129
	 * See if it is a line-editing character.
1130
	 */
1131
3346
	if (in_mca() && len == 1) {
1132
25
		action = cmd_edit(c);
1133
25
		switch (action) {
1134
		case CC_OK:
1135
		case CC_QUIT:
1136
4
			return (action);
1137
		case CC_PASS:
1138
			break;
1139
		}
1140
	}
1141
1142
	/*
1143
	 * Insert the char into the command buffer.
1144
	 */
1145
3342
	return (cmd_ichar(cmd_mbc_buf, len));
1146
3346
}
1147
1148
/*
1149
 * Return the number currently in the command buffer.
1150
 */
1151
off_t
1152
cmd_int(long *frac)
1153
{
1154
	char *p;
1155
	off_t n = 0;
1156
	int err;
1157
1158
	for (p = cmdbuf; *p >= '0' && *p <= '9'; p++)
1159
		n = (n * 10) + (*p - '0');
1160
	*frac = 0;
1161
	if (*p++ == '.') {
1162
		*frac = getfraction(&p, NULL, &err);
1163
		/* {{ do something if err is set? }} */
1164
	}
1165
	return (n);
1166
}
1167
1168
/*
1169
 * Return a pointer to the command buffer.
1170
 */
1171
char *
1172
get_cmdbuf(void)
1173
{
1174
4944
	return (cmdbuf);
1175
}
1176
1177
/*
1178
 * Return the last (most recent) string in the current command history.
1179
 */
1180
char *
1181
cmd_lastpattern(void)
1182
{
1183
	if (curr_mlist == NULL)
1184
		return (NULL);
1185
	return (curr_mlist->curr_mp->prev->string);
1186
}
1187
1188
/*
1189
 * Get the name of the history file.
1190
 */
1191
static char *
1192
histfile_name(void)
1193
{
1194
	char *home;
1195
	char *name;
1196
1197
	/* See if filename is explicitly specified by $LESSHISTFILE. */
1198
86
	name = lgetenv("LESSHISTFILE");
1199

43
	if (name != NULL && *name != '\0') {
1200
		if (strcmp(name, "-") == 0 || strcmp(name, "/dev/null") == 0)
1201
			/* $LESSHISTFILE == "-" means don't use history file */
1202
			return (NULL);
1203
		return (estrdup(name));
1204
	}
1205
1206
	/* Otherwise, file is in $HOME if enabled. */
1207
43
	if (strcmp(LESSHISTFILE, "-") == 0)
1208
43
		return (NULL);
1209
	home = lgetenv("HOME");
1210
	if (home == NULL || *home == '\0') {
1211
		return (NULL);
1212
	}
1213
	return (easprintf("%s/%s", home, LESSHISTFILE));
1214
43
}
1215
1216
/*
1217
 * Initialize history from a .lesshist file.
1218
 */
1219
void
1220
init_cmdhist(void)
1221
{
1222
	struct mlist *ml = NULL;
1223
78
	char line[CMDBUF_SIZE];
1224
	char *filename;
1225
	FILE *f;
1226
	char *p;
1227
1228
39
	filename = histfile_name();
1229
39
	if (filename == NULL)
1230
39
		return;
1231
	f = fopen(filename, "r");
1232
	free(filename);
1233
	if (f == NULL)
1234
		return;
1235
	if (fgets(line, sizeof (line), f) == NULL ||
1236
	    strncmp(line, HISTFILE_FIRST_LINE,
1237
	    strlen(HISTFILE_FIRST_LINE)) != 0) {
1238
		(void) fclose(f);
1239
		return;
1240
	}
1241
	while (fgets(line, sizeof (line), f) != NULL) {
1242
		for (p = line; *p != '\0'; p++) {
1243
			if (*p == '\n' || *p == '\r') {
1244
				*p = '\0';
1245
				break;
1246
			}
1247
		}
1248
		if (strcmp(line, HISTFILE_SEARCH_SECTION) == 0)
1249
			ml = &mlist_search;
1250
		else if (strcmp(line, HISTFILE_SHELL_SECTION) == 0) {
1251
			ml = &mlist_shell;
1252
		} else if (*line == '"') {
1253
			if (ml != NULL)
1254
				cmd_addhist(ml, line+1);
1255
		}
1256
	}
1257
	(void) fclose(f);
1258
39
}
1259
1260
/*
1261
 *
1262
 */
1263
static void
1264
save_mlist(struct mlist *ml, FILE *f)
1265
{
1266
	int histsize = 0;
1267
	int n;
1268
	char *s;
1269
1270
	s = lgetenv("LESSHISTSIZE");
1271
	if (s != NULL)
1272
		histsize = atoi(s);
1273
	if (histsize == 0)
1274
		histsize = 100;
1275
1276
	ml = ml->prev;
1277
	for (n = 0; n < histsize; n++) {
1278
		if (ml->string == NULL)
1279
			break;
1280
		ml = ml->prev;
1281
	}
1282
	for (ml = ml->next; ml->string != NULL; ml = ml->next)
1283
		(void) fprintf(f, "\"%s\n", ml->string);
1284
}
1285
1286
/*
1287
 *
1288
 */
1289
void
1290
save_cmdhist(void)
1291
{
1292
	char *filename;
1293
	FILE *f;
1294
	int modified = 0;
1295
	int do_chmod = 1;
1296
78
	struct stat statbuf;
1297
	int r;
1298
1299
39
	if (mlist_search.modified)
1300
4
		modified = 1;
1301
39
	if (mlist_shell.modified)
1302
		modified = 1;
1303
39
	if (!modified)
1304
35
		return;
1305
4
	filename = histfile_name();
1306
4
	if (filename == NULL)
1307
4
		return;
1308
	f = fopen(filename, "w");
1309
	free(filename);
1310
	if (f == NULL)
1311
		return;
1312
1313
	/* Make history file readable only by owner. */
1314
	r = fstat(fileno(f), &statbuf);
1315
	if (r < 0 || !S_ISREG(statbuf.st_mode))
1316
		/* Don't chmod if not a regular file. */
1317
		do_chmod = 0;
1318
	if (do_chmod)
1319
		(void) fchmod(fileno(f), 0600);
1320
1321
	(void) fprintf(f, "%s\n", HISTFILE_FIRST_LINE);
1322
1323
	(void) fprintf(f, "%s\n", HISTFILE_SEARCH_SECTION);
1324
	save_mlist(&mlist_search, f);
1325
1326
	(void) fprintf(f, "%s\n", HISTFILE_SHELL_SECTION);
1327
	save_mlist(&mlist_shell, f);
1328
1329
	(void) fclose(f);
1330
39
}