Z – Jump around
github.com
github.com
Eventually I came up with an alternative using fd¹ and fzf². I have a variant of this in my .zshrc:
function n {
cd "$(fd . "${HOME}" --type d --color never | fzf --select-1 --query "${*}")"
ls
}
Call it with `n` and get an interactive fuzzy search for your directories. If you do `n <argument>`, it’ll start the find with `<argument>` already filled in (and if there’s only one match, jump to it directly). The `ls` is optional but I find that I like having the contents visible as soon as I change directories.In my personal setup I’m also including iCloud Drive while excluding the rest of the Library directory³ as that is too noisy. I have a separate `nl` function which searches just inside `~/Library` for when I need it, as well as other specialised `n<char>` functions that search inside specific places that I use frequently.
¹ https://github.com/sharkdp/fd
² https://github.com/junegunn/fzf
³ Change the fd command to: fd . "${HOME}" "${HOME}/Library/Mobile Documents/com~apple~CloudDocs" --exclude '/Library/' --type d --color never
That's a good intermediary solution. But the one that totally changed my flow was to combine autojump[1] and fzf. autojump is similar to Z (this submission). It stores all the directories you've visited in an SQLite DB and can show them to you (ordered by visit frequency) with a command line argument. So I pipe that to fzf.
Now I can extremely quickly jump to any directory I've been to before - it really helps that they're sorted by visit frequency. I honestly use this more than any other approach - and I probably go for days on end without using the usual TAB autocompletion.
I'm going to try out a version of this now.
In fish for example, it's as easy as the following (using zoxide query)
complete -c z -f -k -a "(zoxide query -l)"I've tweaked it to search a lot less folders (depth 5) and to restrict to a pattern beginning by X...
cd "$(fd "^${*}" "${HOME}" --max-depth 4 --type d --color never | fzf --select-1 --query "${*}")"
This makes it super snappy. I probably have waaay too many folders with similar names.ps: out of fun, I wanted to toy with fzf --multi mode
function zzg {
local selection="$(fd . "${HOME}" --type d --color never --max-depth 4 | fzf -m --query "${*}")";
local term="xterm"
for dir in $selection
do
"${term}" -title "zzg({$dir})" -e "cd ${dir} && bash" &
done
}Although, I don't know what the difference is, other than the language of choice.
> A pure-fish port means z is fast and fish-friendly, with tab-completions and lazy-loading. Top that off with great customizability and a small amount of added functionality.
A lot of reinventing the wheel in the z space it seems
Z is really good in this regard, but I still find myself relatively reaching for fzf's Alt+C keybinding, as I outlined in https://andrew-quinn.me/fzf way back. I think I need to come across some killer thing `z` does that I suddenly can't live without to overcome the activation energy.
I use this tiny function:
Z() {
[ $# -gt 0 ] && _z "$*" && return
cd "$(_z -l 2>&1 | fzf --height 40% --nth 2.. --reverse --inline-info +s --tac --query "${*##-* }" | sed 's/^[0-9,.]* *//')"
}e.g. a timetracker tool "squatted" the "t". I'm the one to decide if this tool is that important to get a one-letter. In this case I had to hack around, fork even, to be able to use t (which is my todo.txt stuff: t, tt, ta, tl, tp, etc) and to use the timetracker (b - no idea why I started using the b for this, decades ago: b, bl, bs, etc).
If a developer of a tool decides to "squat" one of the 27 letters in the alphabet, I think they have delusions of grandeur: You are quite certainly not building one of the 27 most important tools ever built.
In this case the app was installed via RBENV, so it works but only if I decide that all of the RBENV stuff must be after anything else, which breaks some other hacks/tricks. So it would work, until I forget about it and years later need some rbenv-installed tool to have precedence over something that was globally installed. After which that alias I forgot about now suddenly breaks.
I hate it when I'm working on A and get distracted because somehow now B fails and some rabbit hole is luring me in. I just want to check my TODO's for today: not fiddle around with $PATHS and aliases.
Aliasing somewhat helps, but is clumsy too:
alias b=t alias t=todotxt alias bl=b list --all
Sure, better organization solves this. But everything would be so much easier if the person writing "t" had stopped and rather just taken 'timetrack', 'ttrack', trac or whatever instead of "t". Somehow we probably won't like it if such a tool suddenly takes over "tr" or "time" either. I feel the same about "t".
26 would be for the English alphabet and therefore the terminal. I was mistaken.
U+0132 LATIN CAPITAL LIGATURE IJ
U+0133 LATIN SMALL LIGATURE IJ
(On the EurKEY layout[1] this is as easy as AltGr + k)See my other comment on combining z and fzf: https://news.ycombinator.com/item?id=39030391
alias .a='cd ~/src/autosqueezer'
alias .g='cd ~/src/gcc-squeeze-plugin'
alias .u='cd ~/src/squeeze-utils'
etc. You have to manage them by hand, but that's easy, and makes the result much more predictable than anything based on history.I also have contextual companions to these, defined as functions, like:
.r() {
local project_root=$(somehow work out the root of the current project)
cd $project_root
}
.b() {
.r
cd build
}``` cd ~/src/autosqueezer wd add a # later, somewhere else wd a ```
`run-help hash` for the basic interface, or zshexpn(1) for the gory details including fancy dynamic naming.
These could be interpolated into commands:
$ cd somewhere # found via CDPATH
/path/to/somewhere
$ cp $cd1/file.txt . # copy file from where we were just before the cd, to here
cd1 would be always the most recent, cd2 second most recent.Here it is up to cd4:
if [ -z "$c1" ] ; then
c4=
c3=
c2=
fi
cdlog_chdir()
{
local cur=$PWD
if command cd "$@" ; then
c4=$c3
c3=$c2
c2=$c1
c1=$cur
fi
}
cdlog_chdir()
{
local cur=$PWD
if command cd "$@" && [ "$PWD" != "$cur" ]; then
# only if we successfully change to a different
# directory do the following
if [ "$cur" == "$c2" ] && [ "$PWD" == "$c1" ] ; then
# Special case: if we changed to the directory that is second
# in the cdlog, then just swap between those two.
c2=$PWD
c1=$cur
else
# Otherwise rotate through all four.
c4=$c3
c3=$c2
c2=$c1
c1=$cur
fi
fi
}
Tip: in bash use ESC Ctrl-E to expand variables in the command line before running it. Then the command is recorded with concrete paths, rather than variables that are changing. You can recall the command from history to repeat it.Handy aliases:
alias cd='cdlog_chdir -P'
alias cs='cdlog_chdir "$c1"'
"cs" goes back to the previous; if that is repeated, it triggers the swapping behavior in cdlog_chdir, hence the "cs" name.The dirs command is a poorly considered interface to what should just be a handful of simple variables.
The whole feature is basically mistake, copied from tcsh in order to help tcsh users switch to Bash or something.
pushd with no arguments swaps between the most recent two. That's an important use case and I nailed that requirement with the "cs" alias.
I just added a function to cdlog_pop aliased to pd, which changes to $c1 and removes it, rotating the entries in the other direction. I extended the history to 9 items (with c5 to c9 being hidden entries not shown by cdlog).
if [ -z "$c1" ] ; then
c9=
c8=
c7=
c6=
c5=
c4=
c3=
c2=
fi
cdlog_chdir()
{
local cur=$PWD
if command cd "$@" && [ "$PWD" != "$cur" ]; then
# only if we successfully change to a different
# directory do the following
if [ "$cur" == "$c2" ] && [ "$PWD" == "$c1" ] ; then
# Special case: if we changed to the directory that is second
# in the cdlog, then just swap between those two.
c2=$PWD
c1=$cur
else
# Otherwise rotate through all
c9=$c8
c8=$c7
c7=$c6
c6=$c5
c5=$c4
c4=$c3
c3=$c2
c2=$c1
c1=$cur
fi
fi
}
cdlog_pop()
{
if [ -n "$c1" ] && command cd "$c1"; then
c1=$c2
c2=$c3
c3=$c4
c4=$c5
c5=$c6
c6=$c7
c7=$c8
c8=$c9
c9=
fi
}
cdlog()
{
# c5 through c9 are hidden
printf "c1: %s\n" "$c1"
printf "c2: %s\n" "$c2"
printf "c3: %s\n" "$c3"
printf "c4: %s\n" "$c4"
}
alias cs='cdlog_chdir "$c1"'
alias cd='cdlog_chdir -P'
alias pd='cdlog_pop'If you're using zsh you can implement the GPs idea by hooking chpwd, so that you don't need to change your muscle memory on cd. You can even combine them by enabling the auto_pushd option and just carrying on with regular cd whilst gaining access to `~-n` with no other changes.
We can "export c1" and the others to make them visible to child processes. That makes it possible to launch a subshell in which the parent shell's directory log is readily available.
Things that can be very effectively and ergonomically achieved with a bit of scripting in the user's environment should never be built into the command interpreter. That just adds to the executable size, documentation, and maintenance.
User names should not begin with a hyphen¹, so it shouldn't interfere with home directory references.
The child process point is an interesting one, I feel like you could workaround it by using a chpwd hook to export $dirstack and set it at startup(or a precmd function and $DIRSTACK if using bash). Something like `export DIRSTACK=${(pj#:#)dirstack}` in the hook and `dirstack=(${(ps#:#)DIRSTACK})` in the startup file. I'll concede this point though, as while a fun thought experiment the end result would be ghastly ;)
¹ https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1...
Also, I can shorten these variables to one letter, like $p, $q, ...
Bash has an option so that tab completion will expand out the directory.
shopt -s direxpand
this is useful because variables like $c1 or syntax like ~-1 entered into the history is not useful for recall.I know about M-x C-e to expand everything, but that is far too clumsy compared to just the tab completion doing it.
Here is something: the direxpand option is not working for ~-n/[Tab] completions. Only for variable names!
$ cd ~-
0 -- ~
1 -- ~/Downloads
2 -- ~/Desktop
The handling of tilde expansion under bash is a readline option, so users would configure it in their inputrc. It is pretty useful in general, because then it is also available to tools like imv¹ beyond just bash. Unfortunately, as it is handled separately from word completion you'd need to change the completion function to make it match the mark-directories behaviour you like.If I wanted the text expanded for history use I'd probably expand it before writing using a zshaddhistory hook, but I don't think there is an equivalent in bash so changing the line buffer probably is the best option.
https://github.com/tkellogg/Jump-Location
Which was fun and all, but eventually replaced by a pure PowerShell implementation that's become far more active:
My "pcd" would similarly rewrite the parent component instead. If you're in "a/b/c" and do "pcd x" you change to "a/x/c"; and "pcd x/y" would change to "a/x/y/c". Useful for when you have some similar directories with parallel structures.
I gave it a numeric argument defaulting to 1. "pcd 0" is like cd, "pcd 1" is like pcd, and then higher integer switch higher components.
These days I use https://github.com/gsamokovarov/jump which I've mapped to `z`. Happy days.
99% of the time it gets it right, 0.9% of the time it's ambiguous, and only a handful of times did it find something I didn't intend (shocking twist: I had misspelled something).
I built my own tool called Zee, that takes a simplified approach by only utilizing explicit user-defined shortcuts: https://github.com/dnsv/zee
It is a must have
I wonder if anyone can make a comparison. From an usage perspective, they seem equivalent. cdr's configuration is more zsh-like.
Just wondering if that is why the hotkey for the jump action in Baldur's Gate 3 is z
The zero flag stores a status from the last instruction the cpu ran, which the jump cpu instructions then reference to affect what happens to code execution. This logic is the most basic form of conditionals, and underpins the implementations of higher level flow control like if statements and loops.
I’d guess that this relationship between Z and jumping is where it comes from. As for Baulders Gate I have no idea, but lean to that being a coincidence.
I assume Windows Linux thing uses Bash? It works even more simply, exactly like $PATH.
As an example, say I have ~/Programs/Firefox/Profiles/kdskdj/Cache.
Most people would put just ~/Programs or ~/Programs/Firefox into CDPATH. If you want to get to the Cache directory, you still need to know "Profiles/kdskdj/Cache". With z, if you're lucky, you'll just do "z Cache". Worst case, "z Fire Cache"
So that is to say, when I type "cd foo", I want foo to be "cd alias" for some "/path/to/foo" or possibly even "/path/to/bar". I know where I want to go; I don't need a search to find it!
Fortunately, there is a way to get that with CDPATH. You can make some directory, say /home/yourself/.cdfarm.
Inside .cdfarm/, you have symlinks to various directories
foo -> /path/to/foo bar -> /path/to/elsewhere/bar
and you put this .cdfarm into your CDPATH:
CDPATH=:~/.cdfarm
and that's it, all other management is via ~/.cdfarm.
Unfortunately, the symlinks appear in the PWD, unless you use "cd -P"; for that you can "alias cd='cd -P".
Even if you use -P, when Bash's cd resolves something via CDPATH, it prints the target path name with the /home/yourself/.cdpath/... in it, as it appears before -P resolution. That's not necessarily a bad thing; it reminds you of how you are getting to where you are.