不能在 zsh 自动补全中使用 '~'
can't use '~' in zsh autocompletion
我用的是zsh,想用自己写的一个函数来代替cd。
此功能使您能够移动到父目录:
$ pwd
/a/b/c/d
$ cl b
$ pwd
/a/b
您也可以移动到父目录的子目录:
$ pwd
/a/b/c/d
$ cl b/e
$ pwd
/a/b/e
如果路径的第一部分不是父目录,它将像普通的 cd 一样工作。我希望这是有道理的。
总而言之,当在/a/b/c/d时,我希望能够移动到/a、/a/b、/a/b/c、/a/b/c/d的所有子目录和任何以 /、~/ 或 ../(或 ./)开头的绝对路径。
我希望这是有道理的。
这是我写的函数:
cl () {
local first=$( echo | cut -d/ -f1 )
if [ $# -eq 0 ]; then
# cl without any arguments moves back to the previous directory
cd - > /dev/null
elif [ -d $first ]; then
# If the first argument is an existing normal directory, move there
cd
else
# Otherwise, move to a parent directory
cd ${PWD%/$first/*}/
fi
}
可能有更好的方法(欢迎提供提示),但到目前为止我还没有遇到任何问题。
现在我想添加自动完成功能。这是我目前所拥有的:
_cl() {
pth=${words[2]}
opts=""
new=${pth##*/}
[[ "$pth" != *"/"*"/"* ]] && middle="" || middle="${${pth%/*}#*/}/"
if [[ "$pth" != *"/"* ]]; then
# If this is the start of the path
# In this case we should also show the parent directories
opts+=" "
first=""
d="${${PWD#/}%/*}/"
opts+="${d//\/// }"
dir=$PWD
else
first=${pth%%/*}
if [[ "$first" == "" ]]; then
# path starts with "/"
dir="/$middle"
elif [[ "$first" == "~" ]]; then
# path starts with "~/"
dir="$HOME/$middle"
elif [ -d $first ]; then
# path starts with a directory in the current directory
dir="$PWD/$first/$middle"
else
# path starts with parent directory
dir=${PWD%/$first/*}/$first/$middle
fi
first=$first/
fi
# List al sub directories of the $dir directory
if [ -d "$dir" ]; then
for d in $(ls -a $dir); do
if [ -d $dir/$d ] && [[ "$d" != "." ]] && [[ "$d" != ".." ]]; then
opts+="$first$middle$d/ "
fi
done
fi
_multi_parts / "(${opts})"
return 0
}
compdef _cl cl
同样,这可能不是最好的方法,但它确实有效……有点。
其中一个问题是我键入 cl ~/ 时,它会用 cl ~/ 替换它,并且不会在我的主文件夹中提示任何目录。有没有办法让它发挥作用?
编辑
cl () {
local first=$( echo | cut -d/ -f1 )
if [ $# -eq 0 ]; then
# cl without any arguments moves back to the previous directory
local pwd_bu=$PWD
[[ $(dirs) == "~" ]] && return 1
while [[ $PWD == $pwd_bu ]]; do
popd >/dev/null
done
local pwd_nw=$PWD
[[ $(dirs) != "~" ]] && popd >/dev/null
pushd $pwd_bu >/dev/null
pushd $pwd_nw >/dev/null
elif [ -d $first ]; then
pushd >/dev/null # If the first argument is an existing normal directory, move there
else
pushd ${PWD%/$first/*}/ >/dev/null # Otherwise, move to a parent directory or a child of that parent directory
fi
}
_cl() {
_cd
pth=${words[2]}
opts=""
new=${pth##*/}
local expl
# Generate the visual formatting and store it in `$expl`
_description -V ancestor-directories expl 'ancestor directories'
[[ "$pth" != *"/"*"/"* ]] && middle="" || middle="${${pth%/*}#*/}/"
if [[ "$pth" != *"/"* ]]; then
# If this is the start of the path
# In this case we should also show the parent directories
local ancestor=$PWD:h
while (( $#ancestor > 1 )); do
# -f: Treat this as a file (incl. dirs), so you get proper highlighting.
# -Q: Don't quote (escape) any of the characters.
# -W: Specify the parent of the dir we're adding.
# ${ancestor:h}: The parent ("head") of $ancestor.
# ${ancestor:t}: The short name ("tail") of $ancestor.
compadd "$expl[@]" -fQ -W "${ancestor:h}/" - "${ancestor:t}"
# Move on to the next parent.
ancestor=$ancestor:h
done
else
# $first is the first part of the path the user typed in.
# it it is part of the current direoctory, we know the user is trying to go back to a directory
first=${pth%%/*}
# $middle is the rest of the provided path
if [ ! -d $first ]; then
# path starts with parent directory
dir=${PWD%/$first/*}/$first
first=$first/
# List all sub directories of the $dir/$middle directory
if [ -d "$dir/$middle" ]; then
for d in $(ls -a $dir/$middle); do
if [ -d $dir/$middle/$d ] && [[ "$d" != "." ]] && [[ "$d" != ".." ]]; then
compadd "$expl[@]" -fQ -W $dir/ - $first$middle$d
fi
done
fi
fi
fi
}
compdef _cl cl
这是我自己得到的。它确实有效(有点)但有几个问题:
- 返回父目录时,完成大部分工作。但是当你转到 paretn 目录的子目录时,建议是错误的(它们显示你输入的完整路径,而不仅仅是子目录)。结果确实有效
- 我使用语法高亮,但我输入的路径是白色的(当使用转到父目录时。普通的 cd 功能是彩色的)
- 在我的 zshrc 中,我有一行:
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' '+l:|=* r:|=*'
使用 cd 这意味着我可以键入“加载”,它将完成“下载”。对于 cl,这是行不通的。使用普通的 cd 功能时不是事件。
有没有办法解决(其中一些)问题?
我希望你们能理解我的问题。我觉得很难解释这个问题。
感谢您的帮助!
应该这样做:
_cl() {
# Store the number of matches generated so far.
local -i nmatches=$compstate[nmatches]
# Call the built-in completion for `cd`. No need to reinvent the wheel.
_cd
# ${PWD:h}: The parent ("head") of the present working dir.
local ancestor=$PWD:h expl
# Generate the visual formatting and store it in `$expl`
# -V: Don't sort these items; show them in the order we add them.
_description -V ancestor-directories expl 'ancestor directories'
while (( $#ancestor > 1 )); do
# -f: Treat this as a file (incl. dirs), so you get proper highlighting.
# -W: Specify the parent of the dir we're adding.
# ${ancestor:h}: The parent ("head") of $ancestor.
# ${ancestor:t}: The short name ("tail") of $ancestor.
compadd "$expl[@]" -f -W ${ancestor:h}/ - $ancestor:t
# Move on to the next parent.
ancestor=$ancestor:h
done
# Return true if we've added any matches.
(( compstate[nmatches] > nmatches ))
}
# Define the function above as generating completions for `cl`.
compdef _cl cl
# Alternatively, instead of the line above:
# 1. Create a file `_cl` inside a dir that's in your `$fpath`.
# 2. Paste the _contents_ of the function `_cl` into this file.
# 3. Add `#compdef cl` add the top of the file.
# `_cl` will now get loaded automatically when you run `compinit`.
此外,我会像这样重写您的 cl
函数,因此它不再依赖于 cut
或其他外部命令:
cl() {
if (( $# == 0 )); then
# `cl` without any arguments moves back to the previous directory.
cd -
elif [[ -d || -d $PWD/ ]]; then
# If the argument is an existing absolute path or direct child, move there.
cd
else
# Get the longest prefix that ends with the argument.
local ancestor=${(M)${PWD:h}##*}
if [[ -d $ancestor ]]; then
# Move there, if it's an existing dir.
cd $ancestor
else
# Otherwise, print to stderr and return false.
print -u2 "[=11=]: no such ancestor ''"
return 1
fi
fi
}
备选方案
有一种更简单的方法可以做到所有,无需编写cd
替换或任何完成代码:
cdpath() {
# `$PWD` is always equal to the present working directory.
local dir=$PWD
# In addition to searching all children of `$PWD`, `cd` will also search all
# children of all of the dirs in the array `$cdpath`.
cdpath=()
# Add all ancestors of `$PWD` to `$cdpath`.
while (( $#dir > 1 )); do
# `:h` is the direct parent.
dir=$dir:h
cdpath+=( $dir )
done
}
# Run the function above whenever we change directory.
add-zsh-hook chpwd cdpath
Zsh 的 cd
完成代码自动将 $cdpath
考虑在内。甚至不需要配置它。 :)
作为其工作原理的示例,假设您在 /Users/marlon/.zsh/prezto/modules/history-substring-search/external/
。
- 您现在可以键入
cd pre
并按 Tab,Zsh 会将其完成为 cd prezto
。之后,按 Enter 将直接带您到 /Users/marlon/.zsh/prezto/
。
- 或者假设也存在
/Users/marlon/.zsh/prezto/modules/prompt/external/agnoster/
。当你在前一个目录时,你可以执行 cd prompt/external/agnoster
直接转到后者,Zsh 会为你完成这条路径的每一步。
我用的是zsh,想用自己写的一个函数来代替cd。 此功能使您能够移动到父目录:
$ pwd
/a/b/c/d
$ cl b
$ pwd
/a/b
您也可以移动到父目录的子目录:
$ pwd
/a/b/c/d
$ cl b/e
$ pwd
/a/b/e
如果路径的第一部分不是父目录,它将像普通的 cd 一样工作。我希望这是有道理的。
总而言之,当在/a/b/c/d时,我希望能够移动到/a、/a/b、/a/b/c、/a/b/c/d的所有子目录和任何以 /、~/ 或 ../(或 ./)开头的绝对路径。 我希望这是有道理的。
这是我写的函数:
cl () {
local first=$( echo | cut -d/ -f1 )
if [ $# -eq 0 ]; then
# cl without any arguments moves back to the previous directory
cd - > /dev/null
elif [ -d $first ]; then
# If the first argument is an existing normal directory, move there
cd
else
# Otherwise, move to a parent directory
cd ${PWD%/$first/*}/
fi
}
可能有更好的方法(欢迎提供提示),但到目前为止我还没有遇到任何问题。
现在我想添加自动完成功能。这是我目前所拥有的:
_cl() {
pth=${words[2]}
opts=""
new=${pth##*/}
[[ "$pth" != *"/"*"/"* ]] && middle="" || middle="${${pth%/*}#*/}/"
if [[ "$pth" != *"/"* ]]; then
# If this is the start of the path
# In this case we should also show the parent directories
opts+=" "
first=""
d="${${PWD#/}%/*}/"
opts+="${d//\/// }"
dir=$PWD
else
first=${pth%%/*}
if [[ "$first" == "" ]]; then
# path starts with "/"
dir="/$middle"
elif [[ "$first" == "~" ]]; then
# path starts with "~/"
dir="$HOME/$middle"
elif [ -d $first ]; then
# path starts with a directory in the current directory
dir="$PWD/$first/$middle"
else
# path starts with parent directory
dir=${PWD%/$first/*}/$first/$middle
fi
first=$first/
fi
# List al sub directories of the $dir directory
if [ -d "$dir" ]; then
for d in $(ls -a $dir); do
if [ -d $dir/$d ] && [[ "$d" != "." ]] && [[ "$d" != ".." ]]; then
opts+="$first$middle$d/ "
fi
done
fi
_multi_parts / "(${opts})"
return 0
}
compdef _cl cl
同样,这可能不是最好的方法,但它确实有效……有点。
其中一个问题是我键入 cl ~/ 时,它会用 cl ~/ 替换它,并且不会在我的主文件夹中提示任何目录。有没有办法让它发挥作用?
编辑
cl () {
local first=$( echo | cut -d/ -f1 )
if [ $# -eq 0 ]; then
# cl without any arguments moves back to the previous directory
local pwd_bu=$PWD
[[ $(dirs) == "~" ]] && return 1
while [[ $PWD == $pwd_bu ]]; do
popd >/dev/null
done
local pwd_nw=$PWD
[[ $(dirs) != "~" ]] && popd >/dev/null
pushd $pwd_bu >/dev/null
pushd $pwd_nw >/dev/null
elif [ -d $first ]; then
pushd >/dev/null # If the first argument is an existing normal directory, move there
else
pushd ${PWD%/$first/*}/ >/dev/null # Otherwise, move to a parent directory or a child of that parent directory
fi
}
_cl() {
_cd
pth=${words[2]}
opts=""
new=${pth##*/}
local expl
# Generate the visual formatting and store it in `$expl`
_description -V ancestor-directories expl 'ancestor directories'
[[ "$pth" != *"/"*"/"* ]] && middle="" || middle="${${pth%/*}#*/}/"
if [[ "$pth" != *"/"* ]]; then
# If this is the start of the path
# In this case we should also show the parent directories
local ancestor=$PWD:h
while (( $#ancestor > 1 )); do
# -f: Treat this as a file (incl. dirs), so you get proper highlighting.
# -Q: Don't quote (escape) any of the characters.
# -W: Specify the parent of the dir we're adding.
# ${ancestor:h}: The parent ("head") of $ancestor.
# ${ancestor:t}: The short name ("tail") of $ancestor.
compadd "$expl[@]" -fQ -W "${ancestor:h}/" - "${ancestor:t}"
# Move on to the next parent.
ancestor=$ancestor:h
done
else
# $first is the first part of the path the user typed in.
# it it is part of the current direoctory, we know the user is trying to go back to a directory
first=${pth%%/*}
# $middle is the rest of the provided path
if [ ! -d $first ]; then
# path starts with parent directory
dir=${PWD%/$first/*}/$first
first=$first/
# List all sub directories of the $dir/$middle directory
if [ -d "$dir/$middle" ]; then
for d in $(ls -a $dir/$middle); do
if [ -d $dir/$middle/$d ] && [[ "$d" != "." ]] && [[ "$d" != ".." ]]; then
compadd "$expl[@]" -fQ -W $dir/ - $first$middle$d
fi
done
fi
fi
fi
}
compdef _cl cl
这是我自己得到的。它确实有效(有点)但有几个问题:
- 返回父目录时,完成大部分工作。但是当你转到 paretn 目录的子目录时,建议是错误的(它们显示你输入的完整路径,而不仅仅是子目录)。结果确实有效
- 我使用语法高亮,但我输入的路径是白色的(当使用转到父目录时。普通的 cd 功能是彩色的)
- 在我的 zshrc 中,我有一行:
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' '+l:|=* r:|=*'
使用 cd 这意味着我可以键入“加载”,它将完成“下载”。对于 cl,这是行不通的。使用普通的 cd 功能时不是事件。
有没有办法解决(其中一些)问题? 我希望你们能理解我的问题。我觉得很难解释这个问题。
感谢您的帮助!
应该这样做:
_cl() {
# Store the number of matches generated so far.
local -i nmatches=$compstate[nmatches]
# Call the built-in completion for `cd`. No need to reinvent the wheel.
_cd
# ${PWD:h}: The parent ("head") of the present working dir.
local ancestor=$PWD:h expl
# Generate the visual formatting and store it in `$expl`
# -V: Don't sort these items; show them in the order we add them.
_description -V ancestor-directories expl 'ancestor directories'
while (( $#ancestor > 1 )); do
# -f: Treat this as a file (incl. dirs), so you get proper highlighting.
# -W: Specify the parent of the dir we're adding.
# ${ancestor:h}: The parent ("head") of $ancestor.
# ${ancestor:t}: The short name ("tail") of $ancestor.
compadd "$expl[@]" -f -W ${ancestor:h}/ - $ancestor:t
# Move on to the next parent.
ancestor=$ancestor:h
done
# Return true if we've added any matches.
(( compstate[nmatches] > nmatches ))
}
# Define the function above as generating completions for `cl`.
compdef _cl cl
# Alternatively, instead of the line above:
# 1. Create a file `_cl` inside a dir that's in your `$fpath`.
# 2. Paste the _contents_ of the function `_cl` into this file.
# 3. Add `#compdef cl` add the top of the file.
# `_cl` will now get loaded automatically when you run `compinit`.
此外,我会像这样重写您的 cl
函数,因此它不再依赖于 cut
或其他外部命令:
cl() {
if (( $# == 0 )); then
# `cl` without any arguments moves back to the previous directory.
cd -
elif [[ -d || -d $PWD/ ]]; then
# If the argument is an existing absolute path or direct child, move there.
cd
else
# Get the longest prefix that ends with the argument.
local ancestor=${(M)${PWD:h}##*}
if [[ -d $ancestor ]]; then
# Move there, if it's an existing dir.
cd $ancestor
else
# Otherwise, print to stderr and return false.
print -u2 "[=11=]: no such ancestor ''"
return 1
fi
fi
}
备选方案
有一种更简单的方法可以做到所有,无需编写cd
替换或任何完成代码:
cdpath() {
# `$PWD` is always equal to the present working directory.
local dir=$PWD
# In addition to searching all children of `$PWD`, `cd` will also search all
# children of all of the dirs in the array `$cdpath`.
cdpath=()
# Add all ancestors of `$PWD` to `$cdpath`.
while (( $#dir > 1 )); do
# `:h` is the direct parent.
dir=$dir:h
cdpath+=( $dir )
done
}
# Run the function above whenever we change directory.
add-zsh-hook chpwd cdpath
Zsh 的 cd
完成代码自动将 $cdpath
考虑在内。甚至不需要配置它。 :)
作为其工作原理的示例,假设您在 /Users/marlon/.zsh/prezto/modules/history-substring-search/external/
。
- 您现在可以键入
cd pre
并按 Tab,Zsh 会将其完成为cd prezto
。之后,按 Enter 将直接带您到/Users/marlon/.zsh/prezto/
。 - 或者假设也存在
/Users/marlon/.zsh/prezto/modules/prompt/external/agnoster/
。当你在前一个目录时,你可以执行cd prompt/external/agnoster
直接转到后者,Zsh 会为你完成这条路径的每一步。