読者です 読者をやめる 読者になる 読者になる

zsh補完関数を自作すると便利

会社でコードを書いていると、いろんなプロジェクトのディレクトリ間を頻繁に移動します。毎回cdするのはだるいので、それを解消する便利なzsh用の関数と補完関数を書いたので紹介します。はてなのエンジニアのひとと、zsh補完関数を書いてみたい人にはそこそこ役立つといった趣のエントリです。

会社のプロジェクトのリポジトリは、~/work/hatena というところに全部まとめています。だいたい以下のような感じです。

> ls ~/work/hatena
Hatena-Antenna/  Hatena-Diary/    Hatena-Ugomemo/  git-hooks/
Hatena-Bookmark/ Hatena-Group/    Ridge/           iphone/
Hatena-Coco/     Hatena-Star/     android/         sketch/

各プロジェクトのリポジトリには頻繁に移動するので、簡単なzsh関数を作って使っています。

function h {
    cd ~/work/hatena/$1
}

h Hatena-Bookmarkのようにすれば、各プロジェクトにcdできて便利です。しかし、毎回、Hatena-Bookmarkとか打つのは面倒な感じです。なので、zshの補完関数を作ってみました。

h [TAB]と入力すると、以下のように表示されます。

f:id:hakobe932:20120213212605p:plain

はてなの多くのプロジェクトのリポジトリの名前はHatena-から始まるので、その後の部分だけで補完できるようになっています。たとえば h b[TAB]と入力するとh Hatena-Bookmarkに補完できます。

以下のgistを.zshrcのcompinit以降にべたっとはって、パスを調整すれば使えるようになりますので、はてなのエンジニアのみなさまははどうぞご利用ください。

Hatena-Project zsh completion · GitHub

補完関数ができるまで

上記の関数を完成させるまでに試行錯誤をしたので、途中でうまれた補完関数を紹介します。

_files

補完関数を書くための支援関数である_filesを使って ~/work/hatena 以下を全部補完します

function _h {
    _files -W ~/work/hatena/ && return 0;
    return 1;
}

普通はだいたいこれで間に合う気がするのですが、プロジェクトのディレクトリに移動するにはHatena-がじゃまで、何度もTABを入力する必要があってめんどくさい感じです。

compadd

補完候補を登録する関数であるcompaddを直接使います。_filesなどの支援関数は内部でcompaddを呼んでいます。この関数は高機能で引数のprefixやsuffixを指定できたりします。

function _h {
    # プロジェクトっぽいディレクトリを配列に格納
    local -a projects
    projects=( $( find ~/work/hatena/* -type d -maxdepth 0 -exec basename '{}' ';' | grep '^Hatena-' | sed -e 's/^Hatena-//') )

    # 'Hatena-' prefix付きで補完候補に登録
    compadd -P 'Hatena-' $projects

    return 1;
}

-P 'Hatena-'のように指定しておくと、補完した結果にprefixをつけてくれます。b[TAB]と入力してBookmarkを補完すると、結果としてHatena-Bookmarkが得られます。

しかし、このままだと、Hatena-ではじまらないディレクトリが補完できません。二回にわけてcompaddしても良いのですが、二種類の補完候補が混ざってしまいます。

_describe

最終形です。_describeをつかえば、タグで補完候補をまとめつつ、説明をつけて補完候補を登録できます。内部で呼ばれているcompadd関数に引数を直接わたすこともできます。

function _h {
    local allfiles
    local -a _projects _others

    allfiles=`find ~/work/hatena/* -type d -maxdepth 0 -exec basename '{}' ';'`

    # projects というタグで補完候補をまとめつつ
    # "Projects" という descriptionをつけて
    # _projects 内の値を補完候補に登録する
    # compadd に渡るときに "-P Hatena-" オプションをつける
    _projects=( $(echo $allfiles | grep '^Hatena-' | sed -e 's/^Hatena-//') )
    _describe -t projects "Projects" _projects -P Hatena-

    #Projects と同様だが compadd にオプションはつけない
    _others=( $(echo $allfiles | grep -v '^Hatena-') )
    _describe -t others "Others" _others

    return 1;
}

という感じで求める補完関数にたどりつきました。

まとめ

弊社内では便利な関数と補完関数について紹介しました。

説明した関数以外にも、条件に応じて補完を制御できる_arguments関数や、コマンドの実行結果をキャッシュできる _store_cacheなど便利(だけど使い方が難しい)関数がそろっています。PerlとかRubyで適当に値のリストをつくって登録するだけでもそこそこ便利なので、気軽に作って見ると良いと思います。

参考

  • zsh補完関数の書き方: 結構有名なzsh補完関数の書き方記事です。読むとなんとなく雰囲気がわかるようになります。
  • man zshcompsys: 補完についての全体について説明されています。 _describe_filesの解説はこのマニュアルに記載されています。
  • man zshcompwid: よりローレベルな補完の仕組みについて説明されています。compaddの解説はここに記載されています。
  • $fpath: zshの補完関数の実態が置かれています。manページはリファレンスには使えますが、どういうときにどの関数を使うかはあんまりわかんないので、実例を読むのが良いと思います。