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