NeovimでMarkdownをプレビューする(render-markdown.nvim / markdown-preview.nvim)
NeovimでMarkdownを書いていると、見出しや表を整った形で見たくなります。検索するといちばん名前が出てくるのはmarkdown-preview.nvimですが、GitHubのスターが8,015と多い一方で、cloneしたmasterの最新コミットは2023-10-17でした。いまのNeovim 0.12.4で本当に動くのか、動かないならどこで止まるのかを確かめるところから始めました。
結論から書くと、markdown-preview.nvimは入れただけではCannot find module 'tslib'で止まり、npm installを流すと動きます。後継として名前が挙がるrender-markdown.nvimとmarkview.nvimは、ブラウザを開かずバッファの文字を描き替える道具なので、新しさで選ぶと用途を外します。
検証環境:NVIM v0.12.4(macOS)、Node v18.20.2(npm 10.9.0)。プラグインはvim.pack.add()で一時ディレクトリに入れ、XDG_CONFIG_HOMEとXDG_DATA_HOMEもそこへ向けてから、tmuxの中のNeovimで同じsample.mdを開いて画面を取りました。入れた版はrender-markdown.nvim v8.14.0(コミット8e87953)、markview.nvim v29.0.0(ecc5a88)、markdown-preview.nvim(a923f5f)です。スター数と最終pushは2026-10-19にGitHub APIで取得しました。ブラウザ側の確認はChromeで行っています。
目次(9項目)
表示を変える道具は2系統ある
Markdownの見え方を変えるプラグインは、描く場所で2つに分かれます。render-markdown.nvimとmarkview.nvimはNeovimのバッファの中で、記号を隠して枠線やアイコンを重ねます。markdown-preview.nvimはローカルにHTTPサーバーを立て、ブラウザでHTMLとして表示します。
| 名前 | 描く場所 | スター | 最終push | 必要なもの |
|---|---|---|---|---|
| render-markdown.nvim | バッファの中 | 5,174 | 2026-10-10 | Nerd Font(アイコン用) |
| markview.nvim | バッファの中 | 3,665 | 2026-10-18 | Nerd Font推奨 |
| markdown-preview.nvim | ブラウザ | 8,015 | 2024-07-23 | Node.jsとnpm install |
render-markdown.nvimのdoc/markdown-ecosystem.mdも同じ分け方で、markview.nvimを衝突する仲間に、markdown-preview.nvimを併用できる側に置いています。
本体だけでどこまで見えるか(conceallevel と treesitter)
プラグインを入れる前に、Neovim本体の力を確かめておきます。0.12.4にはmarkdownとmarkdown_inlineのパーサが同梱されていて、ftplugin/markdown.luaの1行目がvim.treesitter.start()です。nvim-treesitterを入れていなくても、Markdownを開いた時点でtreesitterのハイライトが動きます。nvim --clean sample.mdで開き、vim.treesitter.highlighter.activeを見るとtrueでした。
既定のconceallevelは0なので、記号はそのまま見えています。:set conceallevel=2にして、カーソルを別の行へ動かすと次のようになりました。
本文に **太字** と *斜体* と `code` と [リンク](https://vim.blue/) があります。
本文に 太字 と 斜体 と code と リンク があります。
強調の**と*、インラインコードのバッククォート、リンクの[]と(URL)がまとめて消えます。コードブロックを囲む```luaと```の行は、行ごと空白に見えました。一方で、見出しの#、チェックボックスの[ ]、表の|、引用の>は残ります。本体の仕事は「記号を隠す」までで、表に罫線を引いたり見出しに色帯を付けたりはしません。
:lua vim.treesitter.stop()でtreesitterを止めて従来の正規表現のシンタックスに戻すと、太字と斜体の記号は隠れたままでしたが、バッククォートとリンクのURL、フェンスの行は表示に戻りました。conceallevel=1にした場合は、消えた記号の位置に空白が1つずつ入り、文字の位置がずれにくくなります。
カーソルのある行は、concealcursorが既定で空なので生のテキストに戻ります。見出しの一覧はgOでロケーションリストに出せました。これも同じftplugin/markdown.luaが定義しています。nvim-treesitterのmain移行で書いたとおり、0.12は本体だけで動く範囲が広く、Markdownも例外ではありません。拡張子が.mdでないファイルなら、ファイルタイプを手動で指定すれば同じ表示になります。
render-markdown.nvim をバッファ内で描画する
vim.packで入れる場合、設定は2行です。nvim-treesitterは入れていませんが、本体同梱のパーサで動きました。
vim.pack.add({ 'https://github.com/MeanderingProgrammer/render-markdown.nvim' })
require('render-markdown').setup({})
同じsample.mdを開くと、見出しの#はアイコンに、チェックボックスは四角とチェックのアイコンに、引用の>は縦の棒に置き換わりました。表には罫線が引かれます。
┌────────────┬────────────────┐
│ 名前 │ 説明 │
├────────────┼────────────────┤
│ vim │ エディタ │
│ 日本語の列 │ 全角を含むセル │
└────────────┴────────────────┘
コードブロックは、フェンスの行が言語名luaだけの帯になり、背景に色が付きます。アイコンはNerd Fontの私用領域の文字なので、Nerd Fontを入れていない端末では豆腐になります。
カーソル行が生のテキストに戻る動き(READMEではanti-concealと呼んでいます)は、行単位で効きます。表の3行目にカーソルを置くと、その行だけが| vim | エディタ |に戻り、上下の罫線と他の行は描かれたままでした。見出しの行に置くと、#が見えてアイコンが消えます。
描画するモードは、ソースの既定値がrender_modes = { 'n', 'c', 't' }です。ノーマルモードでは描き、iで挿入モードに入ると、バッファ全体が生のMarkdownに戻りました。描画中はconceallevelが3に上がり、:RenderMarkdown toggleを打つとバッファ全体が生のテキストに戻って、もう一度打つと描画が戻りました。
markview.nvim との違いと、同時に入れたとき
markview.nvimはvim.pack.add()の1行だけで有効になります。READMEにも遅延読み込みをしないよう書かれていて、setup()を呼ばなくても.mdを開けば描画されました。見た目は近いものの、既定の動きが2点違います。
1つ目はカーソル行です。markviewの既定はhybrid_modes = {}で、カーソルを置いた行も描かれたままでした。生のテキストを見るには挿入モードに入るか、:Markview toggleで切ります。setup({ preview = { hybrid_modes = { 'n' } } })にすると、ノーマルモードでもカーソルの下が生に戻ります。ただし戻る単位は行ではなく要素で、表の1行にカーソルを置くと表全体が生の|区切りに戻りました。行単位に切り替えるlinewise_hybrid_modeという設定もありますが、これは試していません。
2つ目は表の罫線です。既定の'wrap'がオンのままだと、上下の角の罫線は引かれるのに、行の区切りは|のままでした。
╭────────────┬────────────────╮
| 名前 | 説明 |
| ---------- | -------------- |
| vim | エディタ |
| 日本語の列 | 全角を含むセル |
╰────────────┴────────────────╯
ソースを読むと、表の描画関数がウィンドウのwrapを見ていて、BUG, wrap breaks table rendering.というコメントとともに縦線を|へ切り替えています。--cmd 'set nowrap'を付けて開き直すと、│と├─┼─┤の罫線になりました。
では2つを同時に入れるとどうなるか。render-markdownのsetup({})を呼び、markviewも入れたまま開くと、どちらかが勝つのではなく両方が重ねて描きました。見出しにはアイコンが2つ並び、## 見出し2は先頭の文字が欠けてし2と表示され、チェックボックスもアイコンが2つになって本文の先頭が欠けました。表は罫線が二重です。
╭────────────┬────────────────╮┌────────────┬────────────────┐
│ 名前 │ 説明 │
├────────────┼────────────────┤
│ vim │ エディタ │
│ 日本語の列 │ 全角を含むセル │
╰────────────┴────────────────╯└────────────┴────────────────┘
render-markdown側のドキュメントも、markviewと一緒に使うと機能がぶつかると書いています。試しにmarkviewへsetup({ preview = { enable = false } })を渡すと、render-markdownの描画だけが残りました。とはいえ、使わないほうは入れないのがいちばん確実です。
markdown-preview.nvim はブラウザに出す(2023年10月からコミットなし)
表の最終pushは2024-07-23ですが、cloneしたリポジトリのどのブランチにも2023-10-17より新しいコミットはなく、タグは2022年5月のv0.0.10が最後でした。描く場所はブラウザで、READMEによると数式(KaTeX)やMermaid、PlantUMLの図、ローカルの画像にも対応しています。今回確かめたのは見出しと本文の表示、スクロールの同期、更新の反映です。vim.pack.add({ 'https://github.com/iamcco/markdown-preview.nvim' })で入れ、そのまま:MarkdownPreviewを打つと、ブラウザは開かず、:messagesに次のエラーが残りました(パスは一部伏せています)。
Error: Cannot find module 'tslib'
Require stack:
- <TMP>/mp/data/nvim/site/pack/core/opt/markdown-preview.nvim/app/lib/app/index.js
- <TMP>/mp/data/nvim/site/pack/core/opt/markdown-preview.nvim/app/index.js
...
code: 'MODULE_NOT_FOUND',
...
Node.js v18.20.2
プラグインのappディレクトリにある依存パッケージが入っていないためです。lazy.nvimやvim-plugではbuildやdoにインストールの手順を書きますが、vim.packにはその欄がありません。代わりにPackChangedイベントでnpm installを流します。
vim.api.nvim_create_autocmd('PackChanged', {
callback = function(ev)
local name, kind = ev.data.spec.name, ev.data.kind
if name == 'markdown-preview.nvim' and (kind == 'install' or kind == 'update') then
vim.system({ 'npm', 'install' }, { cwd = ev.data.path .. '/app' }):wait()
end
end,
})
vim.pack.add({ 'https://github.com/iamcco/markdown-preview.nvim' })
autocmdはvim.pack.add()より前に書きます。初回のインストールでkindがinstallのイベントが届き、Node v18.20.2でnpm installが終了コード0で終わって、app/node_modules/tslibができました。手で入れる場合も、appでnpm installを流すと3秒ほどで60パッケージが入りました。READMEにはビルド済みの実行ファイルを取ってくるmkdp#util#install()も載っていますが、今回はnpmの経路だけを試しています。
依存を入れたあと:MarkdownPreviewを打つと、http://localhost:8728/page/1のようなURLでサーバーが立ち、ページは200を返しました。60節ある文書でNeovimのカーソルを120行目へ動かすと、ブラウザのscrollYは0から3019.5へ変わり、スクロールは同期しています。挿入モードで見出しに文字を足すと、保存しなくてもブラウザの見出しが書き換わりました。0.12.4とNode 18の組み合わせでは、依存さえ入れれば動きます。
気を付けたいのは、npm installのあとプラグインの作業ツリーが汚れることです。git statusを見るとapp/yarn.lockが変更扱いになり、app/package-lock.jsonが増えていました。
日本語の表と見出しで桁がずれないか
全角を含む表は、render-markdownとmarkviewのどちらでも列がそろいました。上の2つの表のとおり、日本語の列(全角5文字で10桁)とvim(3桁)の行で右端の縦線が同じ位置に来ています。どちらも表示幅で計算していて、markviewはwrapのせいで|表示になったときも、空白を足して幅をそろえていました。和文と英字が混ざる見出しも崩れませんでした。
ずれたのは2つを同時に入れたときだけで、これは日本語のせいではなく、2つのプラグインが同じ行に別々の空白を足した結果です。
和文に特化した描画が欲しい場合は、delphinusさんのmd-render.nvim(スター231、2026-10-08に最終push)があります。READMEによると、禁則処理とBudouXによる文節単位の折り返しに対応し、バッファの中ではなくフローティングウィンドウやタブ、ページャーとして表示する作りです。Neovim 0.12以上が必要とされています。今回は実機に入れておらず、READMEで確かめた範囲です。
選び方
| やりたいこと | 選ぶもの |
|---|---|
| 記号を隠すだけでいい | 本体のconceallevel=2 |
| カーソル行だけ生に戻して書きながら読む | render-markdown.nvim |
| カーソルを動かしても描画を保ちたい | markview.nvim |
| 画像や図も含めてブラウザで確認したい | markdown-preview.nvim |
バッファ内の2つはどちらか1つにします。ブラウザ系はこれと競合しないので、普段はバッファ内で描き、人に渡す前だけブラウザで見る組み合わせもできます。
いちばん時間を使ったのはmarkviewの表でした。罫線が|のまま出たのを見て、最初は全角の幅の計算で崩れているのだと思い込み、英字だけの表を作って開き直しました。それでも同じだったので、ようやくソースを読んでwrapの分岐にたどり着いています。markdown-preview.nvimも、:MarkdownPreviewを打った直後の画面には何も出ず、:messagesを開いて初めてNodeのエラーに気づきました。どちらも、見えている結果から原因を推測すると外す壊れ方でした。
まとめ
0.12.4なら本体だけでconceallevel=2が効き、記号は隠せます。表や見出しまで整えるならrender-markdown.nvimかmarkview.nvimを1つだけ入れます。markdown-preview.nvimはappでnpm installを流せば今も動き、vim.packならPackChangedに書いておきます。0.12で変わった本体の機能全体はNeovim 0.12の新機能にまとめています。