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