zsh 字符串的长度,可能包含 unicode 和转义字符
zsh length of a string with possibly unicode and escape characters
上下文:我想右对齐部分提示。在这样做时,我目前的做法是计算它的左右部分的长度,并用空格填充中间部分。
问题:应对 %G
(参见 prompt expansion) when the string possibly contains unicode (for example git status). Possibly the actual problem is that I don't grasp it correctly. The use of %G
was suggested in another thread answer about ,这可能是我困惑的根源。以下片段说明了问题:
strlen() {
FOO=
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) 任何额外的 references/explanation 也将受到欢迎。
问题:什么时候才真正使用%G
?或者我应该按照上面的代码片段的建议放弃它吗?
简答:
使用 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:
This is useful when outputting characters that otherwise cannot be correctly handled by the shell, such as the alternate character set on some terminals.
由于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
启动粗体模式)
以下函数计算字符串长度的方法与提示符展开时的方法相同。与其他解决方案不同,它可以正确处理所有输入。
# 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 (( ${${(%):-%$y(l.1.0)}[-1]} )); do
x=y
(( y *= 2 ))
done
while (( y > x + 1 )); do
(( m = x + (y - x) / 2 ))
(( ${${(%):-%$m(l.x.y)}[-1]} = m ))
done
fi
echo $x
}
此函数来自Powerlevel10k ZSH theme where it's used to implement multi-line right prompt and responsive current directory truncation (demo). More info: Multi-line prompt: The missing ingredient.
上下文:我想右对齐部分提示。在这样做时,我目前的做法是计算它的左右部分的长度,并用空格填充中间部分。
问题:应对 %G
(参见 prompt expansion) when the string possibly contains unicode (for example git status). Possibly the actual problem is that I don't grasp it correctly. The use of %G
was suggested in another thread answer about
strlen() {
FOO=
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) 任何额外的 references/explanation 也将受到欢迎。
问题:什么时候才真正使用%G
?或者我应该按照上面的代码片段的建议放弃它吗?
简答:
使用 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:
This is useful when outputting characters that otherwise cannot be correctly handled by the shell, such as the alternate character set on some terminals.
由于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
启动粗体模式)
以下函数计算字符串长度的方法与提示符展开时的方法相同。与其他解决方案不同,它可以正确处理所有输入。
# 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 (( ${${(%):-%$y(l.1.0)}[-1]} )); do
x=y
(( y *= 2 ))
done
while (( y > x + 1 )); do
(( m = x + (y - x) / 2 ))
(( ${${(%):-%$m(l.x.y)}[-1]} = m ))
done
fi
echo $x
}
此函数来自Powerlevel10k ZSH theme where it's used to implement multi-line right prompt and responsive current directory truncation (demo). More info: Multi-line prompt: The missing ingredient.