1
0
Fork 0
mirror of git://git.code.sf.net/p/zsh/code synced 2025-11-01 18:30:55 +01:00

29413: "print -S" for saving to history with lexical word split

This commit is contained in:
Peter Stephenson 2011-06-03 19:54:43 +00:00
parent 42a260df99
commit 12e246495c
4 changed files with 187 additions and 115 deletions

View file

@ -1,3 +1,9 @@
2011-06-03 Peter Stephenson <p.w.stephenson@ntlworld.com>
* 29413: Doc/Zsh/builtins.yo, Src/builtin.c, Src/hist.c: print
-S takes a single argument, applies lexical history word
splitting, and puts it on the history.
2011-06-02 Frank Terbeck <ft@bewatermyfriend.org>
* 29434: Doc/Zsh/contrib.yo: Use PLUS() to avoid a + being
@ -14925,5 +14931,5 @@
*****************************************************
* This is used by the shell to define $ZSH_PATCHLEVEL
* $Revision: 1.5355 $
* $Revision: 1.5356 $
*****************************************************

View file

@ -914,7 +914,7 @@ and the new directory stack is not printed. This is useful for calls to
tt(popd) that do not change the environment seen by an interactive user.
)
findex(print)
xitem(tt(print) [ tt(-abcDilmnNoOpPrsz) ] [ tt(-u) var(n) ] [ tt(-f) var(format) ] [ tt(-C) var(cols) ])
xitem(tt(print) [ tt(-abcDilmnNoOpPrsSz) ] [ tt(-u) var(n) ] [ tt(-f) var(format) ] [ tt(-C) var(cols) ])
item( [ tt(-R) [ tt(-en) ]] [ var(arg) ... ])(
With the `tt(-f)' option the arguments are printed as described by tt(printf).
With no flags or with the flag `tt(-)', the arguments are printed on
@ -994,6 +994,15 @@ tt(-R); all other arguments and options are printed.
)
item(tt(-s))(
Place the results in the history list instead of on the standard output.
Each argument to the tt(print) command is treated as a single word in the
history, regardless of its content.
)
item(tt(-S))(
Place the results in the history list instead of on the standard output.
In this case only a single argument is allowed; it will be split into
words as if it were a full shell command line. The effect is
similar to reading the line from a history file with the
tt(HIST_LEX_WORDS) option active.
)
item(tt(-u) var(n))(
Print the arguments to file descriptor var(n).

View file

@ -99,7 +99,7 @@ static struct builtin builtins[] =
#endif
BUILTIN("popd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 1, BIN_POPD, "q", NULL),
BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsu:z-", NULL),
BUILTIN("print", BINF_PRINTOPTS, bin_print, 0, -1, BIN_PRINT, "abcC:Df:ilmnNoOpPrRsSu:z-", NULL),
BUILTIN("printf", 0, bin_print, 1, -1, BIN_PRINTF, NULL, NULL),
BUILTIN("pushd", BINF_SKIPINVALID | BINF_SKIPDASH | BINF_DASHDASHVALID, bin_cd, 0, 2, BIN_PUSHD, "qsPL", NULL),
BUILTIN("pushln", 0, bin_print, 0, -1, BIN_PRINT, NULL, "-nz"),
@ -3965,25 +3965,45 @@ bin_print(char *name, char **args, Options ops, int func)
return 0;
}
/* -s option -- add the arguments to the history list */
if (OPT_ISSET(ops,'s')) {
if (OPT_ISSET(ops,'s') || OPT_ISSET(ops,'S')) {
int nwords = 0, nlen, iwords;
char **pargs = args;
queue_signals();
ent = prepnexthistent();
while (*pargs++)
nwords++;
if ((ent->nwords = nwords)) {
ent->words = (short *)zalloc(nwords*2*sizeof(short));
nlen = iwords = 0;
for (pargs = args; *pargs; pargs++) {
ent->words[iwords++] = nlen;
nlen += strlen(*pargs);
ent->words[iwords++] = nlen;
nlen++;
if (nwords) {
if (OPT_ISSET(ops,'S')) {
int wordsize;
short *words;
if (nwords > 1) {
zwarnnam(name, "option -S takes a single argument");
return 1;
}
words = NULL;
wordsize = 0;
histsplitwords(*args, &words, &wordsize, &nwords, 1);
ent = prepnexthistent();
ent->words = (short *)zalloc(nwords*sizeof(short));
memcpy(ent->words, words, nwords*sizeof(short));
free(words);
ent->nwords = nwords/2;
} else {
ent = prepnexthistent();
ent->words = (short *)zalloc(nwords*2*sizeof(short));
ent->nwords = nwords;
nlen = iwords = 0;
for (pargs = args; *pargs; pargs++) {
ent->words[iwords++] = nlen;
nlen += strlen(*pargs);
ent->words[iwords++] = nlen;
nlen++;
}
}
} else
} else {
ent = prepnexthistent();
ent->words = (short *)NULL;
}
ent->node.nam = zjoin(args, ' ', 0);
ent->stim = ent->ftim = time(NULL);
ent->node.flags = 0;

View file

@ -2338,110 +2338,11 @@ readhistfile(char *fn, int err, int readflags)
/*
* Divide up the words.
*/
nwordpos = 0;
start = pt;
uselex = isset(HISTLEXWORDS) && !(readflags & HFILE_FAST);
if (uselex) {
/*
* Attempt to do this using the lexer.
*/
LinkList wordlist = bufferwords(NULL, pt, NULL,
LEXFLAGS_COMMENTS_KEEP);
LinkNode wordnode;
int nwords_max;
nwords_max = 2 * countlinknodes(wordlist);
if (nwords_max > nwords) {
nwords = nwords_max;
words = (short *)realloc(words, nwords*sizeof(short));
}
for (wordnode = firstnode(wordlist);
wordnode;
incnode(wordnode)) {
char *word = getdata(wordnode);
for (;;) {
/*
* Not really an oddity: "\\\n" is
* removed from input as if whitespace.
*/
if (inblank(*pt))
pt++;
else if (pt[0] == '\\' && pt[1] == '\n')
pt += 2;
else
break;
}
if (!strpfx(word, pt)) {
int bad = 0;
/*
* Oddity 1: newlines turn into semicolons.
*/
if (!strcmp(word, ";"))
continue;
while (*pt) {
if (!*word) {
bad = 1;
break;
}
/*
* Oddity 2: !'s turn into |'s.
*/
if (*pt == *word ||
(*pt == '!' && *word == '|')) {
pt++;
word++;
} else {
bad = 1;
break;
}
}
if (bad) {
#ifdef DEBUG
dputs(ERRMSG("bad wordsplit reading history: "
"%s\nat: %s\nword: %s"),
start, pt, word);
#endif
pt = start;
nwordpos = 0;
uselex = 0;
break;
}
} else if (!strcmp(word, ";") && strpfx(";;", pt)) {
/*
* Don't get confused between a semicolon that's
* probably really a newline and a double
* semicolon that's terminating a case.
*/
continue;
}
words[nwordpos++] = pt - start;
pt += strlen(word);
words[nwordpos++] = pt - start;
}
histsplitwords(pt, &words, &nwords, &nwordpos, uselex);
if (uselex)
freeheap();
}
if (!uselex) {
do {
for (;;) {
if (inblank(*pt))
pt++;
else if (pt[0] == '\\' && pt[1] == '\n')
pt += 2;
else
break;
}
if (*pt) {
if (nwordpos >= nwords)
words = (short *)
realloc(words, (nwords += 64)*sizeof(short));
words[nwordpos++] = pt - start;
while (*pt && !inblank(*pt))
pt++;
words[nwordpos++] = pt - start;
}
} while (*pt);
}
he->nwords = nwordpos/2;
if (he->nwords) {
@ -3141,6 +3042,142 @@ bufferwords(LinkList list, char *buf, int *index, int flags)
return list;
}
/*
* Split up a line into words for use in a history file.
*
* lineptr is the line to be split.
*
* *wordsp and *nwordsp are an array already allocated to hold words
* and its length. The array holds both start and end positions,
* so *nwordsp actually counts twice the number of words in the
* original string. *nwordsp may be zero in which case the array
* will be allocated.
*
* *nwordposp returns the used length of *wordsp in the same units as
* *nwordsp, i.e. twice the number of words in the input line.
*
* If uselex is 1, attempt to do this using the lexical analyser.
* This is more accurate, but slower; for reading history files it's
* controlled by the option HISTLEXWORDS. If this failed (which
* indicates a bug in the shell) it falls back to whitespace-separated
* strings, printing a message if in debug mode.
*
* If uselex is 0, just look for whitespace-separated words; the only
* special handling is for a backslash-newline combination as used
* by the history file format to save multiline buffers.
*/
/**/
mod_export void
histsplitwords(char *lineptr, short **wordsp, int *nwordsp, int *nwordposp,
int uselex)
{
int nwords = *nwordsp, nwordpos = 0;
short *words = *wordsp;
char *start = lineptr;
if (uselex) {
LinkList wordlist = bufferwords(NULL, lineptr, NULL,
LEXFLAGS_COMMENTS_KEEP);
LinkNode wordnode;
int nwords_max;
nwords_max = 2 * countlinknodes(wordlist);
if (nwords_max > nwords) {
*nwordsp = nwords = nwords_max;
*wordsp = words = (short *)zrealloc(words, nwords*sizeof(short));
}
for (wordnode = firstnode(wordlist);
wordnode;
incnode(wordnode)) {
char *word = getdata(wordnode);
for (;;) {
/*
* Not really an oddity: "\\\n" is
* removed from input as if whitespace.
*/
if (inblank(*lineptr))
lineptr++;
else if (lineptr[0] == '\\' && lineptr[1] == '\n')
lineptr += 2;
else
break;
}
if (!strpfx(word, lineptr)) {
int bad = 0;
/*
* Oddity 1: newlines turn into semicolons.
*/
if (!strcmp(word, ";"))
continue;
while (*lineptr) {
if (!*word) {
bad = 1;
break;
}
/*
* Oddity 2: !'s turn into |'s.
*/
if (*lineptr == *word ||
(*lineptr == '!' && *word == '|')) {
lineptr++;
word++;
} else {
bad = 1;
break;
}
}
if (bad) {
#ifdef DEBUG
dputs(ERRMSG("bad wordsplit reading history: "
"%s\nat: %s\nword: %s"),
start, lineptr, word);
#endif
lineptr = start;
nwordpos = 0;
uselex = 0;
break;
}
} else if (!strcmp(word, ";") && strpfx(";;", lineptr)) {
/*
* Don't get confused between a semicolon that's
* probably really a newline and a double
* semicolon that's terminating a case.
*/
continue;
}
words[nwordpos++] = lineptr - start;
lineptr += strlen(word);
words[nwordpos++] = lineptr - start;
}
}
if (!uselex) {
do {
for (;;) {
if (inblank(*lineptr))
lineptr++;
else if (lineptr[0] == '\\' && lineptr[1] == '\n')
lineptr += 2;
else
break;
}
if (*lineptr) {
if (nwordpos >= nwords) {
*nwordsp = nwords = nwords + 64;
*wordsp = words = (short *)
zrealloc(words, nwords*sizeof(*words));
}
words[nwordpos++] = lineptr - start;
while (*lineptr && !inblank(*lineptr))
lineptr++;
words[nwordpos++] = lineptr - start;
}
} while (*lineptr);
}
*nwordposp = nwordpos;
}
/* Move the current history list out of the way and prepare a fresh history
* list using hf for HISTFILE, hs for HISTSIZE, and shs for SAVEHIST. If
* the hf value is an empty string, HISTFILE will be unset from the new