From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-1.0 required=5.0 tests=DKIM_ADSP_CUSTOM_MED, FREEMAIL_FROM,MAILING_LIST_MULTI,RCVD_IN_DNSWL_NONE autolearn=ham autolearn_force=no version=3.4.2 Received: from primenet.com.au (ns1.primenet.com.au [203.24.36.2]) by inbox.vuxu.org (OpenSMTPD) with ESMTP id 973d1524 for ; Fri, 17 Jan 2020 16:47:13 +0000 (UTC) Received: (qmail 28203 invoked by alias); 17 Jan 2020 16:47:05 -0000 Mailing-List: contact zsh-workers-help@zsh.org; run by ezmlm Precedence: bulk X-No-Archive: yes List-Id: Zsh Workers List List-Post: List-Help: List-Unsubscribe: X-Seq: 45326 Received: (qmail 19756 invoked by uid 1010); 17 Jan 2020 16:47:05 -0000 X-Qmail-Scanner-Diagnostics: from mail-wr1-f48.google.com by f.primenet.com.au (envelope-from , uid 7791) with qmail-scanner-2.11 (clamdscan: 0.102.1/25691. spamassassin: 3.4.2. Clear:RC:0(209.85.221.48):SA:0(-2.0/5.0):. Processed in 4.558938 secs); 17 Jan 2020 16:47:05 -0000 X-Envelope-From: alonid@gmail.com X-Qmail-Scanner-Mime-Attachments: | X-Qmail-Scanner-Zip-Files: | Received-SPF: pass (ns1.primenet.com.au: SPF record at _netblocks.google.com designates 209.85.221.48 as permitted sender) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=tzboOdM/Z2bPlDQUqPqnMWWv2coX9WFG27cjfvENtU0=; b=Cyb0SxNEqa3Yrh1o/9OyIFZyszXAfumlzyrGhuwfU+H/PefZzmDg4Z8A5xd4NDZlQj amNrikkc50YEVd5emnSDBkZzmtnig8qLAvoiO/Q1IODPPkuIDOcCNeF4S4GyTzgwaJLE zInG4DQbG1XL3Or3BzRQz9eItNfk1TWMYThTjXJ8e4eDGww9VzvzTOGVSpe2Gid9OV1t YDwmne+6axzg3U0r4m9d8VvOVgBLg/Dgnl7Qmwai6PJVdNcAsMmqC4BOwssCWqsvlO6Q ZUWI6HxegQQjaDhxTfAyXo3I0iRM0MBDauNt6X6oYXRmyMc5g3Gf1fi0UkXr/WcXAhbl YvPQ== X-Gm-Message-State: APjAAAUbiU22XX7XTwrS1Gn6Bmrxl0jJcJUQKp4PYe40v61FnPmF25fw aooLiNYwoP+rMeqTsVz6X78PhbZgoIM= X-Google-Smtp-Source: APXvYqxlBM+WAAarDwIodhBxrbikq201yX1JKLA1fxbVZkhly5/7V+Hi6EFo0kbwKUr1p1k+G4Lb3Q== X-Received: by 2002:adf:82e7:: with SMTP id 94mr4349146wrc.60.1579279585209; Fri, 17 Jan 2020 08:46:25 -0800 (PST) From: Dan Aloni To: zsh-workers@zsh.org Subject: [PATCH] Per-directory history implementation Date: Fri, 17 Jan 2020 18:46:21 +0200 Message-Id: <20200117164621.2954-1-alonid@gmail.com> X-Mailer: git-send-email 2.21.0 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Qmail-Scanner-2.11: added fake Content-Type header Content-Type: text/plain 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