| /* $OpenBSD: edit.c,v 1.41 2015/09/01 13:12:31 tedu Exp $ */ |
| /* $OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $ */ |
| /* $OpenBSD: emacs.c,v 1.52 2015/09/10 22:48:58 nicm Exp $ */ |
| /* $OpenBSD: vi.c,v 1.30 2015/09/10 22:48:58 nicm Exp $ */ |
| |
| /*- |
| * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, |
| * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, |
| * 2019, 2020 |
| * mirabilos <[email protected]> |
| * |
| * Provided that these terms and disclaimer and all copyright notices |
| * are retained or reproduced in an accompanying document, permission |
| * is granted to deal in this work without restriction, including un- |
| * limited rights to use, publicly perform, distribute, sell, modify, |
| * merge, give away, or sublicence. |
| * |
| * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to |
| * the utmost extent permitted by applicable law, neither express nor |
| * implied; without malicious intent or gross negligence. In no event |
| * may a licensor, author or contributor be held liable for indirect, |
| * direct, other damage, loss, or other issues arising in any way out |
| * of dealing in the work, even if advised of the possibility of such |
| * damage or existence of a defect, except proven that it results out |
| * of said person's immediate fault when using the work as intended. |
| */ |
| |
| #include "sh.h" |
| |
| #ifndef MKSH_NO_CMDLINE_EDITING |
| |
| __RCSID("$MirOS: src/bin/mksh/edit.c,v 1.357 2020/10/31 05:02:17 tg Exp $"); |
| |
| /* |
| * in later versions we might use libtermcap for this, but since external |
| * dependencies are problematic, this has not yet been decided on; another |
| * good string is KSH_ESC_STRING "c" except on hardware terminals like the |
| * DEC VT420 which do a full power cycle then... |
| */ |
| #ifndef MKSH_CLS_STRING |
| #define MKSH_CLS_STRING KSH_ESC_STRING "[;H" KSH_ESC_STRING "[J" |
| #endif |
| |
| #if !defined(MKSH_SMALL) || !MKSH_S_NOVI |
| static const char ctrl_x_e[] = "fc -e \"${VISUAL:-${EDITOR:-vi}}\" --"; |
| #endif |
| |
| /* tty driver characters we are interested in */ |
| #define EDCHAR_DISABLED 0xFFFFU |
| #define EDCHAR_INITIAL 0xFFFEU |
| static struct { |
| unsigned short erase; |
| unsigned short kill; |
| unsigned short werase; |
| unsigned short intr; |
| unsigned short quit; |
| unsigned short eof; |
| } edchars; |
| |
| #define isched(x,e) ((unsigned short)(unsigned char)(x) == (e)) |
| #define isedchar(x) (!((x) & ~0xFF)) |
| #ifndef _POSIX_VDISABLE |
| #define toedchar(x) ((unsigned short)(unsigned char)(x)) |
| #else |
| #define toedchar(x) (((_POSIX_VDISABLE != -1) && ((x) == _POSIX_VDISABLE)) ? \ |
| ((unsigned short)EDCHAR_DISABLED) : \ |
| ((unsigned short)(unsigned char)(x))) |
| #endif |
| |
| /* x_cf_glob() flags */ |
| #define XCF_COMMAND BIT(0) /* Do command completion */ |
| #define XCF_FILE BIT(1) /* Do file completion */ |
| #define XCF_FULLPATH BIT(2) /* command completion: store full path */ |
| #define XCF_COMMAND_FILE (XCF_COMMAND | XCF_FILE) |
| #define XCF_IS_COMMAND BIT(3) /* return flag: is command */ |
| #define XCF_IS_NOSPACE BIT(4) /* return flag: do not append a space */ |
| |
| static char editmode; |
| static int xx_cols; /* for Emacs mode */ |
| static int modified; /* buffer has been "modified" */ |
| static char *holdbufp; /* place to hold last edit buffer */ |
| |
| /* 0=dumb 1=tmux (for now) */ |
| static uint8_t x_term_mode; |
| |
| static void x_adjust(void); |
| static int x_getc(void); |
| static void x_putcf(int); |
| static void x_modified(void); |
| static void x_mode(bool); |
| static int x_do_comment(char *, ssize_t, ssize_t *); |
| static void x_print_expansions(int, char * const *, bool); |
| static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***); |
| static size_t x_longest_prefix(int, char * const *); |
| static void x_glob_hlp_add_qchar(char *); |
| static char *x_glob_hlp_tilde_and_rem_qchar(char *, bool); |
| static size_t x_basename(const char *, const char *); |
| static void x_free_words(int, char **); |
| static int x_escape(const char *, size_t, int (*)(const char *, size_t)); |
| static int x_emacs(char *); |
| static void x_init_prompt(bool); |
| #if !MKSH_S_NOVI |
| static int x_vi(char *); |
| #endif |
| static void x_intr(int, int) MKSH_A_NORETURN; |
| |
| #define x_flush() shf_flush(shl_out) |
| #if defined(MKSH_SMALL) && !defined(MKSH_SMALL_BUT_FAST) |
| #define x_putc(c) x_putcf(c) |
| #else |
| #define x_putc(c) shf_putc((c), shl_out) |
| #endif |
| |
| static int path_order_cmp(const void *, const void *); |
| static void glob_table(const char *, XPtrV *, struct table *); |
| static void glob_path(int, const char *, XPtrV *, const char *); |
| static int x_file_glob(int *, char *, char ***); |
| static int x_command_glob(int, char *, char ***); |
| static int x_locate_word(const char *, int, int, int *, bool *); |
| |
| static int x_e_getmbc(char *); |
| |
| /* +++ generic editing functions +++ */ |
| |
| /* |
| * read an edited command line |
| */ |
| int |
| x_read(char *buf) |
| { |
| int i; |
| |
| x_mode(true); |
| modified = 1; |
| if (Flag(FEMACS) || Flag(FGMACS)) |
| i = x_emacs(buf); |
| #if !MKSH_S_NOVI |
| else if (Flag(FVI)) |
| i = x_vi(buf); |
| #endif |
| else |
| /* internal error */ |
| i = -1; |
| editmode = 0; |
| x_mode(false); |
| return (i); |
| } |
| |
| /* tty I/O */ |
| |
| static int |
| x_getc(void) |
| { |
| #ifdef __OS2__ |
| return (_read_kbd(0, 1, 0)); |
| #else |
| char c; |
| ssize_t n; |
| |
| while ((n = blocking_read(0, &c, 1)) < 0 && errno == EINTR) |
| if (trap) { |
| x_mode(false); |
| runtraps(0); |
| #ifdef SIGWINCH |
| if (got_winch) { |
| change_winsz(); |
| if (x_cols != xx_cols && editmode == 1) { |
| /* redraw line in Emacs mode */ |
| xx_cols = x_cols; |
| x_init_prompt(false); |
| x_adjust(); |
| } |
| } |
| #endif |
| x_mode(true); |
| } |
| return ((n == 1) ? (int)(unsigned char)c : -1); |
| #endif |
| } |
| |
| static void |
| x_putcf(int c) |
| { |
| shf_putc_i(c, shl_out); |
| } |
| |
| /********************************* |
| * Misc common code for vi/emacs * |
| *********************************/ |
| |
| /*- |
| * Handle the commenting/uncommenting of a line. |
| * Returns: |
| * 1 if a carriage return is indicated (comment added) |
| * 0 if no return (comment removed) |
| * -1 if there is an error (not enough room for comment chars) |
| * If successful, *lenp contains the new length. Note: cursor should be |
| * moved to the start of the line after (un)commenting. |
| */ |
| static int |
| x_do_comment(char *buf, ssize_t bsize, ssize_t *lenp) |
| { |
| ssize_t i, j, len = *lenp; |
| |
| if (len == 0) |
| /* somewhat arbitrary - it's what AT&T ksh does */ |
| return (1); |
| |
| /* Already commented? */ |
| if (buf[0] == '#') { |
| bool saw_nl = false; |
| |
| for (j = 0, i = 1; i < len; i++) { |
| if (!saw_nl || buf[i] != '#') |
| buf[j++] = buf[i]; |
| saw_nl = buf[i] == '\n'; |
| } |
| *lenp = j; |
| return (0); |
| } else { |
| int n = 1; |
| |
| /* See if there's room for the #s - 1 per \n */ |
| for (i = 0; i < len; i++) |
| if (buf[i] == '\n') |
| n++; |
| if (len + n >= bsize) |
| return (-1); |
| /* Now add them... */ |
| for (i = len, j = len + n; --i >= 0; ) { |
| if (buf[i] == '\n') |
| buf[--j] = '#'; |
| buf[--j] = buf[i]; |
| } |
| buf[0] = '#'; |
| *lenp += n; |
| return (1); |
| } |
| } |
| |
| /**************************************************** |
| * Common file/command completion code for vi/emacs * |
| ****************************************************/ |
| |
| static void |
| x_print_expansions(int nwords, char * const *words, bool is_command) |
| { |
| bool use_copy = false; |
| size_t prefix_len; |
| XPtrV l = { NULL, 0, 0 }; |
| struct columnise_opts co; |
| |
| /* |
| * Check if all matches are in the same directory (in this |
| * case, we want to omit the directory name) |
| */ |
| if (!is_command && |
| (prefix_len = x_longest_prefix(nwords, words)) > 0) { |
| int i; |
| |
| /* Special case for 1 match (prefix is whole word) */ |
| if (nwords == 1) |
| prefix_len = x_basename(words[0], NULL); |
| /* Any (non-trailing) slashes in non-common word suffixes? */ |
| for (i = 0; i < nwords; i++) |
| if (x_basename(words[i] + prefix_len, NULL) > |
| prefix_len) |
| break; |
| /* All in same directory? */ |
| if (i == nwords) { |
| while (prefix_len > 0 && |
| !mksh_cdirsep(words[0][prefix_len - 1])) |
| prefix_len--; |
| use_copy = true; |
| XPinit(l, nwords + 1); |
| for (i = 0; i < nwords; i++) |
| XPput(l, words[i] + prefix_len); |
| XPput(l, NULL); |
| } |
| } |
| /* |
| * Enumerate expansions |
| */ |
| x_putc('\r'); |
| x_putc('\n'); |
| co.shf = shl_out; |
| co.linesep = '\n'; |
| co.do_last = true; |
| co.prefcol = false; |
| pr_list(&co, use_copy ? (char **)XPptrv(l) : words); |
| |
| if (use_copy) |
| /* not x_free_words() */ |
| XPfree(l); |
| } |
| |
| /* |
| * Convert backslash-escaped string to QCHAR-escaped |
| * string useful for globbing; loses QCHAR unless it |
| * can squeeze in, eg. by previous loss of backslash |
| */ |
| static void |
| x_glob_hlp_add_qchar(char *cp) |
| { |
| char ch, *dp = cp; |
| bool escaping = false; |
| |
| while ((ch = *cp++)) { |
| if (ch == '\\' && !escaping) { |
| escaping = true; |
| continue; |
| } |
| if (escaping || (ch == QCHAR && (cp - dp) > 1)) { |
| /* |
| * empirically made list of chars to escape |
| * for globbing as well as QCHAR itself |
| */ |
| switch (ord(ch)) { |
| case QCHAR: |
| case ORD('$'): |
| case ORD('*'): |
| case ORD('?'): |
| case ORD('['): |
| case ORD('\\'): |
| case ORD('`'): |
| *dp++ = QCHAR; |
| break; |
| } |
| escaping = false; |
| } |
| *dp++ = ch; |
| } |
| *dp = '\0'; |
| } |
| |
| /* |
| * Run tilde expansion on argument string, return the result |
| * after unescaping; if the flag is set, the original string |
| * is freed if changed and assumed backslash-escaped, if not |
| * it is assumed QCHAR-escaped |
| */ |
| static char * |
| x_glob_hlp_tilde_and_rem_qchar(char *s, bool magic_flag) |
| { |
| char ch, *cp, *dp; |
| |
| /* |
| * On the string, check whether we have a tilde expansion, |
| * and if so, discern "~foo/bar" and "~/baz" from "~blah"; |
| * if we have a directory part (the former), try to expand |
| */ |
| if (*s == '~' && (cp = /* not sdirsep */ strchr(s, '/')) != NULL) { |
| /* ok, so split into "~foo"/"bar" or "~"/"baz" */ |
| *cp++ = 0; |
| /* try to expand the tilde */ |
| if (!(dp = do_tilde(s + 1))) { |
| /* nope, revert damage */ |
| *--cp = '/'; |
| } else { |
| /* ok, expand and replace */ |
| strpathx(cp, dp, cp, 1); |
| if (magic_flag) |
| afree(s, ATEMP); |
| s = cp; |
| } |
| } |
| |
| /* ... convert it from backslash-escaped via QCHAR-escaped... */ |
| if (magic_flag) |
| x_glob_hlp_add_qchar(s); |
| /* ... to unescaped, for comparison with the matches */ |
| cp = dp = s; |
| |
| while ((ch = *cp++)) { |
| if (ch == QCHAR && !(ch = *cp++)) |
| break; |
| *dp++ = ch; |
| } |
| *dp = '\0'; |
| |
| return (s); |
| } |
| |
| /** |
| * Do file globbing: |
| * - does expansion, checks for no match, etc. |
| * - sets *wordsp to array of matching strings |
| * - returns number of matching strings |
| */ |
| static int |
| x_file_glob(int *flagsp, char *toglob, char ***wordsp) |
| { |
| char **words, *cp; |
| int nwords; |
| XPtrV w; |
| struct source *s, *sold; |
| |
| /* remove all escaping backward slashes */ |
| x_glob_hlp_add_qchar(toglob); |
| |
| /* |
| * Convert "foo*" (toglob) to an array of strings (words) |
| */ |
| sold = source; |
| s = pushs(SWSTR, ATEMP); |
| s->start = s->str = toglob; |
| source = s; |
| if (yylex(ONEWORD | LQCHAR) != LWORD) { |
| source = sold; |
| internal_warningf(Tfg_badsubst); |
| return (0); |
| } |
| source = sold; |
| afree(s, ATEMP); |
| XPinit(w, 32); |
| cp = yylval.cp; |
| while (*cp == CHAR || *cp == QCHAR) |
| cp += 2; |
| nwords = DOGLOB | DOTILDE | DOMARKDIRS; |
| if (*cp != EOS) { |
| /* probably a $FOO expansion */ |
| *flagsp |= XCF_IS_NOSPACE; |
| /* this always results in at most one match */ |
| nwords = 0; |
| } |
| expand(yylval.cp, &w, nwords); |
| XPput(w, NULL); |
| words = (char **)XPclose(w); |
| |
| for (nwords = 0; words[nwords]; nwords++) |
| ; |
| if (nwords == 1) { |
| struct stat statb; |
| |
| /* Expand any tilde and drop all QCHAR for comparison */ |
| toglob = x_glob_hlp_tilde_and_rem_qchar(toglob, false); |
| |
| /* |
| * Check if globbing failed (returned glob pattern), |
| * but be careful (e.g. toglob == "ab*" when the file |
| * "ab*" exists is not an error). |
| * Also, check for empty result - happens if we tried |
| * to glob something which evaluated to an empty |
| * string (e.g., "$FOO" when there is no FOO, etc). |
| */ |
| if ((strcmp(words[0], toglob) == 0 && |
| stat(words[0], &statb) < 0) || |
| words[0][0] == '\0') { |
| x_free_words(nwords, words); |
| words = NULL; |
| nwords = 0; |
| } |
| } |
| |
| if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL) |
| x_free_words(nwords, words); |
| |
| return (nwords); |
| } |
| |
| /* Data structure used in x_command_glob() */ |
| struct path_order_info { |
| char *word; |
| size_t base; |
| size_t path_order; |
| }; |
| |
| /* Compare routine used in x_command_glob() */ |
| static int |
| path_order_cmp(const void *aa, const void *bb) |
| { |
| const struct path_order_info *a = (const struct path_order_info *)aa; |
| const struct path_order_info *b = (const struct path_order_info *)bb; |
| int t; |
| |
| if ((t = ascstrcmp(a->word + a->base, b->word + b->base))) |
| return (t); |
| if (a->path_order > b->path_order) |
| return (1); |
| if (a->path_order < b->path_order) |
| return (-1); |
| return (0); |
| } |
| |
| static int |
| x_command_glob(int flags, char *toglob, char ***wordsp) |
| { |
| char *pat, *fpath; |
| size_t nwords; |
| XPtrV w; |
| struct block *l; |
| |
| /* Convert "foo*" (toglob) to a pattern for future use */ |
| pat = evalstr(toglob, DOPAT | DOTILDE); |
| |
| XPinit(w, 32); |
| |
| glob_table(pat, &w, &keywords); |
| glob_table(pat, &w, &aliases); |
| glob_table(pat, &w, &builtins); |
| for (l = e->loc; l; l = l->next) |
| glob_table(pat, &w, &l->funs); |
| |
| glob_path(flags, pat, &w, path); |
| if ((fpath = str_val(global(TFPATH))) != null) |
| glob_path(flags, pat, &w, fpath); |
| |
| nwords = XPsize(w); |
| |
| if (!nwords) { |
| *wordsp = NULL; |
| XPfree(w); |
| return (0); |
| } |
| /* Sort entries */ |
| if (flags & XCF_FULLPATH) { |
| /* Sort by basename, then path order */ |
| struct path_order_info *info, *last_info = NULL; |
| char **words = (char **)XPptrv(w); |
| size_t i, path_order = 0; |
| |
| info = (struct path_order_info *) |
| alloc2(nwords, sizeof(struct path_order_info), ATEMP); |
| for (i = 0; i < nwords; i++) { |
| info[i].word = words[i]; |
| info[i].base = x_basename(words[i], NULL); |
| if (!last_info || info[i].base != last_info->base || |
| strncmp(words[i], last_info->word, info[i].base) != 0) { |
| last_info = &info[i]; |
| path_order++; |
| } |
| info[i].path_order = path_order; |
| } |
| qsort(info, nwords, sizeof(struct path_order_info), |
| path_order_cmp); |
| for (i = 0; i < nwords; i++) |
| words[i] = info[i].word; |
| afree(info, ATEMP); |
| } else { |
| /* Sort and remove duplicate entries */ |
| char **words = (char **)XPptrv(w); |
| size_t i, j; |
| |
| qsort(words, nwords, sizeof(void *), ascpstrcmp); |
| for (i = j = 0; i < nwords - 1; i++) { |
| if (strcmp(words[i], words[i + 1])) |
| words[j++] = words[i]; |
| else |
| afree(words[i], ATEMP); |
| } |
| words[j++] = words[i]; |
| w.len = nwords = j; |
| } |
| |
| XPput(w, NULL); |
| *wordsp = (char **)XPclose(w); |
| |
| return (nwords); |
| } |
| |
| #define IS_WORDC(c) (!ctype(c, C_EDNWC)) |
| |
| static int |
| x_locate_word(const char *buf, int buflen, int pos, int *startp, |
| bool *is_commandp) |
| { |
| int start, end; |
| |
| /* Bad call? Probably should report error */ |
| if (pos < 0 || pos > buflen) { |
| *startp = pos; |
| *is_commandp = false; |
| return (0); |
| } |
| /* The case where pos == buflen happens to take care of itself... */ |
| |
| start = pos; |
| /* |
| * Keep going backwards to start of word (has effect of allowing |
| * one blank after the end of a word) |
| */ |
| for (; (start > 0 && IS_WORDC(buf[start - 1])) || |
| (start > 1 && buf[start - 2] == '\\'); start--) |
| ; |
| /* Go forwards to end of word */ |
| for (end = start; end < buflen && IS_WORDC(buf[end]); end++) { |
| if (buf[end] == '\\' && (end + 1) < buflen) |
| end++; |
| } |
| |
| if (is_commandp) { |
| bool iscmd; |
| int p = start - 1; |
| |
| /* Figure out if this is a command */ |
| while (p >= 0 && ctype(buf[p], C_SPACE)) |
| p--; |
| iscmd = p < 0 || ctype(buf[p], C_EDCMD); |
| if (iscmd) { |
| /* |
| * If command has a /, path, etc. is not searched; |
| * only current directory is searched which is just |
| * like file globbing. |
| */ |
| for (p = start; p < end; p++) |
| if (mksh_cdirsep(buf[p])) |
| break; |
| iscmd = p == end; |
| } |
| *is_commandp = iscmd; |
| } |
| *startp = start; |
| |
| return (end - start); |
| } |
| |
| static int |
| x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp, |
| int *endp, char ***wordsp) |
| { |
| int len, nwords = 0; |
| char **words = NULL; |
| bool is_command; |
| |
| len = x_locate_word(buf, buflen, pos, startp, &is_command); |
| if (!((*flagsp) & XCF_COMMAND)) |
| is_command = false; |
| /* |
| * Don't do command globing on zero length strings - it takes too |
| * long and isn't very useful. File globs are more likely to be |
| * useful, so allow these. |
| */ |
| if (len == 0 && is_command) |
| return (0); |
| |
| if (len >= 0) { |
| char *toglob, *s; |
| |
| /* |
| * Given a string, copy it and possibly add a '*' to the end. |
| */ |
| |
| strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP); |
| toglob[len] = '\0'; |
| |
| /* |
| * If the pathname contains a wildcard (an unquoted '*', |
| * '?', or '[') or an extglob, then it is globbed based |
| * on that value (i.e., without the appended '*'). Same |
| * for parameter substitutions (as in “cat $HOME/.ss↹”) |
| * without appending a trailing space (LP: #710539), as |
| * well as for “~foo” (but not “~foo/”). |
| */ |
| for (s = toglob; *s; s++) { |
| if (*s == '\\' && s[1]) |
| s++; |
| else if (ctype(*s, C_QUEST | C_DOLAR) || |
| ord(*s) == ORD('*') || ord(*s) == ORD('[') || |
| /* ?() *() +() @() !() but two already checked */ |
| (ord(s[1]) == ORD('(' /*)*/) && |
| (ord(*s) == ORD('+') || ord(*s) == ORD('@') || |
| ord(*s) == ORD('!')))) { |
| /* |
| * just expand based on the extglob |
| * or parameter |
| */ |
| goto dont_add_glob; |
| } |
| } |
| |
| if (*toglob == '~' && /* not vdirsep */ !vstrchr(toglob, '/')) { |
| /* neither for '~foo' (but '~foo/bar') */ |
| *flagsp |= XCF_IS_NOSPACE; |
| goto dont_add_glob; |
| } |
| |
| /* append a glob */ |
| toglob[len] = '*'; |
| toglob[len + 1] = '\0'; |
| dont_add_glob: |
| /* |
| * Expand (glob) it now. |
| */ |
| |
| nwords = is_command ? |
| x_command_glob(*flagsp, toglob, &words) : |
| x_file_glob(flagsp, toglob, &words); |
| afree(toglob, ATEMP); |
| } |
| if (nwords == 0) { |
| *wordsp = NULL; |
| return (0); |
| } |
| if (is_command) |
| *flagsp |= XCF_IS_COMMAND; |
| *wordsp = words; |
| *endp = *startp + len; |
| |
| return (nwords); |
| } |
| |
| /* |
| * Find longest common prefix |
| */ |
| static size_t |
| x_longest_prefix(int nwords, char * const * words) |
| { |
| int i; |
| size_t j, prefix_len; |
| char *p; |
| |
| if (nwords <= 0) |
| return (0); |
| |
| prefix_len = strlen(words[0]); |
| for (i = 1; i < nwords; i++) |
| for (j = 0, p = words[i]; j < prefix_len; j++) |
| if (p[j] != words[0][j]) { |
| prefix_len = j; |
| break; |
| } |
| /* false for nwords==1 as 0 = words[0][prefix_len] then */ |
| if (UTFMODE && prefix_len && (rtt2asc(words[0][prefix_len]) & 0xC0) == 0x80) |
| while (prefix_len && (rtt2asc(words[0][prefix_len]) & 0xC0) != 0xC0) |
| --prefix_len; |
| return (prefix_len); |
| } |
| |
| static void |
| x_free_words(int nwords, char **words) |
| { |
| while (nwords) |
| afree(words[--nwords], ATEMP); |
| afree(words, ATEMP); |
| } |
| |
| /*- |
| * Return the offset of the basename of string s (which ends at se - need not |
| * be null terminated). Trailing slashes are ignored. If s is just a slash, |
| * then the offset is 0 (actually, length - 1). |
| * s Return |
| * /etc 1 |
| * /etc/ 1 |
| * /etc// 1 |
| * /etc/fo 5 |
| * foo 0 |
| * /// 2 |
| * 0 |
| */ |
| static size_t |
| x_basename(const char *s, const char *se) |
| { |
| const char *p; |
| |
| if (se == NULL) |
| se = strnul(s); |
| if (s == se) |
| return (0); |
| |
| /* skip trailing directory separators */ |
| p = se - 1; |
| while (p > s && mksh_cdirsep(*p)) |
| --p; |
| /* drop last component */ |
| while (p > s && !mksh_cdirsep(*p)) |
| --p; |
| if (mksh_cdirsep(*p) && p + 1 < se) |
| ++p; |
| |
| return (p - s); |
| } |
| |
| /* |
| * Apply pattern matching to a table: all table entries that match a pattern |
| * are added to wp. |
| */ |
| static void |
| glob_table(const char *pat, XPtrV *wp, struct table *tp) |
| { |
| struct tstate ts; |
| struct tbl *te; |
| |
| ktwalk(&ts, tp); |
| while ((te = ktnext(&ts))) |
| if (gmatchx(te->name, pat, false)) { |
| char *cp; |
| |
| strdupx(cp, te->name, ATEMP); |
| XPput(*wp, cp); |
| } |
| } |
| |
| static void |
| glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath) |
| { |
| const char *sp = lpath, *p; |
| char *xp, **words; |
| size_t pathlen, patlen, oldsize, newsize, i, j; |
| XString xs; |
| |
| patlen = strlen(pat); |
| checkoktoadd(patlen, 129 + X_EXTRA); |
| ++patlen; |
| Xinit(xs, xp, patlen + 128, ATEMP); |
| while (sp) { |
| xp = Xstring(xs, xp); |
| if (!(p = cstrchr(sp, MKSH_PATHSEPC))) |
| p = strnul(sp); |
| pathlen = p - sp; |
| if (pathlen) { |
| /* |
| * Copy sp into xp, stuffing any MAGIC characters |
| * on the way |
| */ |
| const char *s = sp; |
| |
| XcheckN(xs, xp, pathlen * 2); |
| while (s < p) { |
| if (ISMAGIC(*s)) |
| *xp++ = MAGIC; |
| *xp++ = *s++; |
| } |
| *xp++ = '/'; |
| pathlen++; |
| } |
| sp = p; |
| XcheckN(xs, xp, patlen); |
| memcpy(xp, pat, patlen); |
| |
| oldsize = XPsize(*wp); |
| /* mark dirs */ |
| glob_str(Xstring(xs, xp), wp, true); |
| newsize = XPsize(*wp); |
| |
| /* Check that each match is executable... */ |
| words = (char **)XPptrv(*wp); |
| for (i = j = oldsize; i < newsize; i++) { |
| if (ksh_access(words[i], X_OK) == 0) { |
| words[j] = words[i]; |
| if (!(flags & XCF_FULLPATH)) |
| memmove(words[j], words[j] + pathlen, |
| strlen(words[j] + pathlen) + 1); |
| j++; |
| } else |
| afree(words[i], ATEMP); |
| } |
| wp->len = j; |
| |
| if (!*sp++) |
| break; |
| } |
| Xfree(xs, xp); |
| } |
| |
| /* |
| * if argument string contains any special characters, they will |
| * be escaped and the result will be put into edit buffer by |
| * keybinding-specific function |
| */ |
| static int |
| x_escape(const char *s, size_t len, int (*putbuf_func)(const char *, size_t)) |
| { |
| size_t add = 0, wlen = len; |
| int rval = 0; |
| |
| while (wlen - add > 0) |
| if (ctype(s[add], C_IFS | C_EDQ)) { |
| if (putbuf_func(s, add) != 0) { |
| rval = -1; |
| break; |
| } |
| putbuf_func(s[add] == '\n' ? "'" : "\\", 1); |
| putbuf_func(&s[add], 1); |
| if (s[add] == '\n') |
| putbuf_func("'", 1); |
| |
| add++; |
| wlen -= add; |
| s += add; |
| add = 0; |
| } else |
| ++add; |
| if (wlen > 0 && rval == 0) |
| rval = putbuf_func(s, wlen); |
| |
| return (rval); |
| } |
| |
| |
| /* +++ emacs editing mode +++ */ |
| |
| static Area aedit; |
| #define AEDIT &aedit /* area for kill ring and macro defns */ |
| |
| /* values returned by keyboard functions */ |
| #define KSTD 0 |
| #define KEOL 1 /* ^M, ^J */ |
| #define KINTR 2 /* ^G, ^C */ |
| |
| struct x_ftab { |
| int (*xf_func)(int c); |
| const char *xf_name; |
| short xf_flags; |
| }; |
| |
| struct x_defbindings { |
| unsigned char xdb_func; /* XFUNC_* */ |
| unsigned char xdb_tab; |
| unsigned char xdb_char; |
| }; |
| |
| #define XF_ARG 1 /* command takes number prefix */ |
| #define XF_NOBIND 2 /* not allowed to bind to function */ |
| #define XF_PREFIX 4 /* function sets prefix */ |
| |
| #define X_NTABS 4 /* normal, meta1, meta2, pc */ |
| #define X_TABSZ 256 /* size of keydef tables etc */ |
| |
| /*- |
| * Arguments for do_complete() |
| * 0 = enumerate M-= complete as much as possible and then list |
| * 1 = complete M-Esc |
| * 2 = list M-? |
| */ |
| typedef enum { |
| CT_LIST, /* list the possible completions */ |
| CT_COMPLETE, /* complete to longest prefix */ |
| CT_COMPLIST /* complete and then list (if non-exact) */ |
| } Comp_type; |
| |
| /* |
| * The following are used for my horizontal scrolling stuff |
| */ |
| static char *xbuf; /* beg input buffer */ |
| static char *xend; /* end input buffer */ |
| static char *xcp; /* current position */ |
| static char *xep; /* current end */ |
| static char *xbp; /* start of visible portion of input buffer */ |
| static char *xlp; /* last char visible on screen */ |
| static bool x_adj_ok; |
| /* |
| * we use x_adj_done so that functions can tell |
| * whether x_adjust() has been called while they are active. |
| */ |
| static int x_adj_done; /* is incremented by x_adjust() */ |
| |
| static int x_displen; |
| static int x_arg; /* general purpose arg */ |
| static bool x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */ |
| |
| static bool xlp_valid; /* lastvis pointer was recalculated */ |
| |
| static char **x_histp; /* history position */ |
| static int x_nextcmd; /* for newline-and-next */ |
| static char **x_histncp; /* saved x_histp for " */ |
| static char **x_histmcp; /* saved x_histp for " */ |
| static char *xmp; /* mark pointer */ |
| static unsigned char x_last_command; |
| static unsigned char (*x_tab)[X_TABSZ]; /* key definition */ |
| #ifndef MKSH_SMALL |
| static char *(*x_atab)[X_TABSZ]; /* macro definitions */ |
| #endif |
| static unsigned char x_bound[(X_TABSZ * X_NTABS + 7) / 8]; |
| #define KILLSIZE 20 |
| static char *killstack[KILLSIZE]; |
| static int killsp, killtp; |
| static int x_curprefix; |
| #ifndef MKSH_SMALL |
| static char *macroptr; /* bind key macro active? */ |
| #endif |
| #if !MKSH_S_NOVI |
| static int winwidth; /* width of window */ |
| static char *wbuf[2]; /* window buffers */ |
| static int wbuf_len; /* length of window buffers (x_cols - 3) */ |
| static int win; /* window buffer in use */ |
| static char morec; /* more character at right of window */ |
| static int holdlen; /* length of holdbuf */ |
| #endif |
| static int pwidth; /* width of prompt */ |
| static int prompt_trunc; /* how much of prompt to truncate or -1 */ |
| static int x_col; /* current column on line */ |
| |
| static int x_ins(const char *); |
| static void x_delete(size_t, bool); |
| static size_t x_bword(void); |
| static size_t x_fword(bool); |
| static void x_goto(char *); |
| static char *x_bs0(char *, char *) MKSH_A_PURE; |
| static void x_bs3(char **); |
| static int x_size2(char *, char **); |
| static void x_zots(char *); |
| static void x_zotc3(char **); |
| static void x_vi_zotc(int); |
| static void x_load_hist(char **); |
| static int x_search(const char *, int, int); |
| #ifndef MKSH_SMALL |
| static int x_search_dir(int); |
| #endif |
| static int x_match(const char *, const char *); |
| static void x_redraw(int); |
| static void x_push(size_t); |
| static void x_bind_showone(int, int); |
| static void x_e_ungetc(int); |
| static int x_e_getc(void); |
| static void x_e_putc2(int); |
| static void x_e_putc3(const char **); |
| static void x_e_puts(const char *); |
| #ifndef MKSH_SMALL |
| static int x_fold_case(int); |
| #endif |
| static char *x_lastcp(void); |
| static void x_lastpos(void); |
| static void do_complete(int, Comp_type); |
| static size_t x_nb2nc(size_t) MKSH_A_PURE; |
| |
| static int unget_char = -1; |
| |
| static int x_do_ins(const char *, size_t); |
| static void bind_if_not_bound(int, int, int); |
| |
| enum emacs_funcs { |
| #define EMACSFN_ENUMS |
| #include "emacsfn.h" |
| XFUNC_MAX |
| }; |
| |
| #define EMACSFN_DEFNS |
| #include "emacsfn.h" |
| |
| static const struct x_ftab x_ftab[] = { |
| #define EMACSFN_ITEMS |
| #include "emacsfn.h" |
| }; |
| |
| static struct x_defbindings const x_defbindings[] = { |
| { XFUNC_del_back, 0, CTRL_QM }, |
| { XFUNC_del_bword, 1, CTRL_QM }, |
| { XFUNC_eot_del, 0, CTRL_D }, |
| { XFUNC_del_back, 0, CTRL_H }, |
| { XFUNC_del_bword, 1, CTRL_H }, |
| { XFUNC_del_bword, 1, 'h' }, |
| { XFUNC_mv_bword, 1, 'b' }, |
| { XFUNC_mv_fword, 1, 'f' }, |
| { XFUNC_del_fword, 1, 'd' }, |
| { XFUNC_mv_back, 0, CTRL_B }, |
| { XFUNC_mv_forw, 0, CTRL_F }, |
| { XFUNC_search_char_forw, 0, CTRL_BC }, |
| { XFUNC_search_char_back, 1, CTRL_BC }, |
| { XFUNC_newline, 0, CTRL_M }, |
| { XFUNC_newline, 0, CTRL_J }, |
| { XFUNC_end_of_text, 0, CTRL_US }, |
| { XFUNC_abort, 0, CTRL_G }, |
| { XFUNC_prev_com, 0, CTRL_P }, |
| { XFUNC_next_com, 0, CTRL_N }, |
| { XFUNC_nl_next_com, 0, CTRL_O }, |
| { XFUNC_search_hist, 0, CTRL_R }, |
| { XFUNC_beg_hist, 1, '<' }, |
| { XFUNC_end_hist, 1, '>' }, |
| { XFUNC_goto_hist, 1, 'g' }, |
| { XFUNC_mv_end, 0, CTRL_E }, |
| { XFUNC_mv_beg, 0, CTRL_A }, |
| { XFUNC_draw_line, 0, CTRL_L }, |
| { XFUNC_cls, 1, CTRL_L }, |
| { XFUNC_meta1, 0, CTRL_BO }, |
| { XFUNC_meta2, 0, CTRL_X }, |
| { XFUNC_kill, 0, CTRL_K }, |
| { XFUNC_yank, 0, CTRL_Y }, |
| { XFUNC_meta_yank, 1, 'y' }, |
| { XFUNC_literal, 0, CTRL_CA }, |
| { XFUNC_comment, 1, '#' }, |
| { XFUNC_transpose, 0, CTRL_T }, |
| { XFUNC_complete, 1, CTRL_BO }, |
| { XFUNC_comp_list, 0, CTRL_I }, |
| { XFUNC_comp_list, 1, '=' }, |
| { XFUNC_enumerate, 1, '?' }, |
| { XFUNC_expand, 1, '*' }, |
| { XFUNC_comp_file, 1, CTRL_X }, |
| { XFUNC_comp_comm, 2, CTRL_BO }, |
| { XFUNC_list_comm, 2, '?' }, |
| { XFUNC_list_file, 2, CTRL_Y }, |
| { XFUNC_set_mark, 1, ' ' }, |
| { XFUNC_kill_region, 0, CTRL_W }, |
| { XFUNC_xchg_point_mark, 2, CTRL_X }, |
| { XFUNC_literal, 0, CTRL_V }, |
| { XFUNC_version, 1, CTRL_V }, |
| { XFUNC_prev_histword, 1, '.' }, |
| { XFUNC_prev_histword, 1, '_' }, |
| { XFUNC_set_arg, 1, '0' }, |
| { XFUNC_set_arg, 1, '1' }, |
| { XFUNC_set_arg, 1, '2' }, |
| { XFUNC_set_arg, 1, '3' }, |
| { XFUNC_set_arg, 1, '4' }, |
| { XFUNC_set_arg, 1, '5' }, |
| { XFUNC_set_arg, 1, '6' }, |
| { XFUNC_set_arg, 1, '7' }, |
| { XFUNC_set_arg, 1, '8' }, |
| { XFUNC_set_arg, 1, '9' }, |
| #ifndef MKSH_SMALL |
| { XFUNC_fold_upper, 1, 'U' }, |
| { XFUNC_fold_upper, 1, 'u' }, |
| { XFUNC_fold_lower, 1, 'L' }, |
| { XFUNC_fold_lower, 1, 'l' }, |
| { XFUNC_fold_capitalise, 1, 'C' }, |
| { XFUNC_fold_capitalise, 1, 'c' }, |
| #endif |
| /* |
| * These for ANSI arrow keys: arguablely shouldn't be here by |
| * default, but its simpler/faster/smaller than using termcap |
| * entries. |
| */ |
| { XFUNC_meta2, 1, '[' }, |
| { XFUNC_meta2, 1, 'O' }, |
| { XFUNC_prev_com, 2, 'A' }, |
| { XFUNC_next_com, 2, 'B' }, |
| { XFUNC_mv_forw, 2, 'C' }, |
| { XFUNC_mv_back, 2, 'D' }, |
| #ifndef MKSH_SMALL |
| { XFUNC_vt_hack, 2, '1' }, |
| { XFUNC_mv_beg | 0x80, 2, '7' }, |
| { XFUNC_mv_beg, 2, 'H' }, |
| { XFUNC_mv_end | 0x80, 2, '4' }, |
| { XFUNC_mv_end | 0x80, 2, '8' }, |
| { XFUNC_mv_end, 2, 'F' }, |
| { XFUNC_del_char | 0x80, 2, '3' }, |
| { XFUNC_del_char, 2, 'P' }, |
| { XFUNC_search_hist_up | 0x80, 2, '5' }, |
| { XFUNC_search_hist_dn | 0x80, 2, '6' }, |
| #endif |
| /* PC scancodes */ |
| #if !defined(MKSH_SMALL) || defined(__OS2__) |
| { XFUNC_meta3, 0, 0 }, |
| { XFUNC_mv_beg, 3, 71 }, |
| { XFUNC_prev_com, 3, 72 }, |
| #ifndef MKSH_SMALL |
| { XFUNC_search_hist_up, 3, 73 }, |
| #endif |
| { XFUNC_mv_back, 3, 75 }, |
| { XFUNC_mv_forw, 3, 77 }, |
| { XFUNC_mv_end, 3, 79 }, |
| { XFUNC_next_com, 3, 80 }, |
| #ifndef MKSH_SMALL |
| { XFUNC_search_hist_dn, 3, 81 }, |
| #endif |
| { XFUNC_del_char, 3, 83 }, |
| #endif |
| #ifndef MKSH_SMALL |
| /* more non-standard ones */ |
| { XFUNC_eval_region, 1, CTRL_E }, |
| { XFUNC_quote_region, 1, 'Q' }, |
| { XFUNC_edit_line, 2, 'e' } |
| #endif |
| }; |
| |
| static size_t |
| x_nb2nc(size_t nb) |
| { |
| char *cp; |
| size_t nc = 0; |
| |
| for (cp = xcp; cp < (xcp + nb); ++nc) |
| cp += utf_ptradj(cp); |
| return (nc); |
| } |
| |
| static void |
| x_modified(void) |
| { |
| if (!modified) { |
| x_histmcp = x_histp; |
| x_histp = histptr + 1; |
| modified = 1; |
| } |
| } |
| |
| #ifdef MKSH_SMALL |
| #define XFUNC_VALUE(f) (f) |
| #else |
| #define XFUNC_VALUE(f) (f & 0x7F) |
| #endif |
| |
| static int |
| x_e_getmbc(char *sbuf) |
| { |
| int c, pos = 0; |
| unsigned char *buf = (unsigned char *)sbuf; |
| |
| memset(buf, 0, 4); |
| buf[pos++] = c = x_e_getc(); |
| if (c == -1) |
| return (-1); |
| if (UTFMODE) { |
| if ((rtt2asc(buf[0]) >= (unsigned char)0xC2) && |
| (rtt2asc(buf[0]) < (unsigned char)0xF0)) { |
| c = x_e_getc(); |
| if (c == -1) |
| return (-1); |
| if ((rtt2asc(c) & 0xC0) != 0x80) { |
| x_e_ungetc(c); |
| return (1); |
| } |
| buf[pos++] = c; |
| } |
| if ((rtt2asc(buf[0]) >= (unsigned char)0xE0) && |
| (rtt2asc(buf[0]) < (unsigned char)0xF0)) { |
| /* XXX x_e_ungetc is one-octet only */ |
| buf[pos++] = c = x_e_getc(); |
| if (c == -1) |
| return (-1); |
| } |
| } |
| return (pos); |
| } |
| |
| /* |
| * minimum required space to work with on a line - if the prompt |
| * leaves less space than this on a line, the prompt is truncated |
| */ |
| #define MIN_EDIT_SPACE 7 |
| |
| static void |
| x_init_prompt(bool doprint) |
| { |
| prompt_trunc = pprompt(prompt, doprint ? 0 : -1); |
| pwidth = prompt_trunc % x_cols; |
| prompt_trunc -= pwidth; |
| if ((mksh_uari_t)pwidth > ((mksh_uari_t)x_cols - 3 - MIN_EDIT_SPACE)) { |
| /* force newline after prompt */ |
| prompt_trunc = -1; |
| pwidth = 0; |
| if (doprint) |
| x_e_putc2('\n'); |
| } |
| } |
| |
| static int |
| x_emacs(char *buf) |
| { |
| int c, i; |
| unsigned char f; |
| |
| xbp = xbuf = buf; |
| xend = buf + LINE; |
| xlp = xcp = xep = buf; |
| *xcp = 0; |
| xlp_valid = true; |
| xmp = NULL; |
| x_curprefix = 0; |
| x_histmcp = x_histp = histptr + 1; |
| x_last_command = XFUNC_error; |
| |
| x_init_prompt(true); |
| x_displen = (xx_cols = x_cols) - 2 - (x_col = pwidth); |
| x_adj_done = 0; |
| x_adj_ok = true; |
| |
| x_histncp = NULL; |
| if (x_nextcmd >= 0) { |
| int off = source->line - x_nextcmd; |
| if (histptr - history >= off) { |
| x_load_hist(histptr - off); |
| x_histncp = x_histp; |
| } |
| x_nextcmd = -1; |
| } |
| editmode = 1; |
| while (/* CONSTCOND */ 1) { |
| x_flush(); |
| if ((c = x_e_getc()) < 0) |
| return (0); |
| |
| f = x_curprefix == -1 ? XFUNC_insert : |
| x_tab[x_curprefix][c]; |
| #ifndef MKSH_SMALL |
| if (f & 0x80) { |
| f &= 0x7F; |
| if ((i = x_e_getc()) != '~') |
| x_e_ungetc(i); |
| } |
| |
| /* avoid bind key macro recursion */ |
| if (macroptr && f == XFUNC_ins_string) |
| f = XFUNC_insert; |
| #endif |
| |
| if (!(x_ftab[f].xf_flags & XF_PREFIX) && |
| x_last_command != XFUNC_set_arg) { |
| x_arg = 1; |
| x_arg_defaulted = true; |
| } |
| i = c | (x_curprefix << 8); |
| x_curprefix = 0; |
| switch ((*x_ftab[f].xf_func)(i)) { |
| case KSTD: |
| if (!(x_ftab[f].xf_flags & XF_PREFIX)) |
| x_last_command = f; |
| break; |
| case KEOL: |
| i = xep - xbuf; |
| return (i); |
| case KINTR: |
| /* special case for interrupt */ |
| x_intr(SIGINT, c); |
| } |
| /* ad-hoc hack for fixing the cursor position */ |
| x_goto(xcp); |
| } |
| } |
| |
| static int |
| x_insert(int c) |
| { |
| static int left, pos, save_arg; |
| static char str[4]; |
| |
| /* |
| * Should allow tab and control chars. |
| */ |
| if (c == 0) { |
| invmbs: |
| left = 0; |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| if (UTFMODE) { |
| if (((rtt2asc(c) & 0xC0) == 0x80) && left) { |
| str[pos++] = c; |
| if (!--left) { |
| str[pos] = '\0'; |
| x_arg = save_arg; |
| while (x_arg--) |
| x_ins(str); |
| } |
| return (KSTD); |
| } |
| if (left) { |
| if (x_curprefix == -1) { |
| /* flush invalid multibyte */ |
| str[pos] = '\0'; |
| while (save_arg--) |
| x_ins(str); |
| } |
| } |
| if ((c >= 0xC2) && (c < 0xE0)) |
| left = 1; |
| else if ((c >= 0xE0) && (c < 0xF0)) |
| left = 2; |
| else if (c > 0x7F) |
| goto invmbs; |
| else |
| left = 0; |
| if (left) { |
| save_arg = x_arg; |
| pos = 1; |
| str[0] = c; |
| return (KSTD); |
| } |
| } |
| left = 0; |
| str[0] = c; |
| str[1] = '\0'; |
| while (x_arg--) |
| x_ins(str); |
| return (KSTD); |
| } |
| |
| #ifndef MKSH_SMALL |
| static int |
| x_ins_string(int c) |
| { |
| macroptr = x_atab[c >> 8][c & 255]; |
| /* |
| * we no longer need to bother checking if macroptr is |
| * not NULL but first char is NUL; x_e_getc() does it |
| */ |
| return (KSTD); |
| } |
| #endif |
| |
| static int |
| x_do_ins(const char *cp, size_t len) |
| { |
| if (xep + len >= xend) { |
| x_e_putc2(KSH_BEL); |
| return (-1); |
| } |
| memmove(xcp + len, xcp, xep - xcp + 1); |
| memmove(xcp, cp, len); |
| xcp += len; |
| xep += len; |
| x_modified(); |
| return (0); |
| } |
| |
| static int |
| x_ins(const char *s) |
| { |
| char *cp = xcp; |
| int adj = x_adj_done; |
| |
| if (x_do_ins(s, strlen(s)) < 0) |
| return (-1); |
| /* |
| * x_zots() may result in a call to x_adjust() |
| * we want xcp to reflect the new position. |
| */ |
| xlp_valid = false; |
| x_lastcp(); |
| x_adj_ok = tobool(xcp >= xlp); |
| x_zots(cp); |
| if (adj == x_adj_done) |
| /* x_adjust() has not been called */ |
| x_lastpos(); |
| x_adj_ok = true; |
| return (0); |
| } |
| |
| static int |
| x_del_back(int c MKSH_A_UNUSED) |
| { |
| ssize_t i = 0; |
| |
| if (xcp == xbuf) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| do { |
| x_goto(xcp - 1); |
| } while ((++i < x_arg) && (xcp != xbuf)); |
| x_delete(i, false); |
| return (KSTD); |
| } |
| |
| static int |
| x_del_char(int c MKSH_A_UNUSED) |
| { |
| char *cp, *cp2; |
| size_t i = 0; |
| |
| cp = xcp; |
| while (i < (size_t)x_arg) { |
| utf_ptradjx(cp, cp2); |
| if (cp2 > xep) |
| break; |
| cp = cp2; |
| i++; |
| } |
| |
| if (!i) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| x_delete(i, false); |
| return (KSTD); |
| } |
| |
| /* Delete nc chars to the right of the cursor (including cursor position) */ |
| static void |
| x_delete(size_t nc, bool push) |
| { |
| size_t i, nb, nw; |
| char *cp; |
| |
| if (nc == 0) |
| return; |
| |
| nw = 0; |
| cp = xcp; |
| for (i = 0; i < nc; ++i) { |
| char *cp2; |
| int j; |
| |
| j = x_size2(cp, &cp2); |
| if (cp2 > xep) |
| break; |
| cp = cp2; |
| nw += j; |
| } |
| nb = cp - xcp; |
| /* nc = i; */ |
| |
| if (xmp != NULL && xmp > xcp) { |
| if (xcp + nb > xmp) |
| xmp = xcp; |
| else |
| xmp -= nb; |
| } |
| /* |
| * This lets us yank a word we have deleted. |
| */ |
| if (push) |
| x_push(nb); |
| |
| xep -= nb; |
| /* Copies the NUL */ |
| memmove(xcp, xcp + nb, xep - xcp + 1); |
| /* don't redraw */ |
| x_adj_ok = false; |
| xlp_valid = false; |
| x_zots(xcp); |
| /* |
| * if we are already filling the line, |
| * there is no need to ' ', '\b'. |
| * But if we must, make sure we do the minimum. |
| */ |
| if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) { |
| nw = i = (nw < i) ? nw : i; |
| while (i--) |
| x_e_putc2(' '); |
| if (x_col == xx_cols - 2) { |
| x_e_putc2((xep > xlp) ? '>' : (xbp > xbuf) ? '<' : ' '); |
| ++nw; |
| } |
| while (nw--) |
| x_e_putc2('\b'); |
| } |
| /*x_goto(xcp);*/ |
| x_adj_ok = true; |
| xlp_valid = false; |
| x_lastpos(); |
| x_modified(); |
| return; |
| } |
| |
| static int |
| x_del_bword(int c MKSH_A_UNUSED) |
| { |
| x_delete(x_bword(), true); |
| return (KSTD); |
| } |
| |
| static int |
| x_mv_bword(int c MKSH_A_UNUSED) |
| { |
| x_bword(); |
| return (KSTD); |
| } |
| |
| static int |
| x_mv_fword(int c MKSH_A_UNUSED) |
| { |
| x_fword(true); |
| return (KSTD); |
| } |
| |
| static int |
| x_del_fword(int c MKSH_A_UNUSED) |
| { |
| x_delete(x_fword(false), true); |
| return (KSTD); |
| } |
| |
| static size_t |
| x_bword(void) |
| { |
| size_t nb = 0; |
| char *cp = xcp; |
| |
| if (cp == xbuf) { |
| x_e_putc2(KSH_BEL); |
| return (0); |
| } |
| while (x_arg--) { |
| while (cp != xbuf && ctype(cp[-1], C_MFS)) { |
| cp--; |
| nb++; |
| } |
| while (cp != xbuf && !ctype(cp[-1], C_MFS)) { |
| cp--; |
| nb++; |
| } |
| } |
| x_goto(cp); |
| return (x_nb2nc(nb)); |
| } |
| |
| static size_t |
| x_fword(bool move) |
| { |
| size_t nc; |
| char *cp = xcp; |
| |
| if (cp == xep) { |
| x_e_putc2(KSH_BEL); |
| return (0); |
| } |
| while (x_arg--) { |
| while (cp != xep && ctype(*cp, C_MFS)) |
| cp++; |
| while (cp != xep && !ctype(*cp, C_MFS)) |
| cp++; |
| } |
| nc = x_nb2nc(cp - xcp); |
| if (move) |
| x_goto(cp); |
| return (nc); |
| } |
| |
| static void |
| x_goto(char *cp) |
| { |
| cp = cp >= xep ? xep : x_bs0(cp, xbuf); |
| if (cp < xbp || cp >= utf_skipcols(xbp, x_displen, NULL)) { |
| /* we are heading off screen */ |
| xcp = cp; |
| x_adjust(); |
| } else if (cp < xcp) { |
| /* move back */ |
| while (cp < xcp) |
| x_bs3(&xcp); |
| } else if (cp > xcp) { |
| /* move forward */ |
| while (cp > xcp) |
| x_zotc3(&xcp); |
| } |
| } |
| |
| static char * |
| x_bs0(char *cp, char *lower_bound) |
| { |
| if (UTFMODE) |
| while ((!lower_bound || (cp > lower_bound)) && |
| ((rtt2asc(*cp) & 0xC0) == 0x80)) |
| --cp; |
| return (cp); |
| } |
| |
| static void |
| x_bs3(char **p) |
| { |
| int i; |
| |
| *p = x_bs0((*p) - 1, NULL); |
| i = x_size2(*p, NULL); |
| while (i--) |
| x_e_putc2('\b'); |
| } |
| |
| static int |
| x_size2(char *cp, char **dcp) |
| { |
| uint8_t c = *(unsigned char *)cp; |
| |
| if (UTFMODE && (rtt2asc(c) > 0x7F)) |
| return (utf_widthadj(cp, (const char **)dcp)); |
| if (dcp) |
| *dcp = cp + 1; |
| if (c == '\t') |
| /* Kludge, tabs are always four spaces. */ |
| return (4); |
| if (ksh_isctrl(c)) |
| /* control unsigned char */ |
| return (2); |
| return (1); |
| } |
| |
| static void |
| x_zots(char *str) |
| { |
| int adj = x_adj_done; |
| |
| x_lastcp(); |
| while (*str && str < xlp && x_col < xx_cols && adj == x_adj_done) |
| x_zotc3(&str); |
| } |
| |
| static void |
| x_zotc3(char **cp) |
| { |
| unsigned char c = **(unsigned char **)cp; |
| |
| if (c == '\t') { |
| /* Kludge, tabs are always four spaces. */ |
| x_e_puts(T4spaces); |
| (*cp)++; |
| } else if (ksh_isctrl(c)) { |
| x_e_putc2('^'); |
| x_e_putc2(ksh_unctrl(c)); |
| (*cp)++; |
| } else |
| x_e_putc3((const char **)cp); |
| } |
| |
| static int |
| x_mv_back(int c MKSH_A_UNUSED) |
| { |
| if (xcp == xbuf) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| while (x_arg--) { |
| x_goto(xcp - 1); |
| if (xcp == xbuf) |
| break; |
| } |
| return (KSTD); |
| } |
| |
| static int |
| x_mv_forw(int c MKSH_A_UNUSED) |
| { |
| char *cp = xcp, *cp2; |
| |
| if (xcp == xep) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| while (x_arg--) { |
| utf_ptradjx(cp, cp2); |
| if (cp2 > xep) |
| break; |
| cp = cp2; |
| } |
| x_goto(cp); |
| return (KSTD); |
| } |
| |
| static int |
| x_search_char_forw(int c MKSH_A_UNUSED) |
| { |
| char *cp = xcp; |
| char tmp[4]; |
| |
| *xep = '\0'; |
| if (x_e_getmbc(tmp) < 0) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| while (x_arg--) { |
| if ((cp = (cp == xep) ? NULL : strstr(cp + 1, tmp)) == NULL && |
| (cp = strstr(xbuf, tmp)) == NULL) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| } |
| x_goto(cp); |
| return (KSTD); |
| } |
| |
| static int |
| x_search_char_back(int c MKSH_A_UNUSED) |
| { |
| char *cp = xcp, *p, tmp[4]; |
| bool b; |
| |
| if (x_e_getmbc(tmp) < 0) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| for (; x_arg--; cp = p) |
| for (p = cp; ; ) { |
| if (p-- == xbuf) |
| p = xep; |
| if (p == cp) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| if ((tmp[1] && ((p+1) > xep)) || |
| (tmp[2] && ((p+2) > xep))) |
| continue; |
| b = true; |
| if (*p != tmp[0]) |
| b = false; |
| if (b && tmp[1] && p[1] != tmp[1]) |
| b = false; |
| if (b && tmp[2] && p[2] != tmp[2]) |
| b = false; |
| if (b) |
| break; |
| } |
| x_goto(cp); |
| return (KSTD); |
| } |
| |
| static int |
| x_newline(int c MKSH_A_UNUSED) |
| { |
| x_e_putc2('\r'); |
| x_e_putc2('\n'); |
| x_flush(); |
| *xep++ = '\n'; |
| return (KEOL); |
| } |
| |
| static int |
| x_end_of_text(int c MKSH_A_UNUSED) |
| { |
| unsigned char tmp[1], *cp = tmp; |
| |
| *tmp = isedchar(edchars.eof) ? (unsigned char)edchars.eof : |
| (unsigned char)CTRL_D; |
| x_zotc3((char **)&cp); |
| x_putc('\r'); |
| x_putc('\n'); |
| x_flush(); |
| return (KEOL); |
| } |
| |
| static int |
| x_beg_hist(int c MKSH_A_UNUSED) |
| { |
| x_load_hist(history); |
| return (KSTD); |
| } |
| |
| static int |
| x_end_hist(int c MKSH_A_UNUSED) |
| { |
| x_load_hist(histptr); |
| return (KSTD); |
| } |
| |
| static int |
| x_prev_com(int c MKSH_A_UNUSED) |
| { |
| x_load_hist(x_histp - x_arg); |
| return (KSTD); |
| } |
| |
| static int |
| x_next_com(int c MKSH_A_UNUSED) |
| { |
| x_load_hist(x_histp + x_arg); |
| return (KSTD); |
| } |
| |
| /* |
| * Goto a particular history number obtained from argument. |
| * If no argument is given history 1 is probably not what you |
| * want so we'll simply go to the oldest one. |
| */ |
| static int |
| x_goto_hist(int c MKSH_A_UNUSED) |
| { |
| if (x_arg_defaulted) |
| x_load_hist(history); |
| else |
| x_load_hist(histptr + x_arg - source->line); |
| return (KSTD); |
| } |
| |
| static void |
| x_load_hist(char **hp) |
| { |
| char *sp = NULL; |
| |
| if (hp == histptr + 1) { |
| sp = holdbufp; |
| modified = 0; |
| } else if (hp < history || hp > histptr) { |
| x_e_putc2(KSH_BEL); |
| return; |
| } |
| if (sp == NULL) |
| sp = *hp; |
| x_histp = hp; |
| if (modified) |
| strlcpy(holdbufp, xbuf, LINE); |
| strlcpy(xbuf, sp, xend - xbuf); |
| xbp = xbuf; |
| xep = xcp = strnul(xbuf); |
| x_adjust(); |
| modified = 0; |
| } |
| |
| static int |
| x_nl_next_com(int c MKSH_A_UNUSED) |
| { |
| if (!modified) |
| x_histmcp = x_histp; |
| if (!x_histncp || (x_histmcp != x_histncp && x_histmcp != histptr + 1)) |
| /* fresh start of ^O */ |
| x_histncp = x_histmcp; |
| x_nextcmd = source->line - (histptr - x_histncp) + 1; |
| return (x_newline('\n')); |
| } |
| |
| static int |
| x_eot_del(int c) |
| { |
| if (xep == xbuf && x_arg_defaulted) |
| return (x_end_of_text(c)); |
| else |
| return (x_del_char(c)); |
| } |
| |
| /* reverse incremental history search */ |
| static int |
| x_search_hist(int c) |
| { |
| int offset = -1; /* offset of match in xbuf, else -1 */ |
| char pat[80 + 1]; /* pattern buffer */ |
| char *p = pat; |
| unsigned char f; |
| |
| *p = '\0'; |
| while (/* CONSTCOND */ 1) { |
| if (offset < 0) { |
| x_e_puts("\nI-search: "); |
| x_e_puts(pat); |
| } |
| x_flush(); |
| if ((c = x_e_getc()) < 0) |
| return (KSTD); |
| f = x_tab[0][c]; |
| if (c == CTRL_BO) { |
| if ((f & 0x7F) == XFUNC_meta1) { |
| if ((c = x_e_getc()) < 0) |
| return (KSTD); |
| f = x_tab[1][c] & 0x7F; |
| if (f == XFUNC_meta1 || f == XFUNC_meta2) |
| x_meta1(CTRL_BO); |
| x_e_ungetc(c); |
| } |
| break; |
| } |
| #ifndef MKSH_SMALL |
| if (f & 0x80) { |
| f &= 0x7F; |
| if ((c = x_e_getc()) != '~') |
| x_e_ungetc(c); |
| } |
| #endif |
| if (f == XFUNC_search_hist) |
| offset = x_search(pat, 0, offset); |
| else if (f == XFUNC_del_back) { |
| if (p == pat) { |
| offset = -1; |
| break; |
| } |
| if (p > pat) { |
| p = x_bs0(p - 1, pat); |
| *p = '\0'; |
| } |
| if (p == pat) |
| offset = -1; |
| else |
| offset = x_search(pat, 1, offset); |
| continue; |
| } else if (f == XFUNC_insert) { |
| /* add char to pattern */ |
| /* overflow check... */ |
| if ((size_t)(p - pat) >= sizeof(pat) - 1) { |
| x_e_putc2(KSH_BEL); |
| continue; |
| } |
| *p++ = c, *p = '\0'; |
| if (offset >= 0) { |
| /* already have partial match */ |
| offset = x_match(xbuf, pat); |
| if (offset >= 0) { |
| x_goto(xbuf + offset + (p - pat) - |
| (*pat == '^' ? 1 : 0)); |
| continue; |
| } |
| } |
| offset = x_search(pat, 0, offset); |
| } else if (f == XFUNC_abort) { |
| if (offset >= 0) |
| x_load_hist(histptr + 1); |
| break; |
| } else { |
| /* other command */ |
| x_e_ungetc(c); |
| break; |
| } |
| } |
| if (offset < 0) |
| x_redraw('\n'); |
| return (KSTD); |
| } |
| |
| /* search backward from current line */ |
| static int |
| x_search(const char *pat, int sameline, int offset) |
| { |
| char **hp; |
| int i; |
| size_t patlen = strlen(pat); |
| |
| if (*pat == '^') |
| --patlen; |
| for (hp = x_histp - (sameline ? 0 : 1); hp >= history; --hp) { |
| i = x_match(*hp, pat); |
| if (i >= 0) { |
| if (offset < 0) |
| x_e_putc2('\n'); |
| x_load_hist(hp); |
| x_goto(xbuf + i + patlen); |
| return (i); |
| } |
| } |
| x_e_putc2(KSH_BEL); |
| x_histp = histptr; |
| return (-1); |
| } |
| |
| #ifndef MKSH_SMALL |
| /* anchored search up from current line */ |
| static int |
| x_search_hist_up(int c MKSH_A_UNUSED) |
| { |
| return (x_search_dir(-1)); |
| } |
| |
| /* anchored search down from current line */ |
| static int |
| x_search_hist_dn(int c MKSH_A_UNUSED) |
| { |
| return (x_search_dir(1)); |
| } |
| |
| /* anchored search in the indicated direction */ |
| static int |
| x_search_dir(int search_dir /* should've been bool */) |
| { |
| char **hp = x_histp + search_dir; |
| size_t curs = xcp - xbuf; |
| |
| while (histptr >= hp && hp >= history) { |
| if (strncmp(xbuf, *hp, curs) == 0) { |
| x_load_hist(hp); |
| x_goto(xbuf + curs); |
| break; |
| } |
| hp += search_dir; |
| } |
| return (KSTD); |
| } |
| #endif |
| |
| /* return position of first match of pattern in string, else -1 */ |
| static int |
| x_match(const char *str, const char *pat) |
| { |
| if (*pat == '^') { |
| return ((strncmp(str, pat + 1, strlen(pat + 1)) == 0) ? 0 : -1); |
| } else { |
| char *q = strstr(str, pat); |
| return ((q == NULL) ? -1 : q - str); |
| } |
| } |
| |
| static int |
| x_del_line(int c MKSH_A_UNUSED) |
| { |
| *xep = 0; |
| x_push(xep - (xcp = xbuf)); |
| xlp = xbp = xep = xbuf; |
| xlp_valid = true; |
| *xcp = 0; |
| xmp = NULL; |
| x_redraw('\r'); |
| x_modified(); |
| return (KSTD); |
| } |
| |
| static int |
| x_mv_end(int c MKSH_A_UNUSED) |
| { |
| x_goto(xep); |
| return (KSTD); |
| } |
| |
| static int |
| x_mv_beg(int c MKSH_A_UNUSED) |
| { |
| x_goto(xbuf); |
| return (KSTD); |
| } |
| |
| static int |
| x_draw_line(int c MKSH_A_UNUSED) |
| { |
| x_redraw('\n'); |
| return (KSTD); |
| } |
| |
| static int |
| x_cls(int c MKSH_A_UNUSED) |
| { |
| shf_puts(MKSH_CLS_STRING, shl_out); |
| x_redraw(0); |
| return (KSTD); |
| } |
| |
| /* |
| * clear line from x_col (current cursor position) to xx_cols - 2, |
| * then output lastch, then go back to x_col; if lastch is space, |
| * clear with termcap instead of spaces, or not if line_was_cleared; |
| * lastch MUST be an ASCII character with wcwidth(lastch) == 1 |
| */ |
| static void |
| x_clrtoeol(int lastch, bool line_was_cleared) |
| { |
| int col; |
| |
| if (lastch == ' ' && !line_was_cleared && x_term_mode == 1) { |
| shf_puts(KSH_ESC_STRING "[K", shl_out); |
| line_was_cleared = true; |
| } |
| if (lastch == ' ' && line_was_cleared) |
| return; |
| |
| col = x_col; |
| while (col < (xx_cols - 2)) { |
| x_putc(' '); |
| ++col; |
| } |
| x_putc(lastch); |
| ++col; |
| while (col > x_col) { |
| x_putc('\b'); |
| --col; |
| } |
| } |
| |
| /* output the prompt, assuming a line has just been started */ |
| static void |
| x_pprompt(void) |
| { |
| if (prompt_trunc != -1) |
| pprompt(prompt, prompt_trunc); |
| x_col = pwidth; |
| } |
| |
| /* output CR, then redraw the line, clearing to EOL if needed (cr ≠ 0, LF) */ |
| static void |
| x_redraw(int cr) |
| { |
| int lch; |
| |
| x_adj_ok = false; |
| /* clear the line */ |
| x_e_putc2(cr ? cr : '\r'); |
| x_flush(); |
| /* display the prompt */ |
| if (xbp == xbuf) |
| x_pprompt(); |
| x_displen = xx_cols - 2 - x_col; |
| /* display the line content */ |
| xlp_valid = false; |
| x_zots(xbp); |
| /* check whether there is more off-screen */ |
| lch = xep > xlp ? (xbp > xbuf ? '*' : '>') : (xbp > xbuf) ? '<' : ' '; |
| /* clear the rest of the line */ |
| x_clrtoeol(lch, !cr || cr == '\n'); |
| /* go back to actual cursor position */ |
| x_lastpos(); |
| x_adj_ok = true; |
| } |
| |
| static int |
| x_transpose(int c MKSH_A_UNUSED) |
| { |
| unsigned int tmpa, tmpb; |
| |
| /*- |
| * What transpose is meant to do seems to be up for debate. This |
| * is a general summary of the options; the text is abcd with the |
| * upper case character or underscore indicating the cursor position: |
| * Who Before After Before After |
| * AT&T ksh in emacs mode: abCd abdC abcd_ (bell) |
| * AT&T ksh in gmacs mode: abCd baCd abcd_ abdc_ |
| * gnu emacs: abCd acbD abcd_ abdc_ |
| * Pdksh currently goes with GNU behavior since I believe this is the |
| * most common version of emacs, unless in gmacs mode, in which case |
| * it does the AT&T ksh gmacs mode. |
| * This should really be broken up into 3 functions so users can bind |
| * to the one they want. |
| */ |
| if (xcp == xbuf) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } else if (xcp == xep || Flag(FGMACS)) { |
| if (xcp - xbuf == 1) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| /* |
| * Gosling/Unipress emacs style: Swap two characters before |
| * the cursor, do not change cursor position |
| */ |
| x_bs3(&xcp); |
| if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| x_bs3(&xcp); |
| if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| utf_wctomb(xcp, tmpa); |
| x_zotc3(&xcp); |
| utf_wctomb(xcp, tmpb); |
| x_zotc3(&xcp); |
| } else { |
| /* |
| * GNU emacs style: Swap the characters before and under the |
| * cursor, move cursor position along one. |
| */ |
| if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| x_bs3(&xcp); |
| if (utf_mbtowc(&tmpb, xcp) == (size_t)-1) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| utf_wctomb(xcp, tmpa); |
| x_zotc3(&xcp); |
| utf_wctomb(xcp, tmpb); |
| x_zotc3(&xcp); |
| } |
| x_modified(); |
| return (KSTD); |
| } |
| |
| static int |
| x_literal(int c MKSH_A_UNUSED) |
| { |
| x_curprefix = -1; |
| return (KSTD); |
| } |
| |
| static int |
| x_meta1(int c MKSH_A_UNUSED) |
| { |
| x_curprefix = 1; |
| return (KSTD); |
| } |
| |
| static int |
| x_meta2(int c MKSH_A_UNUSED) |
| { |
| x_curprefix = 2; |
| return (KSTD); |
| } |
| |
| static int |
| x_meta3(int c MKSH_A_UNUSED) |
| { |
| x_curprefix = 3; |
| return (KSTD); |
| } |
| |
| static int |
| x_kill(int c MKSH_A_UNUSED) |
| { |
| size_t col = xcp - xbuf; |
| size_t lastcol = xep - xbuf; |
| size_t ndel, narg; |
| |
| if (x_arg_defaulted || (narg = x_arg) > lastcol) |
| narg = lastcol; |
| if (narg < col) { |
| x_goto(xbuf + narg); |
| ndel = col - narg; |
| } else |
| ndel = narg - col; |
| x_delete(x_nb2nc(ndel), true); |
| return (KSTD); |
| } |
| |
| static void |
| x_push(size_t nchars) |
| { |
| afree(killstack[killsp], AEDIT); |
| strndupx(killstack[killsp], xcp, nchars, AEDIT); |
| killsp = (killsp + 1) % KILLSIZE; |
| } |
| |
| static int |
| x_yank(int c MKSH_A_UNUSED) |
| { |
| if (killsp == 0) |
| killtp = KILLSIZE; |
| else |
| killtp = killsp; |
| killtp--; |
| if (killstack[killtp] == 0) { |
| x_e_puts("\nnothing to yank"); |
| x_redraw('\n'); |
| return (KSTD); |
| } |
| xmp = xcp; |
| x_ins(killstack[killtp]); |
| return (KSTD); |
| } |
| |
| static int |
| x_meta_yank(int c MKSH_A_UNUSED) |
| { |
| size_t len; |
| |
| if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) || |
| killstack[killtp] == 0) { |
| killtp = killsp; |
| x_e_puts("\nyank something first"); |
| x_redraw('\n'); |
| return (KSTD); |
| } |
| len = strlen(killstack[killtp]); |
| x_goto(xcp - len); |
| x_delete(x_nb2nc(len), false); |
| do { |
| if (killtp == 0) |
| killtp = KILLSIZE - 1; |
| else |
| killtp--; |
| } while (killstack[killtp] == 0); |
| x_ins(killstack[killtp]); |
| return (KSTD); |
| } |
| |
| /* fake receiving an interrupt */ |
| static void |
| x_intr(int signo, int c) |
| { |
| x_vi_zotc(c); |
| *xep = '\0'; |
| strip_nuls(xbuf, xep - xbuf); |
| if (*xbuf) |
| histsave(&source->line, xbuf, HIST_STORE, true); |
| xlp = xep = xcp = xbp = xbuf; |
| xlp_valid = true; |
| *xcp = 0; |
| x_modified(); |
| x_flush(); |
| trapsig(signo); |
| x_mode(false); |
| unwind(LSHELL); |
| } |
| |
| static int |
| x_abort(int c MKSH_A_UNUSED) |
| { |
| return (KINTR); |
| } |
| |
| static int |
| x_error(int c MKSH_A_UNUSED) |
| { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| |
| #ifndef MKSH_SMALL |
| /* special VT100 style key sequence hack */ |
| static int |
| x_vt_hack(int c) |
| { |
| /* we only support PF2-'1' for now */ |
| if (c != (2 << 8 | '1')) |
| return (x_error(c)); |
| |
| /* what's the next character? */ |
| switch ((c = x_e_getc())) { |
| case '~': |
| x_arg = 1; |
| x_arg_defaulted = true; |
| return (x_mv_beg(0)); |
| case ';': |
| /* "interesting" sequence detected */ |
| break; |
| default: |
| goto unwind_err; |
| } |
| |
| /* XXX x_e_ungetc is one-octet only */ |
| if ((c = x_e_getc()) != '5' && c != '3') |
| goto unwind_err; |
| |
| /*- |
| * At this point, we have read the following octets so far: |
| * - ESC+[ or ESC+O or Ctrl-X (Prefix 2) |
| * - 1 (vt_hack) |
| * - ; |
| * - 5 (Ctrl key combiner) or 3 (Alt key combiner) |
| * We can now accept one more octet designating the key. |
| */ |
| |
| switch ((c = x_e_getc())) { |
| case 'C': |
| return (x_mv_fword(c)); |
| case 'D': |
| return (x_mv_bword(c)); |
| } |
| |
| unwind_err: |
| x_e_ungetc(c); |
| return (x_error(c)); |
| } |
| #endif |
| |
| int |
| x_bind_check(void) |
| { |
| return (x_tab == NULL); |
| } |
| |
| static XString x_bind_show_xs; |
| static char *x_bind_show_xp; |
| |
| static void |
| x_bind_show_ch(unsigned char ch) |
| { |
| Xcheck(x_bind_show_xs, x_bind_show_xp); |
| switch (ch) { |
| case ORD('^'): |
| case ORD('\\'): |
| case ORD('='): |
| *x_bind_show_xp++ = '\\'; |
| *x_bind_show_xp++ = ch; |
| break; |
| default: |
| if (ksh_isctrl(ch)) { |
| *x_bind_show_xp++ = '^'; |
| *x_bind_show_xp++ = ksh_unctrl(ch); |
| } else |
| *x_bind_show_xp++ = ch; |
| break; |
| } |
| } |
| |
| static void |
| x_bind_showone(int prefix, int key) |
| { |
| unsigned char f = XFUNC_VALUE(x_tab[prefix][key]); |
| |
| if (!x_bind_show_xs.areap) |
| XinitN(x_bind_show_xs, 16, AEDIT); |
| |
| x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp); |
| shf_puts("bind ", shl_stdout); |
| #ifndef MKSH_SMALL |
| if (f == XFUNC_ins_string) |
| shf_puts("-m ", shl_stdout); |
| #endif |
| switch (prefix) { |
| case 1: |
| x_bind_show_ch(CTRL_BO); |
| break; |
| case 2: |
| x_bind_show_ch(CTRL_X); |
| break; |
| case 3: |
| x_bind_show_ch(0); |
| break; |
| } |
| x_bind_show_ch(key); |
| #ifndef MKSH_SMALL |
| if (x_tab[prefix][key] & 0x80) |
| *x_bind_show_xp++ = '~'; |
| #endif |
| *x_bind_show_xp = '\0'; |
| x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp); |
| print_value_quoted(shl_stdout, x_bind_show_xp); |
| shf_putc('=', shl_stdout); |
| #ifndef MKSH_SMALL |
| if (f == XFUNC_ins_string) { |
| const unsigned char *cp = (const void *)x_atab[prefix][key]; |
| unsigned char c; |
| |
| while ((c = *cp++)) |
| x_bind_show_ch(c); |
| *x_bind_show_xp = '\0'; |
| x_bind_show_xp = Xstring(x_bind_show_xs, x_bind_show_xp); |
| print_value_quoted(shl_stdout, x_bind_show_xp); |
| } else |
| #endif |
| shf_puts(x_ftab[f].xf_name, shl_stdout); |
| shf_putc('\n', shl_stdout); |
| } |
| |
| int |
| x_bind_list(void) |
| { |
| size_t f; |
| |
| for (f = 0; f < NELEM(x_ftab); f++) |
| if (!(x_ftab[f].xf_flags & XF_NOBIND)) |
| shprintf(Tf_sN, x_ftab[f].xf_name); |
| return (0); |
| } |
| |
| int |
| x_bind_showall(void) |
| { |
| int prefix, key; |
| |
| for (prefix = 0; prefix < X_NTABS; prefix++) |
| for (key = 0; key < X_TABSZ; key++) |
| switch (XFUNC_VALUE(x_tab[prefix][key])) { |
| case XFUNC_error: /* unset */ |
| case XFUNC_insert: /* auto-insert */ |
| break; |
| default: |
| x_bind_showone(prefix, key); |
| break; |
| } |
| return (0); |
| } |
| |
| static unsigned int |
| x_bind_getc(const char **ccpp) |
| { |
| unsigned int ch, ec; |
| |
| if ((ch = ord(**ccpp))) |
| ++(*ccpp); |
| switch (ch) { |
| case ORD('^'): |
| ch = ksh_toctrl(**ccpp) | 0x100U; |
| if (**ccpp) |
| ++(*ccpp); |
| break; |
| case ORD('\\'): |
| switch ((ec = ord(**ccpp))) { |
| case ORD('^'): |
| case ORD('\\'): |
| case ORD('='): |
| ch = ec | 0x100U; |
| ++(*ccpp); |
| break; |
| } |
| break; |
| } |
| return (ch); |
| } |
| |
| int |
| x_bind(const char *s SMALLP(bool macro)) |
| { |
| const char *ccp = s; |
| int prefix, key; |
| unsigned int c; |
| #ifndef MKSH_SMALL |
| bool hastilde = false; |
| char *ms = NULL; |
| #endif |
| |
| prefix = 0; |
| c = x_bind_getc(&ccp); |
| if (!c || c == ORD('=')) { |
| bi_errorf("no key to bind"); |
| return (1); |
| } |
| key = c & 0xFF; |
| while ((c = x_bind_getc(&ccp)) != ORD('=')) { |
| if (!c) { |
| x_bind_showone(prefix, key); |
| return (0); |
| } |
| switch (XFUNC_VALUE(x_tab[prefix][key])) { |
| case XFUNC_meta1: |
| prefix = 1; |
| if (0) |
| /* FALLTHROUGH */ |
| case XFUNC_meta2: |
| prefix = 2; |
| if (0) |
| /* FALLTHROUGH */ |
| case XFUNC_meta3: |
| prefix = 3; |
| key = c & 0xFF; |
| continue; |
| } |
| #ifndef MKSH_SMALL |
| if (c == ORD('~')) { |
| hastilde = true; |
| continue; |
| } |
| #endif |
| bi_errorf("too long key sequence: %s", s); |
| return (-1); |
| } |
| |
| #ifndef MKSH_SMALL |
| if (macro) { |
| char *cp; |
| |
| cp = ms = alloc(strlen(ccp) + 1, AEDIT); |
| while ((c = x_bind_getc(&ccp))) |
| *cp++ = c; |
| *cp = '\0'; |
| c = XFUNC_ins_string; |
| } else |
| #endif |
| if (!*ccp) { |
| c = XFUNC_insert; |
| } else { |
| for (c = 0; c < NELEM(x_ftab); ++c) |
| if (!strcmp(x_ftab[c].xf_name, ccp)) |
| break; |
| if (c == NELEM(x_ftab) || x_ftab[c].xf_flags & XF_NOBIND) { |
| bi_errorf("%s: no such editing command", ccp); |
| return (1); |
| } |
| } |
| |
| #ifndef MKSH_SMALL |
| if (XFUNC_VALUE(x_tab[prefix][key]) == XFUNC_ins_string) |
| afree(x_atab[prefix][key], AEDIT); |
| x_atab[prefix][key] = ms; |
| if (hastilde) |
| c |= 0x80U; |
| #endif |
| x_tab[prefix][key] = c; |
| |
| /* track what the user has bound, so x_mode(true) won't toast things */ |
| if (c == XFUNC_insert) |
| x_bound[(prefix * X_TABSZ + key) / 8] &= |
| ~(1 << ((prefix * X_TABSZ + key) % 8)); |
| else |
| x_bound[(prefix * X_TABSZ + key) / 8] |= |
| (1 << ((prefix * X_TABSZ + key) % 8)); |
| |
| return (0); |
| } |
| |
| static void |
| bind_if_not_bound(int p, int k, int func) |
| { |
| int t; |
| |
| /* |
| * Has user already bound this key? |
| * If so, do not override it. |
| */ |
| t = p * X_TABSZ + k; |
| if (x_bound[t >> 3] & (1 << (t & 7))) |
| return; |
| |
| x_tab[p][k] = func; |
| } |
| |
| static int |
| x_set_mark(int c MKSH_A_UNUSED) |
| { |
| xmp = xcp; |
| return (KSTD); |
| } |
| |
| static int |
| x_kill_region(int c MKSH_A_UNUSED) |
| { |
| size_t rsize; |
| char *xr; |
| |
| if (xmp == NULL) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| if (xmp > xcp) { |
| rsize = xmp - xcp; |
| xr = xcp; |
| } else { |
| rsize = xcp - xmp; |
| xr = xmp; |
| } |
| x_goto(xr); |
| x_delete(x_nb2nc(rsize), true); |
| xmp = xr; |
| return (KSTD); |
| } |
| |
| static int |
| x_xchg_point_mark(int c MKSH_A_UNUSED) |
| { |
| char *tmp; |
| |
| if (xmp == NULL) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| tmp = xmp; |
| xmp = xcp; |
| x_goto(tmp); |
| return (KSTD); |
| } |
| |
| static int |
| x_noop(int c MKSH_A_UNUSED) |
| { |
| return (KSTD); |
| } |
| |
| /* |
| * File/command name completion routines |
| */ |
| static int |
| x_comp_comm(int c MKSH_A_UNUSED) |
| { |
| do_complete(XCF_COMMAND, CT_COMPLETE); |
| return (KSTD); |
| } |
| |
| static int |
| x_list_comm(int c MKSH_A_UNUSED) |
| { |
| do_complete(XCF_COMMAND, CT_LIST); |
| return (KSTD); |
| } |
| |
| static int |
| x_complete(int c MKSH_A_UNUSED) |
| { |
| do_complete(XCF_COMMAND_FILE, CT_COMPLETE); |
| return (KSTD); |
| } |
| |
| static int |
| x_enumerate(int c MKSH_A_UNUSED) |
| { |
| do_complete(XCF_COMMAND_FILE, CT_LIST); |
| return (KSTD); |
| } |
| |
| static int |
| x_comp_file(int c MKSH_A_UNUSED) |
| { |
| do_complete(XCF_FILE, CT_COMPLETE); |
| return (KSTD); |
| } |
| |
| static int |
| x_list_file(int c MKSH_A_UNUSED) |
| { |
| do_complete(XCF_FILE, CT_LIST); |
| return (KSTD); |
| } |
| |
| static int |
| x_comp_list(int c MKSH_A_UNUSED) |
| { |
| do_complete(XCF_COMMAND_FILE, CT_COMPLIST); |
| return (KSTD); |
| } |
| |
| static int |
| x_expand(int c MKSH_A_UNUSED) |
| { |
| char **words; |
| int start, end, nwords, i; |
| |
| i = XCF_FILE; |
| nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf, |
| &start, &end, &words); |
| |
| if (nwords == 0) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| x_goto(xbuf + start); |
| x_delete(x_nb2nc(end - start), false); |
| |
| i = 0; |
| while (i < nwords) { |
| if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 || |
| (++i < nwords && x_ins(T1space) < 0)) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| } |
| x_adjust(); |
| |
| return (KSTD); |
| } |
| |
| static void |
| do_complete( |
| /* XCF_{COMMAND,FILE,COMMAND_FILE} */ |
| int flags, |
| /* 0 for list, 1 for complete and 2 for complete-list */ |
| Comp_type type) |
| { |
| char **words; |
| int start, end, nlen, olen, nwords; |
| bool completed; |
| |
| nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf, |
| &start, &end, &words); |
| /* no match */ |
| if (nwords == 0) { |
| x_e_putc2(KSH_BEL); |
| return; |
| } |
| if (type == CT_LIST) { |
| x_print_expansions(nwords, words, |
| tobool(flags & XCF_IS_COMMAND)); |
| x_redraw(0); |
| x_free_words(nwords, words); |
| return; |
| } |
| olen = end - start; |
| nlen = x_longest_prefix(nwords, words); |
| if (nwords == 1) { |
| /* |
| * always complete single matches; |
| * any expansion of parameter substitution |
| * is always at most one result, too |
| */ |
| completed = true; |
| } else { |
| char *unescaped; |
| |
| /* make a copy of the original string part */ |
| strndupx(unescaped, xbuf + start, olen, ATEMP); |
| |
| /* expand any tilde and unescape the string for comparison */ |
| unescaped = x_glob_hlp_tilde_and_rem_qchar(unescaped, true); |
| |
| /* |
| * match iff entire original string is part of the |
| * longest prefix, implying the latter is at least |
| * the same size (after unescaping) |
| */ |
| completed = !strncmp(words[0], unescaped, strlen(unescaped)); |
| |
| afree(unescaped, ATEMP); |
| } |
| if (type == CT_COMPLIST && nwords > 1) { |
| /* |
| * print expansions, since we didn't get back |
| * just a single match |
| */ |
| x_print_expansions(nwords, words, |
| tobool(flags & XCF_IS_COMMAND)); |
| } |
| if (completed) { |
| /* expand on the command line */ |
| xmp = NULL; |
| xcp = xbuf + start; |
| xep -= olen; |
| memmove(xcp, xcp + olen, xep - xcp + 1); |
| x_escape(words[0], nlen, x_do_ins); |
| } |
| x_adjust(); |
| /* |
| * append a space if this is a single non-directory match |
| * and not a parameter or homedir substitution |
| */ |
| if (nwords == 1 && !mksh_cdirsep(words[0][nlen - 1]) && |
| !(flags & XCF_IS_NOSPACE)) { |
| x_ins(T1space); |
| } |
| |
| x_free_words(nwords, words); |
| } |
| |
| /*- |
| * NAME: |
| * x_adjust - redraw the line adjusting starting point etc. |
| * |
| * DESCRIPTION: |
| * This function is called when we have exceeded the bounds |
| * of the edit window. It increments x_adj_done so that |
| * functions like x_ins and x_delete know that we have been |
| * called and can skip the x_bs() stuff which has already |
| * been done by x_redraw. |
| * |
| * RETURN VALUE: |
| * None |
| */ |
| static void |
| x_adjust(void) |
| { |
| int col_left, n; |
| |
| /* flag the fact that we were called */ |
| x_adj_done++; |
| |
| /* |
| * calculate the amount of columns we need to "go back" |
| * from xcp to set xbp to (but never < xbuf) to 2/3 of |
| * the display width; take care of pwidth though |
| */ |
| if ((col_left = xx_cols * 2 / 3) < MIN_EDIT_SPACE) { |
| /* |
| * cowardly refuse to do anything |
| * if the available space is too small; |
| * fall back to dumb pdksh code |
| */ |
| if ((xbp = xcp - (x_displen / 2)) < xbuf) |
| xbp = xbuf; |
| /* elide UTF-8 fixup as penalty */ |
| goto x_adjust_out; |
| } |
| |
| /* fix up xbp to just past a character end first */ |
| xbp = xcp >= xep ? xep : x_bs0(xcp, xbuf); |
| /* walk backwards */ |
| while (xbp > xbuf && col_left > 0) { |
| xbp = x_bs0(xbp - 1, xbuf); |
| col_left -= (n = x_size2(xbp, NULL)); |
| } |
| /* check if we hit the prompt */ |
| if (xbp == xbuf && xcp != xbuf && col_left >= 0 && col_left < pwidth) { |
| /* so we did; force scrolling occurs */ |
| xbp += utf_ptradj(xbp); |
| } |
| |
| x_adjust_out: |
| xlp_valid = false; |
| x_redraw('\r'); |
| x_flush(); |
| } |
| |
| static void |
| x_e_ungetc(int c) |
| { |
| unget_char = c < 0 ? -1 : (c & 255); |
| } |
| |
| static int |
| x_e_getc(void) |
| { |
| int c; |
| |
| if (unget_char >= 0) { |
| c = unget_char; |
| unget_char = -1; |
| return (c); |
| } |
| |
| #ifndef MKSH_SMALL |
| if (macroptr) { |
| if ((c = (unsigned char)*macroptr++)) |
| return (c); |
| macroptr = NULL; |
| } |
| #endif |
| |
| return (x_getc()); |
| } |
| |
| static void |
| x_e_putc2(int c) |
| { |
| int width = 1; |
| |
| if (ctype(c, C_CR | C_LF)) |
| x_col = 0; |
| if (x_col < xx_cols) { |
| #ifndef MKSH_EBCDIC |
| if (UTFMODE && (c > 0x7F)) { |
| char utf_tmp[3]; |
| size_t x; |
| |
| if (c < 0xA0) |
| c = 0xFFFD; |
| x = utf_wctomb(utf_tmp, c); |
| x_putc(utf_tmp[0]); |
| if (x > 1) |
| x_putc(utf_tmp[1]); |
| if (x > 2) |
| x_putc(utf_tmp[2]); |
| width = utf_wcwidth(c); |
| } else |
| #endif |
| x_putc(c); |
| switch (c) { |
| case KSH_BEL: |
| break; |
| case '\r': |
| case '\n': |
| break; |
| case '\b': |
| x_col--; |
| break; |
| default: |
| x_col += width; |
| break; |
| } |
| } |
| if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2))) |
| x_adjust(); |
| } |
| |
| static void |
| x_e_putc3(const char **cp) |
| { |
| int width = 1, c = **(const unsigned char **)cp; |
| |
| if (ctype(c, C_CR | C_LF)) |
| x_col = 0; |
| if (x_col < xx_cols) { |
| if (UTFMODE && (c > 0x7F)) { |
| char *cp2; |
| |
| width = utf_widthadj(*cp, (const char **)&cp2); |
| if (cp2 == *cp + 1) { |
| (*cp)++; |
| #ifdef MKSH_EBCDIC |
| x_putc(asc2rtt(0xEF)); |
| x_putc(asc2rtt(0xBF)); |
| x_putc(asc2rtt(0xBD)); |
| #else |
| shf_puts("\xEF\xBF\xBD", shl_out); |
| #endif |
| } else |
| while (*cp < cp2) |
| x_putcf(*(*cp)++); |
| } else { |
| (*cp)++; |
| x_putc(c); |
| } |
| switch (c) { |
| case KSH_BEL: |
| break; |
| case '\r': |
| case '\n': |
| break; |
| case '\b': |
| x_col--; |
| break; |
| default: |
| x_col += width; |
| break; |
| } |
| } |
| if (x_adj_ok && (x_col < 0 || x_col >= (xx_cols - 2))) |
| x_adjust(); |
| } |
| |
| static void |
| x_e_puts(const char *s) |
| { |
| int adj = x_adj_done; |
| |
| while (*s && adj == x_adj_done) |
| x_e_putc3(&s); |
| } |
| |
| /*- |
| * NAME: |
| * x_set_arg - set an arg value for next function |
| * |
| * DESCRIPTION: |
| * This is a simple implementation of M-[0-9]. |
| * |
| * RETURN VALUE: |
| * KSTD |
| */ |
| static int |
| x_set_arg(int c) |
| { |
| unsigned int n = 0; |
| bool first = true; |
| |
| /* strip command prefix */ |
| c &= 255; |
| while (c >= 0 && ctype(c, C_DIGIT)) { |
| n = n * 10 + ksh_numdig(c); |
| if (n > LINE) |
| /* upper bound for repeat */ |
| goto x_set_arg_too_big; |
| c = x_e_getc(); |
| first = false; |
| } |
| if (c < 0 || first) { |
| x_set_arg_too_big: |
| x_e_putc2(KSH_BEL); |
| x_arg = 1; |
| x_arg_defaulted = true; |
| } else { |
| x_e_ungetc(c); |
| x_arg = n; |
| x_arg_defaulted = false; |
| } |
| return (KSTD); |
| } |
| |
| /* Comment or uncomment the current line. */ |
| static int |
| x_comment(int c MKSH_A_UNUSED) |
| { |
| ssize_t len = xep - xbuf; |
| int ret = x_do_comment(xbuf, xend - xbuf, &len); |
| |
| if (ret < 0) |
| x_e_putc2(KSH_BEL); |
| else { |
| x_modified(); |
| xep = xbuf + len; |
| *xep = '\0'; |
| xcp = xbp = xbuf; |
| x_redraw('\r'); |
| if (ret > 0) |
| return (x_newline('\n')); |
| } |
| return (KSTD); |
| } |
| |
| static int |
| x_version(int c MKSH_A_UNUSED) |
| { |
| char *o_xbuf = xbuf, *o_xend = xend; |
| char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp; |
| char *v; |
| |
| strdupx(v, KSH_VERSION, ATEMP); |
| |
| xbuf = xbp = xcp = v; |
| xend = xep = strnul(v); |
| x_redraw('\r'); |
| x_flush(); |
| |
| c = x_e_getc(); |
| xbuf = o_xbuf; |
| xend = o_xend; |
| xbp = o_xbp; |
| xep = o_xep; |
| xcp = o_xcp; |
| x_redraw('\r'); |
| |
| if (c < 0) |
| return (KSTD); |
| /* This is what AT&T ksh seems to do... Very bizarre */ |
| if (c != ' ') |
| x_e_ungetc(c); |
| |
| afree(v, ATEMP); |
| return (KSTD); |
| } |
| |
| #ifndef MKSH_SMALL |
| static int |
| x_edit_line(int c MKSH_A_UNUSED) |
| { |
| if (x_arg_defaulted) { |
| if (modified) { |
| *xep = '\0'; |
| histsave(&source->line, xbuf, HIST_STORE, true); |
| x_arg = 0; |
| } else |
| x_arg = source->line - (histptr - x_histp); |
| } |
| if (x_arg) |
| shf_snprintf(xbuf, xend - xbuf, Tf_sd, ctrl_x_e, x_arg); |
| else |
| strlcpy(xbuf, ctrl_x_e, xend - xbuf); |
| xep = strnul(xbuf); |
| return (x_newline('\n')); |
| } |
| #endif |
| |
| /*- |
| * NAME: |
| * x_prev_histword - recover word from prev command |
| * |
| * DESCRIPTION: |
| * This function recovers the last word from the previous |
| * command and inserts it into the current edit line. If a |
| * numeric arg is supplied then the n'th word from the |
| * start of the previous command is used. |
| * As a side effect, trashes the mark in order to achieve |
| * being called in a repeatable fashion. |
| * |
| * Bound to M-. |
| * |
| * RETURN VALUE: |
| * KSTD |
| */ |
| static int |
| x_prev_histword(int c MKSH_A_UNUSED) |
| { |
| char *rcp, *cp; |
| char **xhp; |
| int m = 1; |
| /* -1 = defaulted; 0+ = argument */ |
| static int last_arg = -1; |
| |
| if (x_last_command == XFUNC_prev_histword) { |
| if (xmp && modified > 1) |
| x_kill_region(0); |
| if (modified) |
| m = modified; |
| } else |
| last_arg = x_arg_defaulted ? -1 : x_arg; |
| xhp = histptr - (m - 1); |
| if ((xhp < history) || !(cp = *xhp)) { |
| x_e_putc2(KSH_BEL); |
| x_modified(); |
| return (KSTD); |
| } |
| x_set_mark(0); |
| if ((x_arg = last_arg) == -1) { |
| /* x_arg_defaulted */ |
| |
| rcp = &cp[strlen(cp) - 1]; |
| /* |
| * ignore white-space after the last word |
| */ |
| while (rcp > cp && ctype(*rcp, C_CFS)) |
| rcp--; |
| while (rcp > cp && !ctype(*rcp, C_CFS)) |
| rcp--; |
| if (ctype(*rcp, C_CFS)) |
| rcp++; |
| x_ins(rcp); |
| } else { |
| /* not x_arg_defaulted */ |
| char ch; |
| |
| rcp = cp; |
| /* |
| * ignore white-space at start of line |
| */ |
| while (*rcp && ctype(*rcp, C_CFS)) |
| rcp++; |
| while (x_arg-- > 0) { |
| while (*rcp && !ctype(*rcp, C_CFS)) |
| rcp++; |
| while (*rcp && ctype(*rcp, C_CFS)) |
| rcp++; |
| } |
| cp = rcp; |
| while (*rcp && !ctype(*rcp, C_CFS)) |
| rcp++; |
| ch = *rcp; |
| *rcp = '\0'; |
| x_ins(cp); |
| *rcp = ch; |
| } |
| if (!modified) |
| x_histmcp = x_histp; |
| modified = m + 1; |
| return (KSTD); |
| } |
| |
| #ifndef MKSH_SMALL |
| /* Uppercase N(1) words */ |
| static int |
| x_fold_upper(int c MKSH_A_UNUSED) |
| { |
| return (x_fold_case('U')); |
| } |
| |
| /* Lowercase N(1) words */ |
| static int |
| x_fold_lower(int c MKSH_A_UNUSED) |
| { |
| return (x_fold_case('L')); |
| } |
| |
| /* Titlecase N(1) words */ |
| static int |
| x_fold_capitalise(int c MKSH_A_UNUSED) |
| { |
| return (x_fold_case('C')); |
| } |
| |
| /*- |
| * NAME: |
| * x_fold_case - convert word to UPPER/lower/Capital case |
| * |
| * DESCRIPTION: |
| * This function is used to implement M-U/M-u, M-L/M-l, M-C/M-c |
| * to UPPER CASE, lower case or Capitalise Words. |
| * |
| * RETURN VALUE: |
| * None |
| */ |
| static int |
| x_fold_case(int c) |
| { |
| char *cp = xcp; |
| |
| if (cp == xep) { |
| x_e_putc2(KSH_BEL); |
| return (KSTD); |
| } |
| while (x_arg--) { |
| /* |
| * first skip over any white-space |
| */ |
| while (cp != xep && ctype(*cp, C_MFS)) |
| cp++; |
| /* |
| * do the first char on its own since it may be |
| * a different action than for the rest. |
| */ |
| if (cp != xep) { |
| if (c == 'L') |
| /* lowercase */ |
| *cp = ksh_tolower(*cp); |
| else |
| /* uppercase, capitalise */ |
| *cp = ksh_toupper(*cp); |
| cp++; |
| } |
| /* |
| * now for the rest of the word |
| */ |
| while (cp != xep && !ctype(*cp, C_MFS)) { |
| if (c == 'U') |
| /* uppercase */ |
| *cp = ksh_toupper(*cp); |
| else |
| /* lowercase, capitalise */ |
| *cp = ksh_tolower(*cp); |
| cp++; |
| } |
| } |
| x_goto(cp); |
| x_modified(); |
| return (KSTD); |
| } |
| #endif |
| |
| /*- |
| * NAME: |
| * x_lastcp - last visible char |
| * |
| * DESCRIPTION: |
| * This function returns a pointer to that char in the |
| * edit buffer that will be the last displayed on the |
| * screen. |
| */ |
| static char * |
| x_lastcp(void) |
| { |
| if (!xlp_valid) { |
| int i = 0, j; |
| char *xlp2; |
| |
| xlp = xbp; |
| while (xlp < xep) { |
| j = x_size2(xlp, &xlp2); |
| if ((i + j) > x_displen) |
| break; |
| i += j; |
| xlp = xlp2; |
| } |
| } |
| xlp_valid = true; |
| return (xlp); |
| } |
| |
| /* correctly position the cursor on the screen from end of visible area */ |
| static void |
| x_lastpos(void) |
| { |
| char *cp = x_lastcp(); |
| |
| while (cp > xcp) |
| x_bs3(&cp); |
| } |
| |
| static void |
| x_mode(bool onoff) |
| { |
| static bool x_cur_mode; |
| |
| if (x_cur_mode == onoff) |
| return; |
| x_cur_mode = onoff; |
| |
| if (onoff) { |
| x_mkraw(tty_fd, NULL, false); |
| |
| edchars.erase = toedchar(tty_state.c_cc[VERASE]); |
| edchars.kill = toedchar(tty_state.c_cc[VKILL]); |
| edchars.intr = toedchar(tty_state.c_cc[VINTR]); |
| edchars.quit = toedchar(tty_state.c_cc[VQUIT]); |
| edchars.eof = toedchar(tty_state.c_cc[VEOF]); |
| #ifdef VWERASE |
| edchars.werase = toedchar(tty_state.c_cc[VWERASE]); |
| #else |
| edchars.werase = 0; |
| #endif |
| |
| if (!edchars.erase) |
| edchars.erase = CTRL_H; |
| if (!edchars.kill) |
| edchars.kill = CTRL_U; |
| if (!edchars.intr) |
| edchars.intr = CTRL_C; |
| if (!edchars.quit) |
| edchars.quit = CTRL_BK; |
| if (!edchars.eof) |
| edchars.eof = CTRL_D; |
| if (!edchars.werase) |
| edchars.werase = CTRL_W; |
| |
| if (isedchar(edchars.erase)) { |
| bind_if_not_bound(0, edchars.erase, XFUNC_del_back); |
| bind_if_not_bound(1, edchars.erase, XFUNC_del_bword); |
| } |
| if (isedchar(edchars.kill)) |
| bind_if_not_bound(0, edchars.kill, XFUNC_del_line); |
| if (isedchar(edchars.werase)) |
| bind_if_not_bound(0, edchars.werase, XFUNC_del_bword); |
| if (isedchar(edchars.intr)) |
| bind_if_not_bound(0, edchars.intr, XFUNC_abort); |
| if (isedchar(edchars.quit)) |
| bind_if_not_bound(0, edchars.quit, XFUNC_noop); |
| } else |
| mksh_tcset(tty_fd, &tty_state); |
| } |
| |
| #if !MKSH_S_NOVI |
| /* +++ vi editing mode +++ */ |
| |
| struct edstate { |
| char *cbuf; |
| ssize_t winleft; |
| ssize_t cbufsize; |
| ssize_t linelen; |
| ssize_t cursor; |
| }; |
| |
| static int vi_hook(int); |
| static int nextstate(int); |
| static int vi_insert(int); |
| static int vi_cmd(int, const char *); |
| static int domove(int, const char *, int); |
| static int domovebeg(void); |
| static int redo_insert(int); |
| static void yank_range(int, int); |
| static int bracktype(int); |
| static void save_cbuf(void); |
| static void restore_cbuf(void); |
| static int putbuf(const char *, ssize_t, bool); |
| static void del_range(int, int); |
| static int findch(int, int, bool, bool) MKSH_A_PURE; |
| static int forwword(int); |
| static int backword(int); |
| static int endword(int); |
| static int Forwword(int); |
| static int Backword(int); |
| static int Endword(int); |
| static int grabhist(int, int); |
| static int grabsearch(const char *, int, int, bool); |
| static void redraw_line(bool); |
| static void refresh(bool); |
| static int outofwin(void); |
| static void rewindow(void); |
| static int newcol(unsigned char, int); |
| static void display(char *, char *, bool); |
| static void ed_mov_opt(int, char *); |
| static int expand_word(int); |
| static int complete_word(int, int); |
| static int print_expansions(struct edstate *, int); |
| static void vi_error(void); |
| static void vi_macro_reset(void); |
| static int x_vi_putbuf(const char *, size_t); |
| #define char_len(c) (ksh_isctrl(c) ? 2 : 1) |
| |
| #define vC 0x01 /* a valid command that isn't a vM, vE, vU */ |
| #define vM 0x02 /* movement command (h, l, etc.) */ |
| #define vE 0x04 /* extended command (c, d, y) */ |
| #define vX 0x08 /* long command (@, f, F, t, T, etc.) */ |
| #define vU 0x10 /* an UN-undoable command (that isn't a vM) */ |
| #define vB 0x20 /* bad command (^@) */ |
| #define vZ 0x40 /* repeat count defaults to 0 (not 1) */ |
| #define vS 0x80 /* search (/, ?) */ |
| |
| #define is_bad(c) (classify[rtt2asc(c) & 0x7F] & vB) |
| #define is_cmd(c) (classify[rtt2asc(c) & 0x7F] & (vM | vE | vC | vU)) |
| #define is_move(c) (classify[rtt2asc(c) & 0x7F] & vM) |
| #define is_extend(c) (classify[rtt2asc(c) & 0x7F] & vE) |
| #define is_long(c) (classify[rtt2asc(c) & 0x7F] & vX) |
| #define is_undoable(c) (!(classify[rtt2asc(c) & 0x7F] & vU)) |
| #define is_srch(c) (classify[rtt2asc(c) & 0x7F] & vS) |
| #define is_zerocount(c) (classify[rtt2asc(c) & 0x7F] & vZ) |
| |
| static const unsigned char classify[128] = { |
| /* 0 1 2 3 4 5 6 7 */ |
| /* 0 ^@ ^A ^B ^C ^D ^E ^F ^G */ |
| vB, 0, 0, 0, 0, vC|vU, vC|vZ, 0, |
| /* 1 ^H ^I ^J ^K ^L ^M ^N ^O */ |
| vM, vC|vZ, 0, 0, vC|vU, 0, vC, 0, |
| /* 2 ^P ^Q ^R ^S ^T ^U ^V ^W */ |
| vC, 0, vC|vU, 0, 0, 0, vC, 0, |
| /* 3 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */ |
| vC, 0, 0, vC|vZ, 0, 0, 0, 0, |
| /* 4 <space> ! " # $ % & ' */ |
| vM, 0, 0, vC, vM, vM, 0, 0, |
| /* 5 ( ) * + , - . / */ |
| 0, 0, vC, vC, vM, vC, 0, vC|vS, |
| /* 6 0 1 2 3 4 5 6 7 */ |
| vM, 0, 0, 0, 0, 0, 0, 0, |
| /* 7 8 9 : ; < = > ? */ |
| 0, 0, 0, vM, 0, vC, 0, vC|vS, |
| /* 8 @ A B C D E F G */ |
| vC|vX, vC, vM, vC, vC, vM, vM|vX, vC|vU|vZ, |
| /* 9 H I J K L M N O */ |
| 0, vC, 0, 0, 0, 0, vC|vU, vU, |
| /* A P Q R S T U V W */ |
| vC, 0, vC, vC, vM|vX, vC, 0, vM, |
| /* B X Y Z [ \ ] ^ _ */ |
| vC, vC|vU, 0, vU, vC|vZ, 0, vM, vC|vZ, |
| /* C ` a b c d e f g */ |
| 0, vC, vM, vE, vE, vM, vM|vX, vC|vZ, |
| /* D h i j k l m n o */ |
| vM, vC, vC|vU, vC|vU, vM, 0, vC|vU, 0, |
| /* E p q r s t u v w */ |
| vC, 0, vX, vC, vM|vX, vC|vU, vC|vU|vZ, vM, |
| /* F x y z { | } ~ ^? */ |
| vC, vE|vU, 0, 0, vM|vZ, 0, vC, 0 |
| }; |
| |
| #define MAXVICMD 3 |
| #define SRCHLEN 40 |
| |
| #define INSERT 1 |
| #define REPLACE 2 |
| |
| #define VNORMAL 0 /* command, insert or replace mode */ |
| #define VARG1 1 /* digit prefix (first, eg, 5l) */ |
| #define VEXTCMD 2 /* cmd + movement (eg, cl) */ |
| #define VARG2 3 /* digit prefix (second, eg, 2c3l) */ |
| #define VXCH 4 /* f, F, t, T, @ */ |
| #define VFAIL 5 /* bad command */ |
| #define VCMD 6 /* single char command (eg, X) */ |
| #define VREDO 7 /* . */ |
| #define VLIT 8 /* ^V */ |
| #define VSEARCH 9 /* /, ? */ |
| #define VVERSION 10 /* <ESC> ^V */ |
| #define VPREFIX2 11 /* ^[[ and ^[O in insert mode */ |
| |
| static struct edstate *save_edstate(struct edstate *old); |
| static void restore_edstate(struct edstate *old, struct edstate *news); |
| static void free_edstate(struct edstate *old); |
| |
| static struct edstate ebuf; |
| static struct edstate undobuf; |
| |
| static struct edstate *vs; /* current Vi editing mode state */ |
| static struct edstate *undo; |
| |
| static char *ibuf; /* input buffer */ |
| static bool first_insert; /* set when starting in insert mode */ |
| static int saved_inslen; /* saved inslen for first insert */ |
| static int inslen; /* length of input buffer */ |
| static int srchlen; /* length of current search pattern */ |
| static char *ybuf; /* yank buffer */ |
| static int yanklen; /* length of yank buffer */ |
| static uint8_t fsavecmd = ORD(' '); /* last find command */ |
| static int fsavech; /* character to find */ |
| static char lastcmd[MAXVICMD]; /* last non-move command */ |
| static int lastac; /* argcnt for lastcmd */ |
| static uint8_t lastsearch = ORD(' '); /* last search command */ |
| static char srchpat[SRCHLEN]; /* last search pattern */ |
| static int insert; /* <>0 in insert mode */ |
| static int hnum; /* position in history */ |
| static int ohnum; /* history line copied (after mod) */ |
| static int hlast; /* 1 past last position in history */ |
| static int state; |
| |
| /* |
| * Information for keeping track of macros that are being expanded. |
| * The format of buf is the alias contents followed by a NUL byte followed |
| * by the name (letter) of the alias. The end of the buffer is marked by |
| * a double NUL. The name of the alias is stored so recursive macros can |
| * be detected. |
| */ |
| struct macro_state { |
| unsigned char *p; /* current position in buf */ |
| unsigned char *buf; /* pointer to macro(s) being expanded */ |
| size_t len; /* how much data in buffer */ |
| }; |
| static struct macro_state macro; |
| |
| /* last input was expanded */ |
| static enum expand_mode { |
| NONE = 0, EXPAND, COMPLETE, PRINT |
| } expanded; |
| |
| static int |
| x_vi(char *buf) |
| { |
| int c; |
| |
| state = VNORMAL; |
| ohnum = hnum = hlast = histnum(-1) + 1; |
| insert = INSERT; |
| saved_inslen = inslen; |
| first_insert = true; |
| inslen = 0; |
| vi_macro_reset(); |
| |
| ebuf.cbuf = buf; |
| if (undobuf.cbuf == NULL) { |
| ibuf = alloc(LINE, AEDIT); |
| ybuf = alloc(LINE, AEDIT); |
| undobuf.cbuf = alloc(LINE, AEDIT); |
| } |
| undobuf.cbufsize = ebuf.cbufsize = LINE; |
| undobuf.linelen = ebuf.linelen = 0; |
| undobuf.cursor = ebuf.cursor = 0; |
| undobuf.winleft = ebuf.winleft = 0; |
| vs = &ebuf; |
| undo = &undobuf; |
| |
| x_init_prompt(true); |
| x_col = pwidth; |
| |
| if (wbuf_len != x_cols - 3 && ((wbuf_len = x_cols - 3))) { |
| wbuf[0] = aresize(wbuf[0], wbuf_len, AEDIT); |
| wbuf[1] = aresize(wbuf[1], wbuf_len, AEDIT); |
| } |
| if (wbuf_len) { |
| memset(wbuf[0], ' ', wbuf_len); |
| memset(wbuf[1], ' ', wbuf_len); |
| } |
| winwidth = x_cols - pwidth - 3; |
| win = 0; |
| morec = ' '; |
| holdlen = 0; |
| |
| editmode = 2; |
| x_flush(); |
| while (/* CONSTCOND */ 1) { |
| if (macro.p) { |
| c = (unsigned char)*macro.p++; |
| /* end of current macro? */ |
| if (!c) { |
| /* more macros left to finish? */ |
| if (*macro.p++) |
| continue; |
| /* must be the end of all the macros */ |
| vi_macro_reset(); |
| c = x_getc(); |
| } |
| } else |
| c = x_getc(); |
| |
| if (c == -1) |
| break; |
| if (state != VLIT) { |
| if (isched(c, edchars.intr) || |
| isched(c, edchars.quit)) { |
| /* shove input buffer away */ |
| xbuf = ebuf.cbuf; |
| xep = xbuf; |
| if (ebuf.linelen > 0) |
| xep += ebuf.linelen; |
| /* pretend we got an interrupt */ |
| x_intr(isched(c, edchars.intr) ? |
| SIGINT : SIGQUIT, c); |
| } else if (isched(c, edchars.eof) && |
| state != VVERSION) { |
| if (vs->linelen == 0) { |
| x_vi_zotc(c); |
| c = -1; |
| break; |
| } |
| continue; |
| } |
| } |
| if (vi_hook(c)) |
| break; |
| x_flush(); |
| } |
| |
| x_putc('\r'); |
| x_putc('\n'); |
| x_flush(); |
| |
| if (c == -1 || (ssize_t)LINE <= vs->linelen) |
| return (-1); |
| |
| if (vs->cbuf != buf) |
| memcpy(buf, vs->cbuf, vs->linelen); |
| |
| buf[vs->linelen++] = '\n'; |
| |
| return (vs->linelen); |
| } |
| |
| static int |
| vi_hook(int ch) |
| { |
| static char curcmd[MAXVICMD], locpat[SRCHLEN]; |
| static int cmdlen, argc1, argc2; |
| |
| switch (state) { |
| |
| case VNORMAL: |
| /* PC scancodes */ |
| if (!ch) { |
| cmdlen = 0; |
| switch (ch = x_getc()) { |
| case 71: ch = ORD('0'); goto pseudo_vi_command; |
| case 72: ch = ORD('k'); goto pseudo_vi_command; |
| case 73: ch = ORD('A'); goto vi_xfunc_search; |
| case 75: ch = ORD('h'); goto pseudo_vi_command; |
| case 77: ch = ORD('l'); goto pseudo_vi_command; |
| case 79: ch = ORD('$'); goto pseudo_vi_command; |
| case 80: ch = ORD('j'); goto pseudo_vi_command; |
| case 81: ch = ORD('B'); goto vi_xfunc_search; |
| case 83: ch = ORD('x'); goto pseudo_vi_command; |
| default: ch = 0; goto vi_insert_failed; |
| } |
| } |
| if (insert != 0) { |
| if (ch == CTRL_V) { |
| state = VLIT; |
| ch = ORD('^'); |
| } |
| switch (vi_insert(ch)) { |
| case -1: |
| vi_insert_failed: |
| vi_error(); |
| state = VNORMAL; |
| break; |
| case 0: |
| if (state == VLIT) { |
| vs->cursor--; |
| refresh(false); |
| } else |
| refresh(insert != 0); |
| break; |
| case 1: |
| return (1); |
| } |
| } else { |
| if (ctype(ch, C_CR | C_LF)) |
| return (1); |
| cmdlen = 0; |
| argc1 = 0; |
| if (ctype(ch, C_DIGIT) && ord(ch) != ORD('0')) { |
| argc1 = ksh_numdig(ch); |
| state = VARG1; |
| } else { |
| pseudo_vi_command: |
| curcmd[cmdlen++] = ch; |
| state = nextstate(ch); |
| if (state == VSEARCH) { |
| save_cbuf(); |
| vs->cursor = 0; |
| vs->linelen = 0; |
| if (putbuf(ord(ch) == ORD('/') ? |
| "/" : "?", 1, false) != 0) |
| return (-1); |
| refresh(false); |
| } |
| if (state == VVERSION) { |
| save_cbuf(); |
| vs->cursor = 0; |
| vs->linelen = 0; |
| putbuf(KSH_VERSION, |
| strlen(KSH_VERSION), false); |
| refresh(false); |
| } |
| } |
| } |
| break; |
| |
| case VLIT: |
| if (is_bad(ch)) { |
| del_range(vs->cursor, vs->cursor + 1); |
| vi_error(); |
| } else |
| vs->cbuf[vs->cursor++] = ch; |
| refresh(true); |
| state = VNORMAL; |
| break; |
| |
| case VVERSION: |
| restore_cbuf(); |
| state = VNORMAL; |
| refresh(false); |
| break; |
| |
| case VARG1: |
| if (ctype(ch, C_DIGIT)) |
| argc1 = argc1 * 10 + ksh_numdig(ch); |
| else { |
| curcmd[cmdlen++] = ch; |
| state = nextstate(ch); |
| } |
| break; |
| |
| case VEXTCMD: |
| argc2 = 0; |
| if (ctype(ch, C_DIGIT) && ord(ch) != ORD('0')) { |
| argc2 = ksh_numdig(ch); |
| state = VARG2; |
| return (0); |
| } else { |
| curcmd[cmdlen++] = ch; |
| if (ch == curcmd[0]) |
| state = VCMD; |
| else if (is_move(ch)) |
| state = nextstate(ch); |
| else |
| state = VFAIL; |
| } |
| break; |
| |
| case VARG2: |
| if (ctype(ch, C_DIGIT)) |
| argc2 = argc2 * 10 + ksh_numdig(ch); |
| else { |
| if (argc1 == 0) |
| argc1 = argc2; |
| else |
| argc1 *= argc2; |
| curcmd[cmdlen++] = ch; |
| if (ch == curcmd[0]) |
| state = VCMD; |
| else if (is_move(ch)) |
| state = nextstate(ch); |
| else |
| state = VFAIL; |
| } |
| break; |
| |
| case VXCH: |
| if (ch == CTRL_BO) |
| state = VNORMAL; |
| else { |
| curcmd[cmdlen++] = ch; |
| state = VCMD; |
| } |
| break; |
| |
| case VSEARCH: |
| if (ctype(ch, C_CR | C_LF) /* || ch == CTRL_BO */ ) { |
| restore_cbuf(); |
| /* Repeat last search? */ |
| if (srchlen == 0) { |
| if (!srchpat[0]) { |
| vi_error(); |
| state = VNORMAL; |
| refresh(false); |
| return (0); |
| } |
| } else { |
| locpat[srchlen] = '\0'; |
| memcpy(srchpat, locpat, srchlen + 1); |
| } |
| state = VCMD; |
| } else if (isched(ch, edchars.erase) || ch == CTRL_H) { |
| if (srchlen != 0) { |
| srchlen--; |
| vs->linelen -= char_len(locpat[srchlen]); |
| vs->cursor = vs->linelen; |
| refresh(false); |
| return (0); |
| } |
| restore_cbuf(); |
| state = VNORMAL; |
| refresh(false); |
| } else if (isched(ch, edchars.kill)) { |
| srchlen = 0; |
| vs->linelen = 1; |
| vs->cursor = 1; |
| refresh(false); |
| return (0); |
| } else if (isched(ch, edchars.werase)) { |
| unsigned int i, n; |
| struct edstate new_es, *save_es; |
| |
| new_es.cursor = srchlen; |
| new_es.cbuf = locpat; |
| |
| save_es = vs; |
| vs = &new_es; |
| n = backword(1); |
| vs = save_es; |
| |
| i = (unsigned)srchlen; |
| while (i-- > n) |
| vs->linelen -= char_len(locpat[i]); |
| srchlen = (int)n; |
| vs->cursor = vs->linelen; |
| refresh(false); |
| return (0); |
| } else { |
| if (srchlen == SRCHLEN - 1) |
| vi_error(); |
| else { |
| locpat[srchlen++] = ch; |
| if (ksh_isctrl(ch)) { |
| if ((size_t)vs->linelen + 2 > |
| (size_t)vs->cbufsize) |
| vi_error(); |
| vs->cbuf[vs->linelen++] = '^'; |
| vs->cbuf[vs->linelen++] = ksh_unctrl(ch); |
| } else { |
| if (vs->linelen >= vs->cbufsize) |
| vi_error(); |
| vs->cbuf[vs->linelen++] = ch; |
| } |
| vs->cursor = vs->linelen; |
| refresh(false); |
| } |
| return (0); |
| } |
| break; |
| |
| case VPREFIX2: |
| vi_xfunc_search: |
| state = VFAIL; |
| switch (ch) { |
| case ORD('A'): |
| case ORD('B'): |
| /* the cursor may not be at the BOL */ |
| if (!vs->cursor) |
| break; |
| /* nor further in the line than we can search for */ |
| if ((size_t)vs->cursor >= sizeof(srchpat) - 1) |
| vs->cursor = sizeof(srchpat) - 2; |
| /* anchor the search pattern */ |
| srchpat[0] = '^'; |
| /* take current line up to the cursor */ |
| memcpy(srchpat + 1, vs->cbuf, vs->cursor); |
| srchpat[vs->cursor + 1] = '\0'; |
| /* set a magic flag */ |
| argc1 = 2 + (int)vs->cursor; |
| /* and emulate a history search */ |
| /* search backwards if PgUp, forwards for PgDn */ |
| lastsearch = ch == ORD('A') ? '/' : '?'; |
| *curcmd = 'n'; |
| goto pseudo_VCMD; |
| } |
| break; |
| } |
| |
| switch (state) { |
| case VCMD: |
| pseudo_VCMD: |
| state = VNORMAL; |
| switch (vi_cmd(argc1, curcmd)) { |
| case -1: |
| vi_error(); |
| refresh(false); |
| break; |
| case 0: |
| if (insert != 0) |
| inslen = 0; |
| refresh(insert != 0); |
| break; |
| case 1: |
| refresh(false); |
| return (1); |
| case 2: |
| /* back from a 'v' command - don't redraw the screen */ |
| return (1); |
| } |
| break; |
| |
| case VREDO: |
| state = VNORMAL; |
| if (argc1 != 0) |
| lastac = argc1; |
| switch (vi_cmd(lastac, lastcmd)) { |
| case -1: |
| vi_error(); |
| refresh(false); |
| break; |
| case 0: |
| if (insert != 0) { |
| if (lastcmd[0] == 's' || |
| ksh_eq(lastcmd[0], 'C', 'c')) { |
| if (redo_insert(1) != 0) |
| vi_error(); |
| } else { |
| if (redo_insert(lastac) != 0) |
| vi_error(); |
| } |
| } |
| refresh(false); |
| break; |
| case 1: |
| refresh(false); |
| return (1); |
| case 2: |
| /* back from a 'v' command - can't happen */ |
| break; |
| } |
| break; |
| |
| case VFAIL: |
| state = VNORMAL; |
| vi_error(); |
| break; |
| } |
| return (0); |
| } |
| |
| static int |
| nextstate(int ch) |
| { |
| if (is_extend(ch)) |
| return (VEXTCMD); |
| else if (is_srch(ch)) |
| return (VSEARCH); |
| else if (is_long(ch)) |
| return (VXCH); |
| else if (ch == '.') |
| return (VREDO); |
| else if (ch == CTRL_V) |
| return (VVERSION); |
| else if (is_cmd(ch)) |
| return (VCMD); |
| else |
| return (VFAIL); |
| } |
| |
| static int |
| vi_insert(int ch) |
| { |
| int tcursor; |
| |
| if (isched(ch, edchars.erase) || ch == CTRL_H) { |
| if (insert == REPLACE) { |
| if (vs->cursor == undo->cursor) { |
| vi_error(); |
| return (0); |
| } |
| if (inslen > 0) |
| inslen--; |
| vs->cursor--; |
| if (vs->cursor >= undo->linelen) |
| vs->linelen--; |
| else |
| vs->cbuf[vs->cursor] = undo->cbuf[vs->cursor]; |
| } else { |
| if (vs->cursor == 0) |
| return (0); |
| if (inslen > 0) |
| inslen--; |
| vs->cursor--; |
| vs->linelen--; |
| memmove(&vs->cbuf[vs->cursor], &vs->cbuf[vs->cursor + 1], |
| vs->linelen - vs->cursor + 1); |
| } |
| expanded = NONE; |
| return (0); |
| } |
| if (isched(ch, edchars.kill)) { |
| if (vs->cursor != 0) { |
| inslen = 0; |
| memmove(vs->cbuf, &vs->cbuf[vs->cursor], |
| vs->linelen - vs->cursor); |
| vs->linelen -= vs->cursor; |
| vs->cursor = 0; |
| } |
| expanded = NONE; |
| return (0); |
| } |
| if (isched(ch, edchars.werase)) { |
| if (vs->cursor != 0) { |
| tcursor = backword(1); |
| memmove(&vs->cbuf[tcursor], &vs->cbuf[vs->cursor], |
| vs->linelen - vs->cursor); |
| vs->linelen -= vs->cursor - tcursor; |
| if (inslen < vs->cursor - tcursor) |
| inslen = 0; |
| else |
| inslen -= vs->cursor - tcursor; |
| vs->cursor = tcursor; |
| } |
| expanded = NONE; |
| return (0); |
| } |
| /* |
| * If any chars are entered before escape, trash the saved insert |
| * buffer (if user inserts & deletes char, ibuf gets trashed and |
| * we don't want to use it) |
| */ |
| if (first_insert && ch != CTRL_BO) |
| saved_inslen = 0; |
| switch (ch) { |
| case '\0': |
| return (-1); |
| |
| case '\r': |
| case '\n': |
| return (1); |
| |
| case CTRL_BO: |
| expanded = NONE; |
| if (first_insert) { |
| first_insert = false; |
| if (inslen == 0) { |
| inslen = saved_inslen; |
| return (redo_insert(0)); |
| } |
| lastcmd[0] = 'a'; |
| lastac = 1; |
| } |
| if (lastcmd[0] == 's' || ksh_eq(lastcmd[0], 'C', 'c')) |
| return (redo_insert(0)); |
| else |
| return (redo_insert(lastac - 1)); |
| |
| /* { start nonstandard vi commands */ |
| case CTRL_X: |
| expand_word(0); |
| break; |
| |
| case CTRL_F: |
| complete_word(0, 0); |
| break; |
| |
| case CTRL_E: |
| print_expansions(vs, 0); |
| break; |
| |
| case CTRL_I: |
| if (Flag(FVITABCOMPLETE)) { |
| complete_word(0, 0); |
| break; |
| } |
| /* FALLTHROUGH */ |
| /* end nonstandard vi commands } */ |
| |
| default: |
| if (vs->linelen >= vs->cbufsize - 1) |
| return (-1); |
| ibuf[inslen++] = ch; |
| if (insert == INSERT) { |
| memmove(&vs->cbuf[vs->cursor + 1], &vs->cbuf[vs->cursor], |
| vs->linelen - vs->cursor); |
| vs->linelen++; |
| } |
| vs->cbuf[vs->cursor++] = ch; |
| if (insert == REPLACE && vs->cursor > vs->linelen) |
| vs->linelen++; |
| expanded = NONE; |
| } |
| return (0); |
| } |
| |
| static int |
| vi_cmd(int argcnt, const char *cmd) |
| { |
| int ncursor; |
| int cur, c1, c2; |
| int any; |
| bool b; |
| struct edstate *t; |
| |
| if (argcnt == 0 && !is_zerocount(*cmd)) |
| argcnt = 1; |
| |
| if (is_move(*cmd)) { |
| if ((cur = domove(argcnt, cmd, 0)) >= 0) { |
| if (cur == vs->linelen && cur != 0) |
| cur--; |
| vs->cursor = cur; |
| } else |
| return (-1); |
| } else { |
| /* Don't save state in middle of macro.. */ |
| if (is_undoable(*cmd) && !macro.p) { |
| undo->winleft = vs->winleft; |
| memmove(undo->cbuf, vs->cbuf, vs->linelen); |
| undo->linelen = vs->linelen; |
| undo->cursor = vs->cursor; |
| lastac = argcnt; |
| memmove(lastcmd, cmd, MAXVICMD); |
| } |
| switch (ord(*cmd)) { |
| |
| case CTRL_L: |
| case CTRL_R: |
| redraw_line(true); |
| break; |
| |
| case ORD('@'): |
| { |
| static char alias[] = "_\0"; |
| struct tbl *ap; |
| size_t olen, nlen; |
| char *p, *nbuf; |
| |
| /* lookup letter in alias list... */ |
| alias[1] = cmd[1]; |
| ap = ktsearch(&aliases, alias, hash(alias)); |
| if (!cmd[1] || !ap || !(ap->flag & ISSET)) |
| return (-1); |
| /* check if this is a recursive call... */ |
| if ((p = (char *)macro.p)) |
| while ((p = strnul(p)) && p[1]) |
| if (*++p == cmd[1]) |
| return (-1); |
| /* insert alias into macro buffer */ |
| nlen = strlen(ap->val.s) + 1; |
| olen = !macro.p ? 2 : |
| macro.len - (macro.p - macro.buf); |
| /* |
| * at this point, it's fairly reasonable that |
| * nlen + olen + 2 doesn't overflow |
| */ |
| nbuf = alloc(nlen + 1 + olen, AEDIT); |
| memcpy(nbuf, ap->val.s, nlen); |
| nbuf[nlen++] = cmd[1]; |
| if (macro.p) { |
| memcpy(nbuf + nlen, macro.p, olen); |
| afree(macro.buf, AEDIT); |
| nlen += olen; |
| } else { |
| nbuf[nlen++] = '\0'; |
| nbuf[nlen++] = '\0'; |
| } |
| macro.p = macro.buf = (unsigned char *)nbuf; |
| macro.len = nlen; |
| } |
| break; |
| |
| case ORD('a'): |
| modified = 1; |
| hnum = hlast; |
| if (vs->linelen != 0) |
| vs->cursor++; |
| insert = INSERT; |
| break; |
| |
| case ORD('A'): |
| modified = 1; |
| hnum = hlast; |
| del_range(0, 0); |
| vs->cursor = vs->linelen; |
| insert = INSERT; |
| break; |
| |
| case ORD('S'): |
| vs->cursor = domovebeg(); |
| del_range(vs->cursor, vs->linelen); |
| modified = 1; |
| hnum = hlast; |
| insert = INSERT; |
| break; |
| |
| case ORD('Y'): |
| cmd = "y$"; |
| /* ahhhhhh... */ |
| |
| /* FALLTHROUGH */ |
| case ORD('c'): |
| case ORD('d'): |
| case ORD('y'): |
| if (*cmd == cmd[1]) { |
| c1 = *cmd == 'c' ? domovebeg() : 0; |
| c2 = vs->linelen; |
| } else if (!is_move(cmd[1])) |
| return (-1); |
| else { |
| if ((ncursor = domove(argcnt, &cmd[1], 1)) < 0) |
| return (-1); |
| if (*cmd == 'c' && ksh_eq(cmd[1], 'W', 'w') && |
| !ctype(vs->cbuf[vs->cursor], C_SPACE)) { |
| do { |
| --ncursor; |
| } while (ctype(vs->cbuf[ncursor], C_SPACE)); |
| ncursor++; |
| } |
| if (ncursor > vs->cursor) { |
| c1 = vs->cursor; |
| c2 = ncursor; |
| } else { |
| c1 = ncursor; |
| c2 = vs->cursor; |
| if (cmd[1] == '%') |
| c2++; |
| } |
| } |
| if (*cmd != 'c' && c1 != c2) |
| yank_range(c1, c2); |
| if (*cmd != 'y') { |
| del_range(c1, c2); |
| vs->cursor = c1; |
| } |
| if (*cmd == 'c') { |
| modified = 1; |
| hnum = hlast; |
| insert = INSERT; |
| } |
| break; |
| |
| case ORD('p'): |
| modified = 1; |
| hnum = hlast; |
| if (vs->linelen != 0) |
| vs->cursor++; |
| while (putbuf(ybuf, yanklen, false) == 0 && |
| --argcnt > 0) |
| ; |
| if (vs->cursor != 0) |
| vs->cursor--; |
| if (argcnt != 0) |
| return (-1); |
| break; |
| |
| case ORD('P'): |
| modified = 1; |
| hnum = hlast; |
| any = 0; |
| while (putbuf(ybuf, yanklen, false) == 0 && |
| --argcnt > 0) |
| any = 1; |
| if (any && vs->cursor != 0) |
| vs->cursor--; |
| if (argcnt != 0) |
| return (-1); |
| break; |
| |
| case ORD('C'): |
| modified = 1; |
| hnum = hlast; |
| del_range(vs->cursor, vs->linelen); |
| insert = INSERT; |
| break; |
| |
| case ORD('D'): |
| yank_range(vs->cursor, vs->linelen); |
| del_range(vs->cursor, vs->linelen); |
| if (vs->cursor != 0) |
| vs->cursor--; |
| break; |
| |
| case ORD('g'): |
| if (!argcnt) |
| argcnt = hlast; |
| /* FALLTHROUGH */ |
| case ORD('G'): |
| if (!argcnt) |
| argcnt = 1; |
| else |
| argcnt = hlast - (source->line - argcnt); |
| if (grabhist(modified, argcnt - 1) < 0) |
| return (-1); |
| else { |
| modified = 0; |
| hnum = argcnt - 1; |
| } |
| break; |
| |
| case ORD('i'): |
| modified = 1; |
| hnum = hlast; |
| insert = INSERT; |
| break; |
| |
| case ORD('I'): |
| modified = 1; |
| hnum = hlast; |
| vs->cursor = domovebeg(); |
| insert = INSERT; |
| break; |
| |
| case ORD('j'): |
| case ORD('+'): |
| case CTRL_N: |
| if (grabhist(modified, hnum + argcnt) < 0) |
| return (-1); |
| else { |
| modified = 0; |
| hnum += argcnt; |
| } |
| break; |
| |
| case ORD('k'): |
| case ORD('-'): |
| case CTRL_P: |
| if (grabhist(modified, hnum - argcnt) < 0) |
| return (-1); |
| else { |
| modified = 0; |
| hnum -= argcnt; |
| } |
| break; |
| |
| case ORD('r'): |
| if (vs->linelen == 0) |
| return (-1); |
| modified = 1; |
| hnum = hlast; |
| if (cmd[1] == 0) |
| vi_error(); |
| else { |
| int n; |
| |
| if (vs->cursor + argcnt > vs->linelen) |
| return (-1); |
| for (n = 0; n < argcnt; ++n) |
| vs->cbuf[vs->cursor + n] = cmd[1]; |
| vs->cursor += n - 1; |
| } |
| break; |
| |
| case ORD('R'): |
| modified = 1; |
| hnum = hlast; |
| insert = REPLACE; |
| break; |
| |
| case ORD('s'): |
| if (vs->linelen == 0) |
| return (-1); |
| modified = 1; |
| hnum = hlast; |
| if (vs->cursor + argcnt > vs->linelen) |
| argcnt = vs->linelen - vs->cursor; |
| del_range(vs->cursor, vs->cursor + argcnt); |
| insert = INSERT; |
| break; |
| |
| case ORD('v'): |
| if (!argcnt) { |
| if (modified) { |
| vs->cbuf[vs->linelen] = '\0'; |
| histsave(&source->line, vs->cbuf, |
| HIST_STORE, true); |
| } else |
| argcnt = source->line + 1 - |
| (hlast - hnum); |
| } |
| if (argcnt) |
| shf_snprintf(vs->cbuf, vs->cbufsize, Tf_sd, |
| ctrl_x_e, argcnt); |
| else |
| strlcpy(vs->cbuf, ctrl_x_e, vs->cbufsize); |
| vs->linelen = strlen(vs->cbuf); |
| return (2); |
| |
| case ORD('x'): |
| if (vs->linelen == 0) |
| return (-1); |
| modified = 1; |
| hnum = hlast; |
| if (vs->cursor + argcnt > vs->linelen) |
| argcnt = vs->linelen - vs->cursor; |
| yank_range(vs->cursor, vs->cursor + argcnt); |
| del_range(vs->cursor, vs->cursor + argcnt); |
| break; |
| |
| case ORD('X'): |
| if (vs->cursor > 0) { |
| modified = 1; |
| hnum = hlast; |
| if (vs->cursor < argcnt) |
| argcnt = vs->cursor; |
| yank_range(vs->cursor - argcnt, vs->cursor); |
| del_range(vs->cursor - argcnt, vs->cursor); |
| vs->cursor -= argcnt; |
| } else |
| return (-1); |
| break; |
| |
| case ORD('u'): |
| t = vs; |
| vs = undo; |
| undo = t; |
| break; |
| |
| case ORD('U'): |
| if (!modified) |
| return (-1); |
| if (grabhist(modified, ohnum) < 0) |
| return (-1); |
| modified = 0; |
| hnum = ohnum; |
| break; |
| |
| case ORD('?'): |
| if (hnum == hlast) |
| hnum = -1; |
| /* ahhh */ |
| |
| /* FALLTHROUGH */ |
| case ORD('/'): |
| c1 = 1; |
| srchlen = 0; |
| lastsearch = *cmd; |
| if (0) |
| /* FALLTHROUGH */ |
| case ORD('n'): |
| case ORD('N'): |
| c1 = 0; |
| if (lastsearch == ORD(' ')) |
| return (-1); |
| b = (lastsearch == ORD('?')); |
| if (*cmd == 'N') |
| b = !b; |
| if ((c2 = grabsearch(srchpat, modified, hnum, b)) < 0) { |
| if (c1) { |
| restore_cbuf(); |
| refresh(false); |
| } |
| return (-1); |
| } else { |
| modified = 0; |
| hnum = c2; |
| ohnum = hnum; |
| } |
| if (argcnt >= 2) { |
| /* flag from cursor-up command */ |
| vs->cursor = argcnt - 2; |
| return (0); |
| } |
| break; |
| case ORD('_'): |
| { |
| bool inspace; |
| char *p, *sp; |
| |
| if (histnum(-1) < 0) |
| return (-1); |
| p = *histpos(); |
| if (argcnt) { |
| while (ctype(*p, C_SPACE)) |
| p++; |
| while (*p && --argcnt) { |
| while (*p && !ctype(*p, C_SPACE)) |
| p++; |
| while (ctype(*p, C_SPACE)) |
| p++; |
| } |
| if (!*p) |
| return (-1); |
| sp = p; |
| } else { |
| sp = p; |
| inspace = false; |
| while (*p) { |
| if (ctype(*p, C_SPACE)) |
| inspace = true; |
| else if (inspace) { |
| inspace = false; |
| sp = p; |
| } |
| p++; |
| } |
| p = sp; |
| } |
| modified = 1; |
| hnum = hlast; |
| if (vs->cursor != vs->linelen) |
| vs->cursor++; |
| while (*p && !ctype(*p, C_SPACE)) { |
| argcnt++; |
| p++; |
| } |
| if (putbuf(T1space, 1, false) != 0 || |
| putbuf(sp, argcnt, false) != 0) { |
| if (vs->cursor != 0) |
| vs->cursor--; |
| return (-1); |
| } |
| insert = INSERT; |
| } |
| break; |
| |
| case ORD('~'): |
| { |
| char *p; |
| int i; |
| |
| if (vs->linelen == 0) |
| return (-1); |
| for (i = 0; i < argcnt; i++) { |
| p = &vs->cbuf[vs->cursor]; |
| if (ctype(*p, C_LOWER)) { |
| modified = 1; |
| hnum = hlast; |
| *p = ksh_toupper(*p); |
| } else if (ctype(*p, C_UPPER)) { |
| modified = 1; |
| hnum = hlast; |
| *p = ksh_tolower(*p); |
| } |
| if (vs->cursor < vs->linelen - 1) |
| vs->cursor++; |
| } |
| break; |
| } |
| |
| case ORD('#'): |
| { |
| int ret = x_do_comment(vs->cbuf, vs->cbufsize, |
| &vs->linelen); |
| if (ret >= 0) |
| vs->cursor = 0; |
| return (ret); |
| } |
| |
| /* AT&T ksh */ |
| case ORD('='): |
| /* Nonstandard vi/ksh */ |
| case CTRL_E: |
| print_expansions(vs, 1); |
| break; |
| |
| |
| /* Nonstandard vi/ksh */ |
| case CTRL_I: |
| if (!Flag(FVITABCOMPLETE)) |
| return (-1); |
| complete_word(1, argcnt); |
| break; |
| |
| /* some annoying AT&T kshs */ |
| case CTRL_BO: |
| if (!Flag(FVIESCCOMPLETE)) |
| return (-1); |
| /* FALLTHROUGH */ |
| /* AT&T ksh */ |
| case ORD('\\'): |
| /* Nonstandard vi/ksh */ |
| case CTRL_F: |
| complete_word(1, argcnt); |
| break; |
| |
| |
| /* AT&T ksh */ |
| case ORD('*'): |
| /* Nonstandard vi/ksh */ |
| case CTRL_X: |
| expand_word(1); |
| break; |
| |
| |
| /* mksh: cursor movement */ |
| case ORD('['): |
| case ORD('O'): |
| state = VPREFIX2; |
| if (vs->linelen != 0) |
| vs->cursor++; |
| insert = INSERT; |
| return (0); |
| } |
| if (insert == 0 && vs->cursor != 0 && vs->cursor >= vs->linelen) |
| vs->cursor--; |
| } |
| return (0); |
| } |
| |
| static int |
| domove(int argcnt, const char *cmd, int sub) |
| { |
| int ncursor = 0, i = 0, t; |
| unsigned int bcount; |
| |
| switch (ord(*cmd)) { |
| case ORD('b'): |
| if (!sub && vs->cursor == 0) |
| return (-1); |
| ncursor = backword(argcnt); |
| break; |
| |
| case ORD('B'): |
| if (!sub && vs->cursor == 0) |
| return (-1); |
| ncursor = Backword(argcnt); |
| break; |
| |
| case ORD('e'): |
| if (!sub && vs->cursor + 1 >= vs->linelen) |
| return (-1); |
| ncursor = endword(argcnt); |
| if (sub && ncursor < vs->linelen) |
| ncursor++; |
| break; |
| |
| case ORD('E'): |
| if (!sub && vs->cursor + 1 >= vs->linelen) |
| return (-1); |
| ncursor = Endword(argcnt); |
| if (sub && ncursor < vs->linelen) |
| ncursor++; |
| break; |
| |
| case ORD('f'): |
| case ORD('F'): |
| case ORD('t'): |
| case ORD('T'): |
| fsavecmd = *cmd; |
| fsavech = cmd[1]; |
| /* FALLTHROUGH */ |
| case ORD(','): |
| case ORD(';'): |
| if (fsavecmd == ORD(' ')) |
| return (-1); |
| i = ksh_eq(fsavecmd, 'F', 'f'); |
| t = rtt2asc(fsavecmd) > rtt2asc('a'); |
| if (*cmd == ',') |
| t = !t; |
| if ((ncursor = findch(fsavech, argcnt, tobool(t), |
| tobool(i))) < 0) |
| return (-1); |
| if (sub && t) |
| ncursor++; |
| break; |
| |
| case ORD('h'): |
| case CTRL_H: |
| if (!sub && vs->cursor == 0) |
| return (-1); |
| ncursor = vs->cursor - argcnt; |
| if (ncursor < 0) |
| ncursor = 0; |
| break; |
| |
| case ORD(' '): |
| case ORD('l'): |
| if (!sub && vs->cursor + 1 >= vs->linelen) |
| return (-1); |
| if (vs->linelen != 0) { |
| ncursor = vs->cursor + argcnt; |
| if (ncursor > vs->linelen) |
| ncursor = vs->linelen; |
| } |
| break; |
| |
| case ORD('w'): |
| if (!sub && vs->cursor + 1 >= vs->linelen) |
| return (-1); |
| ncursor = forwword(argcnt); |
| break; |
| |
| case ORD('W'): |
| if (!sub && vs->cursor + 1 >= vs->linelen) |
| return (-1); |
| ncursor = Forwword(argcnt); |
| break; |
| |
| case ORD('0'): |
| ncursor = 0; |
| break; |
| |
| case ORD('^'): |
| ncursor = domovebeg(); |
| break; |
| |
| case ORD('|'): |
| ncursor = argcnt; |
| if (ncursor > vs->linelen) |
| ncursor = vs->linelen; |
| if (ncursor) |
| ncursor--; |
| break; |
| |
| case ORD('$'): |
| if (vs->linelen != 0) |
| ncursor = vs->linelen; |
| else |
| ncursor = 0; |
| break; |
| |
| case ORD('%'): |
| ncursor = vs->cursor; |
| while (ncursor < vs->linelen && |
| (i = bracktype(vs->cbuf[ncursor])) == 0) |
| ncursor++; |
| if (ncursor == vs->linelen) |
| return (-1); |
| bcount = 1; |
| do { |
| if (i > 0) { |
| if (++ncursor >= vs->linelen) |
| return (-1); |
| } else { |
| if (--ncursor < 0) |
| return (-1); |
| } |
| t = bracktype(vs->cbuf[ncursor]); |
| if (t == i) |
| bcount++; |
| else if (t == -i) |
| bcount--; |
| } while (bcount != 0); |
| if (sub && i > 0) |
| ncursor++; |
| break; |
| |
| default: |
| return (-1); |
| } |
| return (ncursor); |
| } |
| |
| static int |
| domovebeg(void) |
| { |
| int ncursor = 0; |
| |
| while (ncursor < vs->linelen - 1 && |
| ctype(vs->cbuf[ncursor], C_SPACE)) |
| ncursor++; |
| return (ncursor); |
| } |
| |
| static int |
| redo_insert(int count) |
| { |
| while (count-- > 0) |
| if (putbuf(ibuf, inslen, tobool(insert == REPLACE)) != 0) |
| return (-1); |
| if (vs->cursor > 0) |
| vs->cursor--; |
| insert = 0; |
| return (0); |
| } |
| |
| static void |
| yank_range(int a, int b) |
| { |
| yanklen = b - a; |
| if (yanklen != 0) |
| memmove(ybuf, &vs->cbuf[a], yanklen); |
| } |
| |
| static int |
| bracktype(int ch) |
| { |
| switch (ord(ch)) { |
| |
| case ORD('('): |
| return (1); |
| |
| case ORD('['): |
| return (2); |
| |
| case ORD('{'): |
| return (3); |
| |
| case ORD(')'): |
| return (-1); |
| |
| case ORD(']'): |
| return (-2); |
| |
| case ORD('}'): |
| return (-3); |
| |
| default: |
| return (0); |
| } |
| } |
| |
| /* |
| * Non user interface editor routines below here |
| */ |
| |
| static void |
| save_cbuf(void) |
| { |
| memmove(holdbufp, vs->cbuf, vs->linelen); |
| holdlen = vs->linelen; |
| holdbufp[holdlen] = '\0'; |
| } |
| |
| static void |
| restore_cbuf(void) |
| { |
| vs->cursor = 0; |
| vs->linelen = holdlen; |
| memmove(vs->cbuf, holdbufp, holdlen); |
| } |
| |
| /* return a new edstate */ |
| static struct edstate * |
| save_edstate(struct edstate *old) |
| { |
| struct edstate *news; |
| |
| news = alloc(sizeof(struct edstate), AEDIT); |
| news->cbuf = alloc(old->cbufsize, AEDIT); |
| memcpy(news->cbuf, old->cbuf, old->linelen); |
| news->cbufsize = old->cbufsize; |
| news->linelen = old->linelen; |
| news->cursor = old->cursor; |
| news->winleft = old->winleft; |
| return (news); |
| } |
| |
| static void |
| restore_edstate(struct edstate *news, struct edstate *old) |
| { |
| memcpy(news->cbuf, old->cbuf, old->linelen); |
| news->linelen = old->linelen; |
| news->cursor = old->cursor; |
| news->winleft = old->winleft; |
| free_edstate(old); |
| } |
| |
| static void |
| free_edstate(struct edstate *old) |
| { |
| afree(old->cbuf, AEDIT); |
| afree(old, AEDIT); |
| } |
| |
| /* |
| * this is used for calling x_escape() in complete_word() |
| */ |
| static int |
| x_vi_putbuf(const char *s, size_t len) |
| { |
| return (putbuf(s, len, false)); |
| } |
| |
| static int |
| putbuf(const char *buf, ssize_t len, bool repl) |
| { |
| if (len == 0) |
| return (0); |
| if (repl) { |
| if (vs->cursor + len >= vs->cbufsize) |
| return (-1); |
| if (vs->cursor + len > vs->linelen) |
| vs->linelen = vs->cursor + len; |
| } else { |
| if (vs->linelen + len >= vs->cbufsize) |
| return (-1); |
| memmove(&vs->cbuf[vs->cursor + len], &vs->cbuf[vs->cursor], |
| vs->linelen - vs->cursor); |
| vs->linelen += len; |
| } |
| memmove(&vs->cbuf[vs->cursor], buf, len); |
| vs->cursor += len; |
| return (0); |
| } |
| |
| static void |
| del_range(int a, int b) |
| { |
| if (vs->linelen != b) |
| memmove(&vs->cbuf[a], &vs->cbuf[b], vs->linelen - b); |
| vs->linelen -= b - a; |
| } |
| |
| static int |
| findch(int ch, int cnt, bool forw, bool incl) |
| { |
| int ncursor; |
| |
| if (vs->linelen == 0) |
| return (-1); |
| ncursor = vs->cursor; |
| while (cnt--) { |
| do { |
| if (forw) { |
| if (++ncursor == vs->linelen) |
| return (-1); |
| } else { |
| if (--ncursor < 0) |
| return (-1); |
| } |
| } while (vs->cbuf[ncursor] != ch); |
| } |
| if (!incl) { |
| if (forw) |
| ncursor--; |
| else |
| ncursor++; |
| } |
| return (ncursor); |
| } |
| |
| static int |
| forwword(int argcnt) |
| { |
| int ncursor; |
| |
| ncursor = vs->cursor; |
| while (ncursor < vs->linelen && argcnt--) { |
| if (ctype(vs->cbuf[ncursor], C_ALNUX)) |
| while (ncursor < vs->linelen && |
| ctype(vs->cbuf[ncursor], C_ALNUX)) |
| ncursor++; |
| else if (!ctype(vs->cbuf[ncursor], C_SPACE)) |
| while (ncursor < vs->linelen && |
| !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE)) |
| ncursor++; |
| while (ncursor < vs->linelen && |
| ctype(vs->cbuf[ncursor], C_SPACE)) |
| ncursor++; |
| } |
| return (ncursor); |
| } |
| |
| static int |
| backword(int argcnt) |
| { |
| int ncursor; |
| |
| ncursor = vs->cursor; |
| while (ncursor > 0 && argcnt--) { |
| while (--ncursor > 0 && ctype(vs->cbuf[ncursor], C_SPACE)) |
| ; |
| if (ncursor > 0) { |
| if (ctype(vs->cbuf[ncursor], C_ALNUX)) |
| while (--ncursor >= 0 && |
| ctype(vs->cbuf[ncursor], C_ALNUX)) |
| ; |
| else |
| while (--ncursor >= 0 && |
| !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE)) |
| ; |
| ncursor++; |
| } |
| } |
| return (ncursor); |
| } |
| |
| static int |
| endword(int argcnt) |
| { |
| int ncursor; |
| |
| ncursor = vs->cursor; |
| while (ncursor < vs->linelen && argcnt--) { |
| while (++ncursor < vs->linelen - 1 && |
| ctype(vs->cbuf[ncursor], C_SPACE)) |
| ; |
| if (ncursor < vs->linelen - 1) { |
| if (ctype(vs->cbuf[ncursor], C_ALNUX)) |
| while (++ncursor < vs->linelen && |
| ctype(vs->cbuf[ncursor], C_ALNUX)) |
| ; |
| else |
| while (++ncursor < vs->linelen && |
| !ctype(vs->cbuf[ncursor], C_ALNUX | C_SPACE)) |
| ; |
| ncursor--; |
| } |
| } |
| return (ncursor); |
| } |
| |
| static int |
| Forwword(int argcnt) |
| { |
| int ncursor; |
| |
| ncursor = vs->cursor; |
| while (ncursor < vs->linelen && argcnt--) { |
| while (ncursor < vs->linelen && |
| !ctype(vs->cbuf[ncursor], C_SPACE)) |
| ncursor++; |
| while (ncursor < vs->linelen && |
| ctype(vs->cbuf[ncursor], C_SPACE)) |
| ncursor++; |
| } |
| return (ncursor); |
| } |
| |
| static int |
| Backword(int argcnt) |
| { |
| int ncursor; |
| |
| ncursor = vs->cursor; |
| while (ncursor > 0 && argcnt--) { |
| while (--ncursor >= 0 && ctype(vs->cbuf[ncursor], C_SPACE)) |
| ; |
| while (ncursor >= 0 && !ctype(vs->cbuf[ncursor], C_SPACE)) |
| ncursor--; |
| ncursor++; |
| } |
| return (ncursor); |
| } |
| |
| static int |
| Endword(int argcnt) |
| { |
| int ncursor; |
| |
| ncursor = vs->cursor; |
| while (ncursor < vs->linelen - 1 && argcnt--) { |
| while (++ncursor < vs->linelen - 1 && |
| ctype(vs->cbuf[ncursor], C_SPACE)) |
| ; |
| if (ncursor < vs->linelen - 1) { |
| while (++ncursor < vs->linelen && |
| !ctype(vs->cbuf[ncursor], C_SPACE)) |
| ; |
| ncursor--; |
| } |
| } |
| return (ncursor); |
| } |
| |
| static int |
| grabhist(int save, int n) |
| { |
| char *hptr; |
| |
| if (n < 0 || n > hlast) |
| return (-1); |
| if (n == hlast) { |
| restore_cbuf(); |
| ohnum = n; |
| return (0); |
| } |
| (void)histnum(n); |
| if ((hptr = *histpos()) == NULL) { |
| internal_warningf("grabhist: bad history array"); |
| return (-1); |
| } |
| if (save) |
| save_cbuf(); |
| if ((vs->linelen = strlen(hptr)) >= vs->cbufsize) |
| vs->linelen = vs->cbufsize - 1; |
| memmove(vs->cbuf, hptr, vs->linelen); |
| vs->cursor = 0; |
| ohnum = n; |
| return (0); |
| } |
| |
| static int |
| grabsearch(const char *pat, int save, int start, bool fwd) |
| { |
| char *hptr; |
| int hist; |
| bool anchored; |
| |
| if ((start == 0 && !fwd) || (start >= hlast - 1 && fwd)) |
| return (-1); |
| if (fwd) |
| start++; |
| else |
| start--; |
| anchored = *pat == '^' ? (++pat, true) : false; |
| if ((hist = findhist(start, pat, fwd, anchored)) < 0) { |
| /* (start != 0 && fwd && match(holdbufp, pat) >= 0) */ |
| if (start != 0 && fwd && strcmp(holdbufp, pat) >= 0) { |
| restore_cbuf(); |
| return (0); |
| } else |
| return (-1); |
| } |
| if (save) |
| save_cbuf(); |
| histnum(hist); |
| hptr = *histpos(); |
| if ((vs->linelen = strlen(hptr)) >= vs->cbufsize) |
| vs->linelen = vs->cbufsize - 1; |
| memmove(vs->cbuf, hptr, vs->linelen); |
| vs->cursor = 0; |
| return (hist); |
| } |
| |
| static void |
| redraw_line(bool newl) |
| { |
| if (wbuf_len) |
| memset(wbuf[win], ' ', wbuf_len); |
| if (newl) { |
| x_putc('\r'); |
| x_putc('\n'); |
| } |
| x_pprompt(); |
| morec = ' '; |
| } |
| |
| static void |
| refresh(bool leftside) |
| { |
| if (outofwin()) |
| rewindow(); |
| display(wbuf[1 - win], wbuf[win], leftside); |
| win = 1 - win; |
| } |
| |
| static int |
| outofwin(void) |
| { |
| int cur, col; |
| |
| if (vs->cursor < vs->winleft) |
| return (1); |
| col = 0; |
| cur = vs->winleft; |
| while (cur < vs->cursor) |
| col = newcol((unsigned char)vs->cbuf[cur++], col); |
| if (col >= winwidth) |
| return (1); |
| return (0); |
| } |
| |
| static void |
| rewindow(void) |
| { |
| int tcur, tcol; |
| int holdcur1, holdcol1; |
| int holdcur2, holdcol2; |
| |
| holdcur1 = holdcur2 = tcur = 0; |
| holdcol1 = holdcol2 = tcol = 0; |
| while (tcur < vs->cursor) { |
| if (tcol - holdcol2 > winwidth / 2) { |
| holdcur1 = holdcur2; |
| holdcol1 = holdcol2; |
| holdcur2 = tcur; |
| holdcol2 = tcol; |
| } |
| tcol = newcol((unsigned char)vs->cbuf[tcur++], tcol); |
| } |
| while (tcol - holdcol1 > winwidth / 2) |
| holdcol1 = newcol((unsigned char)vs->cbuf[holdcur1++], |
| holdcol1); |
| vs->winleft = holdcur1; |
| } |
| |
| static int |
| newcol(unsigned char ch, int col) |
| { |
| if (ch == '\t') |
| return ((col | 7) + 1); |
| return (col + char_len(ch)); |
| } |
| |
| static void |
| display(char *wb1, char *wb2, bool leftside) |
| { |
| unsigned char ch; |
| char *twb1, *twb2, mc; |
| int cur, col, cnt; |
| int ncol = 0; |
| int moreright; |
| |
| col = 0; |
| cur = vs->winleft; |
| moreright = 0; |
| twb1 = wb1; |
| while (col < winwidth && cur < vs->linelen) { |
| if (cur == vs->cursor && leftside) |
| ncol = col + pwidth; |
| if ((ch = vs->cbuf[cur]) == '\t') |
| do { |
| *twb1++ = ' '; |
| } while (++col < winwidth && (col & 7) != 0); |
| else if (col < winwidth) { |
| if (ksh_isctrl(ch)) { |
| *twb1++ = '^'; |
| if (++col < winwidth) { |
| *twb1++ = ksh_unctrl(ch); |
| col++; |
| } |
| } else { |
| *twb1++ = ch; |
| col++; |
| } |
| } |
| if (cur == vs->cursor && !leftside) |
| ncol = col + pwidth - 1; |
| cur++; |
| } |
| if (cur == vs->cursor) |
| ncol = col + pwidth; |
| if (col < winwidth) { |
| while (col < winwidth) { |
| *twb1++ = ' '; |
| col++; |
| } |
| } else |
| moreright++; |
| *twb1 = ' '; |
| |
| col = pwidth; |
| cnt = winwidth; |
| twb1 = wb1; |
| twb2 = wb2; |
| while (cnt--) { |
| if (*twb1 != *twb2) { |
| if (x_col != col) |
| ed_mov_opt(col, wb1); |
| x_putc(*twb1); |
| x_col++; |
| } |
| twb1++; |
| twb2++; |
| col++; |
| } |
| if (vs->winleft > 0 && moreright) |
| /* |
| * POSIX says to use * for this but that is a globbing |
| * character and may confuse people; + is more innocuous |
| */ |
| mc = '+'; |
| else if (vs->winleft > 0) |
| mc = '<'; |
| else if (moreright) |
| mc = '>'; |
| else |
| mc = ' '; |
| if (mc != morec) { |
| ed_mov_opt(pwidth + winwidth + 1, wb1); |
| x_putc(mc); |
| x_col++; |
| morec = mc; |
| } |
| if (x_col != ncol) |
| ed_mov_opt(ncol, wb1); |
| } |
| |
| static void |
| ed_mov_opt(int col, char *wb) |
| { |
| if (col < x_col) { |
| if (col + 1 < x_col - col) { |
| x_putc('\r'); |
| x_pprompt(); |
| while (x_col++ < col) |
| x_putcf(*wb++); |
| } else { |
| while (x_col-- > col) |
| x_putc('\b'); |
| } |
| } else { |
| wb = &wb[x_col - pwidth]; |
| while (x_col++ < col) |
| x_putcf(*wb++); |
| } |
| x_col = col; |
| } |
| |
| |
| /* replace word with all expansions (ie, expand word*) */ |
| static int |
| expand_word(int cmd) |
| { |
| static struct edstate *buf; |
| int rval = 0, nwords, start, end, i; |
| char **words; |
| |
| /* Undo previous expansion */ |
| if (cmd == 0 && expanded == EXPAND && buf) { |
| restore_edstate(vs, buf); |
| buf = 0; |
| expanded = NONE; |
| return (0); |
| } |
| if (buf) { |
| free_edstate(buf); |
| buf = 0; |
| } |
| |
| i = XCF_COMMAND_FILE | XCF_FULLPATH; |
| nwords = x_cf_glob(&i, vs->cbuf, vs->linelen, vs->cursor, |
| &start, &end, &words); |
| if (nwords == 0) { |
| vi_error(); |
| return (-1); |
| } |
| |
| buf = save_edstate(vs); |
| expanded = EXPAND; |
| del_range(start, end); |
| vs->cursor = start; |
| i = 0; |
| while (i < nwords) { |
| if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) { |
| rval = -1; |
| break; |
| } |
| if (++i < nwords && putbuf(T1space, 1, false) != 0) { |
| rval = -1; |
| break; |
| } |
| } |
| i = buf->cursor - end; |
| if (rval == 0 && i > 0) |
| vs->cursor += i; |
| modified = 1; |
| hnum = hlast; |
| insert = INSERT; |
| lastac = 0; |
| refresh(false); |
| return (rval); |
| } |
| |
| static int |
| complete_word(int cmd, int count) |
| { |
| static struct edstate *buf; |
| int rval, nwords, start, end, flags; |
| size_t match_len; |
| char **words; |
| char *match; |
| bool is_unique; |
| |
| /* Undo previous completion */ |
| if (cmd == 0 && expanded == COMPLETE && buf) { |
| print_expansions(buf, 0); |
| expanded = PRINT; |
| return (0); |
| } |
| if (cmd == 0 && expanded == PRINT && buf) { |
| restore_edstate(vs, buf); |
| buf = 0; |
| expanded = NONE; |
| return (0); |
| } |
| if (buf) { |
| free_edstate(buf); |
| buf = 0; |
| } |
| |
| /* |
| * XCF_FULLPATH for count 'cause the menu printed by |
| * print_expansions() was done this way. |
| */ |
| flags = XCF_COMMAND_FILE; |
| if (count) |
| flags |= XCF_FULLPATH; |
| nwords = x_cf_glob(&flags, vs->cbuf, vs->linelen, vs->cursor, |
| &start, &end, &words); |
| if (nwords == 0) { |
| vi_error(); |
| return (-1); |
| } |
| if (count) { |
| int i; |
| |
| count--; |
| if (count >= nwords) { |
| vi_error(); |
| x_print_expansions(nwords, words, |
| tobool(flags & XCF_IS_COMMAND)); |
| x_free_words(nwords, words); |
| redraw_line(false); |
| return (-1); |
| } |
| /* |
| * Expand the count'th word to its basename |
| */ |
| if (flags & XCF_IS_COMMAND) { |
| match = words[count] + |
| x_basename(words[count], NULL); |
| /* If more than one possible match, use full path */ |
| for (i = 0; i < nwords; i++) |
| if (i != count && |
| strcmp(words[i] + x_basename(words[i], |
| NULL), match) == 0) { |
| match = words[count]; |
| break; |
| } |
| } else |
| match = words[count]; |
| match_len = strlen(match); |
| is_unique = true; |
| /* expanded = PRINT; next call undo */ |
| } else { |
| match = words[0]; |
| match_len = x_longest_prefix(nwords, words); |
| /* next call will list completions */ |
| expanded = COMPLETE; |
| is_unique = nwords == 1; |
| } |
| |
| buf = save_edstate(vs); |
| del_range(start, end); |
| vs->cursor = start; |
| |
| /* |
| * escape all shell-sensitive characters and put the result into |
| * command buffer |
| */ |
| rval = x_escape(match, match_len, x_vi_putbuf); |
| |
| if (rval == 0 && is_unique) { |
| /* |
| * If exact match, don't undo. Allows directory completions |
| * to be used (ie, complete the next portion of the path). |
| */ |
| expanded = NONE; |
| |
| /* |
| * append a space if this is a non-directory match |
| * and not a parameter or homedir substitution |
| */ |
| if (match_len > 0 && !mksh_cdirsep(match[match_len - 1]) && |
| !(flags & XCF_IS_NOSPACE)) |
| rval = putbuf(T1space, 1, false); |
| } |
| x_free_words(nwords, words); |
| |
| modified = 1; |
| hnum = hlast; |
| insert = INSERT; |
| /* prevent this from being redone... */ |
| lastac = 0; |
| refresh(false); |
| |
| return (rval); |
| } |
| |
| static int |
| print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED) |
| { |
| int start, end, nwords, i; |
| char **words; |
| |
| i = XCF_COMMAND_FILE | XCF_FULLPATH; |
| nwords = x_cf_glob(&i, est->cbuf, est->linelen, est->cursor, |
| &start, &end, &words); |
| if (nwords == 0) { |
| vi_error(); |
| return (-1); |
| } |
| x_print_expansions(nwords, words, tobool(i & XCF_IS_COMMAND)); |
| x_free_words(nwords, words); |
| redraw_line(false); |
| return (0); |
| } |
| #endif /* !MKSH_S_NOVI */ |
| |
| /* Similar to x_zotc(emacs.c), but no tab weirdness */ |
| static void |
| x_vi_zotc(int c) |
| { |
| if (ksh_isctrl(c)) { |
| x_putc('^'); |
| c = ksh_unctrl(c); |
| } |
| x_putc(c); |
| } |
| |
| #if !MKSH_S_NOVI |
| static void |
| vi_error(void) |
| { |
| /* Beem out of any macros as soon as an error occurs */ |
| vi_macro_reset(); |
| x_putc(KSH_BEL); |
| x_flush(); |
| } |
| |
| static void |
| vi_macro_reset(void) |
| { |
| if (macro.p) { |
| afree(macro.buf, AEDIT); |
| memset((char *)¯o, 0, sizeof(macro)); |
| } |
| } |
| #endif /* !MKSH_S_NOVI */ |
| |
| /* called from main.c */ |
| void |
| x_init(void) |
| { |
| int i, j; |
| |
| /* |
| * set edchars to force initial binding, except we need |
| * default values for ^W for some deficient systems… |
| */ |
| edchars.erase = edchars.kill = edchars.intr = edchars.quit = |
| edchars.eof = EDCHAR_INITIAL; |
| edchars.werase = 027; |
| |
| /* command line editing specific memory allocation */ |
| ainit(AEDIT); |
| holdbufp = alloc(LINE, AEDIT); |
| |
| /* initialise Emacs command line editing mode */ |
| x_nextcmd = -1; |
| |
| x_tab = alloc2(X_NTABS, sizeof(*x_tab), AEDIT); |
| for (j = 0; j < X_TABSZ; j++) |
| x_tab[0][j] = XFUNC_insert; |
| for (i = 1; i < X_NTABS; i++) |
| for (j = 0; j < X_TABSZ; j++) |
| x_tab[i][j] = XFUNC_error; |
| for (i = 0; i < (int)NELEM(x_defbindings); i++) |
| x_tab[x_defbindings[i].xdb_tab][x_defbindings[i].xdb_char] |
| = x_defbindings[i].xdb_func; |
| |
| #ifndef MKSH_SMALL |
| x_atab = alloc2(X_NTABS, sizeof(*x_atab), AEDIT); |
| for (i = 1; i < X_NTABS; i++) |
| for (j = 0; j < X_TABSZ; j++) |
| x_atab[i][j] = NULL; |
| #endif |
| } |
| |
| #ifdef DEBUG_LEAKS |
| void |
| x_done(void) |
| { |
| if (x_tab != NULL) |
| afreeall(AEDIT); |
| } |
| #endif |
| |
| void |
| x_initterm(const char *termtype) |
| { |
| /* default must be 0 (bss) */ |
| x_term_mode = 0; |
| /* catch any of the TERM types tmux uses, don’t ask m̲e̲ about it… */ |
| switch (*termtype) { |
| case 's': |
| if (!strncmp(termtype, "screen", 6) && |
| (termtype[6] == '\0' || termtype[6] == '-')) |
| x_term_mode = 1; |
| break; |
| case 't': |
| if (!strncmp(termtype, "tmux", 4) && |
| (termtype[4] == '\0' || termtype[4] == '-')) |
| x_term_mode = 1; |
| break; |
| } |
| } |
| |
| #ifndef MKSH_SMALL |
| static char * |
| x_eval_region_helper(const char *cmd, size_t len) |
| { |
| char * volatile cp; |
| newenv(E_ERRH); |
| |
| if (!kshsetjmp(e->jbuf)) { |
| char *wds = alloc(len + 3, ATEMP); |
| |
| wds[0] = FUNASUB; |
| memcpy(wds + 1, cmd, len); |
| wds[len + 1] = '\0'; |
| wds[len + 2] = EOS; |
| |
| cp = evalstr(wds, DOSCALAR); |
| afree(wds, ATEMP); |
| strdupx(cp, cp, AEDIT); |
| } else |
| /* command cannot be parsed */ |
| cp = NULL; |
| quitenv(NULL); |
| return (cp); |
| } |
| |
| static int |
| x_operate_region(char *(*helper)(const char *, size_t)) |
| { |
| char *rgbeg, *rgend, *cp; |
| size_t newlen; |
| /* only for LINE overflow checking */ |
| size_t restlen; |
| |
| if (xmp == NULL) { |
| rgbeg = xbuf; |
| rgend = xep; |
| } else if (xmp < xcp) { |
| rgbeg = xmp; |
| rgend = xcp; |
| } else { |
| rgbeg = xcp; |
| rgend = xmp; |
| } |
| |
| x_e_putc2('\r'); |
| x_clrtoeol(' ', false); |
| x_flush(); |
| x_mode(false); |
| cp = helper(rgbeg, rgend - rgbeg); |
| x_mode(true); |
| |
| if (cp == NULL) { |
| /* error return from helper */ |
| x_eval_region_err: |
| x_e_putc2(KSH_BEL); |
| x_redraw('\r'); |
| return (KSTD); |
| } |
| |
| newlen = strlen(cp); |
| restlen = xep - rgend; |
| /* check for LINE overflow, until this is dynamically allocated */ |
| if (rgbeg + newlen + restlen >= xend) |
| goto x_eval_region_err; |
| |
| xmp = rgbeg; |
| xcp = rgbeg + newlen; |
| xep = xcp + restlen; |
| memmove(xcp, rgend, restlen + /* NUL */ 1); |
| memcpy(xmp, cp, newlen); |
| afree(cp, AEDIT); |
| x_adjust(); |
| x_modified(); |
| return (KSTD); |
| } |
| |
| static int |
| x_eval_region(int c MKSH_A_UNUSED) |
| { |
| return (x_operate_region(x_eval_region_helper)); |
| } |
| |
| static char * |
| x_quote_region_helper(const char *cmd, size_t len) |
| { |
| char *s; |
| size_t newlen; |
| struct shf shf; |
| |
| strndupx(s, cmd, len, ATEMP); |
| newlen = len < 256 ? 256 : 4096; |
| shf_sopen(alloc(newlen, AEDIT), newlen, SHF_WR | SHF_DYNAMIC, &shf); |
| shf.areap = AEDIT; |
| shf.flags |= SHF_ALLOCB; |
| print_value_quoted(&shf, s); |
| afree(s, ATEMP); |
| return (shf_sclose(&shf)); |
| } |
| |
| static int |
| x_quote_region(int c MKSH_A_UNUSED) |
| { |
| return (x_operate_region(x_quote_region_helper)); |
| } |
| #endif /* !MKSH_SMALL */ |
| #endif /* !MKSH_NO_CMDLINE_EDITING */ |