其实老早就可以写这个了,但是一直懒懒的没有动手。事情的缘起是因为有个人在zsh的mailing list里面再一次问起这个问题,两天都没有人回复—-照我以前搜索的结果,这样的问题是不会有人回复的,因为没有人知道。但是用zsh的都是非正常人,“为什么fish可以zsh不可以”这样的问题,肯定是会在脑袋里不停的转的,所以这问题也一次又一次的被人提起。

于是我忍不住“又”搜索了一下。Google上搜索东西的诀窍在于,如果你搜不到,那你就花两天时间来搜,而我正是那种有这种执念的宅男之一。终于,我搜到了一个日本人的网页,他有个zsh的脚本,用来做“complete as you type”的,其中用到了一个叫做“region_highlight”的array变量,专门就是用来定义命令行里面的颜色的。

这下简单了,剩下的就是怎么上色和怎么在zle里面启用了。我1个小时就搞出了第一个版本,放到mailing list里面,很快就有了回复:我的单括号在他们那边行不通,他们又鄙视我没有用case语句而用了一排的if。不过鄙视归鄙视,几封email来回之后,现在的版本已经很成熟了。下面是我的版本:

TOKENS_FOLLOWED_BY_COMMANDS=('|' '||' ';' '&' '&&' 'sudo' 'do' 'time' 'strace')

recolor-cmd() {
    region_highlight=()
    colorize=true
    start_pos=0
    for arg in ${(z)BUFFER}; do
        ((start_pos+=${#BUFFER[$start_pos+1,-1]}-${#${BUFFER[$start_pos+1,-1]## #}}))
        ((end_pos=$start_pos+${#arg}))
        if $colorize; then
            colorize=false
            res=$(LC_ALL=C builtin type $arg 2>/dev/null)
            case $res in
                *'reserved word'*)   style="fg=magenta,bold";;
                *'alias for'*)       style="fg=cyan,bold";;
                *'shell builtin'*)   style="fg=yellow,bold";;
                *'shell function'*)  style='fg=green,bold';;
                *"$arg is"*)         
                    [[ $arg = 'sudo' ]] && style="fg=red,bold" || style="fg=blue,bold";;
                *)                   style='none,bold';;
            esac
            region_highlight+=("$start_pos $end_pos $style")
        fi
        [[ ${${TOKENS_FOLLOWED_BY_COMMANDS[(r)${arg//|/\|}]}:+yes} = 'yes' ]] && colorize=true
        start_pos=$end_pos
    done
}

check-cmd-self-insert() { zle .self-insert && recolor-cmd }
check-cmd-backward-delete-char() { zle .backward-delete-char && recolor-cmd }

zle -N self-insert check-cmd-self-insert
zle -N backward-delete-char check-cmd-backward-delete-char

原理很简单的:recolor-cmd函数专门用来给一个字符串上色,里面split字符的时候用的是(z),也就是按照zsh的语法来分割;check-cmd-self-insertcheck-cmd-backward-delete-char是两个新定义的zle widget,代替原本在插入和删除字符时候执行的默认widget,以达到上色的效果。

这东西的坏处是,如果你从别的地方粘贴什么东西到zsh里面去,其实是逐字插入的,你可以感觉到插入速度变慢。另外,直接使用外部的syntax highlighter也是可能的,我就试过用source-highlight的输出直接转化成region_highlight的,代码都写的差不多了,就有些颜色上错位的小问题,但是粘贴命令已经慢到让人无法忍受了,最后还是作罢。

zsh_color_cmd