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