Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
25
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

@ryotako

fishの補完設定覚え書き

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は入力中のプロセス1(-p)のカーソルが置かれた語の前までの語(-c)を単語に分割して出力(-o)するコマンドです.補完の条件判定でよく使われています.
補間設定ファイル内で定義された関数は,そのファイル内でだけ有効2です.
また,complete-f (--no-files)オプションが付いているのは,補完候補としてファイル名一覧が表示されるのを抑制するためです.

スクリーンショット 2017-03-19 12.16.52.png

サブコマンドのオプションを補完するには?

これも条件付き補完で,判定式は__fish_seen_subcommand_fromというものが用意されています.hubaliasサブコマンドは-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オプションは,シェルの名前を指定するとhubgitという名前でエイリアスを貼る方法を表示してくれます.そのため-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サブコマンドは第一引数としてリポジトリ名を,第二引数としてキーワードを取ります.入力中の語が何番目の引数なのか? を調べる関数3を定義すれば次のように補完設定ができます.

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

  1. プロセスはコマンドとその引数からなる文で,パイプや;で複数コマンドが組み合わせられる前のものを指します. 

  2. ~/.config/fish/completions以下の補完設定が自動読込された場合には,関数がグローバルに定義されることはありませんが,sourceで再読込した場合はグローバルな関数になります. 

  3. 何番目の語なのか? を調べる関数としては__fish_is_token_nがありまが,これはオプションもカウントしてしまします.一方,最初の引数かどうか調べる__fish_is_first_tokenはオプションを無視する実装になっていて,いまひとつ統一感がありません. 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
25
Help us understand the problem. What are the problem?