ddu.vimのsource作成の備忘録
はじめに
本記事はVim/Neovimのpluginであるdenopsで書かれたddu.vimの拡張plugin,ddu-source-qfを作成した際の備忘録です.ddu.vimの拡張pluginの作成ハードルの低さをお伝えできればと思います.
動機
私は普段Neovimを使っています.
その中でLaTeXで文書を作成するときにVimTeXとTeXLabを同時に使っています.
ビルドや編集時に出力されるエラーやWarningはそれぞれ別々のQuickFixに出力されます(場合によります).しかしQuickFixを一つ一つ切り替える操作が必要で,(筆者のRAMやROMが足りないので)操作時に様々な情報を失ってしまいます.そこで,これら2つを同時に表示できないかを検討していました.
結果
以下の画像のような感じに表示できます.過去のQuickFixを複数取り出して1つのfloating windowに表示されています.
また,vimgrepの結果を複数表示することが可能です.
これらは以下のように設定を加えると;q
や;v
で起動できます(保存時にDiagnosticsからqflistに変換しています)
call ddu#custom#patch_local('qf', {
\ 'ui': 'ff',
\ 'sourceOptions': {
\ '_': {
\ 'matchers': ['matcher_substring'],
\ },
\ },
\ 'uiParams': {
\ 'ff': {
\ 'split': 'floating',
\ 'startFilter': v:false,
\ 'highlights': {'selected': 'Statement'},
\ 'winCol': &columns/8,
\ 'winWidth': 3*&columns/4,
\ 'winRow': &lines/8,
\ 'winHeight': 3*&lines/8,
\ 'autoAction': {'name':'preview'},
\ 'previewFloating': v:true,
\ 'previewCol': &columns/8,
\ 'previewWidth': 3*&columns/4,
\ 'previewRow': 7*&lines/8-1,
\ 'previewHeight': 3*&lines/8,
\ }
\ },
\ 'kindOptions': {
\ 'custom-list': {
\ 'defaultAction': 'open',
\ },
\ }
\ })
augroup mergeqf
autocmd!
autocmd BufWritePost * lua vim.diagnostic.setqflist({open=false})
augroup END
let format='%T|%p|%y|%t'
nmap <silent> ;q <Cmd>call ddu#start({
\ 'name': 'qf',
\ 'sources': [{
\ 'name': 'qf',
\ 'params': {'what': {'title': 'Diagnostics'}, 'format': format}
\ },
\ {'name': 'qf',
\ 'params': {'what': {'title': 'VimTeX'}, 'format': format,
\ 'isSubst': v:true,
\ }
\ }]})<CR>
nmap <silent> ;v <Cmd> call ddu#start({
\ 'name': 'qf', 'sources': [
\ {'name': 'qf',
\ 'params': {'what': {'title': ':vimgrep'},
\ 'isSubst': v:true,
\ 'format': format,
\ 'dup': v:true}},
\ {'name': 'qf',
\ 'params': {'what': {'title': ':lvimgrep'},
\ 'isSubst': v:true,
\ 'format': format,
\ 'nr': 0,
\ 'dup': v:true}}]})<CR>
ddu.vimと拡張方法
作者様が書かれた記事がかなりわかりやすいので本格的に作成したい場合はこちらを参照してください.
source開発時に考えるべきこと
開発者はsourceの対象を決めることだけです.そしてコーディング時には対象から必要な情報をItemにするだけです.実際に本拡張pluginの本質はユーザが指定した{what}
に対応するqflistをgetqflist()
を使ってqflistを拾ってきてそれぞれItemにするしかしていません.
source開発時に考えるべき”でない”こと
一度に複数のsourceを拾うこと
ddu.vimの関数に本体を起動するddu#start()
やそのグローバルな設定を与えるddu#custom#patch_global()
があります.その引数にどのsourceを与えるかを決めるsources
というオプションがあります.このsources
に複数のsourceを入れることで実質mergeしてくれます.なのでシンプルにただ欲しい物だけをひとつひとつItemにしてつっこむだけです.
拾ってくるときにフィルタリングをしっかりやる必要はない
ddu.vimはfilterも分離されており,ddu#start()
後にユーザが欲しい情報をフィルタリングできます.
なのでsourceがやるべきことはなんでもいいから重複してもいいからとにかく拾ってくることだけです(重複してよいかどうかはものによるかもしれません).
ddu-kind-fileに依存する場合の注意点
本拡張pluginはddu-kind-fileに依存しています.ユーザが選択したアイテムに対してどのような操作を行うかを与えるものです.以下は作成時にddu-kind-fileに依存する部分でつまずいた点です.作者様に教えていただいて解決しました.
pathにはVimのカレントディレクトリを使った絶対パスを指定する
denoにも絶対パスを取得する(検索する?)関数が用意されています.
ですが,vimにはchdirコマンドでカレントディレクトリを移動したりできるので,検索されたパスと編集中であったり取得したいパスが異なる場合があります.なのでDenopsでフルパスを作らず,vimscriptのgetcwd()
とbufnr()
を結合したものが推奨されます.
bufferに取り込まれているがhiddenなファイルはactionのbufNrを指定しない
タイトルのとおりです.対処の方法は最初にgetbufinfo()
でバッファの情報を取得してhiddenかどうかを判定すればいいです.開発時はこの理由はよくわかっていませんでした.今もよくわかっていませんが,ddu-kind-fileはbufNrが指定されているかどうかの判定を先に行っていて,hiddenのフラグが立っているときにはpreviewが表示されないみたいです.
- 修正されて,hiddenなbufferもpreviewできるように修正されました.(多分このcommitです)
- listedなbufferかどうかの判定が必要です.(ddu-ui-ffにlistedなbufferかどうかを判定して正常にpreviewできるように制御してくれています[2].)
まとめ
ddu.vimのインタフェースは洗礼されていてきれいに分割されているのでddu.vimのsourceはとにかく拾ってきてItemに入れるだけで完成するすごいpluginなので使いましょう.
今後の開発方針
- textやwordに入れるときのformatを設定できるようにしていますが,種類が少ないのでformatを拡張していきたいです(現在フルパスを指定できますが,例えば相対パスやファイル名のみが欲しい場合があるかもしれません).
- 現在ddu-source-qfはddu-kind-fileに依存していますが,対象のidと一致するqflistをcopenしたい場合があるのでもしかしたら不要かもしれません(が,ddu-kind-fileにbufNrを渡す渡さないをユーザに任せるのは結構大変な気がします).
- (既出かもしれませんが)ActionDataの中身ををfilterするddu-filter-action_dataやQuickFixまわりの操作をするddu-kind-qfを作れないかなと思っています.
最後に
本拡張pluginの作成にあたり,ddu.vimの作者様には指導していただき,質問に返答頂いたvim-jpの皆様にたくさんの助言をいただきました.心より感謝致します.
-
https://twitter.com/ShougoMatsu/status/1623826837951619074?s=20 ↩︎
-
214行目で存在判定を行って232行目以降で具体的に対応しているそうです.bdeleteでユーザがバッファから削除した場合を想定されています ↩︎
Discussion