GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
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 |
240 |
cp = cmdbuf; |
|
100 |
120 |
*cp = '\0'; |
|
101 |
120 |
cmd_col = 0; |
|
102 |
120 |
cmd_offset = 0; |
|
103 |
120 |
literal = 0; |
|
104 |
120 |
cmd_mbc_buf_len = 0; |
|
105 |
120 |
updown_match = -1; |
|
106 |
120 |
} |
|
107 |
|||
108 |
/* |
||
109 |
* Clear command line. |
||
110 |
*/ |
||
111 |
void |
||
112 |
clear_cmd(void) |
||
113 |
{ |
||
114 |
154 |
cmd_col = prompt_col = 0; |
|
115 |
77 |
cmd_mbc_buf_len = 0; |
|
116 |
77 |
updown_match = -1; |
|
117 |
77 |
} |
|
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 |
72 |
char *endline = s + strlen(s); |
|
128 |
✓✓ | 144 |
while (*s != '\0') { |
129 |
36 |
char *ns = s; |
|
130 |
36 |
ch = step_char(&ns, +1, endline); |
|
131 |
✓✓ | 144 |
while (s < ns) |
132 |
36 |
putchr(*s++); |
|
133 |
✓✗ | 36 |
if (!utf_mode) { |
134 |
36 |
cmd_col++; |
|
135 |
36 |
prompt_col++; |
|
136 |
✗✗✗✗ |
36 |
} 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 |
36 |
} |
|
144 |
36 |
} |
|
145 |
|||
146 |
/* |
||
147 |
* How many characters are in the command buffer? |
||
148 |
*/ |
||
149 |
int |
||
150 |
len_cmdbuf(void) |
||
151 |
{ |
||
152 |
228 |
char *s = cmdbuf; |
|
153 |
114 |
char *endline = s + strlen(s); |
|
154 |
int len = 0; |
||
155 |
|||
156 |
✓✓ | 868 |
while (*s != '\0') { |
157 |
320 |
step_char(&s, +1, endline); |
|
158 |
320 |
len++; |
|
159 |
} |
||
160 |
114 |
return (len); |
|
161 |
114 |
} |
|
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 |
✓✗ | 420 |
if (len == 1) { |
172 |
420 |
pr = prchar((int)ch); |
|
173 |
✓✗ | 420 |
if (pwidth != NULL || bswidth != NULL) { |
174 |
420 |
int prlen = strlen(pr); |
|
175 |
✓✗ | 420 |
if (pwidth != NULL) |
176 |
420 |
*pwidth = prlen; |
|
177 |
✓✓ | 420 |
if (bswidth != NULL) |
178 |
140 |
*bswidth = prlen; |
|
179 |
420 |
} |
|
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 |
420 |
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 |
560 |
char *p = *pp; |
|
222 |
280 |
LWCHAR ch = step_char(pp, +1, p + strlen(p)); |
|
223 |
|||
224 |
280 |
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 |
280 |
char *p = *pp; |
|
234 |
140 |
LWCHAR ch = step_char(pp, -1, cmdbuf); |
|
235 |
|||
236 |
140 |
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 |
280 |
clear_eol(); |
|
250 |
✓✓ | 560 |
while (*cp != '\0') { |
251 |
140 |
char *np = cp; |
|
252 |
140 |
int width; |
|
253 |
140 |
char *pr = cmd_step_right(&np, &width, NULL); |
|
254 |
✗✓ | 140 |
if (cmd_col + width >= sc_width) |
255 |
break; |
||
256 |
140 |
cp = np; |
|
257 |
140 |
putstr(pr); |
|
258 |
140 |
cmd_col += width; |
|
259 |
✓✗ | 280 |
} |
260 |
✗✓ | 280 |
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 |
✓✓ | 560 |
while (cp > old_cp) |
274 |
140 |
cmd_left(); |
|
275 |
140 |
} |
|
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 |
280 |
char *ncp; |
|
369 |
140 |
int width; |
|
370 |
|||
371 |
✗✓ | 140 |
if (*cp == '\0') { |
372 |
/* Already at the end of the line. */ |
||
373 |
return (CC_OK); |
||
374 |
} |
||
375 |
140 |
ncp = cp; |
|
376 |
140 |
pr = cmd_step_right(&ncp, &width, NULL); |
|
377 |
✗✓ | 140 |
if (cmd_col + width >= sc_width) |
378 |
cmd_lshift(); |
||
379 |
✗✓✗✗ |
140 |
else if (cmd_col + width == sc_width - 1 && cp[1] != '\0') |
380 |
cmd_lshift(); |
||
381 |
140 |
cp = ncp; |
|
382 |
140 |
cmd_col += width; |
|
383 |
140 |
putstr(pr); |
|
384 |
✗✓ | 280 |
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 |
140 |
return (CC_OK); |
|
392 |
140 |
} |
|
393 |
|||
394 |
/* |
||
395 |
* Move cursor left one character. |
||
396 |
*/ |
||
397 |
static int |
||
398 |
cmd_left(void) |
||
399 |
{ |
||
400 |
280 |
char *ncp; |
|
401 |
140 |
int width, bswidth; |
|
402 |
|||
403 |
✗✓ | 140 |
if (cp <= cmdbuf) { |
404 |
/* Already at the beginning of the line */ |
||
405 |
return (CC_OK); |
||
406 |
} |
||
407 |
140 |
ncp = cp; |
|
408 |
✓✗ | 280 |
while (ncp > cmdbuf) { |
409 |
140 |
cmd_step_left(&ncp, &width, &bswidth); |
|
410 |
✗✓ | 140 |
if (width > 0) |
411 |
break; |
||
412 |
} |
||
413 |
✗✓ | 140 |
if (cmd_col < prompt_col + width) |
414 |
cmd_rshift(); |
||
415 |
140 |
cp = ncp; |
|
416 |
140 |
cmd_col -= width; |
|
417 |
✓✓ | 688 |
while (bswidth-- > 0) |
418 |
204 |
putbs(); |
|
419 |
140 |
return (CC_OK); |
|
420 |
140 |
} |
|
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 |
✗✓ | 280 |
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 |
✓✓ | 560 |
for (s = &cmdbuf[strlen(cmdbuf)]; s >= cp; s--) |
440 |
140 |
s[clen] = s[0]; |
|
441 |
/* |
||
442 |
* Insert the character into the buffer. |
||
443 |
*/ |
||
444 |
✓✓ | 560 |
for (s = cp; s < cp + clen; s++) |
445 |
140 |
*s = *cs++; |
|
446 |
/* |
||
447 |
* Reprint the tail of the line from the inserted char. |
||
448 |
*/ |
||
449 |
140 |
updown_match = -1; |
|
450 |
140 |
cmd_repaint(cp); |
|
451 |
140 |
cmd_right(); |
|
452 |
140 |
return (CC_OK); |
|
453 |
140 |
} |
|
454 |
|||
455 |
/* |
||
456 |
* Backspace in the command buffer. |
||
457 |
* Delete the char to the left of the cursor. |
||
458 |
*/ |
||
459 |
static int |
||
460 |
cmd_erase(void) |
||
461 |
{ |
||
462 |
char *s; |
||
463 |
int clen; |
||
464 |
|||
465 |
if (cp == cmdbuf) { |
||
466 |
/* |
||
467 |
* Backspace past beginning of the buffer: |
||
468 |
* this usually means abort the command. |
||
469 |
*/ |
||
470 |
return (CC_QUIT); |
||
471 |
} |
||
472 |
/* |
||
473 |
* Move cursor left (to the char being erased). |
||
474 |
*/ |
||
475 |
s = cp; |
||
476 |
cmd_left(); |
||
477 |
clen = s - cp; |
||
478 |
|||
479 |
/* |
||
480 |
* Remove the char from the buffer (shift the buffer left). |
||
481 |
*/ |
||
482 |
for (s = cp; ; s++) { |
||
483 |
s[0] = s[clen]; |
||
484 |
if (s[0] == '\0') |
||
485 |
break; |
||
486 |
} |
||
487 |
|||
488 |
/* |
||
489 |
* Repaint the buffer after the erased char. |
||
490 |
*/ |
||
491 |
updown_match = -1; |
||
492 |
cmd_repaint(cp); |
||
493 |
|||
494 |
/* |
||
495 |
* We say that erasing the entire command string causes us |
||
496 |
* to abort the current command, if CF_QUIT_ON_ERASE is set. |
||
497 |
*/ |
||
498 |
if ((curr_cmdflags & CF_QUIT_ON_ERASE) && cp == cmdbuf && *cp == '\0') |
||
499 |
return (CC_QUIT); |
||
500 |
return (CC_OK); |
||
501 |
} |
||
502 |
|||
503 |
/* |
||
504 |
* Delete the char under the cursor. |
||
505 |
*/ |
||
506 |
static int |
||
507 |
cmd_delete(void) |
||
508 |
{ |
||
509 |
if (*cp == '\0') { |
||
510 |
/* At end of string; there is no char under the cursor. */ |
||
511 |
return (CC_OK); |
||
512 |
} |
||
513 |
/* |
||
514 |
* Move right, then use cmd_erase. |
||
515 |
*/ |
||
516 |
cmd_right(); |
||
517 |
cmd_erase(); |
||
518 |
return (CC_OK); |
||
519 |
} |
||
520 |
|||
521 |
/* |
||
522 |
* Delete the "word" to the left of the cursor. |
||
523 |
*/ |
||
524 |
static int |
||
525 |
cmd_werase(void) |
||
526 |
{ |
||
527 |
if (cp > cmdbuf && cp[-1] == ' ') { |
||
528 |
/* |
||
529 |
* If the char left of cursor is a space, |
||
530 |
* erase all the spaces left of cursor (to the first non-space). |
||
531 |
*/ |
||
532 |
while (cp > cmdbuf && cp[-1] == ' ') |
||
533 |
(void) cmd_erase(); |
||
534 |
} else { |
||
535 |
/* |
||
536 |
* If the char left of cursor is not a space, |
||
537 |
* erase all the nonspaces left of cursor (the whole "word"). |
||
538 |
*/ |
||
539 |
while (cp > cmdbuf && cp[-1] != ' ') |
||
540 |
(void) cmd_erase(); |
||
541 |
} |
||
542 |
return (CC_OK); |
||
543 |
} |
||
544 |
|||
545 |
/* |
||
546 |
* Delete the "word" under the cursor. |
||
547 |
*/ |
||
548 |
static int |
||
549 |
cmd_wdelete(void) |
||
550 |
{ |
||
551 |
if (*cp == ' ') { |
||
552 |
/* |
||
553 |
* If the char under the cursor is a space, |
||
554 |
* delete it and all the spaces right of cursor. |
||
555 |
*/ |
||
556 |
while (*cp == ' ') |
||
557 |
(void) cmd_delete(); |
||
558 |
} else { |
||
559 |
/* |
||
560 |
* If the char under the cursor is not a space, |
||
561 |
* delete it and all nonspaces right of cursor (the whole word). |
||
562 |
*/ |
||
563 |
while (*cp != ' ' && *cp != '\0') |
||
564 |
(void) cmd_delete(); |
||
565 |
} |
||
566 |
return (CC_OK); |
||
567 |
} |
||
568 |
|||
569 |
/* |
||
570 |
* Delete all chars in the command buffer. |
||
571 |
*/ |
||
572 |
static int |
||
573 |
cmd_kill(void) |
||
574 |
{ |
||
575 |
if (cmdbuf[0] == '\0') { |
||
576 |
/* Buffer is already empty; abort the current command. */ |
||
577 |
return (CC_QUIT); |
||
578 |
} |
||
579 |
cmd_offset = 0; |
||
580 |
cmd_home(); |
||
581 |
*cp = '\0'; |
||
582 |
updown_match = -1; |
||
583 |
cmd_repaint(cp); |
||
584 |
|||
585 |
/* |
||
586 |
* We say that erasing the entire command string causes us |
||
587 |
* to abort the current command, if CF_QUIT_ON_ERASE is set. |
||
588 |
*/ |
||
589 |
if (curr_cmdflags & CF_QUIT_ON_ERASE) |
||
590 |
return (CC_QUIT); |
||
591 |
return (CC_OK); |
||
592 |
} |
||
593 |
|||
594 |
/* |
||
595 |
* Select an mlist structure to be the current command history. |
||
596 |
*/ |
||
597 |
void |
||
598 |
set_mlist(void *mlist, int cmdflags) |
||
599 |
{ |
||
600 |
74 |
curr_mlist = (struct mlist *)mlist; |
|
601 |
37 |
curr_cmdflags = cmdflags; |
|
602 |
|||
603 |
/* Make sure the next up-arrow moves to the last string in the mlist. */ |
||
604 |
✓✓ | 37 |
if (curr_mlist != NULL) |
605 |
5 |
curr_mlist->curr_mp = curr_mlist; |
|
606 |
37 |
} |
|
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 |
✓✓ | 24 |
if (strlen(cmd) == 0) |
678 |
1 |
return; |
|
679 |
|||
680 |
/* |
||
681 |
* Save the command unless it's a duplicate of the |
||
682 |
* last command in the history. |
||
683 |
*/ |
||
684 |
11 |
ml = mlist->prev; |
|
685 |
✗✓✗✗ |
11 |
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 |
11 |
ml = ecalloc(1, sizeof (struct mlist)); |
|
691 |
11 |
ml->string = estrdup(cmd); |
|
692 |
11 |
ml->next = mlist; |
|
693 |
11 |
ml->prev = mlist->prev; |
|
694 |
11 |
mlist->prev->next = ml; |
|
695 |
11 |
mlist->prev = ml; |
|
696 |
11 |
} |
|
697 |
/* |
||
698 |
* Point to the cmd just after the just-accepted command. |
||
699 |
* Thus, an UPARROW will always retrieve the previous command. |
||
700 |
*/ |
||
701 |
11 |
mlist->curr_mp = ml->next; |
|
702 |
23 |
} |
|
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 |
✓✓ | 88 |
if (curr_mlist == NULL) |
715 |
return; |
||
716 |
4 |
cmd_addhist(curr_mlist, cmdbuf); |
|
717 |
4 |
curr_mlist->modified = 1; |
|
718 |
48 |
} |
|
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 |
✗✓ | 24 |
if (curr_mlist == NULL) |
741 |
/* |
||
742 |
* No current history; don't accept history manipulation cmds. |
||
743 |
*/ |
||
744 |
flags |= EC_NOHISTORY; |
||
745 |
✓✗ | 12 |
if (curr_mlist == ml_search) |
746 |
/* |
||
747 |
* In a search command; don't accept file-completion cmds. |
||
748 |
*/ |
||
749 |
12 |
flags |= EC_NOCOMPLETE; |
|
750 |
|||
751 |
12 |
action = editchar(c, flags); |
|
752 |
|||
753 |
✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✗✗✗✗ ✓ |
12 |
switch (action) { |
754 |
case EC_RIGHT: |
||
755 |
not_in_completion(); |
||
756 |
return (cmd_right()); |
||
757 |
case EC_LEFT: |
||
758 |
not_in_completion(); |
||
759 |
return (cmd_left()); |
||
760 |
case EC_W_RIGHT: |
||
761 |
not_in_completion(); |
||
762 |
while (*cp != '\0' && *cp != ' ') |
||
763 |
cmd_right(); |
||
764 |
while (*cp == ' ') |
||
765 |
cmd_right(); |
||
766 |
return (CC_OK); |
||
767 |
case EC_W_LEFT: |
||
768 |
not_in_completion(); |
||
769 |
while (cp > cmdbuf && cp[-1] == ' ') |
||
770 |
cmd_left(); |
||
771 |
while (cp > cmdbuf && cp[-1] != ' ') |
||
772 |
cmd_left(); |
||
773 |
return (CC_OK); |
||
774 |
case EC_HOME: |
||
775 |
not_in_completion(); |
||
776 |
cmd_offset = 0; |
||
777 |
cmd_home(); |
||
778 |
cmd_repaint(cp); |
||
779 |
return (CC_OK); |
||
780 |
case EC_END: |
||
781 |
not_in_completion(); |
||
782 |
while (*cp != '\0') |
||
783 |
cmd_right(); |
||
784 |
return (CC_OK); |
||
785 |
case EC_INSERT: |
||
786 |
not_in_completion(); |
||
787 |
return (CC_OK); |
||
788 |
case EC_BACKSPACE: |
||
789 |
not_in_completion(); |
||
790 |
return (cmd_erase()); |
||
791 |
case EC_LINEKILL: |
||
792 |
not_in_completion(); |
||
793 |
return (cmd_kill()); |
||
794 |
case EC_ABORT: |
||
795 |
not_in_completion(); |
||
796 |
(void) cmd_kill(); |
||
797 |
return (CC_QUIT); |
||
798 |
case EC_W_BACKSPACE: |
||
799 |
not_in_completion(); |
||
800 |
return (cmd_werase()); |
||
801 |
case EC_DELETE: |
||
802 |
not_in_completion(); |
||
803 |
return (cmd_delete()); |
||
804 |
case EC_W_DELETE: |
||
805 |
not_in_completion(); |
||
806 |
return (cmd_wdelete()); |
||
807 |
case EC_LITERAL: |
||
808 |
literal = 1; |
||
809 |
return (CC_OK); |
||
810 |
case EC_UP: |
||
811 |
case EC_DOWN: |
||
812 |
not_in_completion(); |
||
813 |
return (cmd_updown(action)); |
||
814 |
case EC_F_COMPLETE: |
||
815 |
case EC_B_COMPLETE: |
||
816 |
case EC_EXPAND: |
||
817 |
return (cmd_complete(action)); |
||
818 |
case EC_NOACTION: |
||
819 |
return (CC_OK); |
||
820 |
default: |
||
821 |
12 |
not_in_completion(); |
|
822 |
12 |
return (CC_PASS); |
|
823 |
} |
||
824 |
12 |
} |
|
825 |
|||
826 |
/* |
||
827 |
* Insert a string into the command buffer, at the current position. |
||
828 |
*/ |
||
829 |
static int |
||
830 |
cmd_istr(char *str) |
||
831 |
{ |
||
832 |
char *s; |
||
833 |
int action; |
||
834 |
char *endline = str + strlen(str); |
||
835 |
|||
836 |
for (s = str; *s != '\0'; ) { |
||
837 |
char *os = s; |
||
838 |
step_char(&s, +1, endline); |
||
839 |
action = cmd_ichar(os, s - os); |
||
840 |
if (action != CC_OK) { |
||
841 |
ring_bell(); |
||
842 |
return (action); |
||
843 |
} |
||
844 |
} |
||
845 |
return (CC_OK); |
||
846 |
} |
||
847 |
|||
848 |
/* |
||
849 |
* Find the beginning and end of the "current" word. |
||
850 |
* This is the word which the cursor (cp) is inside or at the end of. |
||
851 |
* Return pointer to the beginning of the word and put the |
||
852 |
* cursor at the end of the word. |
||
853 |
*/ |
||
854 |
static char * |
||
855 |
delimit_word(void) |
||
856 |
{ |
||
857 |
char *word; |
||
858 |
char *p; |
||
859 |
int delim_quoted = 0; |
||
860 |
int meta_quoted = 0; |
||
861 |
char *esc = get_meta_escape(); |
||
862 |
int esclen = strlen(esc); |
||
863 |
|||
864 |
/* |
||
865 |
* Move cursor to end of word. |
||
866 |
*/ |
||
867 |
if (*cp != ' ' && *cp != '\0') { |
||
868 |
/* |
||
869 |
* Cursor is on a nonspace. |
||
870 |
* Move cursor right to the next space. |
||
871 |
*/ |
||
872 |
while (*cp != ' ' && *cp != '\0') |
||
873 |
cmd_right(); |
||
874 |
} |
||
875 |
|||
876 |
/* |
||
877 |
* Find the beginning of the word which the cursor is in. |
||
878 |
*/ |
||
879 |
if (cp == cmdbuf) |
||
880 |
return (NULL); |
||
881 |
/* |
||
882 |
* If we have an unbalanced quote (that is, an open quote |
||
883 |
* without a corresponding close quote), we return everything |
||
884 |
* from the open quote, including spaces. |
||
885 |
*/ |
||
886 |
for (word = cmdbuf; word < cp; word++) |
||
887 |
if (*word != ' ') |
||
888 |
break; |
||
889 |
if (word >= cp) |
||
890 |
return (cp); |
||
891 |
for (p = cmdbuf; p < cp; p++) { |
||
892 |
if (meta_quoted) { |
||
893 |
meta_quoted = 0; |
||
894 |
} else if (esclen > 0 && p + esclen < cp && |
||
895 |
strncmp(p, esc, esclen) == 0) { |
||
896 |
meta_quoted = 1; |
||
897 |
p += esclen - 1; |
||
898 |
} else if (delim_quoted) { |
||
899 |
if (*p == closequote) |
||
900 |
delim_quoted = 0; |
||
901 |
} else { /* (!delim_quoted) */ |
||
902 |
if (*p == openquote) |
||
903 |
delim_quoted = 1; |
||
904 |
else if (*p == ' ') |
||
905 |
word = p+1; |
||
906 |
} |
||
907 |
} |
||
908 |
return (word); |
||
909 |
} |
||
910 |
|||
911 |
/* |
||
912 |
* Set things up to enter completion mode. |
||
913 |
* Expand the word under the cursor into a list of filenames |
||
914 |
* which start with that word, and set tk_text to that list. |
||
915 |
*/ |
||
916 |
static void |
||
917 |
init_compl(void) |
||
918 |
{ |
||
919 |
char *word; |
||
920 |
char c; |
||
921 |
|||
922 |
free(tk_text); |
||
923 |
tk_text = NULL; |
||
924 |
/* |
||
925 |
* Find the original (uncompleted) word in the command buffer. |
||
926 |
*/ |
||
927 |
word = delimit_word(); |
||
928 |
if (word == NULL) |
||
929 |
return; |
||
930 |
/* |
||
931 |
* Set the insertion point to the point in the command buffer |
||
932 |
* where the original (uncompleted) word now sits. |
||
933 |
*/ |
||
934 |
tk_ipoint = word; |
||
935 |
/* |
||
936 |
* Save the original (uncompleted) word |
||
937 |
*/ |
||
938 |
free(tk_original); |
||
939 |
tk_original = ecalloc(cp-word+1, sizeof (char)); |
||
940 |
(void) strncpy(tk_original, word, cp-word); |
||
941 |
/* |
||
942 |
* Get the expanded filename. |
||
943 |
* This may result in a single filename, or |
||
944 |
* a blank-separated list of filenames. |
||
945 |
*/ |
||
946 |
c = *cp; |
||
947 |
*cp = '\0'; |
||
948 |
if (*word != openquote) { |
||
949 |
tk_text = fcomplete(word); |
||
950 |
} else { |
||
951 |
char *qword = shell_quote(word+1); |
||
952 |
if (qword == NULL) |
||
953 |
tk_text = fcomplete(word+1); |
||
954 |
else |
||
955 |
tk_text = fcomplete(qword); |
||
956 |
free(qword); |
||
957 |
} |
||
958 |
*cp = c; |
||
959 |
} |
||
960 |
|||
961 |
/* |
||
962 |
* Return the next word in the current completion list. |
||
963 |
*/ |
||
964 |
static char * |
||
965 |
next_compl(int action, char *prev) |
||
966 |
{ |
||
967 |
switch (action) { |
||
968 |
case EC_F_COMPLETE: |
||
969 |
return (forw_textlist(&tk_tlist, prev)); |
||
970 |
case EC_B_COMPLETE: |
||
971 |
return (back_textlist(&tk_tlist, prev)); |
||
972 |
} |
||
973 |
/* Cannot happen */ |
||
974 |
return ("?"); |
||
975 |
} |
||
976 |
|||
977 |
/* |
||
978 |
* Complete the filename before (or under) the cursor. |
||
979 |
* cmd_complete may be called multiple times. The global in_completion |
||
980 |
* remembers whether this call is the first time (create the list), |
||
981 |
* or a subsequent time (step thru the list). |
||
982 |
*/ |
||
983 |
static int |
||
984 |
cmd_complete(int action) |
||
985 |
{ |
||
986 |
char *s; |
||
987 |
|||
988 |
if (!in_completion || action == EC_EXPAND) { |
||
989 |
/* |
||
990 |
* Expand the word under the cursor and |
||
991 |
* use the first word in the expansion |
||
992 |
* (or the entire expansion if we're doing EC_EXPAND). |
||
993 |
*/ |
||
994 |
init_compl(); |
||
995 |
if (tk_text == NULL) { |
||
996 |
ring_bell(); |
||
997 |
return (CC_OK); |
||
998 |
} |
||
999 |
if (action == EC_EXPAND) { |
||
1000 |
/* |
||
1001 |
* Use the whole list. |
||
1002 |
*/ |
||
1003 |
tk_trial = tk_text; |
||
1004 |
} else { |
||
1005 |
/* |
||
1006 |
* Use the first filename in the list. |
||
1007 |
*/ |
||
1008 |
in_completion = 1; |
||
1009 |
init_textlist(&tk_tlist, tk_text); |
||
1010 |
tk_trial = next_compl(action, NULL); |
||
1011 |
} |
||
1012 |
} else { |
||
1013 |
/* |
||
1014 |
* We already have a completion list. |
||
1015 |
* Use the next/previous filename from the list. |
||
1016 |
*/ |
||
1017 |
tk_trial = next_compl(action, tk_trial); |
||
1018 |
} |
||
1019 |
|||
1020 |
/* |
||
1021 |
* Remove the original word, or the previous trial completion. |
||
1022 |
*/ |
||
1023 |
while (cp > tk_ipoint) |
||
1024 |
(void) cmd_erase(); |
||
1025 |
|||
1026 |
if (tk_trial == NULL) { |
||
1027 |
/* |
||
1028 |
* There are no more trial completions. |
||
1029 |
* Insert the original (uncompleted) filename. |
||
1030 |
*/ |
||
1031 |
in_completion = 0; |
||
1032 |
if (cmd_istr(tk_original) != CC_OK) |
||
1033 |
goto fail; |
||
1034 |
} else { |
||
1035 |
/* |
||
1036 |
* Insert trial completion. |
||
1037 |
*/ |
||
1038 |
if (cmd_istr(tk_trial) != CC_OK) |
||
1039 |
goto fail; |
||
1040 |
/* |
||
1041 |
* If it is a directory, append a slash. |
||
1042 |
*/ |
||
1043 |
if (is_dir(tk_trial)) { |
||
1044 |
if (cp > cmdbuf && cp[-1] == closequote) |
||
1045 |
(void) cmd_erase(); |
||
1046 |
s = lgetenv("LESSSEPARATOR"); |
||
1047 |
if (s == NULL) |
||
1048 |
s = "/"; |
||
1049 |
if (cmd_istr(s) != CC_OK) |
||
1050 |
goto fail; |
||
1051 |
} |
||
1052 |
} |
||
1053 |
|||
1054 |
return (CC_OK); |
||
1055 |
|||
1056 |
fail: |
||
1057 |
in_completion = 0; |
||
1058 |
ring_bell(); |
||
1059 |
return (CC_OK); |
||
1060 |
} |
||
1061 |
|||
1062 |
/* |
||
1063 |
* Process a single character of a multi-character command, such as |
||
1064 |
* a number, or the pattern of a search command. |
||
1065 |
* Returns: |
||
1066 |
* CC_OK The char was accepted. |
||
1067 |
* CC_QUIT The char requests the command to be aborted. |
||
1068 |
* CC_ERROR The char could not be accepted due to an error. |
||
1069 |
*/ |
||
1070 |
int |
||
1071 |
cmd_char(int c) |
||
1072 |
{ |
||
1073 |
int action; |
||
1074 |
int len; |
||
1075 |
|||
1076 |
✓✗ | 280 |
if (!utf_mode) { |
1077 |
140 |
cmd_mbc_buf[0] = c & 0xff; |
|
1078 |
len = 1; |
||
1079 |
140 |
} 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 |
✗✓ | 140 |
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 |
✓✓ | 140 |
if (in_mca() && len == 1) { |
1132 |
12 |
action = cmd_edit(c); |
|
1133 |
✗✓ | 12 |
switch (action) { |
1134 |
case CC_OK: |
||
1135 |
case CC_QUIT: |
||
1136 |
return (action); |
||
1137 |
case CC_PASS: |
||
1138 |
break; |
||
1139 |
} |
||
1140 |
} |
||
1141 |
|||
1142 |
/* |
||
1143 |
* Insert the char into the command buffer. |
||
1144 |
*/ |
||
1145 |
140 |
return (cmd_ichar(cmd_mbc_buf, len)); |
|
1146 |
140 |
} |
|
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 |
198 |
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 |
✗✓ | 2 |
if (curr_mlist == NULL) |
1184 |
return (NULL); |
||
1185 |
1 |
return (curr_mlist->curr_mp->prev->string); |
|
1186 |
1 |
} |
|
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 |
22 |
name = lgetenv("LESSHISTFILE"); |
|
1199 |
✗✓✗✗ |
11 |
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 |
✓✗ | 11 |
if (strcmp(LESSHISTFILE, "-") == 0) |
1208 |
11 |
return (NULL); |
|
1209 |
home = lgetenv("HOME"); |
||
1210 |
if (home == NULL || *home == '\0') { |
||
1211 |
return (NULL); |
||
1212 |
} |
||
1213 |
return (easprintf("%s/%s", home, LESSHISTFILE)); |
||
1214 |
11 |
} |
|
1215 |
|||
1216 |
/* |
||
1217 |
* Initialize history from a .lesshist file. |
||
1218 |
*/ |
||
1219 |
void |
||
1220 |
init_cmdhist(void) |
||
1221 |
{ |
||
1222 |
struct mlist *ml = NULL; |
||
1223 |
16 |
char line[CMDBUF_SIZE]; |
|
1224 |
char *filename; |
||
1225 |
FILE *f; |
||
1226 |
char *p; |
||
1227 |
|||
1228 |
8 |
filename = histfile_name(); |
|
1229 |
✓✗ | 8 |
if (filename == NULL) |
1230 |
8 |
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 |
8 |
} |
|
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 |
16 |
struct stat statbuf; |
|
1297 |
int r; |
||
1298 |
|||
1299 |
✓✓ | 8 |
if (mlist_search.modified) |
1300 |
3 |
modified = 1; |
|
1301 |
✗✓ | 8 |
if (mlist_shell.modified) |
1302 |
modified = 1; |
||
1303 |
✓✓ | 8 |
if (!modified) |
1304 |
5 |
return; |
|
1305 |
3 |
filename = histfile_name(); |
|
1306 |
✓✗ | 3 |
if (filename == NULL) |
1307 |
3 |
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 |
8 |
} |
Generated by: GCOVR (Version 3.3) |