修改 .zshrc 中的 PROMPT 后,Zsh Tab 补全将命令向右移动几个空格

修改 .zshrc 中的 PROMPT 后,Zsh Tab 补全将命令向右移动几个空格

我最近编写了一个名为 的函数zuperPrompt,它打印出一个漂亮的提示,并且我在 .zshrc 中设置了 PROMPT 变量来调用该函数,如下所示:

setopt PROMPT_SUBST
PROMPT='$(zuperPrompt)'

发现同样的问题这里,但我认为问题出在 awk 命令中使用换行转义字符。我尝试在没有任何换行符的情况下完成整个提示,但得到了相同的结果:(

这是函数:

zuperPrompt() {
 
    # count chars in directory name
    num_chars=1
    if [ ! $(dirs) = '~' ]; then
       let "num_chars=$(basename "`pwd`" | wc -m) - 1"
    fi
 
    # draw line
    line="\n╭──"
    for i in {1..$num_chars}; do
       line+="─"
    done
    line+="──╯"
 
    directory="%F{8}%K{8}%B%F{blue}%1~%k%F{8}"
    arrows="%B%F{magenta}❯%B%F{yellow}❯%F{cyan}❯ "
 
    row1="\n%B%F{green}◆ "$directory"─╮"
    row2=$line
    row3="\n╰─ "$arrows
 
    print -P $row1$row2$row3
}

这是输入后按 Tab 键的结果python3

在此输入图像描述

我不确定这里发生了什么事。如果有人可以提供帮助,请提前致谢!

更新 在我的网络浏览器中查看此语法突出显示后,我发现 $row3 的颜色与其他颜色不同。我只用 $row1$row2 运行我的代码,它工作正常,没有光标移动。有人知道那里发生了什么事吗?

更新2 我从最终的打印语句中删除了 -P 选项,它适用于所有三行。

答案1

shell中的主要提示字符串是分配给或变量的zsh字符串。它可能包含各种转义字符和代码,通知 shell 如何将字符串动态扩展为可见提示符。PROMPTPS1

print -rP -- $PROMPT您可以使用或自行扩展提示字符串print -rP -- $PS1

但是,如果您将一个字符串分配给PROMPT已经用 扩展的字符串print -P,则转义码告诉 shell 提示字符串的哪些部分是可见的以及提示字符串的哪些部分只是用于例如更改颜色的终端代码,shell 不能更长时间地跟踪提示的大小。这会引起您所描述的问题类型。另一个常见问题是长命令的换行变得混乱。

因此,不要使用print -P来生成稍后分配给 的字符串PROMPT,而是print -r使用它来输出“原始”。

这与中描述的问题相同zsh 提示符未正确转义,但原因略有不同(您从提示字符串中删除有关非打印字符的有用信息,而不是从不添加有关非打印字符的信息)。


有点尴尬的代码

num_chars=1
if [ ! $(dirs) = '~' ]; then
   let "num_chars=$(basename "`pwd`" | wc -m) - 1"
fi

可以写成单行

num_chars=$( print -n -P '%1~' | wc -m )

%1~是当前目录名称的提示代码,稍后您将使用该代码在提示中实际包含该字符串。)

还有循环

line="\n╭──"
for i in {1..$num_chars}; do
   line+="─"
done
line+="──╯"

可以写成

printf -v line '\n╭──%*s──╯' $num_chars ''
line=${line// /─}

但你可以将这些组合成

print -v wd -n -P '%1~'
printf -v line '\n╭──%*s──╯' $#wd ''
line=${line// /─}

这摆脱了丑陋的命令替换和对wc.

答案2

启用该promptsubst选项后,您可以参数扩展,命令替换算术展开在提示符扩展时被扩展,这样你就可以有PS1='$(command-that-generates-PS1-dynamically)',但扩展的结果仍然是一个提示字符串,其中%~/%m被扩展。

所以你最不想做的就是command-that-generates-PS1-dynamically打电话print -P。更应该是相反。如果command-that-generates-PS1-dynamically输出要按字面显示,则应该确保%(以及!ifpromptbang打开)被转义,并且所有不移动光标的控制序列都包含在%{%}等中。

$PS1除了所有转义符可以提供的文本之外,另一种更 zsh-y 的方法%x是不设置动态文本$promptsubst,而是在钩子中生成动态文本precmd,并将它们存储在您在, ... 中$psvar引用其成员的数组中。%1v%2v$PS1

所以,而不是:

command-that-generates-PS1-dynamically() {
  local some_dynamic_data=$(some-cmd)
  print -r -- "%F{red}${some_dynamic_data//\%/%%}%f$ "
}
set -o promptsubst
PS1='$(command-that-generates-PS1-dynamically)'

(只${some_dynamic_data//\%/%%}负责转义 s %,而不处理!或转义可能包含的序列$some_dynamic_data)。

做:

prompt-hook() {
  psvar[1]=$(some-cmd)
}
precmd_function+=(prompt-hook)
PS1='%F{red}%1v%f$ '

这还节省了分叉子 shell 的麻烦(这也允许您prompt-hook在提示扩展中维护一些数据),并且%1v扩展(用于可打印文本)会照顾或转换不可打印的字符,使它们变得可见。

在你的情况下,这可能看起来像

display_width() REPLY=$(($#1 * 3 - ${#${(ml[$#1 * 2])1}}))

prompt-hook() {
  display_width ${(%):-%1~}
  psvar[1]=${(l[$REPLY][─])}
}

precmd_functions+=(prompt-hook)

() {
  local directory='%F{8}%K{8}%F{blue}%1~%k%F{8}'
  local arrows='%B%F{magenta}❯%F{yellow}❯%F{cyan}❯'
  local line=$'─╮\n╭──%1v──╯\n╰─'
  PS1=$'\n'"%B%F{green}◆ ${directory}${line} ${arrows}%b%f "
}

其中$PS1是 static (不需要$promptsubst;我们只是在此处使用临时临时变量来提高易读性),但动态自定义部分(与 展开的显示宽度长度相同的行部分%1~)生成为我们钩子的一部分precmd

您会注意到,在此过程中没有分叉任何子 shell。

这个答案获取字符串的显示宽度有关计算字符串显示宽度的函数的详细信息display_width(不一定与其中的字符数相同)。

相关内容