#autocomplete #zsh #tab-completion #zsh-completion
#автозаполнение #zsh #вкладка-завершение #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
Если первая часть пути не является родительским каталогом, он будет работать как обычный компакт-диск. Я надеюсь, что это имеет смысл.
В итоге, когда в /a / b / c / d, я хочу иметь возможность перемещаться в /a, /a / b, / a / b / c, все подкаталоги / a / b / c / d и любой абсолютный путь, начинающийся с /, ~/ или ../ (или ./). Я надеюсь, что это имеет смысл.
Это функция, которую я написал:
cl () {
local first=$( echo $1 | 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 $1
else
# Otherwise, move to a parent directory
cd ${PWD%/$first/*}/$1
fi
}
Вероятно, есть лучший способ для этого (советы приветствуются), но у меня пока не было никаких проблем с этим.
Теперь я хочу добавить автозаполнение. Это то, что у меня есть до сих пор:
_cl() {
pth=${words[2]}
opts=""
new=${pth##*/}
[[ "$pth" != *"/"*"/"* ]] amp;amp; 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 ] amp;amp; [[ "$d" != "." ]] amp;amp; [[ "$d" != ".." ]]; then
opts ="$first$middle$d/ "
fi
done
fi
_multi_parts / "(${opts})"
return 0
}
compdef _cl cl
Опять же, вероятно, не лучший способ сделать это, но он работает… вроде того.
Одна из проблем заключается в том, что то, что я набираю cl ~/, заменяет его на cl ~/ и не предлагает никаких каталогов в моей домашней папке. Есть ли способ заставить это работать?
Редактировать
cl () {
local first=$( echo $1 | cut -d/ -f1 )
if [ $# -eq 0 ]; then
# cl without any arguments moves back to the previous directory
local pwd_bu=$PWD
[[ $(dirs) == "~" ]] amp;amp; return 1
while [[ $PWD == $pwd_bu ]]; do
popd >/dev/null
done
local pwd_nw=$PWD
[[ $(dirs) != "~" ]] amp;amp; popd >/dev/null
pushd $pwd_bu >/dev/null
pushd $pwd_nw >/dev/null
elif [ -d $first ]; then
pushd $1 >/dev/null # If the first argument is an existing normal directory, move there
else
pushd ${PWD%/$first/*}/$1 >/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" != *"/"*"/"* ]] amp;amp; 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 ] amp;amp; [[ "$d" != "." ]] amp;amp; [[ "$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.
Есть ли способ исправить (некоторые из этих) проблем? Надеюсь, вы, ребята, понимаете мои вопросы. Мне трудно объяснить проблему.
Спасибо за вашу помощь!
Комментарии:
1. Я обновил свой ответ. Пожалуйста, посмотрите.
Ответ №1:
Это должно сделать это:
_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 $1 || -d $PWD/$1 ]]; then
# If the argument is an existing absolute path or direct child, move there.
cd $1
else
# Get the longest prefix that ends with the argument.
local ancestor=${(M)${PWD:h}##*$1}
if [[ -d $ancestor ]]; then
# Move there, if it's an existing dir.
cd $ancestor
else
# Otherwise, print to stderr and return false.
print -u2 "$0: no such ancestor '$1'"
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 завершит этот путь для вас на каждом этапе пути.
Комментарии:
1. Идея умная, но если пользователь уже установил для своего cdpath фиксированное значение, это приведет к разрушению его настроек.
2. @MarlonRichet : Проблема в том, что вам нужно изменить эту функцию, чтобы применить изменение. В типичном примере мы настроили вашу функцию где-то в .zshrc, и после некоторого времени ее использования пользователь решает установить явное
cdpath
на лету. Функция не знает об этом изменении. Это будет работать, только если пользователь решит установить cdpath по умолчанию во время определения функции, а не изменять его впоследствии.3. @user1934428
cdr
zsh-autocomplete
Для этого я использую plus. Таким образом, мне не нужно ничего настраивать. Я могу просто ввести любую часть любого каталога, который я недавно посетил, нажать Tab, и он завершит весь путь для меня.4. @user1934428
cdr
поддерживает последние каталоги в текстовом файле, поэтому они сохраняются между сеансами, и вы можете вручную добавлять новые каталоги в файл (что также возможно из командной строки).5. @Tijn Но в конце
cl()
написано: «В противном случае перейдите в родительский каталог или дочерний каталог этого родительского каталога». Таким образом, дочерние элементы родительских каталогов должны быть допустимыми целями. Тогда почему вы не хотите их завершать?