😲zsh-autosuggestions竟如此实现建议!!!🐶😀
起因
最近突发奇想,想用 shell(zsh 或者 bash)来解析 RESP 协议,实现一个 Redis 客户端。我一开始以为并不容易,直到我使用 netcat 去连接我的 docker 内的 Redis。具体效果是下面这样的。

啊这,这还需要解析设么,除了简单的字符串就没啥了。虽然原本的 RESP 协议本身并不复杂,但是至少目前就很让人泄气了。还能不能多加点什么让解析更好玩一点呢?诶,突然想到了 Redis-cli 在输入命令时会提示,也就是下面的效果。

即提示功能。这个提示功能第一个让我想到的就是目前我正在使用的一个 zsh 的插件,也就是标题上的 zsh-autosuggestions 。当然 redis 内部这个部分也是通过一个叫做 linenoise 的模块实现的,这个模块也已经分离出来作为一个单独的库在 github 上可以找到了,但是我并不想使用任何非 shell 脚本的编程语言来实现。 小说一句,我最开始使用 zsh,后面因为 fish 开箱即用的特性切到了 fish 使用了一段时间,但是有因为一些个人使用习惯,最终切换回了 zsh。
下面是 zsh-autosuggestions 在我的日常使用中的一些建议情况

感觉还不错,看能不能参考具体源代码实现。
直接来源码吧
本身我是用的是 oh-my-zsh 管理插件,其实并不是很有所谓,下载了 zsh-autosuggestions.zsh 的文件之后,其实只要在自己的 .zshrc 内添加 source /path/to/zsh-autosuggestions.zsh 即可(当然 source 也可以换成英文句号)。也就是说其实之用看这一个文件即可。源码中src目录下的文件,通过查看其 Makefile 可以得知,其实是zsh-autosuggestions.zsh的一部分。下面是它 Makefile 的部分内容。
SRC_DIR := ./src
SRC_FILES := \
$(SRC_DIR)/config.zsh \
$(SRC_DIR)/util.zsh \
$(SRC_DIR)/bind.zsh \
$(SRC_DIR)/highlight.zsh \
$(SRC_DIR)/widgets.zsh \
$(SRC_DIR)/strategies/*.zsh \
$(SRC_DIR)/fetch.zsh \
$(SRC_DIR)/async.zsh \
$(SRC_DIR)/start.zsh
HEADER_FILES := \
DESCRIPTION \
URL \
VERSION \
LICENSE
PLUGIN_TARGET := zsh-autosuggestions.zsh
all: $(PLUGIN_TARGET)
$(PLUGIN_TARGET): $(HEADER_FILES) $(SRC_FILES)
cat $(HEADER_FILES) | sed -e 's/^/# /g' > $@
cat $(SRC_FILES) >> $@
...
所以大致了解的代码的基本结构之后,可以开始看啦。在看之前还是要说明一个事情,zsh 脚本中全局匿名函数,会在文件被加载或者执行的时候立即执行,并且执行之后会被丢弃。补充这个说明是因为在 zsh-autosuggestions 这个插件最开始使用匿名函数对一些函数和 widget 进行加载和绑定。
匿名函数加载
既然最先开始执行的是全局的匿名函数,那就先找到来吧。具体代码是下面情况。
() {
typeset -ga _ZSH_AUTOSUGGEST_BUILTIN_ACTIONS
_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS=(
clear
fetch
suggest
accept
execute
enable
disable
toggle
)
local action
for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS modify partial_accept; do
eval "_zsh_autosuggest_widget_$action() {
local -i retval
_zsh_autosuggest_highlight_reset
_zsh_autosuggest_$action \$@
retval=\$?
_zsh_autosuggest_highlight_apply
zle -R
return \$retval
}"
done
for action in $_ZSH_AUTOSUGGEST_BUILTIN_ACTIONS; do
zle -N autosuggest-$action _zsh_autosuggest_widget_$action
done
}
可能刚开始看有点蒙,但是只要大概知道在干啥就行,这里设置了一些 action,这些 action 其实已经有实现的函数了,这里使用 for 循环加上 eval 创建了不同的前缀为 _zsh_autosuggest_widget 的函数,内部调用了具体实现的 _zsh_autosuggest_ 函数并且最后使用 zle 命令将 eval 得到的函数绑定到对应 action 名字结尾的 widget 上。
为什么要将函数绑定到 widget 呢?从 zsh 的文档中就能得到答案。 在第 zsh5.9 版本的文档的 18.4 小节 Zle Widgets 的第一行就有表示在 zsh 中所有动作都由 widget 来实现。下面是原话。
All actions in the editor are performed by ‘widgets’. A widget’s job is simply to perform some small action. The ZLE commands that key sequences in keymaps are bound to are in fact widgets. Widgets can be user-defined or built in.
也就是说当前 zsh-autosuggestions 的源码在做的就是重新绑定这些 widget,当相应的行为发生时,调用这些已经被重新绑定的 widget,即可实现重新定义的效果,这里就是获取显示建议。
插件入口
在加载完成一些基本的 widget 之后,插件便开始绑定相关 widget 来实现额外的功能。在插件文件末尾有一句如下。
add-zsh-hook precmd _zsh_autosuggest_start
add-zsh-hook 是 zsh 中添加 hook 的操作,这里将_zsh_autosuggest_start 添加到 precmd 的 hook 中,precmd 从字面意思上就知道,和这个相关的 hook 函数或者 widget 会在命令执行之前执行,或者更具体地说,是在每一次上一个命令结束之后,下一行 prompt 显示出来之前执行 precmd 的相关 hook。(这在文档里说了,在 zsh 的源码中也有体现)
那接下来就是简单看到这个 _zsh_autosuggest_start 函数了。在函数内部也挺简单,首先根据用户选项是否卸载重复绑定,也就是下面的语句。
if (( ${+ZSH_AUTOSUGGEST_MANUAL_REBIND} )); then
add-zsh-hook -d precmd _zsh_autosuggest_start
fi
-d 选项用于卸载 hook。之后直接地用 _zsh_autosuggest_bind_widgets。函数名已经做到了见名知意。 在 _zsh_autosuggest_bind_widgets 内部主要是对一些内置 widget 的重新绑定,内部调用_zsh_autosuggest_bind_widget 来绑定到相应的 widget。
...
for widget in ${${(f)"$(builtin zle -la)"}:#${(j:|:)~ignore_widgets}}; do
if [[ -n ${ZSH_AUTOSUGGEST_CLEAR_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget clear
elif [[ -n ${ZSH_AUTOSUGGEST_ACCEPT_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget accept
elif [[ -n ${ZSH_AUTOSUGGEST_EXECUTE_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget execute
elif [[ -n ${ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS[(r)$widget]} ]]; then
_zsh_autosuggest_bind_widget $widget partial_accept
else
# Assume any unspecified widget might modify the buffer
_zsh_autosuggest_bind_widget $widget modify
fi
done
zle -la 会列举出所有 widget。
_zsh_autosuggest_bind_widget
从这个函数开始套娃绑定咯,从 case 的一个 builtin 分支来看,这里将内部不可被重新绑定的以. 开头的 widget 外头包了一层函数。
case $widget[$widget] in
# ...
builtin)
_zsh_autosuggest_incr_bind_count $widget
eval "_zsh_autosuggest_orig_${(q)widget}() { zle .${(q)widget} }"
zle -N $prefix$bind_count-$widget _zsh_autosuggest_orig_$widget
;;
# ...
包了一层函数之后,又将该函数绑定到了默认 prefix 和 bind_count 组成的 widget 上。然而这还只是套娃的开始。 经过上面的包装,执行 prefix 开头的 widget,就会执行最原始的内部的以 . 开头的原装 widget 这没有问题。 在走完 case 语句之后,又来了几行。
eval "_zsh_autosuggest_bound_${bind_count}_${(q)widget}() {
_zsh_autosuggest_widget_$autosuggest_action $prefix$bind_count-${(q)widget} \$@
}"
# Create the bound widget
zle -N -- $widget _zsh_autosuggest_bound_${bind_count}_$widget
在 eval 的函数内并不是简单将 widget 包装,这里给 _zsh_autosuggest_widget_$autosuggest_action 函数传递了两个参数,一个是上面绑定的函数,一个是这个函数外层所有的参数。看着有点迷糊,其实这个 _zsh_autosuggest_widget_$autosuggest_action 是我们已经见过的,在最开始匿名函数执行的时候就创建了这些个函数。在匿名函数内部创建了 _zsh_autosuggest_widget_$action。而这两个其实是一个东西,回过头来看只是字符串拼接 函数名时使用的变量名不同,即$autosuggest_action 和$action 实际上是大致内容相同的字符串。
最后使用 zle -N 重新将内部 widget 绑定到已经包装过的函数,之后再执行一些基本操作比如简单输入 (执行的是名为 self-insert 的 widget) 时就会执行包装函数的逻辑,而包装函数除了执行原本的在终端插入一个指定字符外还有获取建议,显示建议高亮等额外操作。
再看匿名函数
承接上文 eval 内部的包装函数,再回到最开始的匿名函数,这里将包装了的 widget 传进去作为参数。
eval "_zsh_autosuggest_widget_$action() {
local -i retval
_zsh_autosuggest_highlight_reset
_zsh_autosuggest_$action \$@
retval=\$?
_zsh_autosuggest_highlight_apply
zle -R
return \$retval
}"
在该函数内部首先重置高亮,然后调用对应的 action 函数,将上一层两个参数(一个是包装了内部的 widget 一个是其他参数)传入其中,最后获取返回值,将高亮显示在编辑行中 zle -R 刷新行。
上面所有的操作最终是将原始 widget 包裹,然后传入实现好的 _zsh_autosuggest_$action 进行相关的逻辑操作。外层包装知道了,接下来就来看看如何实现的。
_zsh_autosuggest_$action
$action 的取值在匿名函数的绑定中已经表明,有以下几种。 clear,fetch,suggest,accept,execute,enable,disable,toggle。
我们挑几个主要的来看看,首先来看看 fetch,也就是_zsh_autosuggest_fetch 是干啥的
# Fetch a new suggestion based on what's currently in the buffer
_zsh_autosuggest_fetch() {
if (( ${+ZSH_AUTOSUGGEST_USE_ASYNC} )); then
_zsh_autosuggest_async_request "$BUFFER"
else
local suggestion
_zsh_autosuggest_fetch_suggestion "$BUFFER"
_zsh_autosuggest_suggest "$suggestion"
fi
}
注释已经说的很清楚了,基于现有的 BUFFER 获取建议,首先判断是否使用异步获取(这里异步获取其实就是开另一个进程来获取),如果不使用异步,就调用 fetch_suggestion(太长了函数名,省略相同前缀),将 BUFFER 内容传入,忘记说了,在 zsh 中有很多默认变量,BUFFER 和 POSTDISPLAY 便是其中两个,而也正是对这两个的运用实现了整个插件建议的功能,BUFFER(zsh: 18.5 User-Define Widgets) 存储的是当前行编辑器中的内容也就是你输入的但是在按下回车之前的内容,POSTDISPLAY(zsh: 18.5 User-Define Widgets)则是在当前行 BUFFER 之后要显示的内容。
继续追踪进入 fetch_suggestion
#--------------------------------------------------------------------#
# Fetch Suggestion #
#--------------------------------------------------------------------#
# Loops through all specified strategies and returns a suggestion
# from the first strategy to provide one.
#
_zsh_autosuggest_fetch_suggestion() {
typeset -g suggestion
local -a strategies
local strategy
# Ensure we are working with an array
strategies=(${=ZSH_AUTOSUGGEST_STRATEGY})
for strategy in $strategies; do
# Try to get a suggestion from this strategy
_zsh_autosuggest_strategy_$strategy "$1"
# Ensure the suggestion matches the prefix
[[ "$suggestion" != "$1"* ]] && unset suggestion
# Break once we've found a valid suggestion
[[ -n "$suggestion" ]] && break
done
}
从注释和源码大致知道,循环从策略数组中获得对应的建议策略,然后调用对应的策略函数获取建议,将建议赋值给 suggestion 变量(注意此处已经将 suggestion 设置为全局,还有这里的“$1”也就是上一层的 BUFFER)。 ok,获取到了建议返回上一层,将建议内容送入 _zsh_autosuggest_suggest 函数。
# Offer a suggestion
_zsh_autosuggest_suggest() {
emulate -L zsh
local suggestion="$1"
if [[ -n "$suggestion" ]] && (( $#BUFFER )); then
POSTDISPLAY="${suggestion#$BUFFER}"
else
unset POSTDISPLAY
fi
}
明了了大部分啦,函数内将 POSTDISPLAY 设置为除去 BUFFER 的建议,{suggestion#BUFFER} 表达的意思是从 suggestion 字符串的左边开始匹配与 BUFFER 内容相同的字符串,匹配到了删除,返回剩下的,这里是在 zsh 的语法下,bash 其实也差不多,有关更多的使用,可以看我另一篇文章 Shell 脚本入门。
fetch 看完了,这其实只是获取建议,在日常操作中最常被操作的其实是_zsh_autosuggest_modify 函数,那就来看看里面有什么吧。
# Modify the buffer and get a new suggestion
_zsh_autosuggest_modify() {
local -i retval
# Only available in zsh >= 5.4
local -i KEYS_QUEUED_COUNT
# Save the contents of the buffer/postdisplay
local orig_buffer="$BUFFER"
local orig_postdisplay="$POSTDISPLAY"
# Clear suggestion while waiting for next one
unset POSTDISPLAY
# Original widget may modify the buffer
_zsh_autosuggest_invoke_original_widget $@
retval=$?
emulate -L zsh
# Don't fetch a new suggestion if there's more input to be read immediately
if (( $PENDING > 0 || $KEYS_QUEUED_COUNT > 0 )); then
POSTDISPLAY="$orig_postdisplay"
return $retval
fi
# Optimize if manually typing in the suggestion or if buffer hasn't changed
if [[ "$BUFFER" = "$orig_buffer"* && "$orig_postdisplay" = "${BUFFER:$#orig_buffer}"* ]]; then
POSTDISPLAY="${orig_postdisplay:$(($#BUFFER - $#orig_buffer))}"
return $retval
fi
# Bail out if suggestions are disabled
if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then
return $?
fi
# Get a new suggestion if the buffer is not empty after modification
if (( $#BUFFER > 0 )); then
if [[ -z "$ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE" ]] || (( $#BUFFER <= $ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE )); then
_zsh_autosuggest_fetch
fi
fi
return $retval
}
代码逻辑大体上就是比较清晰的了,首先获得 BUFFER 和 POSTDISPLAY,接着调用 _zsh_autosuggest_invoke_original_widget。不知道大家还记得这里调用的参数$@的具体内容是啥。这里的$@其实是最开始的包装了内部原始 widget 的 widget。下面是 invoke 的代码。
_zsh_autosuggest_invoke_original_widget() {
# Do nothing unless called with at least one arg
(( $# )) || return 0
local original_widget_name="$1"
shift
if (( ${+widgets[$original_widget_name]} )); then
zle $original_widget_name -- $@
fi
}
也就是说最终通过$1 获得包装后的原始 widget 名称,使用 shift 将参数往前推一个,之后的参数用作原始 widget 的参数(这里用 zle 调用包装后的原始 widget)。
之后也是比较简单的逻辑了,对手动输入建议的判断,是否关闭等的判断,最终判断 BUFFER 情况,并根据 SIZE 来判断是否调用 fetch 来获取并设置建议。
整体流程梳理
插件绑定流程(并没有展示获取建议的三个策略以及异步获取)。
-
匿名函数创建对应 action 的 函数,这些函数内部有调用获取建议,设置 POSTDISPLAY,BUFFER,设置高亮,重置高亮的函数。
-
将 start 加入 precmd 的 hook 中,每次在 prompt 之前进行绑定。
-
通过两层函数
_zsh_autosuggest_bind_widgets和_zsh_autosuggest_bind_widget的操作,将原先的 widget 通过函数包装,重新绑定 widget 等操作,绑定到有特殊前缀的 widget 上。 -
将这些包装了原始 widget 的 widget 传入匿名函数创建的函数,最外层包裹一层函数。
-
最终在
_zsh_autosuggest_bind_widget内部重新将最外层的函数绑定回原始不以.开头的可以被重新绑定的原始 widget 上。至此插件绑定操作结束。
下面是我大致画的一个绑定的流程图

插件运行流程。
- 在 zsh 中输入字符,触发 widget
- 触发的 widget 已经是之前重新绑定过的 widget 了,执行 widget 对应绑定的函数。
- 函数内部重置高亮,获取建议,设置建议,处理原始 widget(比如插入字符,删除字符等),设置建议高亮…。
整个插件做的事其实是一个重新绑定。利用的 zsh 中一切操作皆为 widget 的原理。
实现一个类似的建议操作
知道原理之后,实现一个类似建议的操作还是很简单的。为了简化,但是模拟整个插件的套娃操作,那就实现一个在输入命令后显示上一条命令的操作。并且可以接受建议。
也就是下面的样子。

最终实现简单建议需要重新包裹两个原始widget,分别是 .self-insert 和 .forward-char。一个是用作输入字符到终端,一个是用于将光标向右移动,用作接受建议。
每次我输入命令回车之后,在输入下一个命令时,我的光标后面都会显示上一条输入的命令并且获取建议,这个建议是去掉当前BUFFER内容的,就是这么个效果,我顺带改了改提示的颜色为黄色,更好辨认。
直接根据流程来吧。首先将以 . 开头的 不可以被重新绑定的widget进行包裹,我这分别包裹.self-insert 和 .forward-char 这两个widget。
function ori_widget() {
zle .self-insert;
}
function ori_widget() {
zle .forward-char;
}
调用这两个函数也就是使用zle执行 .self-insert 和 .forward-char widget。没毛病,继续咯。
建议的获取与高亮
先来实现获取命令和高亮的逻辑以备后面做菜使用。获取上一条命令并设置给POSTDISPLAY
function auto() {
local histcmd=$history[$(($HISTCMD - 1))]
if [[ ${histcmd#$BUFFER} != ${histcmd} ]]; then
POSTDISPLAY="${histcmd#$BUFFER}"
else
unset POSTDISPLAY
fi
}
变量 HISTCMD 存储的是命令的数量,history是一个存储命令的数组,获取最后一个命令也就是上一条命令就是这么简单。在获取上一条命令之后,判断当前 BUFFER 是不是与上一条命令开头匹配,匹配则设置 POSTDISPLAY否则删除之前已经存在的POSTDISPLAY。
获得命令之后便是将其高亮,在zsh里面有一个叫做 region_highlight 的数组变量,zsh-autosuggestions也使用了这个变量,我这也拿来用用。至于这个变量,下面是摘取自zsh文档18.5 User-Defined Widgets 关于这个变量的一个介绍,具体更多可以去看文档。
Each element of this array may be set to a string that describes highlighting for an arbitrary region of the command line that will take effect the next time the command line is redisplayed. Highlighting of the non-editable parts of the command line in
PREDISPLAYandPOSTDISPLAYare possible, but note that thePflag is needed for character indexing to includePREDISPLAY.
上面也说的很清楚了,高亮命令行的任意部分。那就来把建议高亮成我想要的颜色吧。
function highlight() {
if (( $#POSTDISPLAY )); then
region_highlight+=("$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) fg=11")
fi
}
region_highlight里面的操作其实是从BUFFER 长度的位置开始到POSTDISPLAY长度结束,也就是建议部分高亮,高亮用的颜色码为11。11表示的是黄色,其实排序和我之前在shell脚本文章里说的是一样的。下面是一个我常用的颜色的表格
| 颜色 | 对应fg可用颜色码 |
|---|---|
| 白色 | 7 |
| 灰色 | 8 |
| 红色 | 9 |
| 绿色 | 10 |
| 黄色 | 11 |
| 蓝色 | 12 |
| ... | ... |
接受它
在实现接受之前还要实现一个调用原始widget的函数,因为我接受建议用的是方向键的右键,所以需要重新绑定这个widget,并且在接受时确保这个与原始widget都会被调用。
function invoke_bwidget() {
local bw="$1"
shift
zle $bw -- $@
}
获取传入的widget名称,shift除去刚刚的入参,使用zle调用原始widget以及传入剩下的参数,完事儿。
接受建议其实就是将 POSTDIPLAY 的部分划归到 BUFFER 中去,但还是要注意一些细节问题。下面是具体接受建议的代码。
function high_accept() {
sug=${POSTDISPLAY#$BUFFER}
BUFFER="$BUFFER$sug"
unset POSTDISPLAY
invoke_bwidget $@
region_highlight+=("0 $#BUFFER fg=7")
CURSOR=$#BUFFER
}
除去POSTDISPLAY中的BUFFER字符串,最后拼接到 BUFFER 之后(操作有点多于,但是只是做个例子)。删掉 POSTDISPLAY ,通过invoke_bwidget 调用原始widget,也就是 包装了.forward-char这个widget的widget.最后重新设置高亮为白色。设置光标到BUFFER末尾。这样接受建议的逻辑也完成啦。
实际功能实现完成了,就要开始做一些套娃工作了。
套娃开始
流程一:我要获得建议并高亮。
function test_suggestion() {
auto;
highlight
}
很有道理,先调用auto获取建议之后高亮建议。
流程二:在合适的时候调用建议高亮函数。
function high_enable() {
invoke_bwidget $@
if (( $#BUFFER )); then
test_suggestion $@
fi
}
一开始调用原始widget(这里是.self-insert),当BUFFER长度发生变化时就开始调用建议高亮函数。
流程三:统一封装(也就是zsh-autosuggestions插件匿名函数干的事情)
function high_enable_widget() {
local -i retval;
high_enable $@;
retval=$?;
zle -R
return retval;
}
function high_accept_widget() {
local -i retval;
high_accept $@;
retval=$?;
zle -R
return retval;
}
不做过多解释,dddd了属于是。
流程四:再套一下,middle_widget
zle -N $MYPREFIX-insert ori_insert
zle -N $MYPREFIX-forward_char ori_forward_char
function middle_widget1() {
high_enable_widget $MYPREFIX-insert $@
}
function middle_widget2() {
high_accept_widget $MYPREFIX-forward_char $@
}
$MYPREFIX是我自己定义的一个字符串变量,这里使用zle创建自定义名称的widget绑定到包装了原始的widget的函数,最后将自定义绑定的widget传入enable和accept函数,作为变量,也就是将要被内部的invoke_bwidget执行的参数。
流程五:start函数
function start() {
zle -N -- self-insert middle_widget1;
zle -N -- forward-char middle_widget2;
}
在start函数内部重新将可以被绑定的内部widget重新绑定到已经实现了新功能的函数上,这样在每次出发这两个widget时候就会调用我们已经重新实现的函数啦。
最后的最后记得把start加入precmd的hook中
autoload -U add-zsh-hook
add-zsh-hook precmd start
加载 add-zsh-hook 命令,使用它设置precmd的hook。
🤔
使用 BUFFER 和 POSTDISPLAY 对吧,emmm,突然有个想法🤔。我好像可以用 bash 自己实现一个类似的建议操作(已经在做了,bash中没有BUFFER等变量,也不能直接调用readline命令,这是比较难受的,所以我只能用bash实现了zle的部分功能,比如插入字符,删除字符等操作),作为其他应用的前端,比如 redis 客户端。可能之后试试用 shell 实现一个 redis 客户端?或者服务端。其实到这里所有操作都是基于zsh的,具体来说也就是zle。还差一点点知识,bash就行了,差了啥呢?差了看看zsh的源码。先来看看我没实现完全,但是实现了建议操作的bash版本吧。

一切皆因 widget 而起,那 zsh 究竟怎么在我输入的时候就触发对应的 widget 的呢?这个其实已经有了答案,找个时间聊一聊,这里就不说了。睡觉睡觉~,小命要紧!--来自一个Linux爱好者。
附录
简单实现建议zsh代码。要使用测试的话,直接放在~/.zshrc文件内就好,如果装了zsh-autosuggestions插件记得关掉。
autoload -U add-zsh-hook
# ======================widget wrap start========================
MYPREFIX=lazyhippo
function invoke_bwidget() {
local bw="$1"
shift
zle $bw -- $@
}
function auto() {
local histcmd=$history[$(($HISTCMD - 1))]
if [[ ${histcmd#$BUFFER} != ${histcmd} ]]; then
POSTDISPLAY="${histcmd#$BUFFER}"
else
unset POSTDISPLAY
fi
}
function highlight() {
if (( $#POSTDISPLAY )); then
region_highlight+=("$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) fg=11")
fi
}
function test_suggestion() {
auto;
highlight
}
function high_enable() {
invoke_bwidget $@
if (( $#BUFFER )); then
test_suggestion $@
fi
}
function high_accept() {
sug=${POSTDISPLAY#$BUFFER}
BUFFER="$BUFFER$sug"
unset POSTDISPLAY
invoke_bwidget $@
region_highlight+=("0 $#BUFFER fg=7")
CURSOR=$#BUFFER
}
function high_enable_widget() {
local -i retval;
high_enable $@;
retval=$?;
zle -R
return retval;
}
function high_accept_widget() {
local -i retval;
high_accept $@;
retval=$?;
zle -R
return retval;
}
function ori_insert() {
zle .self-insert;
}
function ori_forward_char() {
zle .forward-char
}
zle -N $MYPREFIX-insert ori_insert
zle -N $MYPREFIX-forward_char ori_forward_char
function middle_widget1() {
high_enable_widget $MYPREFIX-insert $@
}
function middle_widget2() {
high_accept_widget $MYPREFIX-forward_char $@
}
function start() {
zle -N -- self-insert middle_widget1;
zle -N -- forward-char middle_widget2;
}
# ======================widget wrap end==========================
add-zsh-hook precmd start
评论 (...)
加载中...