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