【问题标题】:zsh length of a string with possibly unicode and escape characterszsh 可能带有 unicode 和转义字符的字符串的长度
【发布时间】:2017-04-11 04:38:30
【问题描述】:

上下文:我想右对齐部分提示。这样做时,我目前的方法是计算它的左右分量的长度,并用空格填充中间分量。

问题:当字符串可能包含 unicode(例如 git status)时,处理%G(请参阅prompt expansion)。可能实际的问题是我没有正确掌握它。在关于how to signal zsh that there are characters to be output 的另一个线程答案中建议使用%G,这可能是我困惑的根源。下面的 sn-p 说明了这个问题:

strlen() {
    FOO=$1
    local invisible='%([BSUbfksu]|([FB]|){*})' # (1)
    LEN=${#${(S%%)FOO//$~invisible/}}
    echo $LEN
}

local blob="%{↓%G%}"
echo $blob $(strlen $blob) # (2) Unexpectedly gives 0

local blob="↓"
echo $blob $(strlen $blob) # (3) Gives the wanted output of 1 
                           # but then this result would tell us to not use %G for unicode

strlen 函数来自this tentative explanation of counting user-visible string。不幸的是,对于invisible 部分 # (1) 没有明确的完整解释,也欢迎任何额外的参考/解释。

问题:我应该什么时候真正使用%G?还是我应该按照上面 sn-p 的建议放弃它?

【问题讨论】:

    标签: git unicode zsh prompt


    【解决方案1】:

    简答:

    当使用 Unicode 字符而不是纯 ASCII 时,您不必采取任何额外的步骤。当前版本的zsh 完全支持Unicode 字符并且可以正确处理它们。所以即使一个字符被多个字节编码,zsh 仍然会知道它只是一个字符。


    何时使用%{...%}%G

    %{...%} 用于向zsh 表明里面的字符串不会改变光标位置。例如,如果您想添加用于设置颜色的转义序列,这很有用:

    print -P '%{\e[31m%}terminal red%{\e[0m%}'
    print -P '%{\e[38;2;0;127;255m%}#007FFF%{\e[0m%}'
    

    如果没有%{...%} zsh 将不得不假设转义序列的每个字符将光标向右移动一个位置。

    %{...%}(或%1{...%})内使用%G 告诉zsh 假设将输出单个字符。这仅用于计数目的,它不会自行移动光标。

    根据ZSH Manual

    这在输出 shell 无法正确处理的字符时很有用,例如某些终端上的备用字符集。

    由于zsh 能够处理Unicode 字符,所以那里没有必要(虽然不一定是错的)。


    strlen "%{↓%G%}" 出现意外结果的原因:

    这是因为strlen 实际上只尝试删除任何空长度提示序列(如%B%F{red}),而不是实际测量结果字符串的打印长度(这可能是不可能的反正)。在许多情况下,这工作得很好,但在 "%{↓%G%}" 的情况下却失败了,这实际上相当于 zsh 提示上下文中的 "↓"

    说明:

    为了找到这些空长度提示序列,strlen 将其输入匹配到此模式

    invisible=%([BSUbfksu]|([FB]|){*})'
    

    这还包含子模式%{*},它将匹配%{…%}。那么

    LEN=${#${(S%%)FOO//$~invisible/}}
    

    在计算字符之前从FOO 中删除任何匹配的子字符串。

    最重要的是,它实际上并不以任何方式处理%G,只是将其与周围的%{...%} 一起删除。

    由于整个字符串"%{↓%G%}"与模式匹配,它将被完全删除,导致0的意外字符数。


    顺便说一句:这并不意味着您不应该使用strlen(在我的提示中,我已经使用了相当长一段时间的派生内容)。但您应该注意一些限制:

    • 它不适用于%G(显然)。
    • 它无法处理 %{...%} 的数字参数,例如 %3{...%}
    • 它也不识别 % 之后的数字参数,用于前景色和背景色,例如 %1F(而不是 %F{1}%F{red}
    • 它无法处理嵌套的%{...%},或者实际上是%{...%} 中的任何}。 (例如,当打算使用%D{string} 进行日期格式设置时,这一点很重要,因为格式字符串string 的长度必须与结果日期的长度相匹配,而无需在其周围使用`%{...%}。 )

    最后,原始定义中有一个错误,应该是:

    local invisible='%([BSUbfksu]|([FK]|){*})'
    

    第二个B 应该是K,因为它旨在匹配背景颜色的提示转义。 (%B 开启粗体模式)

    【讨论】:

    • 哇,谢谢@Adaephon,很好的回答。您能否参考invisible 模式中符号的含义? ){*} 子模式之前有什么意义?
    • (…) 用于分组。 x|y 表示“模式x 或模式y”。 | inside (…) 仅限于里面的模式。所以a(b|c|d)e 匹配abeaceade。有问题的) 关闭了组([FK]|)。匹配[FK][…] 中的任何一个字符,因此FK)或不匹配(因为|) 之间没有任何内容)。欲了解更多信息,请查看the section on glob operators in the ZSH manual
    • 谢谢!我认为) 是关于分组的,但我没有想到任何东西可以替代[FK]
    【解决方案2】:

    以下函数计算字符串长度的方式与在提示扩展期间完成的方式相同。与其他解决方案不同,它可以正确处理所有输入。

    # Usage: prompt-length TEXT [COLUMNS]
    #
    # If you run `print -P TEXT`, how many characters will be printed
    # on the last line?
    #
    # Or, equivalently, if you set PROMPT=TEXT with prompt_subst
    # option unset, on which column will the cursor be?
    #
    # The second argument specifies terminal width. Defaults to the
    # real terminal width.
    #
    # Assumes that `%{%}` and `%G` don't lie.
    #
    # Examples:
    #
    #   prompt-length ''            => 0
    #   prompt-length 'abc'         => 3
    #   prompt-length $'abc\nxy'    => 2
    #   prompt-length '❎'          => 2
    #   prompt-length $'\t'         => 8
    #   prompt-length $'\u274E'     => 2
    #   prompt-length '%F{red}abc'  => 3
    #   prompt-length $'%{a\b%Gb%}' => 1
    #   prompt-length '%D'          => 8
    #   prompt-length '%1(l..ab)'   => 2
    #   prompt-length '%(!.a.)'     => 1 if root, 0 if not
    function prompt-length() {
      emulate -L zsh
      local COLUMNS=${2:-$COLUMNS}
      local -i x y=${#1} m
      if (( y )); then
        while (( ${${(%):-$1%$y(l.1.0)}[-1]} )); do
          x=y
          (( y *= 2 ))
        done
        while (( y > x + 1 )); do
          (( m = x + (y - x) / 2 ))
          (( ${${(%):-$1%$m(l.x.y)}[-1]} = m ))
        done
      fi
      echo $x
    }
    

    该函数来自Powerlevel10kZSH 主题,用于实现多行右提示和响应式当前目录截断(demo)。更多信息:Multi-line prompt: The missing ingredient

    【讨论】:

    • 您介意直接在这里添加解决方案的细节吗?
    猜你喜欢
    • 2015-10-16
    • 2023-03-31
    • 2021-11-21
    • 2013-04-03
    • 1970-01-01
    • 2016-11-10
    • 2013-02-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多