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