fishの補完の書き方について調べたので,まとめてみました.
bash, zshでは「補完を書く」なんて考えもしませんでしたが,fishだとかなり簡単に書ける印象です.
このエントリではhubコマンドの補完を書きながら説明していきます.
(hub
を選んだのはサブコマンド持ちのコマンドだからで,とくに詳しいわけではないので何か間違いがあったらご指摘ください.hub
のfish補完にはhub公式の補完が既にあり,そちらも参考にしています.)
参考にしたサイト等
日本語マニュアル: 全訳!fishシェル普及計画
補完についての記事: fish shell で補完の実装 - complete
fish標準添付の補完設定: https://github.com/fish-shell/fish-shell/tree/master/share/completions
補完を定義するには?
~/.config/fish/completions
ディレクトリにコマンド名.fish
という名前のファイルを置いおくと,補完のたびに設定が読み込まれるようです.
次のような関数を定義しておくと補完設定がすぐに書けて便利かもしれません.
function ced --description 'edit completion'
eval "$EDITOR $HOME/.config/fish/completions/$argv[1].fish"
end
今の場合は~/.config/fish/completions/hub.fish
を作成し,その中に補完設定を書いていきます.
オプションを補完するには?
補完の設定をするにはcomplete
コマンドを使います.
オプションの設定は-s (--short-option)
,-l (--long-option)
, -o (--old-option)
でできます.
hub
の場合はオプションに--help
と--version
を取るので,以下のようにします.
complete -c hub -l help -d 'show help'
complete -c hub -l version -d 'print the version'
サブコマンドを補完するには?
hub
はサブコマンドを使うコマンドなので,それを補完できるようにしてみます.
サブコマンド補完は「入力中の語が,コマンドの第一引数であるとき」の条件付き補完となります.条件付き補完をする際には,条件の判定式をcomplete
の-n (--condition)
オプションに渡します.判定式は__fish_use_subcommand
という関数が用意されているので,これを使って次のように書けます.
complete -f -c hub -n '__fish_use_subcommand' -a alias -d "show shell instructions for wrapping git"
complete -f -c hub -n '__fish_use_subcommand' -a browse -d "browse the project on GitHub"
complete -f -c hub -n '__fish_use_subcommand' -a compare -d "lookup commit in GitHub Status API"
complete -f -c hub -n '__fish_use_subcommand' -a create -d "create new repo on GitHub for the current project"
complete -f -c hub -n '__fish_use_subcommand' -a fork -d "fork origin repo on GitHub"
complete -f -c hub -n '__fish_use_subcommand' -a pull-request -d "open a pull request on GitHub"
complete -f -c hub -n '__fish_use_subcommand' -a ci-status -d "display GitHub Status information for a commit"
ただ,__fish_use_subcommand
はマニュアルに記載のない関数なので,将来使えなくなるかもしれません.
判定式を自作する場合は次のようになるかと思います.
function __fish_is_subcommand
test 1 -eq (count (string match -v -- '-*' (commandline -poc)))
end
complete -f -c hub -n '__fish_is_subcommand' -a alias -d "show shell instructions for wrapping git"
...
commandline -poc
は入力中のプロセス^procのカーソルが置かれた語の前までの語(-c)を単語に分割して出力(-o)するコマンドです.補完の条件判定でよく使われています.
補間設定ファイル内で定義された関数は,そのファイル内でだけ有効1です.
また,complete
に-f (--no-files)
オプションが付いているのは,補完候補としてファイル名一覧が表示されるのを抑制するためです.
サブコマンドのオプションを補完するには?
これも条件付き補完で,判定式は__fish_seen_subcommand_from
というものが用意されています.hub
のalias
サブコマンドは-s
オプションを取るので,次のようにします.
complete -x -c hub -n '__fish_seen_subcommand_from alias' -s s -d 'output shell script suitable for eval'
complete
の-x
オプションは-f
と-r
を合わせたものです.
-r
オプションは「オプション引数を必ず取る」指定で,hub alias -sh (= hub alias -s -h)
のようにショートオプションを続けて書くような補完がなされなくなります.
つまり-x
オプションは「他のショートオプションを直に続けて書くことを認めず,オプション引数の補完候補としてファイル名を使わない」となります.
補完候補を自分で用意するには?
hub alias
の-s
オプションは,シェルの名前を指定するとhub
にgit
という名前でエイリアスを貼る方法を表示してくれます.そのため-s
のオプション引数はbash
, zsh
, sh
, ksh
, csh
, fish
のいずれかです.
これらの選択肢を補完候補として登録するには,先程の設定に-a (--arguments)
オプションを加えて次のようにします.
complete -x -c hub -n '__fish_seen_subcommand_from alias' -s s -a 'bash zsh sh ksh csh fish' -d 'output shell script suitable for eval'
この設定も-f
オプションがないとファイル名が補完候補に混じることになるのですが,今の場合は-x
が-f
を兼ねるので大丈夫です.
グローバルオプションをサブコマンドのオプション補完候補から除くには?
ここまでで,alias
サブコマンドに-s
オプションが補完できるようになりました.いまhub alias -
と打ってTabを押すと補完候補は--help
, -s
, --version
となります.
--help
オプションはサブコマンドについても有効ですが,--version
はそうではありません.そのため先程の補完設定を次のように書き換えておきます.
complete -c hub -n '__fish_no_arguments' -l version -d 'print the version'
補完候補をコマンドで生成するには?
次にbrowse
サブコマンドの補完を考えます.このサブコマンドは,リポジトリ名を指定するとそれをブラウザで開いてくれます.
で,このリポジトリ名として**ghqで管理してるリポジトリが補完されたら便利なんじゃないかなー**と思いました.
コマンド(今の場合はghq list
)の出力を補完候補とするには,complete
の-a
オプションの引数の中にコマンド置換を書いておけばよいです.
complete -f -c hub -n "__fish_seen_subcommand_from browse; and type -q ghq" -a "(ghq list | string match -r '(?<=github\.com/).+')"
何番目の引数かによって補完候補を変えるには?
browse
サブコマンドはリポジトリ名に続けてwiki
, commits
, issues
のようなキーワードを続けることで,リポジトリの該当ページを開いてくれます.
つまり,browse
サブコマンドは第一引数としてリポジトリ名を,第二引数としてキーワードを取ります.入力中の語が何番目の引数なのか? を調べる関数2を定義すれば次のように補完設定ができます.
function __fish_is_arg_n --argument-names n
test $n -eq (count (string match -v -- '-*' (commandline -poc)))
end
complete -f -c hub -n "__fish_seen_subcommand_from browse; and __fish_is_arg_n 2; and type -q ghq" -a "(ghq list | string match -r '(?<=github\.com/).+')"
complete -f -c hub -n '__fish_seen_subcommand_from browse; and __fish_is_arg_n 3' -a 'wiki commits issues tree'
他のコマンドから補完設定を引き継ぐには?
最後にhub
からgit
のコマンドを呼び出す場合に,それも補完できるようにします.これはcomplete
コマンドのヘルプにも載っていて,以下のようにします.
complete -c hub -w git
まとめ
結局,hub
の補完設定は以下のようになります.
githubでも公開(ryotako/fish-hub-completion)していますが,最初に述べたようにhub
公式の補完もあるので,そちらを使ってもよいかもしれません.
条件付き補完を使いこなせば,fishでは簡単に強力な補完が書けます.
便利な判定式(__fish_use_subcommand
とか)がマニュアルに載っていないのが,玉に瑕でしょうか...
function __fish_is_arg_n --argument-names n
test $n -eq (count (string match -v -- '-*' (commandline -poc)))
end
# options
complete -c hub -l help -d 'show help'
complete -c hub -n '__fish_no_arguments' -l version -d 'print the version'
# subcommand
complete -f -c hub -n '__fish_use_subcommand' -a alias -d "[hub] show shell instructions for wrapping git"
complete -f -c hub -n '__fish_use_subcommand' -a browse -d "[hub] browse the project on GitHub"
complete -f -c hub -n '__fish_use_subcommand' -a compare -d "[hub] lookup commit in GitHub Status API"
complete -f -c hub -n '__fish_use_subcommand' -a create -d "[hub] create new repo on GitHub for the current project"
complete -f -c hub -n '__fish_use_subcommand' -a fork -d "[hub] fork origin repo on GitHub"
complete -f -c hub -n '__fish_use_subcommand' -a pull-request -d "[hub] open a pull request on GitHub"
complete -f -c hub -n '__fish_use_subcommand' -a ci-status -d "[hub] display GitHub Status information for a commit"
# alias
complete -x -c hub -n '__fish_seen_subcommand_from alias' -s s -a 'bash zsh sh ksh csh fish' -d 'output shell script suitable for eval'
# browse
complete -f -c hub -n '__fish_seen_subcommand_from browse' -s u -d 'print the URL instead of opening it'
complete -f -c hub -n "__fish_seen_subcommand_from browse; and __fish_is_arg_n 2; and type -q ghq" -a "(ghq list | string match -r '(?<=github\.com/).+')"
complete -f -c hub -n '__fish_seen_subcommand_from browse; and __fish_is_arg_n 3' -a 'wiki commits issues tree'
# compare
complete -f -c hub -n '__fish_seen_subcommand_from compare' -s u -d 'print the URL instead of opening it'
# create
complete -f -c hub -n '__fish_seen_subcommand_from create' -s p -d 'create a private repository'
complete -x -c hub -n '__fish_seen_subcommand_from create' -s d -d 'repository description'
complete -x -c hub -n '__fish_seen_subcommand_from create' -s h -d 'homepage URL'
# fork
complete -f -c hub -n '__fish_seen_subcommand_from fork' -l no-remote
# pull-request
complete -c hub -n ' __fish_seen_subcommand_from pull-request' -s f -d "skip the check for unpushed commits"
complete -x -c hub -n ' __fish_seen_subcommand_from pull-request' -s m -d "use the first line of <MESSAGE> as pull request title, and the rest as pull request description"
complete -r -c hub -n ' __fish_seen_subcommand_from pull-request' -s F -d "read the pull request title and description from <FILE>"
complete -f -c hub -n ' __fish_seen_subcommand_from pull-request' -s o -d "open the new pull request in a web browser"
complete -f -c hub -n ' __fish_seen_subcommand_from pull-request' -s b -d 'the base branch in "[OWNER:]BRANCH" format'
complete -f -c hub -n ' __fish_seen_subcommand_from pull-request' -s h -d 'the head branch in "[OWNER:]BRANCH" format'
# ci-status
complete -f -c hub -n '__fish_seen_subcommand_from ci-status' -s v -d 'report all checks and their URLs'
# git
complete -c hub -w git