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