refactor code to be able to track whether we have a parameter substitution

or a “proper” glob expansion; if there’s a dollar, but not a glob, refrain
from appending a space later (LP: #710539)
This commit is contained in:
tg
2011-02-03 15:57:52 +00:00
parent 1c4aab3a3e
commit d4658a569e
3 changed files with 260 additions and 188 deletions

View File

@ -1,4 +1,4 @@
# $MirOS: src/bin/mksh/check.t,v 1.404 2011/01/30 02:18:25 tg Exp $ # $MirOS: src/bin/mksh/check.t,v 1.405 2011/02/03 15:57:49 tg Exp $
# $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: bksl-nl.t,v 1.2 2001/01/28 23:04:56 niklas Exp $
# $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $ # $OpenBSD: history.t,v 1.5 2001/01/28 23:04:56 niklas Exp $
# $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $ # $OpenBSD: read.t,v 1.3 2003/03/10 03:48:16 david Exp $
@ -25,7 +25,7 @@
# http://www.research.att.com/~gsf/public/ifs.sh # http://www.research.att.com/~gsf/public/ifs.sh
expected-stdout: expected-stdout:
@(#)MIRBSD KSH R39 2011/01/29 @(#)MIRBSD KSH R39 2011/02/03
description: description:
Check version of shell. Check version of shell.
stdin: stdin:

424
edit.c
View File

@ -25,7 +25,7 @@
#include "sh.h" #include "sh.h"
__RCSID("$MirOS: src/bin/mksh/edit.c,v 1.202 2011/01/21 22:43:17 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/edit.c,v 1.203 2011/02/03 15:57:50 tg Exp $");
/* /*
* in later versions we might use libtermcap for this, but since external * in later versions we might use libtermcap for this, but since external
@ -57,6 +57,9 @@ static X_chars edchars;
#define XCF_FILE BIT(1) /* Do file completion */ #define XCF_FILE BIT(1) /* Do file completion */
#define XCF_FULLPATH BIT(2) /* command completion: store full path */ #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_VARSUB BIT(4) /* return flag: is $FOO substitution */
#define XCF_IS_EXTGLOB BIT(5) /* return flag: is foo* expansion */
static char editmode; static char editmode;
static int xx_cols; /* for Emacs mode */ static int xx_cols; /* for Emacs mode */
@ -68,8 +71,7 @@ static void x_putcf(int);
static bool x_mode(bool); static bool x_mode(bool);
static int x_do_comment(char *, int, int *); static int x_do_comment(char *, int, int *);
static void x_print_expansions(int, char *const *, bool); static void x_print_expansions(int, char *const *, bool);
static int x_cf_glob(int, const char *, int, int, int *, int *, char ***, static int x_cf_glob(int *, const char *, int, int, int *, int *, char ***);
bool *);
static int x_longest_prefix(int, char *const *); static int x_longest_prefix(int, char *const *);
static int x_basename(const char *, const char *); static int x_basename(const char *, const char *);
static void x_free_words(int, char **); static void x_free_words(int, char **);
@ -89,17 +91,10 @@ static int x_vi(char *, size_t);
#endif #endif
static int path_order_cmp(const void *aa, const void *bb); 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 void glob_table(const char *, XPtrV *, struct table *); static void glob_table(const char *, XPtrV *, struct table *);
static void glob_path(int flags, const char *, XPtrV *, const char *); static void glob_path(int flags, const char *, XPtrV *, const char *);
static int x_file_glob(int, const char *, int, char ***) static int x_file_glob(int, char *, char ***);
MKSH_A_NONNULL((nonnull (2))) static int x_command_glob(int, char *, char ***);
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 int x_locate_word(const char *, int, int, int *, bool *); static int x_locate_word(const char *, int, int, int *, bool *);
static int x_e_getmbc(char *); static int x_e_getmbc(char *);
@ -111,11 +106,14 @@ static int x_e_rebuildline(const char *);
void void
x_init(void) x_init(void)
{ {
/* set to -2 to force initial binding */ /*
* 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.erase = edchars.kill = edchars.intr = edchars.quit =
edchars.eof = -2; edchars.eof = -2;
/* default value for deficient systems */ /* ^W */
edchars.werase = 027; /* ^W */ edchars.werase = 027;
x_init_emacs(); x_init_emacs();
} }
@ -136,7 +134,8 @@ x_read(char *buf, size_t len)
i = x_vi(buf, len); i = x_vi(buf, len);
#endif #endif
else else
i = -1; /* internal error */ /* internal error */
i = -1;
editmode = 0; editmode = 0;
x_mode(false); x_mode(false);
return (i); return (i);
@ -179,7 +178,8 @@ x_putcf(int c)
* Misc common code for vi/emacs * * Misc common code for vi/emacs *
*********************************/ *********************************/
/* Handle the commenting/uncommenting of a line. /*-
* Handle the commenting/uncommenting of a line.
* Returns: * Returns:
* 1 if a carriage return is indicated (comment added) * 1 if a carriage return is indicated (comment added)
* 0 if no return (comment removed) * 0 if no return (comment removed)
@ -193,7 +193,8 @@ x_do_comment(char *buf, int bsize, int *lenp)
int i, j, len = *lenp; int i, j, len = *lenp;
if (len == 0) 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? */ /* Already commented? */
if (buf[0] == '#') { if (buf[0] == '#') {
@ -238,7 +239,8 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
int prefix_len; int prefix_len;
XPtrV l = { NULL, NULL, NULL }; XPtrV l = { NULL, NULL, NULL };
/* 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) * case, we want to omit the directory name)
*/ */
if (!is_command && if (!is_command &&
@ -272,7 +274,8 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
pr_list(use_copy ? (char **)XPptrv(l) : words); pr_list(use_copy ? (char **)XPptrv(l) : words);
if (use_copy) if (use_copy)
XPfree(l); /* not x_free_words() */ /* not x_free_words() */
XPfree(l);
} }
/** /**
@ -283,19 +286,14 @@ x_print_expansions(int nwords, char * const *words, bool is_command)
* - returns number of matching strings * - returns number of matching strings
*/ */
static int static int
x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp) x_file_glob(int flags MKSH_A_UNUSED, char *toglob, char ***wordsp)
{ {
char *toglob, **words; char **words;
int nwords, i, idx; int nwords, i, idx;
bool escaping; bool escaping;
XPtrV w; XPtrV w;
struct source *s, *sold; struct source *s, *sold;
if (slen < 0)
return (0);
toglob = add_glob(str, slen);
/* remove all escaping backward slashes */ /* remove all escaping backward slashes */
escaping = false; escaping = false;
for (i = 0, idx = 0; toglob[i]; i++) { for (i = 0, idx = 0; toglob[i]; i++) {
@ -338,8 +336,9 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
if (nwords == 1) { if (nwords == 1) {
struct stat statb; struct stat statb;
/* Check if globbing failed (returned glob pattern), /*
* but be careful (E.g. toglob == "ab*" when the file * Check if globbing failed (returned glob pattern),
* but be careful (e.g. toglob == "ab*" when the file
* "ab*" exists is not an error). * "ab*" exists is not an error).
* Also, check for empty result - happens if we tried * Also, check for empty result - happens if we tried
* to glob something which evaluated to an empty * to glob something which evaluated to an empty
@ -353,7 +352,6 @@ x_file_glob(int flags MKSH_A_UNUSED, const char *str, int slen, char ***wordsp)
nwords = 0; nwords = 0;
} }
} }
afree(toglob, ATEMP);
if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL) if ((*wordsp = nwords ? words : NULL) == NULL && words != NULL)
x_free_words(nwords, words); x_free_words(nwords, words);
@ -381,21 +379,15 @@ path_order_cmp(const void *aa, const void *bb)
} }
static int 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; char *pat, *fpath;
int nwords; int nwords;
XPtrV w; XPtrV w;
struct block *l; struct block *l;
if (slen < 0)
return (0);
toglob = add_glob(str, slen);
/* Convert "foo*" (toglob) to a pattern for future use */ /* Convert "foo*" (toglob) to a pattern for future use */
pat = evalstr(toglob, DOPAT | DOTILDE); pat = evalstr(toglob, DOPAT | DOTILDE);
afree(toglob, ATEMP);
XPinit(w, 32); XPinit(w, 32);
@ -481,7 +473,8 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
/* The case where pos == buflen happens to take care of itself... */ /* The case where pos == buflen happens to take care of itself... */
start = pos; 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) * one blank after the end of a word)
*/ */
for (; (start > 0 && IS_WORDC(buf[start - 1])) || for (; (start > 0 && IS_WORDC(buf[start - 1])) ||
@ -502,7 +495,8 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
p--; p--;
iscmd = p < 0 || vstrchr(";|&()`", buf[p]); iscmd = p < 0 || vstrchr(";|&()`", buf[p]);
if (iscmd) { 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 * only current directory is searched which is just
* like file globbing. * like file globbing.
*/ */
@ -519,55 +513,34 @@ x_locate_word(const char *buf, int buflen, int pos, int *startp,
} }
static int static int
x_cf_glob(int flags, const char *buf, int buflen, int pos, int *startp, x_cf_glob(int *flagsp, const char *buf, int buflen, int pos, int *startp,
int *endp, char ***wordsp, bool *is_commandp) int *endp, char ***wordsp)
{ {
int len, nwords; int len, nwords = 0;
char **words = NULL; char **words = NULL;
bool is_command; bool is_command;
len = x_locate_word(buf, buflen, pos, startp, &is_command); len = x_locate_word(buf, buflen, pos, startp, &is_command);
if (!(flags & XCF_COMMAND)) if (!(*flagsp & XCF_COMMAND))
is_command = false; 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 * long and isn't very useful. File globs are more likely to be
* useful, so allow these. * useful, so allow these.
*/ */
if (len == 0 && is_command) if (len == 0 && is_command)
return (0); return (0);
nwords = is_command ? if (len >= 0) {
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;
return (nwords);
}
/* 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; char *toglob, *s;
bool saw_slash = false; bool saw_slash = false, saw_dollar = false, saw_glob = false;
if (slen < 0) /*
return (NULL); * Given a string, copy it and possibly add a '*' to the end.
*/
/* for clang's static analyser, the nonnull attribute isn't enough */ strndupx(toglob, buf + *startp, len + /* the '*' */ 1, ATEMP);
mkssert(str != NULL); toglob[len] = '\0';
strndupx(toglob, str, slen + 1, ATEMP); /* + 1 for "*" */
toglob[slen] = '\0';
/* /*
* If the pathname contains a wildcard (an unquoted '*', * If the pathname contains a wildcard (an unquoted '*',
@ -578,18 +551,56 @@ add_glob(const char *str, int slen)
for (s = toglob; *s; s++) { for (s = toglob; *s; s++) {
if (*s == '\\' && s[1]) if (*s == '\\' && s[1])
s++; s++;
else if (*s == '*' || *s == '[' || *s == '?' || *s == '$' || else if (*s == '$') {
(s[1] == '(' /*)*/ && /* *s in '*','?' already checked */ /*
(*s == '+' || *s == '@' || *s == '!'))) * Do not append a space after the value
break; * if expanding a parameter substitution
else if (*s == '/') * as in: “cat $HOME/.ss↹” (LP: #710539)
*/
saw_dollar = true;
} else if (*s == '?' || *s == '*' || *s == '[' ||
/* ?() *() +() @() !() but two already checked */
(s[1] == '(' /*)*/ &&
(*s == '+' || *s == '@' || *s == '!'))) {
/* just expand based on the extglob */
saw_glob = true;
} else if (*s == '/')
saw_slash = true; saw_slash = true;
} }
if (!*s && (*toglob != '~' || saw_slash)) { if (saw_glob) {
toglob[slen] = '*'; /*
toglob[slen + 1] = '\0'; * do not append a glob, we already have a
* glob or extglob; it works even if this is
* a parameter expansion as we have a glob
*/
*flagsp |= XCF_IS_EXTGLOB;
} else if (saw_dollar) {
/* do not append a glob, nor later a space */
*flagsp |= XCF_IS_VARSUB;
} else if (*toglob != '~' || saw_slash) {
/* append a glob, this is not just a tilde */
toglob[len] = '*';
toglob[len + 1] = '\0';
} }
return (toglob);
/*
* 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);
}
*flagsp |= XCF_IS_COMMAND;
*wordsp = words;
*endp = *startp + len;
return (nwords);
} }
/* /*
@ -622,7 +633,8 @@ x_free_words(int nwords, char **words)
afree(words, ATEMP); 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, * be null terminated). Trailing slashes are ignored. If s is just a slash,
* then the offset is 0 (actually, length - 1). * then the offset is 0 (actually, length - 1).
* s Return * s Return
@ -694,7 +706,8 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
p = sp + strlen(sp); p = sp + strlen(sp);
pathlen = p - sp; pathlen = p - sp;
if (pathlen) { if (pathlen) {
/* Copy sp into xp, stuffing any MAGIC characters /*
* Copy sp into xp, stuffing any MAGIC characters
* on the way * on the way
*/ */
const char *s = sp; const char *s = sp;
@ -713,7 +726,8 @@ glob_path(int flags, const char *pat, XPtrV *wp, const char *lpath)
memcpy(xp, pat, patlen); memcpy(xp, pat, patlen);
oldsize = XPsize(*wp); oldsize = XPsize(*wp);
glob_str(Xstring(xs, xp), wp, 1); /* mark dirs */ /* mark dirs */
glob_str(Xstring(xs, xp), wp, 1);
newsize = XPsize(*wp); newsize = XPsize(*wp);
/* Check that each match is executable... */ /* Check that each match is executable... */
@ -809,7 +823,8 @@ struct x_defbindings {
#define X_NTABS 3 /* normal, meta1, meta2 */ #define X_NTABS 3 /* normal, meta1, meta2 */
#define X_TABSZ 256 /* size of keydef tables etc */ #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 * 0 = enumerate M-= complete as much as possible and then list
* 1 = complete M-Esc * 1 = complete M-Esc
* 2 = list M-? * 2 = list M-?
@ -1000,7 +1015,8 @@ static struct x_defbindings const x_defbindings[] = {
{ XFUNC_fold_capitalise, 1, 'C' }, { XFUNC_fold_capitalise, 1, 'C' },
{ XFUNC_fold_capitalise, 1, 'c' }, { XFUNC_fold_capitalise, 1, 'c' },
#endif #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 * default, but its simpler/faster/smaller than using termcap
* entries. * entries.
*/ */
@ -1156,7 +1172,8 @@ x_emacs(char *buf, size_t len)
case KEOL: case KEOL:
i = xep - xbuf; i = xep - xbuf;
return (i); return (i);
case KINTR: /* special case for interrupt */ case KINTR:
/* special case for interrupt */
trapsig(SIGINT); trapsig(SIGINT);
x_mode(false); x_mode(false);
unwind(LSHELL); unwind(LSHELL);
@ -1267,7 +1284,8 @@ x_ins(const char *s)
x_lastcp(); x_lastcp();
x_adj_ok = (xcp >= xlp); x_adj_ok = (xcp >= xlp);
x_zots(cp); x_zots(cp);
if (adj == x_adj_done) { /* has x_adjust() been called? */ /* has x_adjust() been called? */
if (adj == x_adj_done) {
/* no */ /* no */
cp = xlp; cp = xlp;
while (cp > xcp) while (cp > xcp)
@ -1356,13 +1374,15 @@ x_delete(int nc, int push)
x_push(nb); x_push(nb);
xep -= nb; xep -= nb;
memmove(xcp, xcp + nb, xep - xcp + 1); /* Copies the NUL */ /* Copies the NUL */
x_adj_ok = 0; /* don't redraw */ memmove(xcp, xcp + nb, xep - xcp + 1);
/* don't redraw */
x_adj_ok = 0;
xlp_valid = false; xlp_valid = false;
x_zots(xcp); x_zots(xcp);
/* /*
* if we are already filling the line, * 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. * But if we must, make sure we do the minimum.
*/ */
if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) { if ((i = xx_cols - 2 - x_col) > 0 || xep - xlp == 0) {
@ -1474,10 +1494,12 @@ x_goto(char *cp)
/* we are heading off screen */ /* we are heading off screen */
xcp = cp; xcp = cp;
x_adjust(); x_adjust();
} else if (cp < xcp) { /* move back */ } else if (cp < xcp) {
/* move back */
while (cp < xcp) while (cp < xcp)
x_bs3(&xcp); x_bs3(&xcp);
} else if (cp > xcp) { /* move forward */ } else if (cp > xcp) {
/* move forward */
while (cp > xcp) while (cp > xcp)
x_zotc3(&xcp); x_zotc3(&xcp);
} }
@ -1517,9 +1539,11 @@ x_size2(char *cp, char **dcp)
if (dcp) if (dcp)
*dcp = cp + 1; *dcp = cp + 1;
if (c == '\t') if (c == '\t')
return (4); /* Kludge, tabs are always four spaces. */ /* Kludge, tabs are always four spaces. */
return (4);
if (c < ' ' || c == 0x7f) if (c < ' ' || c == 0x7f)
return (2); /* control unsigned char */ /* control unsigned char */
return (2);
return (1); return (1);
} }
@ -1702,7 +1726,8 @@ x_next_com(int c MKSH_A_UNUSED)
return (KSTD); 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 * If no argument is given history 1 is probably not what you
* want so we'll simply go to the oldest one. * want so we'll simply go to the oldest one.
*/ */
@ -1835,7 +1860,8 @@ x_search_hist(int c)
if (offset >= 0) if (offset >= 0)
x_load_hist(histptr + 1); x_load_hist(histptr + 1);
break; break;
} else { /* other command */ } else {
/* other command */
x_e_ungetc(c); x_e_ungetc(c);
break; break;
} }
@ -1967,7 +1993,8 @@ x_cls(int c MKSH_A_UNUSED)
return (x_e_rebuildline(MKSH_CLS_STRING)); 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 * on a NEW line, otherwise limit is the screen column up to which needs
* redrawing. * redrawing.
*/ */
@ -2004,7 +2031,8 @@ x_redraw(int limit)
limit = xx_cols; limit = xx_cols;
if (limit >= 0) { if (limit >= 0) {
if (xep > xlp) if (xep > xlp)
i = 0; /* we fill the line */ /* we fill the line */
i = 0;
else { else {
char *cpl = xbp; char *cpl = xbp;
@ -2021,7 +2049,8 @@ x_redraw(int limit)
j++; j++;
} }
i = ' '; i = ' ';
if (xep > xlp) { /* more off screen */ if (xep > xlp) {
/* more off screen */
if (xbp > xbuf) if (xbp > xbuf)
i = '*'; i = '*';
else else
@ -2045,7 +2074,8 @@ x_transpose(int c MKSH_A_UNUSED)
{ {
unsigned int tmpa, tmpb; 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 * is a general summary of the options; the text is abcd with the
* upper case character or underscore indicating the cursor position: * upper case character or underscore indicating the cursor position:
* Who Before After Before After * Who Before After Before After
@ -2066,8 +2096,9 @@ x_transpose(int c MKSH_A_UNUSED)
x_e_putc2(7); x_e_putc2(7);
return (KSTD); 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); x_bs3(&xcp);
if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
@ -2084,7 +2115,8 @@ x_transpose(int c MKSH_A_UNUSED)
utf_wctomb(xcp, tmpb); utf_wctomb(xcp, tmpb);
x_zotc3(&xcp); x_zotc3(&xcp);
} else { } 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. * cursor, move cursor position along one.
*/ */
if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) { if (utf_mbtowc(&tmpa, xcp) == (size_t)-1) {
@ -2281,7 +2313,8 @@ x_mapin(const char *cp, Area *ap)
/* XXX -- should handle \^ escape? */ /* XXX -- should handle \^ escape? */
if (*cp == '^') { if (*cp == '^') {
cp++; cp++;
if (*cp >= '?') /* includes '?'; ASCII */ if (*cp >= '?')
/* includes '?'; ASCII */
*op++ = CTRL(*cp); *op++ = CTRL(*cp);
else { else {
*op++ = '^'; *op++ = '^';
@ -2345,9 +2378,11 @@ x_print(int prefix, int key)
int int
x_bind(const char *a1, const char *a2, x_bind(const char *a1, const char *a2,
#ifndef MKSH_SMALL #ifndef MKSH_SMALL
bool macro, /* bind -m */ /* bind -m */
bool macro,
#endif #endif
bool list) /* bind -l */ /* bind -l */
bool list)
{ {
unsigned char f; unsigned char f;
int prefix, key; int prefix, key;
@ -2605,10 +2640,10 @@ x_expand(int c MKSH_A_UNUSED)
{ {
char **words; char **words;
int start, end, nwords, i; int start, end, nwords, i;
bool is_command;
nwords = x_cf_glob(XCF_FILE, xbuf, xep - xbuf, xcp - xbuf, i = XCF_FILE;
&start, &end, &words, &is_command); nwords = x_cf_glob(&i, xbuf, xep - xbuf, xcp - xbuf,
&start, &end, &words);
if (nwords == 0) { if (nwords == 0) {
x_e_putc2(7); x_e_putc2(7);
@ -2616,7 +2651,9 @@ x_expand(int c MKSH_A_UNUSED)
} }
x_goto(xbuf + start); x_goto(xbuf + start);
x_delete(end - start, false); x_delete(end - start, false);
for (i = 0; i < nwords;) {
i = 0;
while (i < nwords) {
if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 || if (x_escape(words[i], strlen(words[i]), x_do_ins) < 0 ||
(++i < nwords && x_ins(" ") < 0)) { (++i < nwords && x_ins(" ") < 0)) {
x_e_putc2(7); x_e_putc2(7);
@ -2628,24 +2665,26 @@ x_expand(int c MKSH_A_UNUSED)
return (KSTD); return (KSTD);
} }
/* type == 0 for list, 1 for complete and 2 for complete-list */
static void 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) Comp_type type)
{ {
char **words; char **words;
int start, end, nlen, olen, nwords; int start, end, nlen, olen, nwords;
bool is_command, completed = false; bool completed = false;
nwords = x_cf_glob(flags, xbuf, xep - xbuf, xcp - xbuf, nwords = x_cf_glob(&flags, xbuf, xep - xbuf, xcp - xbuf,
&start, &end, &words, &is_command); &start, &end, &words);
/* no match */ /* no match */
if (nwords == 0) { if (nwords == 0) {
x_e_putc2(7); x_e_putc2(7);
return; return;
} }
if (type == CT_LIST) { if (type == CT_LIST) {
x_print_expansions(nwords, words, is_command); x_print_expansions(nwords, words, flags & XCF_IS_COMMAND);
x_redraw(0); x_redraw(0);
x_free_words(nwords, words); x_free_words(nwords, words);
return; return;
@ -2660,13 +2699,14 @@ do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */
x_adjust(); x_adjust();
completed = true; completed = true;
} }
/* add space if single non-dir match */ /* add space if single non-dir match and not parameter substitution */
if (nwords == 1 && words[0][nlen - 1] != '/') { if (nwords == 1 && words[0][nlen - 1] != '/' &&
!(flags & XCF_IS_VARSUB)) {
x_ins(" "); x_ins(" ");
completed = true; completed = true;
} }
if (type == CT_COMPLIST && !completed) { if (type == CT_COMPLIST && !completed) {
x_print_expansions(nwords, words, is_command); x_print_expansions(nwords, words, flags & XCF_IS_COMMAND);
completed = true; completed = true;
} }
if (completed) if (completed)
@ -2675,7 +2715,8 @@ do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */
x_free_words(nwords, words); x_free_words(nwords, words);
} }
/* NAME: /*-
* NAME:
* x_adjust - redraw the line adjusting starting point etc. * x_adjust - redraw the line adjusting starting point etc.
* *
* DESCRIPTION: * DESCRIPTION:
@ -2691,7 +2732,8 @@ do_complete(int flags, /* XCF_{COMMAND,FILE,COMMAND_FILE} */
static void static void
x_adjust(void) x_adjust(void)
{ {
x_adj_done++; /* flag the fact that we were called. */ /* flag the fact that we were called. */
x_adj_done++;
/* /*
* we had a problem if the prompt length > xx_cols / 2 * we had a problem if the prompt length > xx_cols / 2
*/ */
@ -2819,7 +2861,8 @@ x_e_puts(const char *s)
x_e_putc3(&s); x_e_putc3(&s);
} }
/* NAME: /*-
* NAME:
* x_set_arg - set an arg value for next function * x_set_arg - set an arg value for next function
* *
* DESCRIPTION: * DESCRIPTION:
@ -2833,7 +2876,8 @@ x_set_arg(int c)
{ {
int n = 0, first = 1; int n = 0, first = 1;
c &= 255; /* strip command prefix */ /* strip command prefix */
c &= 255;
for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0) for (; c >= 0 && ksh_isdigit(c); c = x_e_getc(), first = 0)
n = n * 10 + (c - '0'); n = n * 10 + (c - '0');
if (c < 0 || first) { if (c < 0 || first) {
@ -2929,7 +2973,8 @@ x_edit_line(int c MKSH_A_UNUSED)
} }
#endif #endif
/* NAME: /*-
* NAME:
* x_prev_histword - recover word from prev command * x_prev_histword - recover word from prev command
* *
* DESCRIPTION: * DESCRIPTION:
@ -3026,7 +3071,8 @@ x_fold_capitalise(int c MKSH_A_UNUSED)
return (x_fold_case('C')); return (x_fold_case('C'));
} }
/* NAME: /*-
* NAME:
* x_fold_case - convert word to UPPER/lower/Capital case * x_fold_case - convert word to UPPER/lower/Capital case
* *
* DESCRIPTION: * DESCRIPTION:
@ -3056,9 +3102,11 @@ x_fold_case(int c)
* a different action than for the rest. * a different action than for the rest.
*/ */
if (cp != xep) { if (cp != xep) {
if (c == 'L') /* lowercase */ if (c == 'L')
/* lowercase */
*cp = ksh_tolower(*cp); *cp = ksh_tolower(*cp);
else /* uppercase, capitalise */ else
/* uppercase, capitalise */
*cp = ksh_toupper(*cp); *cp = ksh_toupper(*cp);
cp++; cp++;
} }
@ -3066,9 +3114,11 @@ x_fold_case(int c)
* now for the rest of the word * now for the rest of the word
*/ */
while (cp != xep && !is_mfs(*cp)) { while (cp != xep && !is_mfs(*cp)) {
if (c == 'U') /* uppercase */ if (c == 'U')
/* uppercase */
*cp = ksh_toupper(*cp); *cp = ksh_toupper(*cp);
else /* lowercase, capitalise */ else
/* lowercase, capitalise */
*cp = ksh_tolower(*cp); *cp = ksh_tolower(*cp);
cp++; cp++;
} }
@ -3079,7 +3129,8 @@ x_fold_case(int c)
} }
#endif #endif
/* NAME: /*-
* NAME:
* x_lastcp - last visible char * x_lastcp - last visible char
* *
* SYNOPSIS: * SYNOPSIS:
@ -3146,10 +3197,10 @@ x_mode(bool onoff)
cb.c_iflag &= ~(INLCR | ICRNL); cb.c_iflag &= ~(INLCR | ICRNL);
cb.c_lflag &= ~(ISIG | ICANON | ECHO); cb.c_lflag &= ~(ISIG | ICANON | ECHO);
#if defined(VLNEXT) && defined(_POSIX_VDISABLE) #if defined(VLNEXT) && defined(_POSIX_VDISABLE)
/* osf/1 processes lnext when ~icanon */ /* OSF/1 processes lnext when ~icanon */
cb.c_cc[VLNEXT] = _POSIX_VDISABLE; cb.c_cc[VLNEXT] = _POSIX_VDISABLE;
#endif #endif
/* sunos 4.1.x & osf/1 processes discard(flush) when ~icanon */ /* SunOS 4.1.x & OSF/1 processes discard(flush) when ~icanon */
#if defined(VDISCARD) && defined(_POSIX_VDISABLE) #if defined(VDISCARD) && defined(_POSIX_VDISABLE)
cb.c_cc[VDISCARD] = _POSIX_VDISABLE; cb.c_cc[VDISCARD] = _POSIX_VDISABLE;
#endif #endif
@ -3345,7 +3396,8 @@ static int ohnum; /* history line copied (after mod) */
static int hlast; /* 1 past last position in history */ static int hlast; /* 1 past last position in history */
static int state; 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 * 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 * 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 * a double NUL. The name of the alias is stored so recursive macros can
@ -3809,7 +3861,8 @@ vi_insert(int ch)
expanded = NONE; expanded = NONE;
return (0); 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 * buffer (if user inserts & deletes char, ibuf gets trashed and
* we don't want to use it) * we don't want to use it)
*/ */
@ -4332,29 +4385,37 @@ vi_cmd(int argcnt, const char *cmd)
return (ret); return (ret);
} }
case '=': /* AT&T ksh */ /* AT&T ksh */
case Ctrl('e'): /* Nonstandard vi/ksh */ case '=':
/* Nonstandard vi/ksh */
case Ctrl('e'):
print_expansions(es, 1); print_expansions(es, 1);
break; break;
case Ctrl('i'): /* Nonstandard vi/ksh */ /* Nonstandard vi/ksh */
case Ctrl('i'):
if (!Flag(FVITABCOMPLETE)) if (!Flag(FVITABCOMPLETE))
return (-1); return (-1);
complete_word(1, argcnt); complete_word(1, argcnt);
break; break;
case Ctrl('['): /* some annoying AT&T kshs */ /* some annoying AT&T kshs */
case Ctrl('['):
if (!Flag(FVIESCCOMPLETE)) if (!Flag(FVIESCCOMPLETE))
return (-1); return (-1);
case '\\': /* AT&T ksh */ /* AT&T ksh */
case Ctrl('f'): /* Nonstandard vi/ksh */ case '\\':
/* Nonstandard vi/ksh */
case Ctrl('f'):
complete_word(1, argcnt); complete_word(1, argcnt);
break; break;
case '*': /* AT&T ksh */ /* AT&T ksh */
case Ctrl('x'): /* Nonstandard vi/ksh */ case '*':
/* Nonstandard vi/ksh */
case Ctrl('x'):
expand_word(1); expand_word(1);
break; break;
} }
@ -5005,7 +5066,8 @@ display(char *wb1, char *wb2, int leftside)
col++; col++;
} }
if (es->winleft > 0 && moreright) 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 * character and may confuse people; + is more innocuous
*/ */
mc = '+'; mc = '+';
@ -5054,11 +5116,8 @@ static int
expand_word(int cmd) expand_word(int cmd)
{ {
static struct edstate *buf; static struct edstate *buf;
int rval = 0; int rval = 0, nwords, start, end, i;
int nwords;
int start, end;
char **words; char **words;
int i;
/* Undo previous expansion */ /* Undo previous expansion */
if (cmd == 0 && expanded == EXPAND && buf) { if (cmd == 0 && expanded == EXPAND && buf) {
@ -5072,9 +5131,9 @@ expand_word(int cmd)
buf = 0; buf = 0;
} }
nwords = x_cf_glob(XCF_COMMAND_FILE|XCF_FULLPATH, i = XCF_COMMAND_FILE | XCF_FULLPATH;
es->cbuf, es->linelen, es->cursor, nwords = x_cf_glob(&i, es->cbuf, es->linelen, es->cursor,
&start, &end, &words, NULL); &start, &end, &words);
if (nwords == 0) { if (nwords == 0) {
vi_error(); vi_error();
return (-1); return (-1);
@ -5084,7 +5143,8 @@ expand_word(int cmd)
expanded = EXPAND; expanded = EXPAND;
del_range(start, end); del_range(start, end);
es->cursor = start; 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) { if (x_escape(words[i], strlen(words[i]), x_vi_putbuf) != 0) {
rval = -1; rval = -1;
break; break;
@ -5109,10 +5169,10 @@ static int
complete_word(int cmd, int count) complete_word(int cmd, int count)
{ {
static struct edstate *buf; static struct edstate *buf;
int rval, nwords, start, end, match_len; int rval, nwords, start, end, match_len, flags;
char **words; char **words;
char *match; char *match;
bool is_command, is_unique; bool is_unique;
/* Undo previous completion */ /* Undo previous completion */
if (cmd == 0 && expanded == COMPLETE && buf) { if (cmd == 0 && expanded == COMPLETE && buf) {
@ -5131,12 +5191,15 @@ complete_word(int cmd, int count)
buf = 0; 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), flags = XCF_COMMAND_FILE;
es->cbuf, es->linelen, es->cursor, if (count)
&start, &end, &words, &is_command); flags |= XCF_FULLPATH;
nwords = x_cf_glob(&flags, es->cbuf, es->linelen, es->cursor,
&start, &end, &words);
if (nwords == 0) { if (nwords == 0) {
vi_error(); vi_error();
return (-1); return (-1);
@ -5147,7 +5210,8 @@ complete_word(int cmd, int count)
count--; count--;
if (count >= nwords) { if (count >= nwords) {
vi_error(); vi_error();
x_print_expansions(nwords, words, is_command); x_print_expansions(nwords, words,
flags & XCF_IS_COMMAND);
x_free_words(nwords, words); x_free_words(nwords, words);
redraw_line(0); redraw_line(0);
return (-1); return (-1);
@ -5155,7 +5219,7 @@ complete_word(int cmd, int count)
/* /*
* Expand the count'th word to its basename * Expand the count'th word to its basename
*/ */
if (is_command) { if (flags & XCF_IS_COMMAND) {
match = words[count] + match = words[count] +
x_basename(words[count], NULL); x_basename(words[count], NULL);
/* If more than one possible match, use full path */ /* If more than one possible match, use full path */
@ -5174,7 +5238,8 @@ complete_word(int cmd, int count)
} else { } else {
match = words[0]; match = words[0];
match_len = x_longest_prefix(nwords, words); 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; is_unique = nwords == 1;
} }
@ -5182,18 +5247,25 @@ complete_word(int cmd, int count)
del_range(start, end); del_range(start, end);
es->cursor = start; 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); rval = x_escape(match, match_len, x_vi_putbuf);
if (rval == 0 && is_unique) { 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). * to be used (ie, complete the next portion of the path).
*/ */
expanded = NONE; expanded = NONE;
/* If not a directory, add a space to the end... */ /*
if (match_len > 0 && match[match_len - 1] != '/') * append a space if this is not a directory or the
* result of a parameter substitution
*/
if (match_len > 0 && match[match_len - 1] != '/' &&
!(flags & XCF_IS_VARSUB))
rval = putbuf(" ", 1, 0); rval = putbuf(" ", 1, 0);
} }
x_free_words(nwords, words); x_free_words(nwords, words);
@ -5201,7 +5273,8 @@ complete_word(int cmd, int count)
modified = 1; modified = 1;
hnum = hlast; hnum = hlast;
insert = INSERT; insert = INSERT;
lastac = 0; /* prevent this from being redone... */ /* prevent this from being redone... */
lastac = 0;
refresh(0); refresh(0);
return (rval); return (rval);
@ -5210,18 +5283,17 @@ complete_word(int cmd, int count)
static int static int
print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED) print_expansions(struct edstate *est, int cmd MKSH_A_UNUSED)
{ {
int start, end, nwords; int start, end, nwords, i;
char **words; char **words;
bool is_command;
nwords = x_cf_glob(XCF_COMMAND_FILE | XCF_FULLPATH, i = XCF_COMMAND_FILE | XCF_FULLPATH;
est->cbuf, est->linelen, est->cursor, nwords = x_cf_glob(&i, est->cbuf, est->linelen, est->cursor,
&start, &end, &words, &is_command); &start, &end, &words);
if (nwords == 0) { if (nwords == 0) {
vi_error(); vi_error();
return (-1); return (-1);
} }
x_print_expansions(nwords, words, is_command); x_print_expansions(nwords, words, i & XCF_IS_COMMAND);
x_free_words(nwords, words); x_free_words(nwords, words);
redraw_line(0); redraw_line(0);
return (0); return (0);

4
sh.h
View File

@ -154,9 +154,9 @@
#endif #endif
#ifdef EXTERN #ifdef EXTERN
__RCSID("$MirOS: src/bin/mksh/sh.h,v 1.426 2011/01/30 01:36:00 tg Exp $"); __RCSID("$MirOS: src/bin/mksh/sh.h,v 1.427 2011/02/03 15:57:52 tg Exp $");
#endif #endif
#define MKSH_VERSION "R39 2011/01/29" #define MKSH_VERSION "R39 2011/02/03"
#ifndef MKSH_INCLUDES_ONLY #ifndef MKSH_INCLUDES_ONLY