z.lua 是一个很好的工具,可以快速地进入想要的目录。我在本机 WSL 和多个服务器上都安装了这个程序,但是在执行一些命令(例如 z -c,在当前目录下的子目录中选择)的时候,自动补全有时候可以用,有时候不能用,很不方便。所以我决定自己写一个 zsh 的自动补全脚本。

实现

首先,通过 which z 命令拿到实际的命令执行函数:

image

z.lua 脚本中给出了如何对 z 命令进行补全的方法:

1
2
3
4
5
6
7
8
9
10
_zlua_zsh_tab_completion() {
# tab completion
(( $+compstate )) && compstate[insert]=menu # no expand
local -a tmp=(${(f)"$(_zlua --complete "${words/_zlua/z}")"})
_describe "directory" tmp -U
}
if [ "${+functions[compdef]}" -ne 0 ]; then
compdef _zlua_zsh_tab_completion _zlua 2> /dev/null
compdef ${_ZL_CMD:-z}=_zlua
fi

其中:

  • _zlua_zsh_tab_completion 是自动补全函数;
  • ${(f)"$(_zlua --complete "${words/_zlua/z}")"} 是获取补全的内容,并且按照每行分割转化为数组;
  • _describe 是定义补全的内容,第一个参数是描述,第二个参数是补全的内容;
  • compdef 是定义自动补全函数,第一个参数是自动补全函数,第二个参数是实际的命令执行函数。

参照上面的内容写了一个自动补全脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
_z_comp() {
local -a list=(${(f)"$(_zlua --complete "${words/_zlua/z}")"})
_describe "directory" list -U
}
_zc_comp() {
local -a list=("${(@f)$(fdfind -t d -d 1 .)}")
for ((i=1;i<=$#list;i++)){
list[i]=${list[i]/\//}
}
_describe "subdirectory" list -U
}
_zlua_comp() {
_arguments -C -S -s \
'*:enter directory order by frecent:_z_comp' \
'-c:enter subdirectory:_zc_comp'
}
compdef _zlua_comp _zlua > /dev/null 2>&1
compdef ${_ZL_CMD:-z}=_zlua

首先定义了两个补全函数 _z_comp_zc_comp,分别对应 zz -c 命令的补全内容。然后定义了一个 _zlua_comp 函数,通过 _arguments 定义了命令的参数和补全内容。最后通过 compdef 定义了自动补全函数。需要注意的是,_zc_comp 函数中得到的子目录是带有 / 的,所以需要去掉 /,否则可能会进入到所选择子目录下的深度更深的目录:

image

最后在脚本末尾加入:

1
autoload -Uz compinit && compinit

这样就可以实现自动补全:

image

FZF

如果想要使用 fzf,同样可以参考 z.lua 中的 script_fzf_complete_zsh 脚本,一个示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bindkey '\e[0n' kill-whole-line
_z_comp() {
local list=$(_zlua -l ${words[2,-1]})
if [ -n "$list" ]; then
local selected=$(print $list | ${=zlua_fzf} | sed 's/^[0-9,.]* *//')
if [ -n "$selected" ]; then
cd ${selected}
printf '\e[5n'
fi
fi
}
_zc_comp() {
local -a list=("${(@f)$(fdfind -t d -d 1 .)}")
for ((i=1;i<=$#list;i++)){
list[i]=${list[i]/\//}
}
if [ -n "$list" ]; then
local selected=$(print $list | awk -F " " '{for(row=1;row<=NF;row++) print $row;}' | ${=zlua_fzf})
if [ -n "$selected" ]; then
cd ${selected}
printf '\e[5n'
fi
fi
}
_zlua_comp() {
_arguments -C -S -s \
'*:enter directory order by frecent:_z_comp' \
'-c:enter subdirectory:_zc_comp'
}
compdef _zlua_comp _zlua > /dev/null 2>&1
compdef ${_ZL_CMD:-z}=_zlua

参考资料

  1. Completion System
  2. 给 zsh 自定义命令添加参数自动补全
  3. 命令行自动补全原理
  4. A Guide to the Zsh Completion with Examples