-/* $OpenBSD: edit.c,v 1.34 2010/05/20 01:13:07 fgsch Exp $ */
-/* $OpenBSD: edit.h,v 1.8 2005/03/28 21:28:22 deraadt Exp $ */
-/* $OpenBSD: emacs.c,v 1.42 2009/06/02 06:47:47 halex Exp $ */
-/* $OpenBSD: vi.c,v 1.26 2009/06/29 22:50:19 martynas Exp $ */
+/* $OpenBSD: edit.c,v 1.40 2015/03/12 10:20:30 sthen Exp $ */
+/* $OpenBSD: edit.h,v 1.9 2011/05/30 17:14:35 martynas Exp $ */
+/* $OpenBSD: emacs.c,v 1.50 2015/03/25 12:10:52 jca Exp $ */
+/* $OpenBSD: vi.c,v 1.28 2013/12/18 16:45:46 deraadt Exp $ */
/*-
- * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010
+ * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+ * 2011, 2012, 2013, 2014
* Thorsten Glaser <tg@mirbsd.org>
*
* Provided that these terms and disclaimer and all copyright notices
#include "sh.h"
-__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.196 2010/07/25 11:35:40 tg Exp $");
+#ifndef MKSH_NO_CMDLINE_EDITING
+
+__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.276.2.5 2015/04/12 22:32:22 tg Exp $");
/*
* in later versions we might use libtermcap for this, but since external
static X_chars edchars;
-/* x_fc_glob() flags */
+/* 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_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 holdbuf[LINE]; /* place to hold last edit buffer */
+static char *holdbufp; /* place to hold last edit buffer */
static int x_getc(void);
static void x_putcf(int);
-static bool x_mode(bool);
-static int x_do_comment(char *, int, int *);
-static void x_print_expansions(int, char *const *, bool);
-static int x_cf_glob(int, const char *, int, int, int *, int *, char ***,
- bool *);
-static int x_longest_prefix(int, char *const *);
+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 int 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 *, size_t);
-static void x_init_emacs(void);
-static void x_init_prompt(void);
+static int x_emacs(char *);
+static void x_init_prompt(bool);
#if !MKSH_S_NOVI
-static int x_vi(char *, size_t);
+static int x_vi(char *);
#endif
#define x_flush() shf_flush(shl_out)
-#ifdef MKSH_SMALL
+#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 *aa, const void *bb);
-static char *add_glob(const char *, int)
- MKSH_A_NONNULL((nonnull (1)))
- MKSH_A_BOUNDED(string, 1, 2);
+static int path_order_cmp(const void *, const void *);
static void glob_table(const char *, XPtrV *, struct table *);
-static void glob_path(int flags, const char *, XPtrV *, const char *);
-static int x_file_glob(int, const char *, int, char ***)
- MKSH_A_NONNULL((nonnull (2)))
- MKSH_A_BOUNDED(string, 2, 3);
-static int x_command_glob(int, const char *, int, char ***)
- MKSH_A_NONNULL((nonnull (2)))
- MKSH_A_BOUNDED(string, 2, 3);
+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 +++ */
-/* Called from main */
-void
-x_init(void)
-{
- /* set to -2 to force initial binding */
- edchars.erase = edchars.kill = edchars.intr = edchars.quit =
- edchars.eof = -2;
- /* default value for deficient systems */
- edchars.werase = 027; /* ^W */
- x_init_emacs();
-}
-
/*
* read an edited command line
*/
int
-x_read(char *buf, size_t len)
+x_read(char *buf)
{
int i;
x_mode(true);
modified = 1;
if (Flag(FEMACS) || Flag(FGMACS))
- i = x_emacs(buf, len);
+ i = x_emacs(buf);
#if !MKSH_S_NOVI
else if (Flag(FVI))
- i = x_vi(buf, len);
+ i = x_vi(buf);
#endif
else
- i = -1; /* internal error */
+ /* internal error */
+ i = -1;
editmode = 0;
x_mode(false);
return (i);
x_getc(void)
{
char c;
- int n;
+ ssize_t n;
while ((n = blocking_read(STDIN_FILENO, &c, 1)) < 0 && errno == EINTR)
if (trap) {
if (x_cols != xx_cols && editmode == 1) {
/* redraw line in Emacs mode */
xx_cols = x_cols;
+ x_init_prompt(false);
x_e_rebuildline(MKSH_CLRTOEOL_STRING);
}
}
* Misc common code for vi/emacs *
*********************************/
-/* Handle the commenting/uncommenting of a line.
+/*-
+ * Handle the commenting/uncommenting of a line.
* Returns:
* 1 if a carriage return is indicated (comment added)
* 0 if no return (comment removed)
* moved to the start of the line after (un)commenting.
*/
static int
-x_do_comment(char *buf, int bsize, int *lenp)
+x_do_comment(char *buf, ssize_t bsize, ssize_t *lenp)
{
- int i, j, len = *lenp;
+ ssize_t i, j, len = *lenp;
if (len == 0)
- return (1); /* somewhat arbitrary - it's what AT&T ksh does */
+ /* somewhat arbitrary - it's what AT&T ksh does */
+ return (1);
/* Already commented? */
if (buf[0] == '#') {
{
bool use_copy = false;
int prefix_len;
- XPtrV l = { NULL, NULL, NULL };
+ XPtrV l = { NULL, 0, 0 };
- /* Check if all matches are in the same directory (in this
+ /*
+ * Check if all matches are in the same directory (in this
* case, we want to omit the directory name)
*/
if (!is_command &&
pr_list(use_copy ? (char **)XPptrv(l) : words);
if (use_copy)
- XPfree(l); /* not x_free_words() */
+ /* 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 (ch) {
+ case QCHAR:
+ case '$':
+ case '*':
+ case '?':
+ case '[':
+ case '\\':
+ case '`':
+ *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 = 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 */
+ cp = shf_smprintf("%s/%s", dp, cp);
+ 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:
- * - appends * to (copy of) str if no globbing chars found
* - 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 flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
+x_file_glob(int *flagsp, char *toglob, char ***wordsp)
{
- char *toglob, **words;
- int nwords, i, idx;
- bool escaping;
+ char **words, *cp;
+ int nwords;
XPtrV w;
struct source *s, *sold;
- if (slen < 0)
- return (0);
-
- toglob = add_glob(str, slen);
-
/* remove all escaping backward slashes */
- escaping = false;
- for (i = 0, idx = 0; toglob[i]; i++) {
- if (toglob[i] == '\\' && !escaping) {
- escaping = true;
- continue;
- }
- /* specially escape escaped [ or $ or ` for globbing */
- if (escaping && (toglob[i] == '[' ||
- toglob[i] == '$' || toglob[i] == '`'))
- toglob[idx++] = QCHAR;
-
- toglob[idx] = toglob[i];
- idx++;
- if (escaping)
- escaping = false;
- }
- toglob[idx] = '\0';
+ x_glob_hlp_add_qchar(toglob);
/*
* Convert "foo*" (toglob) to an array of strings (words)
source = s;
if (yylex(ONEWORD | LQCHAR) != LWORD) {
source = sold;
- internal_warningf("fileglob: substitute error");
+ internal_warningf("%s: %s", "fileglob", "bad substitution");
return (0);
}
source = sold;
+ afree(s, ATEMP);
XPinit(w, 32);
- expand(yylval.cp, &w, DOGLOB | DOTILDE | DOMARKDIRS);
+ 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);
if (nwords == 1) {
struct stat statb;
- /* Check if globbing failed (returned glob pattern),
- * but be careful (E.g. toglob == "ab*" when the file
+ /* 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
nwords = 0;
}
}
- afree(toglob, ATEMP);
if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
x_free_words(nwords, words);
}
static int
-x_command_glob(int flags, const char *str, int slen, char ***wordsp)
+x_command_glob(int flags, char *toglob, char ***wordsp)
{
- char *toglob, *pat, *fpath;
- int nwords;
+ char *pat, *fpath;
+ size_t nwords;
XPtrV w;
struct block *l;
- if (slen < 0)
- return (0);
-
- toglob = add_glob(str, slen);
-
/* Convert "foo*" (toglob) to a pattern for future use */
pat = evalstr(toglob, DOPAT | DOTILDE);
- afree(toglob, ATEMP);
XPinit(w, 32);
/* Sort by basename, then path order */
struct path_order_info *info, *last_info = NULL;
char **words = (char **)XPptrv(w);
- int i, path_order = 0;
+ size_t i, path_order = 0;
info = (struct path_order_info *)
- alloc(nwords * sizeof(struct path_order_info), ATEMP);
+ 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);
} else {
/* Sort and remove duplicate entries */
char **words = (char **)XPptrv(w);
- int i, j;
+ size_t i, j;
qsort(words, nwords, sizeof(void *), xstrcmp);
for (i = j = 0; i < nwords - 1; i++) {
afree(words[i], ATEMP);
}
words[j++] = words[i];
- nwords = j;
- w.cur = (void **)&words[j];
+ w.len = nwords = j;
}
XPput(w, NULL);
/* The case where pos == buflen happens to take care of itself... */
start = pos;
- /* Keep going backwards to start of word (has effect of allowing
+ /*
+ * 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])) ||
p--;
iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
if (iscmd) {
- /* If command has a /, path, etc. is not searched;
+ /*
+ * If command has a /, path, etc. is not searched;
* only current directory is searched which is just
* like file globbing.
*/
}
static int
-x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp,
- int *endp, char ***wordsp, bool *is_commandp)
+x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
+ int *endp, char ***wordsp)
{
- int len, nwords;
+ int len, nwords = 0;
char **words = NULL;
bool is_command;
len = x_locate_word(buf, buflen, pos, startp, &is_command);
- if (!(flags & XCF_COMMAND))
+ if (!((*flagsp) & XCF_COMMAND))
is_command = false;
- /* Don't do command globing on zero length strings - it takes too
+ /*
+ * 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);
- nwords = is_command ?
- x_command_glob(flags, buf + *startp, len, &words) :
- x_file_glob(flags, buf + *startp, len, &words);
- if (nwords == 0) {
- *wordsp = NULL;
- return (0);
- }
- if (is_commandp)
- *is_commandp = is_command;
- *wordsp = words;
- *endp = *startp + len;
+ if (len >= 0) {
+ char *toglob, *s;
- return (nwords);
-}
+ /*
+ * Given a string, copy it and possibly add a '*' to the end.
+ */
-/* Given a string, copy it and possibly add a '*' to the end.
- * The new string is returned.
- */
-static char *
-add_glob(const char *str, int slen)
-{
- char *toglob, *s;
- bool saw_slash = false;
+ strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP);
+ toglob[len] = '\0';
- if (slen < 0)
- return (NULL);
+ /*
+ * 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 (*s == '?' || *s == '*' || *s == '[' ||
+ *s == '$' ||
+ /* ?() *() +() @() !() but two already checked */
+ (s[1] == '(' /*)*/ &&
+ (*s == '+' || *s == '@' || *s == '!'))) {
+ /*
+ * just expand based on the extglob
+ * or parameter
+ */
+ goto dont_add_glob;
+ }
+ }
- /* for clang's static analyser, the nonnull attribute isn't enough */
- mkssert(str != NULL);
+ if (*toglob == '~' && !vstrchr(toglob, '/')) {
+ /* neither for '~foo' (but '~foo/bar') */
+ *flagsp |= XCF_IS_NOSPACE;
+ goto dont_add_glob;
+ }
- strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */
- toglob[slen] = '\0';
+ /* append a glob */
+ toglob[len] = '*';
+ toglob[len + 1] = '\0';
+ dont_add_glob:
+ /*
+ * Expand (glob) it now.
+ */
- /*
- * If the pathname contains a wildcard (an unquoted '*',
- * '?', or '[') or parameter expansion ('$'), or a ~username
- * with no trailing slash, then it is globbed based on that
- * value (i.e., without the appended '*').
- */
- for (s = toglob; *s; s++) {
- if (*s == '\\' && s[1])
- s++;
- else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' ||
- (s[1] == '(' /*)*/ && /* *s in '*','?' already checked */
- (*s == '+' || *s == '@' || *s == '!')))
- break;
- else if (*s == '/')
- saw_slash = true;
+ nwords = is_command ?
+ x_command_glob(*flagsp, toglob, &words) :
+ x_file_glob(flagsp, toglob, &words);
+ afree(toglob, ATEMP);
}
- if (!*s && (*toglob != '~' || saw_slash)) {
- toglob[slen] = '*';
- toglob[slen + 1] = '\0';
+ if (nwords == 0) {
+ *wordsp = NULL;
+ return (0);
}
- return (toglob);
+ if (is_command)
+ *flagsp |= XCF_IS_COMMAND;
+ *wordsp = words;
+ *endp = *startp + len;
+
+ return (nwords);
}
/*
* Find longest common prefix
*/
-static int
+static size_t
x_longest_prefix(int nwords, char * const * words)
{
- int i, j, prefix_len;
+ int i;
+ size_t j, prefix_len;
char *p;
if (nwords <= 0)
prefix_len = j;
break;
}
+ /* false for nwords==1 as 0 = words[0][prefix_len] then */
+ if (UTFMODE && prefix_len && (words[0][prefix_len] & 0xC0) == 0x80)
+ while (prefix_len && (words[0][prefix_len] & 0xC0) != 0xC0)
+ --prefix_len;
return (prefix_len);
}
afree(words, ATEMP);
}
-/* Return the offset of the basename of string s (which ends at se - need not
+/*-
+ * 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
static void
glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
{
- const char *sp, *p;
+ const char *sp = lpath, *p;
char *xp, **words;
- int staterr, pathlen, patlen, oldsize, newsize, i, j;
+ size_t pathlen, patlen, oldsize, newsize, i, j;
XString xs;
- patlen = strlen(pat) + 1;
- sp = lpath;
+ patlen = strlen(pat);
+ checkoktoadd(patlen, 129 + X_EXTRA);
+ ++patlen;
Xinit(xs, xp, patlen + 128, ATEMP);
while (sp) {
xp = Xstring(xs, xp);
p = sp + strlen(sp);
pathlen = p - sp;
if (pathlen) {
- /* Copy sp into xp, stuffing any MAGIC characters
+ /*
+ * Copy sp into xp, stuffing any MAGIC characters
* on the way
*/
const char *s = sp;
memcpy(xp, pat, patlen);
oldsize = XPsize(*wp);
- glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */
+ /* 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++) {
- staterr = 0;
- if ((search_access(words[i], X_OK, &staterr) >= 0) ||
- (staterr == EISDIR)) {
+ if (ksh_access(words[i], X_OK) == 0) {
words[j] = words[i];
if (!(flags & XCF_FULLPATH))
memmove(words[j], words[j] + pathlen,
} else
afree(words[i], ATEMP);
}
- wp->cur = (void **)&words[j];
+ wp->len = j;
if (!*sp++)
break;
#define X_NTABS 3 /* normal, meta1, meta2 */
#define X_TABSZ 256 /* size of keydef tables etc */
-/* Arguments for do_complete()
+/*-
+ * Arguments for do_complete()
* 0 = enumerate M-= complete as much as possible and then list
* 1 = complete M-Esc
* 2 = list M-?
static char *xep; /* current end */
static char *xbp; /* start of visible portion of input buffer */
static char *xlp; /* last char visible on screen */
-static int x_adj_ok;
+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;
+static int x_adj_done; /* is incremented by x_adjust() */
-static int x_col;
static int x_displen;
static int x_arg; /* general purpose arg */
-static int x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */
+static bool x_arg_defaulted; /* x_arg not explicitly set; defaulted to 1 */
-static int xlp_valid;
+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 *xmp; /* mark pointer */
static unsigned char x_last_command;
static unsigned char (*x_tab)[X_TABSZ]; /* key definition */
static int killsp, killtp;
static int x_curprefix;
#ifndef MKSH_SMALL
-static char *macroptr = NULL; /* bind key macro active? */
+static char *macroptr; /* bind key macro active? */
#endif
#if !MKSH_S_NOVI
-static int cur_col; /* current column on line */
-static int pwidth; /* width of prompt */
-static int prompt_trunc; /* how much of prompt to truncate */
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 lastref; /* argument to last refresh() */
static int holdlen; /* length of holdbuf */
#endif
-static int prompt_redraw; /* 0 if newline forced after prompt */
+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(int, int);
-static int x_bword(void);
-static int x_fword(int);
+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_size_str(char *);
static int x_size2(char *, char **);
static void x_zots(char *);
-static void x_zotc2(int);
static void x_zotc3(char **);
static void x_load_hist(char **);
static int x_search(char *, int, int);
static int x_match(char *, char *);
static void x_redraw(int);
static void x_push(int);
-static char *x_mapin(const char *, Area *)
- MKSH_A_NONNULL((nonnull (1)));
+static char *x_mapin(const char *, Area *);
static char *x_mapout(int);
static void x_mapout2(int, char **);
static void x_print(int, int);
#endif
static char *x_lastcp(void);
static void do_complete(int, Comp_type);
+static size_t x_nb2nc(size_t) MKSH_A_PURE;
static int unget_char = -1;
static const struct x_ftab x_ftab[] = {
#define EMACSFN_ITEMS
#include "emacsfn.h"
- { 0, NULL, 0 }
};
static struct x_defbindings const x_defbindings[] = {
{ XFUNC_fold_capitalise, 1, 'C' },
{ XFUNC_fold_capitalise, 1, 'c' },
#endif
- /* These for ansi arrow keys: arguablely shouldn't be here by
+ /*
+ * These for ANSI arrow keys: arguablely shouldn't be here by
* default, but its simpler/faster/smaller than using termcap
* entries.
*/
#endif
};
-#ifdef MKSH_SMALL
-static void x_modified(void);
+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)
{
modified = 1;
}
}
+
+#ifdef MKSH_SMALL
#define XFUNC_VALUE(f) (f)
#else
-#define x_modified() do { \
- if (!modified) { \
- x_histp = histptr + 1; \
- modified = 1; \
- } \
-} while (/* CONSTCOND */ 0)
#define XFUNC_VALUE(f) (f & 0x7F)
#endif
}
static void
-x_init_prompt(void)
+x_init_prompt(bool doprint)
{
- x_col = promptlen(prompt);
- x_adj_ok = 1;
- prompt_redraw = 1;
- if (x_col >= xx_cols)
- x_col %= xx_cols;
- x_displen = xx_cols - 2 - x_col;
- x_adj_done = 0;
-
- pprompt(prompt, 0);
- if (x_displen < 1) {
- x_col = 0;
- x_displen = xx_cols - 2;
- x_e_putc2('\n');
- prompt_redraw = 0;
+ 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, size_t len)
+x_emacs(char *buf)
{
int c, i;
unsigned char f;
- xbp = xbuf = buf; xend = buf + len;
+ xbp = xbuf = buf;
+ xend = buf + LINE;
xlp = xcp = xep = buf;
*xcp = 0;
xlp_valid = true;
x_histp = histptr + 1;
x_last_command = XFUNC_error;
- xx_cols = x_cols;
- x_init_prompt();
+ 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)
+ if (histptr - history >= off) {
x_load_hist(histptr - off);
+ x_histncp = x_histp;
+ }
x_nextcmd = -1;
}
editmode = 1;
- while (1) {
+ while (/* CONSTCOND */ 1) {
x_flush();
if ((c = x_e_getc()) < 0)
return (0);
if (!(x_ftab[f].xf_flags & XF_PREFIX) &&
x_last_command != XFUNC_set_arg) {
x_arg = 1;
- x_arg_defaulted = 1;
+ x_arg_defaulted = true;
}
i = c | (x_curprefix << 8);
x_curprefix = 0;
case KEOL:
i = xep - xbuf;
return (i);
- case KINTR: /* special case for interrupt */
+ case KINTR:
+ /* special case for interrupt */
trapsig(SIGINT);
x_mode(false);
unwind(LSHELL);
static int
x_insert(int c)
{
- static int left = 0, pos, save_arg;
+ static int left, pos, save_arg;
static char str[4];
/*
*/
xlp_valid = false;
x_lastcp();
- x_adj_ok = (xcp >= xlp);
+ x_adj_ok = tobool(xcp >= xlp);
x_zots(cp);
- if (adj == x_adj_done) { /* has x_adjust() been called? */
+ /* has x_adjust() been called? */
+ if (adj == x_adj_done) {
/* no */
cp = xlp;
while (cp > xcp)
}
if (xlp == xep - 1)
x_redraw(xx_cols);
- x_adj_ok = 1;
+ x_adj_ok = true;
return (0);
}
static int
x_del_back(int c MKSH_A_UNUSED)
{
- int i = 0;
+ ssize_t i = 0;
if (xcp == xbuf) {
x_e_putc2(7);
x_del_char(int c MKSH_A_UNUSED)
{
char *cp, *cp2;
- int i = 0;
+ size_t i = 0;
cp = xcp;
- while (i < x_arg) {
+ while (i < (size_t)x_arg) {
utf_ptradjx(cp, cp2);
if (cp2 > xep)
break;
/* Delete nc chars to the right of the cursor (including cursor position) */
static void
-x_delete(int nc, int push)
+x_delete(size_t nc, bool push)
{
- int i, nb, nw;
+ size_t i, nb, nw;
char *cp;
if (nc == 0)
x_push(nb);
xep -= nb;
- memmove(xcp, xcp + nb, xep - xcp + 1); /* Copies the NUL */
- x_adj_ok = 0; /* don't redraw */
+ /* 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'.
+ * 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) {
x_e_putc2('\b');
}
/*x_goto(xcp);*/
- x_adj_ok = 1;
+ x_adj_ok = true;
xlp_valid = false;
cp = x_lastcp();
while (cp > xcp)
static int
x_mv_fword(int c MKSH_A_UNUSED)
{
- x_fword(1);
+ x_fword(true);
return (KSTD);
}
static int
x_del_fword(int c MKSH_A_UNUSED)
{
- x_delete(x_fword(0), true);
+ x_delete(x_fword(false), true);
return (KSTD);
}
-static int
+static size_t
x_bword(void)
{
- int nc = 0, nb = 0;
+ size_t nb = 0;
char *cp = xcp;
if (cp == xbuf) {
}
}
x_goto(cp);
- for (cp = xcp; cp < (xcp + nb); ++nc)
- cp += utf_ptradj(cp);
- return (nc);
+ return (x_nb2nc(nb));
}
-static int
-x_fword(int move)
+static size_t
+x_fword(bool move)
{
- int nc = 0;
- char *cp = xcp, *cp2;
+ size_t nc;
+ char *cp = xcp;
if (cp == xep) {
x_e_putc2(7);
while (cp != xep && !is_mfs(*cp))
cp++;
}
- for (cp2 = xcp; cp2 < cp; ++nc)
- cp2 += utf_ptradj(cp2);
+ nc = x_nb2nc(cp - xcp);
if (move)
x_goto(cp);
return (nc);
static void
x_goto(char *cp)
{
- if (UTFMODE)
- while ((cp > xbuf) && ((*cp & 0xC0) == 0x80))
- --cp;
+ cp = cp >= xep ? xep : x_bs0(cp, xbuf);
if (cp < xbp || cp >= utf_skipcols(xbp, x_displen)) {
/* we are heading off screen */
xcp = cp;
x_adjust();
- } else if (cp < xcp) { /* move back */
+ } else if (cp < xcp) {
+ /* move back */
while (cp < xcp)
x_bs3(&xcp);
- } else if (cp > xcp) { /* move forward */
+ } 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)) &&
+ ((*(unsigned char *)cp & 0xC0) == 0x80))
+ --cp;
+ return (cp);
+}
+
static void
x_bs3(char **p)
{
int i;
- (*p)--;
- if (UTFMODE)
- while (((unsigned char)**p & 0xC0) == 0x80)
- (*p)--;
-
+ *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)
{
- int c = *(unsigned char *)cp;
+ uint8_t c = *(unsigned char *)cp;
if (UTFMODE && (c > 0x7F))
return (utf_widthadj(cp, (const char **)dcp));
if (dcp)
*dcp = cp + 1;
if (c == '\t')
- return (4); /* Kludge, tabs are always four spaces. */
- if (c < ' ' || c == 0x7f)
- return (2); /* control unsigned char */
+ /* Kludge, tabs are always four spaces. */
+ return (4);
+ if (ISCTRL(c) && /* but not C1 */ c < 0x80)
+ /* control unsigned char */
+ return (2);
return (1);
}
int adj = x_adj_done;
x_lastcp();
- while (*str && str < xlp && adj == x_adj_done)
+ while (*str && str < xlp && x_col < xx_cols && adj == x_adj_done)
x_zotc3(&str);
}
static void
-x_zotc2(int c)
-{
- if (c == '\t') {
- /* Kludge, tabs are always four spaces. */
- x_e_puts(" ");
- } else if (c < ' ' || c == 0x7f) {
- x_e_putc2('^');
- x_e_putc2(UNCTRL(c));
- } else
- x_e_putc2(c);
-}
-
-static void
x_zotc3(char **cp)
{
unsigned char c = **(unsigned char **)cp;
/* Kludge, tabs are always four spaces. */
x_e_puts(" ");
(*cp)++;
- } else if (c < ' ' || c == 0x7f) {
+ } else if (ISCTRL(c) && /* but not C1 */ c < 0x80) {
x_e_putc2('^');
x_e_putc2(UNCTRL(c));
(*cp)++;
static int
x_end_of_text(int c MKSH_A_UNUSED)
{
- x_zotc2(edchars.eof);
+ char tmp = edchars.eof;
+ char *cp = &tmp;
+
+ x_zotc3(&cp);
x_putc('\r');
x_putc('\n');
x_flush();
return (KSTD);
}
-/* Goto a particular history number obtained from argument.
+/*
+ * 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.
*/
char *sp = NULL;
if (hp == histptr + 1) {
- sp = holdbuf;
+ sp = holdbufp;
modified = 0;
} else if (hp < history || hp > histptr) {
x_e_putc2(7);
x_histp = hp;
oldsize = x_size_str(xbuf);
if (modified)
- strlcpy(holdbuf, xbuf, sizeof(holdbuf));
+ strlcpy(holdbufp, xbuf, LINE);
strlcpy(xbuf, sp, xend - xbuf);
xbp = xbuf;
xep = xcp = xbuf + strlen(xbuf);
static int
x_nl_next_com(int c MKSH_A_UNUSED)
{
- x_nextcmd = source->line - (histptr - x_histp) + 1;
+ if (!x_histncp || (x_histp != x_histncp && x_histp != histptr + 1))
+ /* fresh start of ^O */
+ x_histncp = x_histp;
+ x_nextcmd = source->line - (histptr - x_histncp) + 1;
return (x_newline('\n'));
}
x_search_hist(int c)
{
int offset = -1; /* offset of match in xbuf, else -1 */
- char pat[256 + 1]; /* pattern buffer */
+ char pat[80 + 1]; /* pattern buffer */
char *p = pat;
unsigned char f;
*p = '\0';
- while (1) {
+ while (/* CONSTCOND */ 1) {
if (offset < 0) {
x_e_puts("\nI-search: ");
x_e_puts(pat);
} else if (f == XFUNC_insert) {
/* add char to pattern */
/* overflow check... */
- if (p >= &pat[sizeof(pat) - 1]) {
+ if ((size_t)(p - pat) >= sizeof(pat) - 1) {
x_e_putc2(7);
continue;
}
if (offset >= 0)
x_load_hist(histptr + 1);
break;
- } else { /* other command */
+ } else {
+ /* other command */
x_e_ungetc(c);
break;
}
return (x_e_rebuildline(MKSH_CLS_STRING));
}
-/* Redraw (part of) the line. If limit is < 0, the everything is redrawn
+/*
+ * Redraw (part of) the line. If limit is < 0, the everything is redrawn
* on a NEW line, otherwise limit is the screen column up to which needs
* redrawing.
*/
static void
x_redraw(int limit)
{
- int i, j, x_trunc = 0;
+ int i, j;
char *cp;
- x_adj_ok = 0;
+ x_adj_ok = false;
if (limit == -1)
x_e_putc2('\n');
else
x_e_putc2('\r');
x_flush();
if (xbp == xbuf) {
- x_col = promptlen(prompt);
- if (x_col >= xx_cols)
- x_trunc = (x_col / xx_cols) * xx_cols;
- if (prompt_redraw)
- pprompt(prompt, x_trunc);
- }
- if (x_col >= xx_cols)
- x_col %= xx_cols;
- x_displen = xx_cols - 2 - x_col;
- if (x_displen < 1) {
- x_col = 0;
- x_displen = xx_cols - 2;
+ if (prompt_trunc != -1)
+ pprompt(prompt, prompt_trunc);
+ x_col = pwidth;
}
+ x_displen = xx_cols - 2 - x_col;
xlp_valid = false;
- x_lastcp();
x_zots(xbp);
if (xbp != xbuf || xep > xlp)
limit = xx_cols;
if (limit >= 0) {
if (xep > xlp)
- i = 0; /* we fill the line */
+ /* we fill the line */
+ i = 0;
else {
char *cpl = xbp;
j++;
}
i = ' ';
- if (xep > xlp) { /* more off screen */
+ if (xep > xlp) {
+ /* more off screen */
if (xbp > xbuf)
i = '*';
else
cp = xlp;
while (cp > xcp)
x_bs3(&cp);
- x_adj_ok = 1;
+ x_adj_ok = true;
return;
}
{
unsigned int tmpa, tmpb;
- /* What transpose is meant to do seems to be up for debate. This
+ /*-
+ * 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
x_e_putc2(7);
return (KSTD);
}
- /* Gosling/Unipress emacs style: Swap two characters before the
- * cursor, do not change cursor position
+ /*
+ * 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) {
utf_wctomb(xcp, tmpb);
x_zotc3(&xcp);
} else {
- /* GNU emacs style: Swap the characters before and under the
+ /*
+ * GNU emacs style: Swap the characters before and under the
* cursor, move cursor position along one.
*/
if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
static int
x_kill(int c MKSH_A_UNUSED)
{
- int col = xcp - xbuf;
- int lastcol = xep - xbuf;
- int ndel;
+ size_t col = xcp - xbuf;
+ size_t lastcol = xep - xbuf;
+ size_t ndel, narg;
- if (x_arg_defaulted)
- x_arg = lastcol;
- else if (x_arg > lastcol)
- x_arg = lastcol;
- ndel = x_arg - col;
- if (ndel < 0) {
- x_goto(xbuf + x_arg);
- ndel = -ndel;
- }
- x_delete(ndel, true);
+ 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 int
x_meta_yank(int c MKSH_A_UNUSED)
{
- int len;
+ size_t len;
if ((x_last_command != XFUNC_yank && x_last_command != XFUNC_meta_yank) ||
killstack[killtp] == 0) {
}
len = strlen(killstack[killtp]);
x_goto(xcp - len);
- x_delete(len, false);
+ x_delete(x_nb2nc(len), false);
do {
if (killtp == 0)
killtp = KILLSIZE - 1;
switch ((c = x_e_getc())) {
case '~':
x_arg = 1;
- x_arg_defaulted = 1;
+ x_arg_defaulted = true;
return (x_mv_begin(0));
case ';':
/* "interesting" sequence detected */
/*-
* At this point, we have read the following octets so far:
- * - ESC+[ or ESC+O or Ctrl-X (Præfix 2)
+ * - ESC+[ or ESC+O or Ctrl-X (Prefix 2)
* - 1 (vt_hack)
* - ;
* - 5 (Ctrl key combiner) or 3 (Alt key combiner)
{
char *news, *op;
- /* for clang's static analyser, the nonnull attribute isn't enough */
- mkssert(cp != NULL);
-
strdupx(news, cp, ap);
op = news;
while (*cp) {
/* XXX -- should handle \^ escape? */
if (*cp == '^') {
cp++;
- if (*cp >= '?') /* includes '?'; ASCII */
+ /*XXX or ^^ escape? this is ugly. */
+ if (*cp >= '?')
+ /* includes '?'; ASCII */
*op++ = CTRL(*cp);
else {
*op++ = '^';
{
char *p = *buf;
- if (c < ' ' || c == 0x7f) {
+ if (ISCTRL(c)) {
*p++ = '^';
*p++ = UNCTRL(c);
} else
int
x_bind(const char *a1, const char *a2,
#ifndef MKSH_SMALL
- bool macro, /* bind -m */
+ /* bind -m */
+ bool macro,
#endif
- bool list) /* bind -l */
+ /* bind -l */
+ bool list)
{
unsigned char f;
int prefix, key;
#endif
if (x_tab == NULL) {
- bi_errorf("cannot bind, not a tty");
+ bi_errorf("can't bind, not a tty");
return (1);
}
/* List function names */
if (list) {
for (f = 0; f < NELEM(x_ftab); f++)
- if (x_ftab[f].xf_name &&
- !(x_ftab[f].xf_flags & XF_NOBIND))
+ if (!(x_ftab[f].xf_flags & XF_NOBIND))
shprintf("%s\n", x_ftab[f].xf_name);
return (0);
}
&& ((*m1 != '~') || *(m1 + 1))
#endif
) {
- char msg[256] = "key sequence '";
+ char msg[256];
const char *c = a1;
- m1 = msg + strlen(msg);
- while (*c && m1 < (msg + sizeof(msg) - 3))
+ m1 = msg;
+ while (*c && (size_t)(m1 - msg) < sizeof(msg) - 3)
x_mapout2(*c++, &m1);
- bi_errorf("%s' too long", msg);
+ bi_errorf("%s: %s", "too long key sequence", msg);
return (1);
}
#ifndef MKSH_SMALL
- hastilde = *m1;
+ hastilde = tobool(*m1);
#endif
afree(m2, ATEMP);
#endif
} else {
for (f = 0; f < NELEM(x_ftab); f++)
- if (x_ftab[f].xf_name &&
- strcmp(x_ftab[f].xf_name, a2) == 0)
+ if (!strcmp(x_ftab[f].xf_name, a2))
break;
if (f == NELEM(x_ftab) || x_ftab[f].xf_flags & XF_NOBIND) {
- bi_errorf("%s: no such function", a2);
+ bi_errorf("%s: %s %s", a2, "no such", Tfunction);
return (1);
}
}
}
static void
-x_init_emacs(void)
-{
- int i, j;
-
- ainit(AEDIT);
- x_nextcmd = -1;
-
- x_tab = alloc(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 = alloc(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
-}
-
-static void
bind_if_not_bound(int p, int k, int func)
{
- /* Has user already bound this key? If so, don't override it */
- if (x_bound[((p) * X_TABSZ + (k)) / 8] &
- (1 << (((p) * X_TABSZ + (k)) % 8)))
+ 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_kill_region(int c MKSH_A_UNUSED)
{
- int rsize;
+ size_t rsize;
char *xr;
if (xmp == NULL) {
xr = xmp;
}
x_goto(xr);
- x_delete(rsize, true);
+ x_delete(x_nb2nc(rsize), true);
xmp = xr;
return (KSTD);
}
{
char **words;
int start, end, nwords, i;
- bool is_command;
- nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf,
- &start, &end, &words, &is_command);
+ i = XCF_FILE;
+ nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf,
+ &start, &end, &words);
if (nwords == 0) {
x_e_putc2(7);
return (KSTD);
}
x_goto(xbuf + start);
- x_delete(end - start, false);
- for (i = 0; i < nwords;) {
+ 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(" ") < 0)) {
x_e_putc2(7);
return (KSTD);
}
-/* type == 0 for list, 1 for complete and 2 for complete-list */
static void
-do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */
+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 is_command, completed = false;
+ bool completed;
- nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf,
- &start, &end, &words, &is_command);
+ nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf,
+ &start, &end, &words);
/* no match */
if (nwords == 0) {
x_e_putc2(7);
return;
}
if (type == CT_LIST) {
- x_print_expansions(nwords, words, is_command);
+ 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);
- /* complete */
- if (nwords == 1 || nlen > olen) {
- x_goto(xbuf + start);
- x_delete(olen, false);
- x_escape(words[0], nlen, x_do_ins);
- x_adjust();
+ 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);
}
- /* add space if single non-dir match */
- if (nwords == 1 && words[0][nlen - 1] != '/') {
- x_ins(" ");
- completed = true;
+ 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);
}
- if (type == CT_COMPLIST && !completed) {
- x_print_expansions(nwords, words, is_command);
- completed = true;
+ x_adjust();
+ /*
+ * append a space if this is a single non-directory match
+ * and not a parameter or homedir substitution
+ */
+ if (nwords == 1 && words[0][nlen - 1] != '/' &&
+ !(flags & XCF_IS_NOSPACE)) {
+ x_ins(" ");
}
- if (completed)
- x_redraw(0);
x_free_words(nwords, words);
}
-/* NAME:
+/*-
+ * NAME:
* x_adjust - redraw the line adjusting starting point etc.
*
* DESCRIPTION:
static void
x_adjust(void)
{
- x_adj_done++; /* flag the fact that we were called. */
+ int col_left, n;
+
+ /* flag the fact that we were called */
+ x_adj_done++;
+
/*
- * we had a problem if the prompt length > xx_cols / 2
+ * 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 ((xbp = xcp - (x_displen / 2)) < xbuf)
- xbp = xbuf;
- if (UTFMODE)
- while ((xbp > xbuf) && ((*xbp & 0xC0) == 0x80))
- --xbp;
+ 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(xx_cols);
x_flush();
x_e_putc3(&s);
}
-/* NAME:
+/*-
+ * NAME:
* x_set_arg - set an arg value for next function
*
* DESCRIPTION:
static int
x_set_arg(int c)
{
- int n = 0, first = 1;
+ unsigned int n = 0;
+ bool first = true;
- c &= 255; /* strip command prefix */
- for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0)
+ /* strip command prefix */
+ c &= 255;
+ while (c >= 0 && ksh_isdigit(c)) {
n = n * 10 + (c - '0');
+ 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(7);
x_arg = 1;
- x_arg_defaulted = 1;
+ x_arg_defaulted = true;
} else {
x_e_ungetc(c);
x_arg = n;
- x_arg_defaulted = 0;
+ x_arg_defaulted = false;
}
return (KSTD);
}
x_comment(int c MKSH_A_UNUSED)
{
int oldsize = x_size_str(xbuf);
- int len = xep - xbuf;
+ ssize_t len = xep - xbuf;
int ret = x_do_comment(xbuf, xend - xbuf, &len);
if (ret < 0)
{
char *o_xbuf = xbuf, *o_xend = xend;
char *o_xbp = xbp, *o_xep = xep, *o_xcp = xcp;
- int vlen, lim = x_lastcp() - xbp;
+ int lim = x_lastcp() - xbp;
+ size_t vlen;
char *v;
strdupx(v, KSH_VERSION, ATEMP);
xbp = o_xbp;
xep = o_xep;
xcp = o_xcp;
- x_redraw(vlen);
+ x_redraw((int)vlen);
if (c < 0)
return (KSTD);
}
#endif
-/* NAME:
+/*-
+ * NAME:
* x_prev_histword - recover word from prev command
*
* DESCRIPTION:
{
char *rcp, *cp;
char **xhp;
- int m;
-
- if (xmp && modified > 1)
- x_kill_region(0);
- m = modified ? modified : 1;
+ 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(7);
return (KSTD);
}
x_set_mark(0);
- if (x_arg_defaulted) {
+ if ((x_arg = last_arg) == -1) {
+ /* x_arg_defaulted */
+
rcp = &cp[strlen(cp) - 1];
/*
* ignore white-space after the last word
rcp++;
x_ins(rcp);
} else {
+ /* not x_arg_defaulted */
char ch;
rcp = cp;
*/
while (*rcp && is_cfs(*rcp))
rcp++;
- while (x_arg-- > 1) {
+ while (x_arg-- > 0) {
while (*rcp && !is_cfs(*rcp))
rcp++;
while (*rcp && is_cfs(*rcp))
return (x_fold_case('L'));
}
-/* Lowercase N(1) words */
+/* Titlecase N(1) words */
static int
x_fold_capitalise(int c MKSH_A_UNUSED)
{
return (x_fold_case('C'));
}
-/* NAME:
+/*-
+ * 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 and M-c
- * to UPPER case, lower case or Capitalise words.
+ * 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
* a different action than for the rest.
*/
if (cp != xep) {
- if (c == 'L') /* lowercase */
+ if (c == 'L')
+ /* lowercase */
*cp = ksh_tolower(*cp);
- else /* uppercase, capitalise */
+ else
+ /* uppercase, capitalise */
*cp = ksh_toupper(*cp);
cp++;
}
* now for the rest of the word
*/
while (cp != xep && !is_mfs(*cp)) {
- if (c == 'U') /* uppercase */
+ if (c == 'U')
+ /* uppercase */
*cp = ksh_toupper(*cp);
- else /* lowercase, capitalise */
+ else
+ /* lowercase, capitalise */
*cp = ksh_tolower(*cp);
cp++;
}
}
#endif
-/* NAME:
+/*-
+ * NAME:
* x_lastcp - last visible char
*
* SYNOPSIS:
return (xlp);
}
-static bool
+static void
x_mode(bool onoff)
{
static bool x_cur_mode;
- bool prev;
if (x_cur_mode == onoff)
- return (x_cur_mode);
- prev = x_cur_mode;
+ return;
x_cur_mode = onoff;
if (onoff) {
- struct termios cb;
+ x_mkraw(tty_fd, NULL, false);
- cb = tty_state;
-
- edchars.erase = cb.c_cc[VERASE];
- edchars.kill = cb.c_cc[VKILL];
- edchars.intr = cb.c_cc[VINTR];
- edchars.quit = cb.c_cc[VQUIT];
- edchars.eof = cb.c_cc[VEOF];
+ edchars.erase = tty_state.c_cc[VERASE];
+ edchars.kill = tty_state.c_cc[VKILL];
+ edchars.intr = tty_state.c_cc[VINTR];
+ edchars.quit = tty_state.c_cc[VQUIT];
+ edchars.eof = tty_state.c_cc[VEOF];
#ifdef VWERASE
- edchars.werase = cb.c_cc[VWERASE];
-#endif
- cb.c_iflag &= ~(INLCR | ICRNL);
- cb.c_lflag &= ~(ISIG | ICANON | ECHO);
-#if defined(VLNEXT) && defined(_POSIX_VDISABLE)
- /* osf/1 processes lnext when ~icanon */
- cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
-#endif
- /* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */
-#if defined(VDISCARD) && defined(_POSIX_VDISABLE)
- cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
+ edchars.werase = tty_state.c_cc[VWERASE];
#endif
- cb.c_cc[VTIME] = 0;
- cb.c_cc[VMIN] = 1;
-
- tcsetattr(tty_fd, TCSADRAIN, &cb);
#ifdef _POSIX_VDISABLE
/* Convert unset values to internal 'unset' value */
if (edchars.quit >= 0)
bind_if_not_bound(0, edchars.quit, XFUNC_noop);
} else
- tcsetattr(tty_fd, TCSADRAIN, &tty_state);
-
- return (prev);
+ mksh_tcset(tty_fd, &tty_state);
}
#if !MKSH_S_NOVI
/* +++ vi editing mode +++ */
-#define Ctrl(c) (c&0x1f)
-
struct edstate {
char *cbuf;
- int winleft;
- int cbufsize;
- int linelen;
- int cursor;
+ ssize_t winleft;
+ ssize_t cbufsize;
+ ssize_t linelen;
+ ssize_t cursor;
};
static int vi_hook(int);
static int bracktype(int);
static void save_cbuf(void);
static void restore_cbuf(void);
-static int putbuf(const char *, int, int);
+static int putbuf(const char *, ssize_t, bool);
static void del_range(int, int);
-static int findch(int, int, 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 Backword(int);
static int Endword(int);
static int grabhist(int, int);
-static int grabsearch(int, int, int, char *);
-static void redraw_line(int);
+static int grabsearch(int, int, int, const char *);
+static void redraw_line(bool);
static void refresh(int);
static int outofwin(void);
static void rewindow(void);
-static int newcol(int, int);
+static int newcol(unsigned char, int);
static void display(char *, char *, int);
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);
-#define char_len(c) ((c) < ' ' || (c) == 0x7F ? 2 : 1)
+#define char_len(c) ((ISCTRL((unsigned char)c) && \
+ /* but not C1 */ (unsigned char)c < 0x80) ? 2 : 1)
static void x_vi_zotc(int);
static void vi_error(void);
static void vi_macro_reset(void);
static int x_vi_putbuf(const char *, size_t);
-#define C_ 0x1 /* a valid command that isn't a M_, E_, U_ */
-#define M_ 0x2 /* movement command (h, l, etc.) */
-#define E_ 0x4 /* extended command (c, d, y) */
-#define X_ 0x8 /* long command (@, f, F, t, T, etc.) */
-#define U_ 0x10 /* an UN-undoable command (that isn't a M_) */
-#define B_ 0x20 /* bad command (^@) */
-#define Z_ 0x40 /* repeat count defaults to 0 (not 1) */
-#define S_ 0x80 /* search (/, ?) */
-
-#define is_bad(c) (classify[(c)&0x7f]&B_)
-#define is_cmd(c) (classify[(c)&0x7f]&(M_|E_|C_|U_))
-#define is_move(c) (classify[(c)&0x7f]&M_)
-#define is_extend(c) (classify[(c)&0x7f]&E_)
-#define is_long(c) (classify[(c)&0x7f]&X_)
-#define is_undoable(c) (!(classify[(c)&0x7f]&U_))
-#define is_srch(c) (classify[(c)&0x7f]&S_)
-#define is_zerocount(c) (classify[(c)&0x7f]&Z_)
+#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[(c)&0x7f]&vB)
+#define is_cmd(c) (classify[(c)&0x7f]&(vM|vE|vC|vU))
+#define is_move(c) (classify[(c)&0x7f]&vM)
+#define is_extend(c) (classify[(c)&0x7f]&vE)
+#define is_long(c) (classify[(c)&0x7f]&vX)
+#define is_undoable(c) (!(classify[(c)&0x7f]&vU))
+#define is_srch(c) (classify[(c)&0x7f]&vS)
+#define is_zerocount(c) (classify[(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 */
- B_, 0, 0, 0, 0, C_|U_, C_|Z_, 0,
+ vB, 0, 0, 0, 0, vC|vU, vC|vZ, 0,
/* 1 ^H ^I ^J ^K ^L ^M ^N ^O */
- M_, C_|Z_, 0, 0, C_|U_, 0, C_, 0,
+ vM, vC|vZ, 0, 0, vC|vU, 0, vC, 0,
/* 2 ^P ^Q ^R ^S ^T ^U ^V ^W */
- C_, 0, C_|U_, 0, 0, 0, C_, 0,
+ vC, 0, vC|vU, 0, 0, 0, vC, 0,
/* 3 ^X ^Y ^Z ^[ ^\ ^] ^^ ^_ */
- C_, 0, 0, C_|Z_, 0, 0, 0, 0,
+ vC, 0, 0, vC|vZ, 0, 0, 0, 0,
/* 4 <space> ! " # $ % & ' */
- M_, 0, 0, C_, M_, M_, 0, 0,
+ vM, 0, 0, vC, vM, vM, 0, 0,
/* 5 ( ) * + , - . / */
- 0, 0, C_, C_, M_, C_, 0, C_|S_,
+ 0, 0, vC, vC, vM, vC, 0, vC|vS,
/* 6 0 1 2 3 4 5 6 7 */
- M_, 0, 0, 0, 0, 0, 0, 0,
+ vM, 0, 0, 0, 0, 0, 0, 0,
/* 7 8 9 : ; < = > ? */
- 0, 0, 0, M_, 0, C_, 0, C_|S_,
+ 0, 0, 0, vM, 0, vC, 0, vC|vS,
/* 8 @ A B C D E F G */
- C_|X_, C_, M_, C_, C_, M_, M_|X_, C_|U_|Z_,
+ vC|vX, vC, vM, vC, vC, vM, vM|vX, vC|vU|vZ,
/* 9 H I J K L M N O */
- 0, C_, 0, 0, 0, 0, C_|U_, 0,
+ 0, vC, 0, 0, 0, 0, vC|vU, vU,
/* A P Q R S T U V W */
- C_, 0, C_, C_, M_|X_, C_, 0, M_,
+ vC, 0, vC, vC, vM|vX, vC, 0, vM,
/* B X Y Z [ \ ] ^ _ */
- C_, C_|U_, 0, 0, C_|Z_, 0, M_, C_|Z_,
+ vC, vC|vU, 0, vU, vC|vZ, 0, vM, vC|vZ,
/* C ` a b c d e f g */
- 0, C_, M_, E_, E_, M_, M_|X_, C_|Z_,
+ 0, vC, vM, vE, vE, vM, vM|vX, vC|vZ,
/* D h i j k l m n o */
- M_, C_, C_|U_, C_|U_, M_, 0, C_|U_, 0,
+ vM, vC, vC|vU, vC|vU, vM, 0, vC|vU, 0,
/* E p q r s t u v w */
- C_, 0, X_, C_, M_|X_, C_|U_, C_|U_|Z_, M_,
+ vC, 0, vX, vC, vM|vX, vC|vU, vC|vU|vZ, vM,
/* F x y z { | } ~ ^? */
- C_, E_|U_, 0, 0, M_|Z_, 0, C_, 0
+ vC, vE|vU, 0, 0, vM|vZ, 0, vC, 0
};
#define MAXVICMD 3
#define VLIT 8 /* ^V */
#define VSEARCH 9 /* /, ? */
#define VVERSION 10 /* <ESC> ^V */
-
-static char undocbuf[LINE];
+#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 = { undocbuf, 0, LINE, 0, 0 };
+static struct edstate undobuf;
-static struct edstate *es; /* current editor state */
+static struct edstate *es; /* current editor state */
static struct edstate *undo;
-static char ibuf[LINE]; /* input buffer */
-static int first_insert; /* set when starting in insert mode */
+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[LINE]; /* yank buffer */
+static char *ybuf; /* yank buffer */
static int yanklen; /* length of yank buffer */
static int fsavecmd = ' '; /* last find command */
static int fsavech; /* character to find */
static int lastac; /* argcnt for lastcmd */
static int lastsearch = ' '; /* last search command */
static char srchpat[SRCHLEN]; /* last search pattern */
-static int insert; /* non-zero in insert mode */
+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.
+/*
+ * 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
struct macro_state {
unsigned char *p; /* current position in buf */
unsigned char *buf; /* pointer to macro(s) being expanded */
- int len; /* how much data in buffer */
+ size_t len; /* how much data in buffer */
};
static struct macro_state macro;
-enum expand_mode {
- NONE, EXPAND, COMPLETE, PRINT
-};
-static enum expand_mode expanded = NONE; /* last input was expanded */
+/* last input was expanded */
+static enum expand_mode {
+ NONE = 0, EXPAND, COMPLETE, PRINT
+} expanded;
static int
-x_vi(char *buf, size_t len)
+x_vi(char *buf)
{
int c;
ohnum = hnum = hlast = histnum(-1) + 1;
insert = INSERT;
saved_inslen = inslen;
- first_insert = 1;
+ 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;
es = &ebuf;
- es->cbuf = buf;
undo = &undobuf;
- undo->cbufsize = es->cbufsize = len > LINE ? LINE : len;
- es->linelen = undo->linelen = 0;
- es->cursor = undo->cursor = 0;
- es->winleft = undo->winleft = 0;
+ x_init_prompt(true);
+ x_col = pwidth;
- cur_col = promptlen(prompt);
- prompt_trunc = (cur_col / x_cols) * x_cols;
- cur_col -= prompt_trunc;
-
- pprompt(prompt, 0);
- if (cur_col > x_cols - 3 - MIN_EDIT_SPACE) {
- prompt_redraw = cur_col = 0;
- x_putc('\n');
- } else
- prompt_redraw = 1;
- pwidth = cur_col;
-
- if (!wbuf_len || wbuf_len != x_cols - 3) {
- wbuf_len = x_cols - 3;
- wbuf[0] = aresize(wbuf[0], wbuf_len, APERM);
- wbuf[1] = aresize(wbuf[1], wbuf_len, APERM);
+ 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);
}
- (void)memset(wbuf[0], ' ', wbuf_len);
- (void)memset(wbuf[1], ' ', wbuf_len);
winwidth = x_cols - pwidth - 3;
win = 0;
morec = ' ';
editmode = 2;
x_flush();
- while (1) {
+ while (/* CONSTCOND */ 1) {
if (macro.p) {
- c = *macro.p++;
+ c = (unsigned char)*macro.p++;
/* end of current macro? */
if (!c) {
/* more macros left to finish? */
x_putc('\n');
x_flush();
- if (c == -1 || (ssize_t)len <= es->linelen)
+ if (c == -1 || (ssize_t)LINE <= es->linelen)
return (-1);
if (es->cbuf != buf)
- memmove(buf, es->cbuf, es->linelen);
+ memcpy(buf, es->cbuf, es->linelen);
buf[es->linelen++] = '\n';
case VNORMAL:
if (insert != 0) {
- if (ch == Ctrl('v')) {
+ if (ch == CTRL('v')) {
state = VLIT;
ch = '^';
}
save_cbuf();
es->cursor = 0;
es->linelen = 0;
- if (ch == '/') {
- if (putbuf("/", 1, 0) != 0)
- return (-1);
- } else if (putbuf("?", 1, 0) != 0)
+ if (putbuf(ch == '/' ? "/" : "?", 1,
+ false) != 0)
return (-1);
refresh(0);
}
es->cursor = 0;
es->linelen = 0;
putbuf(KSH_VERSION,
- strlen(KSH_VERSION), 0);
+ strlen(KSH_VERSION), false);
refresh(0);
}
}
break;
case VXCH:
- if (ch == Ctrl('['))
+ if (ch == CTRL('['))
state = VNORMAL;
else {
curcmd[cmdlen++] = ch;
break;
case VSEARCH:
- if (ch == '\r' || ch == '\n' /*|| ch == Ctrl('[')*/ ) {
+ if (ch == '\r' || ch == '\n' /*|| ch == CTRL('[')*/ ) {
restore_cbuf();
/* Repeat last search? */
if (srchlen == 0) {
memcpy(srchpat, locpat, srchlen + 1);
}
state = VCMD;
- } else if (ch == edchars.erase || ch == Ctrl('h')) {
+ } else if (ch == edchars.erase || ch == CTRL('h')) {
if (srchlen != 0) {
srchlen--;
- es->linelen -= char_len((unsigned char)locpat[srchlen]);
+ es->linelen -= char_len(locpat[srchlen]);
es->cursor = es->linelen;
refresh(0);
return (0);
refresh(0);
return (0);
} else if (ch == edchars.werase) {
- int i, n = srchlen;
+ unsigned int i, n;
struct edstate new_es, *save_es;
- new_es.cursor = n;
+ new_es.cursor = srchlen;
new_es.cbuf = locpat;
save_es = es;
n = backword(1);
es = save_es;
- for (i = srchlen; --i >= n; )
- es->linelen -= char_len((unsigned char)locpat[i]);
- srchlen = n;
+ i = (unsigned)srchlen;
+ while (--i >= n)
+ es->linelen -= char_len(locpat[i]);
+ srchlen = (int)n;
es->cursor = es->linelen;
refresh(0);
return (0);
vi_error();
else {
locpat[srchlen++] = ch;
- if (ch < ' ' || ch == 0x7f) {
- if (es->linelen + 2 > es->cbufsize)
+ if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
+ if ((size_t)es->linelen + 2 >
+ (size_t)es->cbufsize)
vi_error();
es->cbuf[es->linelen++] = '^';
- es->cbuf[es->linelen++] = ch ^ '@';
+ es->cbuf[es->linelen++] = UNCTRL(ch);
} else {
if (es->linelen >= es->cbufsize)
vi_error();
return (0);
}
break;
+
+ case VPREFIX2:
+ state = VFAIL;
+ switch (ch) {
+ case 'A':
+ /* the cursor may not be at the BOL */
+ if (!es->cursor)
+ break;
+ /* nor further in the line than we can search for */
+ if ((size_t)es->cursor >= sizeof(srchpat) - 1)
+ es->cursor = sizeof(srchpat) - 2;
+ /* anchor the search pattern */
+ srchpat[0] = '^';
+ /* take the current line up to the cursor */
+ memmove(srchpat + 1, es->cbuf, es->cursor);
+ srchpat[es->cursor + 1] = '\0';
+ /* set a magic flag */
+ argc1 = 2 + (int)es->cursor;
+ /* and emulate a backwards history search */
+ lastsearch = '/';
+ *curcmd = 'n';
+ goto pseudo_VCMD;
+ }
+ break;
}
switch (state) {
case VCMD:
+ pseudo_VCMD:
state = VNORMAL;
switch (vi_cmd(argc1, curcmd)) {
case -1:
return (VXCH);
else if (ch == '.')
return (VREDO);
- else if (ch == Ctrl('v'))
+ else if (ch == CTRL('v'))
return (VVERSION);
else if (is_cmd(ch))
return (VCMD);
{
int tcursor;
- if (ch == edchars.erase || ch == Ctrl('h')) {
+ if (ch == edchars.erase || ch == CTRL('h')) {
if (insert == REPLACE) {
if (es->cursor == undo->cursor) {
vi_error();
expanded = NONE;
return (0);
}
- /* If any chars are entered before escape, trash the saved insert
+ /*
+ * 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('['))
+ if (first_insert && ch != CTRL('['))
saved_inslen = 0;
switch (ch) {
case '\0':
case '\n':
return (1);
- case Ctrl('['):
+ case CTRL('['):
expanded = NONE;
if (first_insert) {
- first_insert = 0;
+ first_insert = false;
if (inslen == 0) {
inslen = saved_inslen;
return (redo_insert(0));
return (redo_insert(lastac - 1));
/* { Begin nonstandard vi commands */
- case Ctrl('x'):
+ case CTRL('x'):
expand_word(0);
break;
- case Ctrl('f'):
+ case CTRL('f'):
complete_word(0, 0);
break;
- case Ctrl('e'):
+ case CTRL('e'):
print_expansions(es, 0);
break;
- case Ctrl('i'):
+ case CTRL('i'):
if (Flag(FVITABCOMPLETE)) {
complete_word(0, 0);
break;
}
switch (*cmd) {
- case Ctrl('l'):
- case Ctrl('r'):
- redraw_line(1);
+ case CTRL('l'):
+ case CTRL('r'):
+ redraw_line(true);
break;
case '@':
{
static char alias[] = "_\0";
struct tbl *ap;
- int olen, nlen;
+ size_t olen, nlen;
char *p, *nbuf;
/* lookup letter in alias list... */
nlen = strlen(ap->val.s) + 1;
olen = !macro.p ? 2 :
macro.len - (macro.p - macro.buf);
- nbuf = alloc(nlen + 1 + olen, APERM);
+ /*
+ * 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, APERM);
+ afree(macro.buf, AEDIT);
nlen += olen;
} else {
nbuf[nlen++] = '\0';
hnum = hlast;
if (es->linelen != 0)
es->cursor++;
- while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+ while (putbuf(ybuf, yanklen, false) == 0 &&
+ --argcnt > 0)
;
if (es->cursor != 0)
es->cursor--;
modified = 1;
hnum = hlast;
any = 0;
- while (putbuf(ybuf, yanklen, 0) == 0 && --argcnt > 0)
+ while (putbuf(ybuf, yanklen, false) == 0 &&
+ --argcnt > 0)
any = 1;
if (any && es->cursor != 0)
es->cursor--;
case 'j':
case '+':
- case Ctrl('n'):
+ case CTRL('n'):
if (grabhist(modified, hnum + argcnt) < 0)
return (-1);
else {
case 'k':
case '-':
- case Ctrl('p'):
+ case CTRL('p'):
if (grabhist(modified, hnum - argcnt) < 0)
return (-1);
else {
hnum = c2;
ohnum = hnum;
}
+ if (argcnt >= 2) {
+ /* flag from cursor-up command */
+ es->cursor = argcnt - 2;
+ return (0);
+ }
break;
case '_':
{
- int inspace;
+ bool inspace;
char *p, *sp;
if (histnum(-1) < 0)
sp = p;
} else {
sp = p;
- inspace = 0;
+ inspace = false;
while (*p) {
if (issp(*p))
- inspace = 1;
+ inspace = true;
else if (inspace) {
- inspace = 0;
+ inspace = false;
sp = p;
}
p++;
argcnt++;
p++;
}
- if (putbuf(" ", 1, 0) != 0)
- argcnt = -1;
- else if (putbuf(sp, argcnt, 0) != 0)
- argcnt = -1;
- if (argcnt < 0) {
+ if (putbuf(" ", 1, false) != 0 ||
+ putbuf(sp, argcnt, false) != 0) {
if (es->cursor != 0)
es->cursor--;
return (-1);
return (ret);
}
- case '=': /* AT&T ksh */
- case Ctrl('e'): /* Nonstandard vi/ksh */
+ /* AT&T ksh */
+ case '=':
+ /* Nonstandard vi/ksh */
+ case CTRL('e'):
print_expansions(es, 1);
break;
- case Ctrl('i'): /* Nonstandard vi/ksh */
+ /* Nonstandard vi/ksh */
+ case CTRL('i'):
if (!Flag(FVITABCOMPLETE))
return (-1);
complete_word(1, argcnt);
break;
- case Ctrl('['): /* some annoying AT&T kshs */
+ /* some annoying AT&T kshs */
+ case CTRL('['):
if (!Flag(FVIESCCOMPLETE))
return (-1);
- case '\\': /* AT&T ksh */
- case Ctrl('f'): /* Nonstandard vi/ksh */
+ /* AT&T ksh */
+ case '\\':
+ /* Nonstandard vi/ksh */
+ case CTRL('f'):
complete_word(1, argcnt);
break;
- case '*': /* AT&T ksh */
- case Ctrl('x'): /* Nonstandard vi/ksh */
+ /* AT&T ksh */
+ case '*':
+ /* Nonstandard vi/ksh */
+ case CTRL('x'):
expand_word(1);
break;
+
+
+ /* mksh: cursor movement */
+ case '[':
+ case 'O':
+ state = VPREFIX2;
+ if (es->linelen != 0)
+ es->cursor++;
+ insert = INSERT;
+ return (0);
}
if (insert == 0 && es->cursor != 0 && es->cursor >= es->linelen)
es->cursor--;
t = fsavecmd > 'a';
if (*cmd == ',')
t = !t;
- if ((ncursor = findch(fsavech, argcnt, t, i)) < 0)
+ if ((ncursor = findch(fsavech, argcnt, tobool(t),
+ tobool(i))) < 0)
return (-1);
if (sub && t)
ncursor++;
break;
case 'h':
- case Ctrl('h'):
+ case CTRL('h'):
if (!sub && es->cursor == 0)
return (-1);
ncursor = es->cursor - argcnt;
redo_insert(int count)
{
while (count-- > 0)
- if (putbuf(ibuf, inslen, insert == REPLACE) != 0)
+ if (putbuf(ibuf, inslen, tobool(insert == REPLACE)) != 0)
return (-1);
if (es->cursor > 0)
es->cursor--;
static void
save_cbuf(void)
{
- memmove(holdbuf, es->cbuf, es->linelen);
+ memmove(holdbufp, es->cbuf, es->linelen);
holdlen = es->linelen;
- holdbuf[holdlen] = '\0';
+ holdbufp[holdlen] = '\0';
}
static void
{
es->cursor = 0;
es->linelen = holdlen;
- memmove(es->cbuf, holdbuf, holdlen);
+ memmove(es->cbuf, holdbufp, holdlen);
}
/* return a new edstate */
{
struct edstate *news;
- news = alloc(sizeof(struct edstate), APERM);
- news->cbuf = alloc(old->cbufsize, APERM);
+ 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;
static void
free_edstate(struct edstate *old)
{
- afree(old->cbuf, APERM);
- afree(old, APERM);
+ afree(old->cbuf, AEDIT);
+ afree(old, AEDIT);
}
/*
static int
x_vi_putbuf(const char *s, size_t len)
{
- return (putbuf(s, len, 0));
+ return (putbuf(s, len, false));
}
static int
-putbuf(const char *buf, int len, int repl)
+putbuf(const char *buf, ssize_t len, bool repl)
{
if (len == 0)
return (0);
}
static int
-findch(int ch, int cnt, int forw, int incl)
+findch(int ch, int cnt, bool forw, bool incl)
{
int ncursor;
}
(void)histnum(n);
if ((hptr = *histpos()) == NULL) {
- internal_warningf("grabhist: bad history array");
+ internal_warningf("%s: %s", "grabhist", "bad history array");
return (-1);
}
if (save)
}
static int
-grabsearch(int save, int start, int fwd, char *pat)
+grabsearch(int save, int start, int fwd, const char *pat)
{
char *hptr;
int hist;
- int anchored;
+ bool anchored;
if ((start == 0 && fwd == 0) || (start >= hlast - 1 && fwd == 1))
return (-1);
start++;
else
start--;
- anchored = *pat == '^' ? (++pat, 1) : 0;
+ anchored = *pat == '^' ? (++pat, true) : false;
if ((hist = findhist(start, fwd, pat, anchored)) < 0) {
- /* if (start != 0 && fwd && match(holdbuf, pat) >= 0) { */
- /* XXX should strcmp be strncmp? */
- if (start != 0 && fwd && strcmp(holdbuf, pat) >= 0) {
+ /* (start != 0 && fwd && match(holdbufp, pat) >= 0) */
+ if (start != 0 && fwd && strcmp(holdbufp, pat) >= 0) {
restore_cbuf();
return (0);
} else
}
static void
-redraw_line(int newl)
+redraw_line(bool newl)
{
- (void)memset(wbuf[win], ' ', wbuf_len);
+ if (wbuf_len)
+ memset(wbuf[win], ' ', wbuf_len);
if (newl) {
x_putc('\r');
x_putc('\n');
}
- if (prompt_redraw)
+ if (prompt_trunc != -1)
pprompt(prompt, prompt_trunc);
- cur_col = pwidth;
+ x_col = pwidth;
morec = ' ';
}
}
static int
-newcol(int ch, int col)
+newcol(unsigned char ch, int col)
{
if (ch == '\t')
return ((col | 7) + 1);
*twb1++ = ' ';
} while (++col < winwidth && (col & 7) != 0);
else if (col < winwidth) {
- if (ch < ' ' || ch == 0x7f) {
+ if (ISCTRL(ch) && /* but not C1 */ ch < 0x80) {
*twb1++ = '^';
if (++col < winwidth) {
- *twb1++ = ch ^ '@';
+ *twb1++ = UNCTRL(ch);
col++;
}
} else {
twb2 = wb2;
while (cnt--) {
if (*twb1 != *twb2) {
- if (cur_col != col)
+ if (x_col != col)
ed_mov_opt(col, wb1);
x_putc(*twb1);
- cur_col++;
+ x_col++;
}
twb1++;
twb2++;
col++;
}
if (es->winleft > 0 && moreright)
- /* POSIX says to use * for this but that is a globbing
+ /*
+ * POSIX says to use * for this but that is a globbing
* character and may confuse people; + is more innocuous
*/
mc = '+';
if (mc != morec) {
ed_mov_opt(pwidth + winwidth + 1, wb1);
x_putc(mc);
- cur_col++;
+ x_col++;
morec = mc;
}
- if (cur_col != ncol)
+ if (x_col != ncol)
ed_mov_opt(ncol, wb1);
}
static void
ed_mov_opt(int col, char *wb)
{
- if (col < cur_col) {
- if (col + 1 < cur_col - col) {
+ if (col < x_col) {
+ if (col + 1 < x_col - col) {
x_putc('\r');
- if (prompt_redraw)
+ if (prompt_trunc != -1)
pprompt(prompt, prompt_trunc);
- cur_col = pwidth;
- while (cur_col++ < col)
+ x_col = pwidth;
+ while (x_col++ < col)
x_putcf(*wb++);
} else {
- while (cur_col-- > col)
+ while (x_col-- > col)
x_putc('\b');
}
} else {
- wb = &wb[cur_col - pwidth];
- while (cur_col++ < col)
+ wb = &wb[x_col - pwidth];
+ while (x_col++ < col)
x_putcf(*wb++);
}
- cur_col = col;
+ x_col = col;
}
expand_word(int cmd)
{
static struct edstate *buf;
- int rval = 0;
- int nwords;
- int start, end;
+ int rval = 0, nwords, start, end, i;
char **words;
- int i;
/* Undo previous expansion */
if (cmd == 0 && expanded == EXPAND && buf) {
buf = 0;
}
- nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH,
- es->cbuf, es->linelen, es->cursor,
- &start, &end, &words, NULL);
+ i = XCF_COMMAND_FILE | XCF_FULLPATH;
+ nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
+ &start, &end, &words);
if (nwords == 0) {
vi_error();
return (-1);
expanded = EXPAND;
del_range(start, end);
es->cursor = start;
- for (i = 0; i < nwords; ) {
+ i = 0;
+ while (i < nwords) {
if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
rval = -1;
break;
}
- if (++i < nwords && putbuf(" ", 1, 0) != 0) {
+ if (++i < nwords && putbuf(" ", 1, false) != 0) {
rval = -1;
break;
}
complete_word(int cmd, int count)
{
static struct edstate *buf;
- int rval, nwords, start, end, match_len;
+ int rval, nwords, start, end, flags;
+ size_t match_len;
char **words;
char *match;
- bool is_command, is_unique;
+ bool is_unique;
/* Undo previous completion */
if (cmd == 0 && expanded == COMPLETE && buf) {
buf = 0;
}
- /* XCF_FULLPATH for count 'cause the menu printed by print_expansions()
- * was done this way.
+ /*
+ * XCF_FULLPATH for count 'cause the menu printed by
+ * print_expansions() was done this way.
*/
- nwords = x_cf_glob(XCF_COMMAND_FILE | (count ? XCF_FULLPATH : 0),
- es->cbuf, es->linelen, es->cursor,
- &start, &end, &words, &is_command);
+ flags = XCF_COMMAND_FILE;
+ if (count)
+ flags |= XCF_FULLPATH;
+ nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
+ &start, &end, &words);
if (nwords == 0) {
vi_error();
return (-1);
count--;
if (count >= nwords) {
vi_error();
- x_print_expansions(nwords, words, is_command);
+ x_print_expansions(nwords, words,
+ tobool(flags & XCF_IS_COMMAND));
x_free_words(nwords, words);
- redraw_line(0);
+ redraw_line(false);
return (-1);
}
/*
* Expand the count'th word to its basename
*/
- if (is_command) {
+ if (flags & XCF_IS_COMMAND) {
match = words[count] +
x_basename(words[count], NULL);
/* If more than one possible match, use full path */
} else {
match = words[0];
match_len = x_longest_prefix(nwords, words);
- expanded = COMPLETE; /* next call will list completions */
+ /* next call will list completions */
+ expanded = COMPLETE;
is_unique = nwords == 1;
}
del_range(start, end);
es->cursor = start;
- /* escape all shell-sensitive characters and put the result into
- * command buffer */
+ /*
+ * 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
+ /*
+ * If exact match, don't undo. Allows directory completions
* to be used (ie, complete the next portion of the path).
*/
expanded = NONE;
- /* If not a directory, add a space to the end... */
- if (match_len > 0 && match[match_len - 1] != '/')
- rval = putbuf(" ", 1, 0);
+ /*
+ * append a space if this is a non-directory match
+ * and not a parameter or homedir substitution
+ */
+ if (match_len > 0 && match[match_len - 1] != '/' &&
+ !(flags & XCF_IS_NOSPACE))
+ rval = putbuf(" ", 1, false);
}
x_free_words(nwords, words);
modified = 1;
hnum = hlast;
insert = INSERT;
- lastac = 0; /* prevent this from being redone... */
+ /* prevent this from being redone... */
+ lastac = 0;
refresh(0);
return (rval);
static int
print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
{
- int start, end, nwords;
+ int start, end, nwords, i;
char **words;
- bool is_command;
- nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH,
- est->cbuf, est->linelen, est->cursor,
- &start, &end, &words, &is_command);
+ 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, is_command);
+ x_print_expansions(nwords, words, tobool(i & XCF_IS_COMMAND));
x_free_words(nwords, words);
- redraw_line(0);
+ redraw_line(false);
return (0);
}
static void
x_vi_zotc(int c)
{
- if (c < ' ' || c == 0x7f) {
+ if (ISCTRL(c)) {
x_putc('^');
- c ^= '@';
+ c = UNCTRL(c);
}
x_putc(c);
}
vi_macro_reset(void)
{
if (macro.p) {
- afree(macro.buf, APERM);
+ 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 -2 to force initial binding, except
+ * we need default values for some deficient systems…
+ */
+ edchars.erase = edchars.kill = edchars.intr = edchars.quit =
+ edchars.eof = -2;
+ /* ^W */
+ 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
+#endif /* !MKSH_NO_CMDLINE_EDITING */