zsh可能是unicode和转义字符的字符串长度

时间:2016-11-27 09:58:50

标签: git unicode zsh prompt

上下文:我想对我提示的部分内容进行右对齐。在这样做时,我目前的方法是计算它的左侧和右侧组件的长度,并用空格填充中间组件。

问题:当字符串可能包含unicode时(例如git status),请处理%G(请参阅prompt expansion)。可能实际问题是我没有正确掌握它。在how to signal zsh that there are characters to be output的另一个帖子回答中建议使用%G,这可能是我混淆的根源。以下代码段说明了问题:

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?或者我应该按照上面的片段建议放弃它?

2 个答案:

答案 0 :(得分:2)

简答:

使用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,则必须假设转义序列的每个字符都将光标向右移动一个位置。

%G(或%{...%})中使用%1{...%}告诉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启动粗体模式)

答案 1 :(得分:0)

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

# 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 $'\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
    local xy
    while (( y > x + 1 )); do
      m=$(( x + (y - x) / 2 ))
      typeset ${${(%):-$1%$m(l.x.y)}[-1]}=$m
    done
  fi
  echo $x
}

此功能来自Powerlevel10k ZSH主题,用于实现多行右提示和响应的当前目录截断(demo)。更多信息:Multi-line prompt: The missing ingredient