绝大部分平常使用Linux和OS X的程序员都会选择zsh做为本身的shell环境,毕竟对比于bash,zsh的便利性/可玩性要胜出不少,同时它又能兼容bash大多数的语法。不过相对而言,zsh补全脚本要比bash补全脚本要难写。zsh提供了很是多的补全的API,并且这些API功能有很多重叠的地方,掌握起来并不容易。不像bash,你只需记住三个API(compgen
,complete
,compopt
)就能实现整个补全脚本。php
这篇的任务跟上一篇的同样,须要实现一个针对pandoc
的补全脚本,囊括下面三个目标:html
在开始以前,须要说明下放置zsh脚本的地方,这样咱们才能让接下来写的补全脚本发挥效力。
zsh在启动时会加载$fpath
路径下的脚本文件。试试echo $fpath
来看看这个变量的值。接下来咱们能够把补全脚本放到$fpath
的路径下,或者建立一个新的在$fpath
路径中的目录:html5
mkdir ~/.fpath
~/.zshrc
中添加fpath=($HOME/.fpath $fpath)
当咱们把本身写的补全脚本放好后,每次zsh一启动,就会加载它。不过总不能每次修改完脚本后,都重启一次zsh吧。若是只是单纯更新补全脚本,能够执行unfunction _pandoc && autoload -U _pandoc
,zsh就会从新加载补全脚本了。(其中_pandoc
是补全脚本的名字)git
仍是跟上一篇同样,先解释一个实现第一个目标的程序,带各位入门:程序员
zsh#compdef pandoc # 把它命名为_pandoc,保存在$fpath路径下 _arguments \ {-f,-r}'[-f FORMAT, -r FORMAT, Specify input format]' \ {-t,-w}'[-t FORMAT, -w FORMAT, Specify output format]' \ {-o,--output}'[-o FILE, --output=FILE, Write output to FILE instead of stdout]' \ {-h,--help}'[Show usage message]' \ {-v,--version}'[Print version]' \ '*:files:_files'
就像bash的complete
,zsh也有一个相对的表示补全的API,就是compdef
。zsh补全脚本以#compdef tools
开头,表示该文件是针对tools
的补全脚本。固然你也能够像bash同样,直接compdef _function tools
来指定tools
的补全函数。github
zsh补全API的第一梯队是_alternative
、_arguments
、_describe
、_gnu_generic
、_regex_arguments
。它们直接提供补全的来源。这些API的概述见https://github.com/zsh-users/zsh-completions/blob/master/zsh-completio...。因为_describe
能作的_arguments
也能作,_gnu_generic
是为GNU拓展的命令参数准备的,_regex_arguments
就是正则匹配版的_arguments
,因此只要记住_arguments
和_alternative
就够用了。shell
_arguments
接受一连串的选项字符串,每一个字符串表明一个选项。另外你还能够经过一些选项指定补全上的细节。举-s
为例:假设你的工具支持-a -b
两个选项,也支持-ab
的方式来同时指定两个选项。若是没给_arguments
提供-s
的选项,那么zsh是不会补全出-ab
,由于并不存在选项-ab
。而提供了-s
后,_arguments
才容许你在已经输入-a
的状况下,补全出-ab
。json
选项字符串的格式是这样的:-x[description]:message:action
。你也能够写作{-x,-y}[description]:message:action
形式,表示-x
和-y
是等价的写法。数组
-x
是选项的名字[description]
是该选项的描述,可选message
这一项我也不知道是什么意义……不过它是可选的,除非你须要指定actionaction
用于生成复杂的补全。在这里你可使用许多补全语法。一个常见的例子是使用辅助函数,好比_files
表示补全当前路径下的文件名。详见:最后一行'*:files:_files'
表示,若是找不到匹配的候选词,就补全文件名。
到目前为止,实现第一阶段目标的脚本所需的知识点已经讲解完毕。bash
_arguments
有一个限制,它要求选项的名字符合某些特殊格式,好比以-
、+
、=
等字符开头(因此才叫_arguments
嘛)。若是你的工具接受add
、remove
之类的子命令,就须要用到_alternative
。
_alternative
支持的选项字符串格式跟_arguments
很像,好比
_arguments \ {-t,-w}'[-t FORMAT, -w FORMAT, Specify output format]'
等价于
_alternative \ 'writer:writer options:((-t\:"-t FORMAT, -w FORMAT, Specify output format" -w\:"-t FORMAT, -w FORMAT, Specify output format"))'
所谓的支持子选项,就是在某些选项存在的状况下,增长多一些选项。因此,咱们所要作的,就是检查当前输入的命令行参数中是否存在某些参数,若是存在,增长新的选项。这一步能够分解成两个步骤,第一个是检查某些参数是否存在,第二个是增长新的选项。
以前写bash补全脚本的时候,是经过遍历某个存储有当前输入的常量数组,来检查某些参数是否存在。在网上搜索一番后,我发现zsh也有一样的常量数组,就叫作words
,正好是bash那个的小写哈。那么接下来就是zsh的语法知识了:
zshif [[ ${words[(i)-f]} -le ${#words} ]] || [[ ${words[(i)-r]} -le ${#words} ]] then # 修改补全候选列表 fi if [[ ${words[(i)-t]} -le ${#words} ]] || [[ ${words[(i)-w]} -le ${#words} ]] then # 修改补全候选列表 fi
这里用到一点zsh特有的下标语法,至关于index()
。
那么下面是第二步,该怎么修改补全候选列表呢?若是直接用_arguments
指定新的补全列表,会覆盖掉前面指定的补全列表。固然也能够把前面的补全列表复制一份,并添加新的选项,用它覆盖掉原来的补全列表。不过这么一来代码就很差看了。
想来zsh应该提供了对应的API的。果不其然,有一个_values
能够用来干这事。_values
功能跟_arguments
差很少,并且它接受的选项列表是添加到原有的选项列表中的,而不是覆盖。因此最后的代码是这样的:
zshif [[ ${words[(i)-f]} -le ${#words} ]] || [[ ${words[(i)-r]} -le ${#words} ]] then _values 'reader options' \ '-R[Parse untranslatable HTML codes and LaTeX as raw]' \ '-S[Produce typographically correct output]' \ '--filter[Specify an executable to be used as a filter]' \ '-p[Preserve tabs instead of converting them to spaces]' fi if [[ ${words[(i)-t]} -le ${#words} ]] || [[ ${words[(i)-w]} -le ${#words} ]] then _values 'writer options' \ '-s[Produce output with an appropriate header and footer]' \ '--template[Use FILE as a custom template for the generated document]' \ '--toc[Include an automatically generated table of contents]' fi
最后一步是给-f
和-r
这两个选项提供读操做支持的FORMAT参数,给-t
和-w
这两个选项提供写操做支持的FORMAT参数。
在Bash篇的实现中,咱们检查上一个词的值,若是它是-f
或-r
,那么对当前词补全读操做的FORMAT参数。对写操做的选项也同理。
在zsh中,咱们能够用一个特殊的Action:->VALUE
来实现。
->VALUE
这样的Action会把$state
变量设置成VALUE
,接下来靠一个case语句块就能根据当前陷入的状态进行对应的参数补全。
那么该如何补全FORMAT参数列表呢?这里能够用上_multi_parts
。_multi_parts
第一个参数是分隔符,以后接受一组候选词或一个候选词数组做为候选词列表。例如_multi_parts , a,b,c
,就会生成a b c
这个补全候选列表。
这里的FORMAT变量直接使用上一章的$READ_FORMAT
和$WRITE_FORMAT
。
我试了一下,若是把FORMAT变量当作字符串传递过去的话,其间的空格会被转义,致使没法分隔开来,因而就把它们改写成数组的形式。
另外,因为补全FORMAT参数时,再也不须要补全选项了。因此把补全FORMAT参数的部分提到补全子选项的前面,并在补全后直接退出程序的执行。
最终完成的代码以下:
zsh#compdef pandoc local READ_FORMAT WRITE_FORMAT READ_FORMAT='(native json markdown markdown_strict markdown_phpextra markdown_github textile rst html docbook opml mediawiki haddock latex)' WRITE_FORMAT='(native json plain markdown markdown_strict markdown_phpextra markdown_github rst html html5 latex beamer context man mediawiki textileorg textinfo opml docbook opendocument odt docx rtf epub epub3 fb2 asciidoc slidy slideous dzslides revealjs s5)' _arguments \ {-f,-r}'[-f FORMAT, -r FORMAT, Specify input format]: :->reader' \ {-t,-w}'[-t FORMAT, -w FORMAT, Specify output format]: :->writer' \ {-o,--output}'[-o FILE, --output=FILE, Write output to FILE instead of stdout]' \ {-h,--help}'[Show usage message]' \ {-v,--version}'[Print version]' \ '*:files:_files' case "$state" in reader ) _multi_parts ' ' $READ_FORMAT && return 0 ;; writer ) _multi_parts ' ' $WRITE_FORMAT && return 0 esac if [[ ${words[(i)-f]} -le ${#words} ]] || [[ ${words[(i)-r]} -le ${#words} ]] then _values 'reader options' \ '-R[Parse untranslatable HTML codes and LaTeX as raw]' \ '-S[Produce typographically correct output]' \ '--filter[Specify an executable to be used as a filter]' \ '-p[Preserve tabs instead of converting them to spaces]' fi if [[ ${words[(i)-t]} -le ${#words} ]] || [[ ${words[(i)-w]} -le ${#words} ]] then _values 'writer options' \ '-s[Produce output with an appropriate header and footer]' \ '--template[Use FILE as a custom template for the generated document]' \ '--toc[Include an automatically generated table of contents]' fi
因为zsh的补全功能实在强大,而这篇文章只是简略地讲讲如何写出一个zsh补全脚本,有许多zsh的补全机制都没能提到。因此补充一些写zsh补全脚本的资料,若是对这方面有兴趣能够继续跳坑:
顺便一提,在查找资料的时候发现有人写了一个完整的pandoc的zsh补全脚本,感兴趣的话能够看一下:
https://github.com/srijanshetty/zsh-pandoc-completion/blob/master/_pan...