нельзя использовать '~' в автозаполнении 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 $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" != *"/"*"/"* ]] && 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 $1 | 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 $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" != *"/"*"/"* ]] && 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:|=*'

С компакт-диском это означает, что я могу набрать load, и он завершится загрузкой. С cl это не работает. Не событие при использовании обычной функциональности компакт-диска.

Есть ли способ исправить (некоторые из этих) проблем? Надеюсь, вы понимаете мои вопросы. Мне трудно объяснить проблему.

Спасибо за вашу помощь!


person user5328080    schedule 21.10.2020    source источник
comment
Я обновил свой ответ. Пожалуйста, посмотрите.   -  person Marlon Richert    schedule 27.10.2020


Ответы (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 завершит этот путь для вас на каждом этапе пути.
person Marlon Richert    schedule 21.10.2020
comment
Идея умная, но если пользователь уже установил для своего cdpath фиксированное значение, это уничтожит его настройки. - person user1934428; 22.10.2020
comment
@MarlonRichet: проблема в том, что вам нужно изменить эту функцию, чтобы применить изменение. В типичном примере мы настроили вашу функцию где-то в .zshrc, и после некоторого времени ее использования пользователь решает на лету установить явное cdpath. Функция не осведомлена об этом изменении. Только если пользователь решит установить cdpath по умолчанию во время определения функции, а не изменять его впоследствии, это сработает. - person user1934428; 22.10.2020
comment
Это первый раз, когда я вижу хорошее применение для $cdpath. Я сомневаюсь, что это обычное явление для большинства пользователей. Но не стесняйтесь предлагать обходной путь или улучшение функции. - person Marlon Richert; 22.10.2020
comment
Вау, я не знал, что это возможно. Это не то, что я имел в виду. Я отредактировал свой вопрос. Я надеюсь, что теперь это более ясно. - person user5328080; 22.10.2020
comment
@MarlonRichert: я использую cdpath для быстрого переключения между каталогами подкаталогов. Вместо cd ~/testdata/testcases/tc1 у меня есть CDPATH=~/testdata/testcases, и я просто делаю cd tc1. - person user1934428; 23.10.2020
comment
@user1934428 user1934428 Я использую cdr плюс zsh-autocomplete для этого. Таким образом, мне не нужно ничего настраивать. Я могу просто ввести любую часть любого каталога, который я недавно посещал, нажать Tab, и он завершит весь путь за меня. - person Marlon Richert; 23.10.2020
comment
@Tijn Я не вижу никакой разницы с вашим первоначальным вопросом. Что именно вы хотите знать? - person Marlon Richert; 23.10.2020
comment
@MarlonRichert: я посмотрю на cdr. Никогда не слышал об этом раньше. Насколько я понимаю, это работает только в том случае, если вы ранее были там cd'ed вручную, в то время как с cdpath я могу установить для своих каталогов значение по умолчанию, которое уже полезно после входа в систему, но, конечно, это менее гибко, поскольку мне нужно управлять списком вручную. - person user1934428; 23.10.2020
comment
@MarlonRichert, правда. вопрос тот же, с разными примерами. разница между вашей функцией и моей в том, что моя предлагает только все родительские каталоги и подкаталоги текущего каталога, тогда как ваша дает все подкаталоги всех родительских каталогов и все подкаталоги текущего каталога. Это действительно хорошее решение, и, возможно, я собираюсь попробовать его, но мне бы очень хотелось, чтобы автозаполнение работало для функции, которую я написал (которая работает). Таким образом, zsh не предлагает больше каталогов, чем мне нужно. - person user5328080; 23.10.2020
comment
@Tijn А, теперь я понял. Спасибо за объяснение. - person Marlon Richert; 24.10.2020
comment
@ user1934428 cdr сохраняет последние каталоги в текстовом файле, поэтому они сохраняются между сеансами, и вы можете вручную добавлять новые каталоги в файл (что также возможно из командной строки). - person Marlon Richert; 24.10.2020
comment
@Tijn Но в конце cl() говорится: в противном случае перейдите в родительский каталог или дочерний каталог этого родительского каталога. Таким образом, дети родительских каталогов должны быть действительными целями. Тогда почему бы вам не захотеть их завершить? - person Marlon Richert; 24.10.2020
comment
@MarlonRichert, нет, я имел в виду то, что описал во втором примере (cl b/e). просто cl e не должен работать. Я получаю путаницу, и я подумаю о лучшем способе выразить это - person user5328080; 24.10.2020
comment
После некоторого дальнейшего тестирования я заметил, что не могу перейти в подкаталог родительского каталога (и он не предлагает эти каталоги). См. второй пример. - person user5328080; 15.12.2020
comment
@user5328080 user5328080 С какой стати у вас #!/bin/bash вверху файла? Это приведет ко всякой ерунде. - person Marlon Richert; 18.12.2020
comment
@MarlonRichert, например? Однажды мне сказали использовать его, но, должен признаться, я мало что знаю об этом и не знаю причин, чтобы не использовать его. - person user5328080; 19.12.2020
comment
Эта строка указывает оболочке запустить ваш код с помощью Bash. Вам нужно либо пропустить его (если вы в любом случае получаете файл только из Zsh), либо заменить его на #!/bin/zsh (или любой другой правильный путь к zsh на вашем компьютере). - person Marlon Richert; 19.12.2020
comment
@MarlonRichert Не могли бы вы проверить, можете ли вы помочь мне с этой проблемой? кажется, вы знаете, как работает завершение zsh. Спасибо!! - person user5328080; 17.01.2021
comment
@user5328080 user5328080 Чем помочь? Разве мой ответ не является решением вашего вопроса? Или, если у вас есть другая проблема, создайте для нее новый вопрос. - person Marlon Richert; 17.01.2021
comment
@MarlonRichert, ваше решение действительно помогает с моей проблемой, но некоторые проблемы все еще есть. см. мое обновление в исходном вопросе (извините, я забыл сказать вам, что обновил свой вопрос). - person user5328080; 18.01.2021
comment
@ user5328080 Я понятия не имею, что вы изменили или добавили в свой вопрос. Возможно, лучше просто создать новый в любом случае. - person Marlon Richert; 18.01.2021
comment
@MarlonRichert все, что ниже редактирования. Почему я должен создать новый вопрос? Я думаю, что это все тот же вопрос. Я очень новичок в публикации в stackoverflow. - person user5328080; 19.01.2021
comment
@ user5328080 stackoverflow.com/help/minimal-reproducible-example - person Marlon Richert; 19.01.2021
comment
@MarlonRichert Я разместил еще один вопрос (stackoverflow.com/questions/66064158/). Я надеюсь, что примеры помогут решить проблему. Дайте мне знать, если что-то неясно - person user5328080; 05.02.2021
comment
@MarlonRichert Я разделил свой вопрос на три: stackoverflow.com/questions/66258645/ stackoverflow. com/questions/66250492/ stackoverflow.com/questions/66246724/ - person user5328080; 18.02.2021