Zsh Mailing List Archive
Messages sorted by:
Reverse Date,
Date,
Thread,
Author
[PATCH] Per-directory history implementation
This change is an attempt to implement this feature straight into zsh,
so that its entry pruning and history file locking logic cover it as
well.
NOTE: There is external plugin that tries to implement this in the
following manner: 1) switching the history file with current directory
change to maintain a separate one, and reloading the zsh history each
time it happens 2) maintaining the global history file in addition.
While I wanted that plugin to work for me, it felt far from perfect and
its downsides were too great for me.
This is a first version, so I'm guessing there's much to iron out.
--
Conditional on the activation of the `extended_history` feature, these
changes implement a `histsavecwd` feature. The current directory of the
command is added to each command in the history file, in this manner:
: 1579121354 /home/user:0;ls -l
Instead of the old format:
: 1579121354:0;ls -l
In addition, the 'fc' commands get a '-c' option that filters out
commands not pertaining to the current directory. This can serve to
implement a command similar to `Ctrl-R` showing only the per-directory
history.
Note that that pathnames that contain the ':' character are not
supported for now - these are expected to mess up history parsing. We
should escape the `:` character if we want to support them, or change
the format altogether.
Older zsh versions should ignore the added information.
---
Completion/Zsh/Command/_set | 2 +-
Src/builtin.c | 26 ++++++++++++++++++++++++--
Src/hashtable.c | 3 +++
Src/hist.c | 26 +++++++++++++++++++++++---
Src/options.c | 3 ++-
Src/zsh.h | 2 ++
6 files changed, 55 insertions(+), 7 deletions(-)
diff --git a/Completion/Zsh/Command/_set b/Completion/Zsh/Command/_set
index 27c7f3c7d82a..3b8f4ff87a81 100644
--- a/Completion/Zsh/Command/_set
+++ b/Completion/Zsh/Command/_set
@@ -22,4 +22,4 @@ noglob _arguments -s -S \
{-,+}h[histignoredups] {-,+}i[interactive] {-,+}k[interactivecomments] \
{-,+}l[login] {-,+}m[monitor] {-,+}n[no-exec] {-,+}p[privileged] \
{-,+}r[restricted] {-,+}t[singlecommand] {-,+}u[no-unset] {-,+}v[verbose] \
- {-,+}w[chaselinks] {-,+}x[xtrace] {-,+}y[shwordsplit]
+ {-,+}w[chaselinks] {-,+}x[xtrace] {-,+}y[shwordsplit] {-,+}j[histsavecwd]
diff --git a/Src/builtin.c b/Src/builtin.c
index aa5767cf135f..d48d99b4bc73 100644
--- a/Src/builtin.c
+++ b/Src/builtin.c
@@ -71,7 +71,7 @@ static struct builtin builtins[] =
* But that's actually not useful, so it's more consistent to
* cause an error.
*/
- BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "aAdDe:EfiIlLmnpPrRt:W", NULL),
+ BUILTIN("fc", 0, bin_fc, 0, -1, BIN_FC, "acAdDe:EfiIlLmnpPrRt:W", NULL),
BUILTIN("fg", 0, bin_fg, 0, -1, BIN_FG, NULL, NULL),
BUILTIN("float", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "E:%F:%HL:%R:%Z:%ghlp:%rtux", "E"),
BUILTIN("functions", BINF_PLUSOPTS, bin_functions, 0, -1, 0, "ckmMstTuUWx:z", NULL),
@@ -83,7 +83,7 @@ static struct builtin builtins[] =
BUILTIN("hashinfo", 0, bin_hashinfo, 0, 0, 0, NULL, NULL),
#endif
- BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "adDEfiLmnpPrt:", "l"),
+ BUILTIN("history", 0, bin_fc, 0, -1, BIN_FC, "acdDEfiLmnpPrt:", "l"),
BUILTIN("integer", BINF_PLUSOPTS | BINF_MAGICEQUALS | BINF_PSPECIAL | BINF_ASSIGN, (HandlerFunc)bin_typeset, 0, -1, 0, "HL:%R:%Z:%ghi:%lp:%rtux", "i"),
BUILTIN("jobs", 0, bin_fg, 0, -1, BIN_JOBS, "dlpZrs", NULL),
BUILTIN("kill", BINF_HANDLES_OPTS, bin_kill, 0, -1, 0, NULL, NULL),
@@ -1755,6 +1755,7 @@ fclist(FILE *f, Options ops, zlong first, zlong last,
int fclistdone = 0, xflags = 0;
zlong tmp;
char *s, *tdfmt, *timebuf;
+ const char *cwd = NULL;
Histent ent;
/* reverse range if required */
@@ -1810,7 +1811,26 @@ fclist(FILE *f, Options ops, zlong first, zlong last,
xflags |= HIST_READ;
}
+ if (OPT_ISSET(ops,'c')) {
+ cwd = zgetcwd();
+ }
for (;;) {
+ if (OPT_ISSET(ops,'c') && cwd) {
+ if (!ent->cwd) {
+ /* the entry does not a have a current directory,
+ * these are entries before the user has
+ * activated the histsavecwd feature.
+ */
+ goto next;
+ }
+ if (strcmp(ent->cwd, cwd)) {
+ /* filter out commands not belonging to the
+ * current directory.
+ */
+ goto next;
+ }
+ }
+
if (ent->node.flags & xflags)
s = NULL;
else
@@ -1856,6 +1876,8 @@ fclist(FILE *f, Options ops, zlong first, zlong last,
putc('\n', f);
}
}
+
+next:
/* move on to the next history line, or quit the loop */
if (first < last) {
if (!(ent = down_histent(ent)) || ent->histnum > last)
diff --git a/Src/hashtable.c b/Src/hashtable.c
index e210ddecac1b..d641019cba8e 100644
--- a/Src/hashtable.c
+++ b/Src/hashtable.c
@@ -1453,6 +1453,9 @@ freehistdata(Histent he, int unlink)
if (!(he->node.flags & (HIST_DUP | HIST_TMPSTORE)))
removehashnode(histtab, he->node.nam);
+ if (he->cwd)
+ zsfree(he->cwd);
+
zsfree(he->node.nam);
if (he->nwords)
zfree(he->words, he->nwords*2*sizeof(short));
diff --git a/Src/hist.c b/Src/hist.c
index 5281e87181ac..c3a5372598b3 100644
--- a/Src/hist.c
+++ b/Src/hist.c
@@ -1595,6 +1595,9 @@ hend(Eprog prog)
he = prepnexthistent();
he->node.nam = ztrdup(chline);
+ if (isset(HISTSAVECWD)) {
+ he->cwd = ztrdup(zgetcwd());
+ }
he->stim = time(NULL);
he->ftim = 0L;
he->node.flags = newflags;
@@ -2666,6 +2669,8 @@ readhistfile(char *fn, int err, int readflags)
newflags |= HIST_MAKEUNIQUE;
while (fpos = ftell(in), (l = readhistline(0, &buf, &bufsiz, in))) {
char *pt;
+ char *directory = NULL;
+ char *directory_end = NULL;
int remeta = 0;
if (l < 0) {
@@ -2701,8 +2706,12 @@ readhistfile(char *fn, int err, int readflags)
if (*pt == ':') {
pt++;
- stim = zstrtol(pt, NULL, 0);
+ stim = zstrtol(pt, &pt, 0);
+ if (*pt == ' ' && pt[1] == '/') {
+ directory = &pt[1];
+ }
for (; *pt != ':' && *pt; pt++);
+ directory_end = pt;
if (*pt) {
pt++;
ftim = zstrtol(pt, NULL, 0);
@@ -2745,6 +2754,11 @@ readhistfile(char *fn, int err, int readflags)
he = prepnexthistent();
he->node.nam = ztrdup(pt);
he->node.flags = newflags;
+ if (directory && directory_end) {
+ *directory_end = '\0';
+ he->cwd = ztrdup(directory);
+ *directory_end = ':';
+ }
if ((he->stim = stim) == 0)
he->stim = he->ftim = tim;
else if (ftim < stim)
@@ -2983,8 +2997,14 @@ savehistfile(char *fn, int err, int writeflags)
}
t = start = he->node.nam;
if (extended_history) {
- ret = fprintf(out, ": %ld:%ld;", (long)he->stim,
- he->ftim? (long)(he->ftim - he->stim) : 0L);
+ if (isset(HISTSAVECWD)) {
+ ret = fprintf(out, ": %ld %s:%ld;", (long)he->stim,
+ he->cwd ? he->cwd : zgetcwd(),
+ he->ftim? (long)(he->ftim - he->stim) : 0L);
+ } else {
+ ret = fprintf(out, ": %ld:%ld;", (long)he->stim,
+ he->ftim? (long)(he->ftim - he->stim) : 0L);
+ }
} else if (*t == ':')
ret = fputc('\\', out);
diff --git a/Src/options.c b/Src/options.c
index 48c14c1795f6..7579f2000e0f 100644
--- a/Src/options.c
+++ b/Src/options.c
@@ -157,6 +157,7 @@ static struct optname optns[] = {
{{NULL, "histignorealldups", 0}, HISTIGNOREALLDUPS},
{{NULL, "histignoredups", 0}, HISTIGNOREDUPS},
{{NULL, "histignorespace", 0}, HISTIGNORESPACE},
+{{NULL, "histsavecwd", 0}, HISTSAVECWD},
{{NULL, "histlexwords", 0}, HISTLEXWORDS},
{{NULL, "histnofunctions", 0}, HISTNOFUNCTIONS},
{{NULL, "histnostore", 0}, HISTNOSTORE},
@@ -345,7 +346,7 @@ static short zshletters[LAST_OPT - FIRST_OPT + 1] = {
/* g */ HISTIGNORESPACE,
/* h */ HISTIGNOREDUPS,
/* i */ INTERACTIVE,
- /* j */ 0,
+ /* j */ HISTSAVECWD,
/* k */ INTERACTIVECOMMENTS,
/* l */ LOGINSHELL,
/* m */ MONITOR,
diff --git a/Src/zsh.h b/Src/zsh.h
index 8341428954c0..2f8b2f088d60 100644
--- a/Src/zsh.h
+++ b/Src/zsh.h
@@ -2199,6 +2199,7 @@ struct histent {
* i.e the same format as the original
* entry
*/
+ char *cwd; /* direcory where the command ran */
time_t stim; /* command started time (datestamp) */
time_t ftim; /* command finished time */
short *words; /* Position of words in history */
@@ -2399,6 +2400,7 @@ enum {
HISTIGNOREALLDUPS,
HISTIGNOREDUPS,
HISTIGNORESPACE,
+ HISTSAVECWD,
HISTLEXWORDS,
HISTNOFUNCTIONS,
HISTNOSTORE,
--
2.21.0
Messages sorted by:
Reverse Date,
Date,
Thread,
Author