💨

Vim/NeoVimでMarkdownをプレビューするプラグインを作った

4 min read

powerd by denops

現在開発中です。使い物にならないような不具合はないはずですが おかしな挙動があった際教えていただけるとうれしいです

初めに

Vim/NeoVim(Windows/Mac/Linux(+WSL))対応のMarkdownプレビュープラグインを作成しました。
この記事では作った経緯とこのプラグインについて紹介します。

https://github.com/kat0h/bufpreview.vim

使い方

インストール

このプラグインはDenodenops.vimに依存しています。
初めにDenoを公式のインストールからインストールしてください。

denops.vimは通常のVimプラグインと同じようにインストールしてください。

Plug-Vim:

Plug 'vim-denops/denops.vim'
Plug 'kat0h/bufpreview.vim'

初回起動時にはTypescriptファイルの依存関係のダウンロードが走るため、起動に時間がかかります。

起動

Markdownファイルを開く(filetypeがmarkdownに設定された時)と下記のコマンドがバッファに登録されます。

:PreviewMarkdown
:PreviewMarkdownClose
:PreviewMarkdownToggle

コマンドを実行すると、自動でデフォルトのブラウザでプレビューが開きます。
また、:PreviewMarkdownCloseコマンドや、バッファの削除でプレビューを閉じることができます。

機能

bufpreview.vimでは大きく下記の機能を備えています。

  • リアルタイムの内容更新
  • エディタのカーソル位置との同期
動作デモ

リアルタイムの内容更新

このプラグインではキー入力の度にバッファの内容をブラウザに送信しています。
あたかもVSCodeの内蔵プレビューワを利用するかのように変更を確認することができます。

エディタのカーソル位置との同期

エディタのカーソル行のレンダリング結果がブラウザ画面の中央に表示されるようになっています。

作成の動機

Vimには既に素晴しいMarkdownレンダプラグインが存在しています。
いくつか種類がありますが、「依存関係の管理が楽」・「即座に変更を確認できる」・「カーソル位置との同期」が上手に動くプラグインはあまりありません。

https://github.com/iamcco/markdown-preview.nvim

当初は↑のプラグインを利用していた(とても素晴しいプラグインです)のですが、nodejsに依存してしまうことやなんかよくわからんえらーが出ることなどの特徴が自分には気にいらなかったので作ってみることにしました。

サーバに利用しているdenops.vim/Denoはnodejsのような面倒な依存関係の管理が必要なく、実行の際Denoのバイナリが一つあれば依存関係を自動で解決してくれるため非常に魅力的でした。

仕組み

ここからはプラグインの使用とは関係のない話です。

本プラグインでは、ブラウザにバッファを送信するサーバとしてTypescriptランタイムであるDenoを利用しています。
また、Vimとの通信にはdenops.vimを利用しています。
denops.vimの詳細は省略します。

今回、denops.vimを利用するために初めてTypescriptを触ったのですが、型のついた言語はとても便利だとおもいました。

バッファの更新

autocmdでバッファの更新を検知し、バッファ全行をDenoに送信しています。
バッファはbuffer.ts(ソース)で管理しており、server.ts(ソース)はBufferに登録したcallback経由で更新の通知を受取ります。
将来的にdenops.vimにbufferモジュールが追加された際に備えてBufferからのイベントを更新に利用する形としました。

ブラウザとの同期

また、サーバからブラウザへの送信にはWebsocketを利用しています。
Denoのstdにはstd/wsがあるのですが、新しいstd/httpと一緒に上手く動作させることができなかったので、Denoのランタイムライブラリにあるhttpサーバを利用しました。(該当部分)

Markdownのレンダリング

MarkdownのレンダリングにはMarkdown-itを利用しています。
iamcco/markdown-preview.nvimなどの大抵のレンダラではブラウザ側でレンダリングしていますが、本プラグインではDeno側でレンダリングしたHTMLを送信しています。
通信量が大きく増えてしまう欠点はあるものの、ページのロードが高速になる等の利点があったため現状はこの方式を採っています。

画面位置の同期

画面位置との同期にはVimのCursorMovedCursorMovedIイベントを利用しています。
レンダリング時にHTMLに行番号の対応を埋め込んでおり、その情報からスクロールを制御しています。

この際、Markdown-itの都合上一部対応が欠損してしまうため、対応するHTML要素が見つからなかった場合は周りの位置からスクロール先を計算しています。
考慮漏れなどでバグらせている可能性があります...

Javascriptからのスクロール制御にはwindow.scrollTo()を利用したのですが、smooth-scrollの設定がSafariで利用できなかったのでPolyfillを利用しました。

サーバで利用するポート

port: 0を指定してサーバを作成しています。ポートはDeno側で自動的に割り当てられます。

今後

iamcco/markdown-preview.nvimの使い心地を目指して9/24頃から開発を初めましたが、ひとまず基本の機能を実装することができました。
今後は挙動の調整やカスタマイズ機能の追加、対応ファイルタイプの追加などをしていきたいと考えています。

多分まだバグらせたところや、WS周りの考慮もれなどhttps://github.com/markdown-it/markdown-itが残っているので修正していきます...

Discussion

ログインするとコメントできます