Autocomplete can';t使用'~';在zsh中自动完成

Autocomplete can';t使用'~';在zsh中自动完成,autocomplete,zsh,tab-completion,zsh-completion,Autocomplete,Zsh,Tab Completion,Zsh Completion,我使用zsh,我想使用我编写的函数来替换cd。 此功能使您能够移动到父目录: $ pwd /a/b/c/d $ cl b $ pwd /a/b $ pwd /a/b/c/d $ cl b/e $ pwd /a/b/e 您还可以移动到父目录的子目录中: $ 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中

我使用zsh,我想使用我编写的函数来替换cd。 此功能使您能够移动到父目录:

$ pwd
/a/b/c/d
$ cl b
$ pwd
/a/b
$ pwd
/a/b/c/d
$ cl b/e
$ pwd
/a/b/e
您还可以移动到父目录的子目录中:

$ 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/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目录的子目录时,建议是错误的(它们显示您键入的完整路径,而不仅仅是子目录)。结果确实有效
  • 我使用语法hightlighting,但我键入的路径仅为白色(当使用转到父目录时。普通cd函数为彩色)
  • 在我的zshrc中,我有一行:
这意味着我可以输入“加载”,它将完成“下载”。对于cl,这不起作用。使用普通cd功能时不发生任何事件

有没有办法解决(其中一些)问题? 我希望你们能理解我的问题。我发现很难解释这个问题

谢谢你的帮助

这应该可以做到:

\u cl(){
#存储到目前为止生成的匹配数。
本地-i nmatches=$compstate[nmatches]
#调用“cd”的内置完成。无需重新发明轮子。
_光盘
#${PWD:h}:当前工作目录的父目录(“头”)。
本地祖先=$PWD:h expl
#生成可视化格式并将其存储在“$expl”中`
#-V:不要对这些项目进行排序;按我们添加它们的顺序显示它们。
_description-V祖先目录解释“祖先目录”
而($#祖先>1)),则
#-f:将其视为一个文件(包括dirs),以便获得适当的高亮显示。
#-W:指定要添加的目录的父级。
#${祖先:h}:$祖先的父项(“头”)。
#${祖先:t}:$祖先的短名称(“尾”)。
compadd“$expl[@]”-f-W${祖先:h}/-$祖先:t
#转到下一个父级。
祖先=$祖先:h
完成
#如果添加了任何匹配项,则返回true。
((compstate[nmatches]>nmatches))
}
#将上面的函数定义为生成'cl'的补全。
compdef_cl
#或者,代替上面的行:
# 1. 在“$fpath”中的目录中创建一个文件“\u cl”。
# 2. 将函数“\u cl”的内容粘贴到此文件中。
# 3. 添加“#compdef cl`添加文件的顶部。
#“u cl”现在将在运行“compinit”时自动加载。
另外,我会像这样重写您的
cl
函数,这样它就不再依赖于
cut
或其他外部命令:

cl(){
如果($#==0));那么
#没有任何参数的`cl`将移回上一个目录。
光盘-
elif[-d$1 | |-d$PWD/$1]];然后
#如果参数是现有的绝对路径或直接子级,请移到那里。
cd$1
其他的
#获取以参数结尾的最长前缀。
本地祖先=${(M)${PWD:h}##*$1}
如果[[-d$祖先]];则
#如果它是现有的目录,请移动到那里。
cd$祖先
其他的
#否则,打印到stderr并返回false。
打印-u2“$0:没有这样的祖先“$1”
返回1
fi
fi
}

替代解决方案 有一种更简单的方法来完成这一切,无需编写
cd
替换或任何完成代码:

cdpath(){
#`$PWD`始终等于当前工作目录。
本地目录=$PWD
#除了搜索“$PWD”的所有子项之外,“cd”还将搜索所有子项
#数组“$cdpath”中所有dir的子级。
cdpath=()
#将“$PWD”的所有祖先添加到“$cdpath”。
而($#dir>1)),则
#`:h`是直接父母。
dir=$dir:h
cdpath+=($dir)
完成
}
#只要我们更改目录,就运行上面的函数。
添加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将为您完成此路径的每一步

这个想法很聪明,但如果用户已经将其cdpath设置为固定值,这将破坏他的设置。@MarlonRichet:问题是您必须修改此函数以应用更改。在一个典型示例中,我们在.zshrc中的某个位置设置了函数,在使用了一段时间后,用户决定动态设置显式的
cdpath
。该功能无法从该更改中识别。仅当用户决定在定义函数时设置默认cdpath,并且在定义后不更改它时