在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 / b / c /的所有子目录/ a,/ a / b,/ 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() {
  # 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将为您完成此路径的每一步。