絕大部分平常使用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...