vim-prettier を作った

tl;dr
GitHub - heavenshell/vim-prettier: Prettier-Eslint-Cli for Vim


Prettier と言う JavaScript のフォーマッターがある。
こいつ単体ではお仕事で設定している ESlint の設定とは関係なくフォーマットするが、prettier-eslint と言うのがあり、これを使えば ESLint の設定通りにフォーマットしてくれる。


で、フォーマッターはエディタから設定したくなる。
オフィシャルのドキュメントには以下のように使えばいいよとある。

autocmd FileType javascript set formatprg=prettier\ --stdin
autocmd BufWritePre *.js :normal gggqG

お手軽だけど、同期的に動く。
そして Prettier は速くはない。
当然 Vim から使えば、その間ブロックされる。

おそらく neoformat とやらも同期で動く。
https://github.com/sbdchd/neoformat/blob/master/autoload/neoformat.vim#L82

Golang の Fmt なんかは速いので同期でも問題ないが、 Prettier は同期では辛い。


というわけでいつも通り Vim の job を使って作った。
ただ、いくつか罠があった。

出力先がバッファ

job は出力先にバッファなファイルを選択できる。
この場合はフォーマットした結果をバッファにて書き換えたいから、バッファを選択してみた。

let s:job = job_start(cmd, {
        \ 'callback': {c, m -> s:callback(c, m)},
        \ 'err_cb': {c, m -> s:error_callback(c, m)},
        \ 'in_io': 'buffer',
        \ 'in_name': file
\ })

こうすると、現在開いているバッファ(file で指定しているバッファ)に追記される。
追記じゃなくて、書き換えてほしい。

出力先がバッファ
let s:job = job_start(cmd, {
        \ 'callback': {c, m -> s:callback(c, m)},
        \ 'err_cb': {c, m -> s:error_callback(c, m)},
        \ 'in_io': 'file',
        \ 'in_name': file
\ })

こうすると、現在開いているファイルをバックグラウンドでフォーマットした結果に書き換える。
現在開いているバッファは書き換わる前の状態。
この状態で :e! でファイルを開き直したら、書き換わった状態に変わる。
当然ファイルが書き換わってるので、フォーマットを実行したらファイルが保存される。


前に作った ESLint の --fix モードもこの方式で実装した*1
vim-frontier/eslint.vim at master · heavenshell/vim-frontier · GitHub


Vim-Swift などもこんな感じで行っている。
https://github.com/dictav/vim-swift/blob/master/autoload/swift/fmt.vim#L10


保存と同時に実行するという制約をつければいいが、なんか釈然としない。
せっかく stdin で受け取れるので、フォーマットを実行したら、保存されずにそのままにしてほしい。
他にないかなと探したら、 id:haya14busa さんの GitHub - haya14busa/vim-gofmt: Formats Go source code asynchronously with multiple Go formatters. でおおっと思った実装がされていた。
Add silent for avoid message by heavenshell · Pull Request #1 · haya14busa/vim-gofmt · GitHub *2
tmp なファイルにフォーマット後のコードを吐き出して、ファイルを読み込んで現在のバッファを全て消してバッファを書き換えていた。


prettier は stdout でフォーマット結果を返してくれるので、job の callback で受け取ってリストに追加、 exit_cb で追加されたリストの値で復元とすれば行けた。

*1:あとで書き換えるかも

*2:最新のコミットで改善されてたようだけど