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