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