Reputation: 212845
Is there a way to collapse the current working directory in the zsh prompt in a unique way, so that I could copy and paste it to another terminal, hit TAB and get the original path?
Let's say we have following directories:
/adam/devl
/alice/devl
/alice/docs
/bob/docs
If the prompt is programmed to show the first characters, and I'm in /b/d, then it is unique. On the other hand, /a/d is not unique, so I would need /ad/d, /al/de and /al/do. And even /ali/… as soon as the user alex appears.
Is it possible to hack this directly in zsh or do I need to write a script, that finds the shortest unique beginning of each parent directory?
Thank you for your ideas!
Upvotes: 10
Views: 2356
Reputation: 378
Yes it is possible to collapse the directories to a first-unique-letter path and have the Z Shell expand that path upon pressing [Tab]. I simply used compinstall (zsh utility script installed with Zsh) to generate the following code. The important part to take note of for expanding path elements is on the sixth zstyle
command, near the end, where the bracket of characters to separate completion points with includes the /
, which is, of course, the directory separator. With this, the unique paths you suggested will fully fill out with just one [Tab] press as if the *
were at the end of each path-name unique letters.
# The following lines were added by compinstall
zstyle ':completion:*' add-space true
zstyle ':completion:*' completer _list _expand _complete _ignored _match _correct _approximate _prefix
zstyle ':completion:*' completions 1
zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS}
zstyle ':completion:*' matcher-list 'm:{[:lower:]}={[:upper:]} r:|[._-]=* r:|=*' 'm:{[:lower:]}={[:upper:]} m:{[:lower:][:upper:]}={[:upper:][:lower:]} r:|[._-]=* r:|=*' 'r:|[._-/]=* r:|=*' 'l:|=* r:|=*'
zstyle ':completion:*' match-original both
zstyle :compinstall filename '/home/micah/.zsh/.zshrc'
autoload -Uz compinit
compinit
# End of lines added by compinstall
As for creating the unique path in the first place and inserting it into the prompt, it is possible with a zsh script or function, and therefore should be possible for the completer or line-editor also, but just sticking to the prompt, you would add a function to the $precmd_functions
array that modifies or adds to the PS1
variable. This special array is a list of function names that are run right before each prompt.
function precmd_unique_pwd {
local pwd_string="$(upwd)"
PS1="%B%n@%m $pwd_string => %b"
}
precmd_functions+=( precmd_unique_pwd )
For getting of the current PWD in shortened form, I think this function is clear and easy to follow, though not necessarily optimized for low resource usage.
#!/bin/zsh
function upwd {
emulate -LR zsh -o nullglob
local dir Path
local -a newpwd tmp stack
local -i length=1 Flag=0
newpwd=( ${(s./.)PWD} )
foreach dir ( $newpwd )
(( length=0, Flag=0 ))
repeat $#dir
do
(( length += 1 ))
tmp=( ${(j.*/.)~stack}/$dir[1,$length]*(/) )
if
(( $#tmp == 1 ))
then
Path=$Path/$dir[1,$length]
stack+=( /$dir )
(( Flag=1 ))
break
fi
done
if
(( Flag ))
then
continue
else
Path=$Path/$dir
fi
end
print -- $Path
}
upwd
Notice that it finds unique paths with directory names because of the Zsh feature (/)
at the end of the globbing. On the last directory (current) this means you may match something else if there is a file with the same name plus an extension.
Upvotes: 0
Reputation: 4808
I'm not aware that zsh
has a built-in function of this sort, but it should be pretty easy to script without resorting to a single subshell or slow pipe:
#!/bin/zsh
paths=(${(s:/:)PWD})
cur_path='/'
cur_short_path='/'
for directory in ${paths[@]}
do
cur_dir=''
for (( i=0; i<${#directory}; i++ )); do
cur_dir+="${directory:$i:1}"
matching=("$cur_path"/"$cur_dir"*/)
if [[ ${#matching[@]} -eq 1 ]]; then
break
fi
done
cur_short_path+="$cur_dir/"
cur_path+="$directory/"
done
printf %q "${cur_short_path: : -1}"
echo
This script will output the shortest path needed for auto-completion to work.
You can throw it in your .zshrc
as a function and then run it from any directory.
function spwd {
paths=(${(s:/:)PWD})
cur_path='/'
cur_short_path='/'
for directory in ${paths[@]}
do
cur_dir=''
for (( i=0; i<${#directory}; i++ )); do
cur_dir+="${directory:$i:1}"
matching=("$cur_path"/"$cur_dir"*/)
if [[ ${#matching[@]} -eq 1 ]]; then
break
fi
done
cur_short_path+="$cur_dir/"
cur_path+="$directory/"
done
printf %q "${cur_short_path: : -1}"
echo
}
Here's a video of it in action:
https://asciinema.org/a/0TyL8foqvQ8ec5ZHS3c1mn5LH
Or if you prefer, some sample output:
~/t $ ls
adam alice bob getshortcwd.zsh
~/t $ ls adam
devl
~/t $ ls alice
devl docs
~/t $ spwd
/h/v/t
~/t $ cd adam/devl
~/t/adam/devl $ spwd
/h/v/t/ad/d
~/t/adam/devl $ cd ../../alice/devl
~/t/alice/devl $ spwd
/h/v/t/al/de
~/t/alice/devl $ cd ../docs
~/t/alice/docs $ spwd
/h/v/t/al/do
~/t/alice/docs $ `spwd` [TAB]
~/t/alice/docs $ /h/v/t/al/do [TAB]
~/t/alice/docs $ /home/vsimonian/t/alice/docs
Upvotes: 12