Neovim のフロートウィンドウ機能を使って git-messenger.vim をつくりなおした
git-messenger.vim は,カーソル下の行のコミット情報を表示する Vim プラグインです.
他所のプロジェクトのコードや OSS のコードを読んでいると,なぜこうなってるんだろう?と思うことがよくあります.コミットメッセージをまともに書いているプロジェクトでは,その部分の変更を加えたコミットメッセージに答えがあることがあるのですが,毎回 git blame
で対象のコミットを引っ張ってきて git show
で読むのは結構大変です.
git-messenger.vim では,その負担を軽減するために,(1) カーソルがいる行にコミット情報を git
から引っ張ってきて (2) 良い感じに表示する という機能を提供します.
もう6年も前につくったプラグインなのですが,Vim の制約上いくつかの問題があり,syohex さんの Emacs port に比べて良いものになりませんでした.Vim のバルーン(マウスを一定時間バッファ上に置いておくと出せるツールチップ)が GUI にしか対応していないのが主な理由でした.
先日,Neovim にフロートウィンドウ機能が入り,CUI でもツールチップのような UI が簡単に使えるようになったので,git-messenger.vim をフルスクラッチでつくりなおしました.Neovim のフロートウィンドウについては後の章でまとめました.
git-messenger.vim
インストール
すべて Vim script で実装されているので,他のプラグインと同様に rhysd/git-messenger.vim
リポジトリをインストールするだけで OK です.あともちろん git
コマンドも要ります.
コマンドの実行は以前は system()
を使っていたのですが,大きいリポジトリでもユーザの入力をブロックしないように job を使って書いています.なので,Neovim または Vim (8 以降)が必要です.
また,フロートウィンドウは最近 Neovim の master に入ったところなのでまだ安定版としてはリリースされていません.開発版の 0.4.0-dev を使う必要があります.例えば macOS であれば brew install neovim --HEAD
で入ります.
使い方
:GitMessenger
もしくはデフォルトでマップされている <Leader>gm
を入力すると,カーソルしたのコミット情報がポップアップウィンドウで表示されます.
- Commit hash
- Author, Committer
- Summary (コミットメッセージの1行目)
- Body (コミットメッセージの2行目以降)
ポップアップウィンドウは Neovim 0.4.0 以降では前述のフロートウィンドウで,それ以外ではプレビューウィンドウで実装されています.その後カーソルを動かすとポップアップは自動で閉じます.
基本的にはこれだけですが,ポップアップを開いたあとにカーソルを動かさずそのままもう一度 :GitMessenger
コマンドか <Leader>gm
を入力するとポップアップウィンドウの中にカーソルを移動できます.コミットメッセージが長すぎて全部表示できなかった時のスクロールやメッセージのクリップボードへのコピーなどができます.
ウィンドウ内ではいくつかローカルなマッピングが定義されており,o
でより古いコミットを手繰ることができます.直近のコミットにほしい情報が書いてあるとは限らない(例えばフォーマッタでの整形が挟まったり,別のリファクタリングが挟まったりなど)ので,そういうときはより古いコミットのメッセージにほしい情報があることがあります.さらに O
で新しいコミットに戻ったり,q
でウィンドウを閉じたりできます(?
でヘルプ)
カスタマイズ
- デフォルトのマッピング
<Leader>gm
が気に入らない場合はg:git_messenger_no_default_mappings
にv:true
をセットして<Plug>(git-messenger)
をマップすれば OK です - 他にもいくつか
<Plug>
マップが定義されています - いくつかの挙動はグローバル変数で制御できるようになっています
- Neovim のみポップアップウィンドウ内のハイライトをカスタマイズできます.デフォルトの色合いがお使いのカラースキームに合わないときは自分で色をカスタマイズできます.Neovim のみなのはウィンドウローカルにハイライト色を変更できる
winhighlight
というオプションを Neovim だけが持っているためです
詳しくはリポジトリの README.md か :help git-messenger
で確認できます.
Neovim のフロートウィンドウについて
Vim のウィンドウは分割することでタイル型に配置されるレイアウトしか対応しておらず,ウィンドウが重なるようなレイアウトは(無理矢理バッファを書き換えてそれっぽく見せるようなことをしない限り)実現できませんでした.
そこで Neovim では CSS の position: relative
や position: absolute
のように位置基準でウィンドウの重なりを許すウィンドウレイアウトを実装しました.
何が良いのか
この機能の設計で特に優れていると感じる点は,レイアウト以外はほぼ完全に普通の Vim のウィンドウと同じところです.
- CUI で使える
- プラグイン開発者はウィンドウの開き方だけ分かれば,あとは普通の Vim のウィンドウと同様に扱える
- ウィンドウ内のコンテンツは普通の Vim のバッファなので,
setline()
などで自由に追加・変更・削除できる - filetype をセットしたり,自由にハイライトできる
- ウィンドウを開く以外はバルーンのような専用の API が要らない
この機能により,プラグイン開発者はウィンドウの上にオーバーレイするような UI を変なハックをしたり妥協することなく実装することができます. ぱっと思いつくのは
などに使えそうです.
使い方
フロートウィンドウを開くのには nvim_open_win()
,サイズや配置を変えるのには nvim_win_set_config()
を使います.
" 開くウィンドウの幅 let width = 40 " 開くウィンドウの高さ let height = 10 " ウィンドウを開いたあと,カーソルをそのウィンドウ内に移動するか let enter = v:true " カーソルを :wincmd やマウスでウィンドウ内に移動できるか let focusable = v:true " 何に対して相対的にウィンドウの配置位置を決めるか " - "editor": エディタのスクリーンに対して.スクリーン上の絶対座標で指定 " - "win": 現在のウィンドウ位置に対して.これを使う場合はオプションに 'win' というキーで別途対象ウィンドウのウィンドウ ID を指定する " - "cursor": カーソル位置に対して let relative = 'cursor' " 基準となるウィンドウの角を四隅のどこにするかを指定します(デフォルト "NW") " - "NW": 左上 " - "NE": 右上 " - "SE": 右下 " - "SW": 左下 let anchor = 'NW' " 'relative' で指定した位置に対する相対的なオフセット let row = 1 let col = 0 " Neovim 内ではなく,GUI フロントエンド側にウィンドウを出すよう依頼するか let external = v:false " 現在のバッファをフロートウィンドウで開く " カーソルのすぐ下に 40x10 のウィンドウが開かれる let win_id = nvim_open_win(bufnr('%'), enter, { \ 'width': width, \ 'height': height, \ 'relative': relative, \ 'anchor': anchor, \ 'row': row, \ 'col': col, \ 'external': external, \}) " 新しいバッファを開いたり enew " 普通に setline() を使ってコンテンツをセットしたり call setline('.', ['hello', 'world!']) " filetype を指定してハイライトを定義したり set filetype=ruby bufhidden=wipe nomodified buftype=nofile " フロートウィンドウを別のハイライトで描画したり set winhighlight=Normal:MyNormal,NormalNC:MyNormalNC " nvim_win_set_config() でウィンドウのレイアウトをやり直せる call nvim_win_set_config(win_id, { \ 'width': 60, \ 'height': 30, \ 'relative': 'editor', \ 'row': 10, \ 'col': 10, \})
フロートウィンドウにはボーダーが無いので,デフォルトのままではフロートウィンドウと下のウィンドウの境界がわかりません.なので特に最後の既存のウィンドウと別のハイライトで背景色を描画するのは重要です.また,フロートウィンドウ内で新しいバッファを開いた際はウィンドウを閉じると中身も自動で開放されるよう bufhidden=wipe
を指定しておきます.
nvim_win_set_config()
でウィンドウのレイアウトをやりなおすことができるので,これを使ってウィンドウのサイズ変更や位置変更をします.
ちなみに row
,col
,width
,height
がエディタのスクリーンからはみ出してしまってもエラーになりません.Neovim はなるべくウィンドウをスクリーン内に収めるように描画します.git-messenger.vim では現在のカーソル位置からポップアップを出すのに十分な幅・高さがあるかを確認し,カーソルの上下左右にフロートウィンドウを出し分けます.