なぜmarp.nvimが必要だったのか
前回の記事でClaude Codeに移行し、Neovimに完全回帰することを決めた。コーディング、ドキュメント作成、設定ファイルの編集――すべてが再びターミナルで完結するようになった。
しかし、一つだけ問題があった。Marpでのプレゼンテーション作成だ。
Marpは素晴らしいツールだが、公式のNeovimサポートは存在しない。プレゼンテーションを作るたびに、仕方なくVSCodeやCursorを起動していた。せっかくNeovimに完全回帰したのに、プレゼン作成のためだけに別のエディタを立ち上げる。この矛盾が許せなかった。
既存のソリューションを探したが、満足できるものはなかった。ならば答えは一つ――自作するしかない。

こうしてmarp.nvimは生まれた。Neovimですべてを完結させるという理想を、妥協なく追求した結果だ。
marp.nvimの技術的アプローチ
アーキテクチャ
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Neovim │────▶│ marp.nvim │────▶│ Marp CLI │
│ Buffer │ │ Lua Plugin │ │ --watch │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐
│ Browser │
│ Auto-open │
└─────────────┘
コア実装の詳細
1. Marp CLIのプロセス管理
これは完全にMarp の作者が優秀なのですがMarpには--watchオプションが存在しています。これを使わない手はないです
-- プロセスをバッファごとに管理 M.active_processes = {} -- jobstart で Marp CLI を起動 local job_id = vim.fn.jobstart(shell_cmd, { pty = true, -- 擬似端末で適切な出力キャプチャ stdout_buffered = false, stderr_buffered = false, on_stdout = function(_, data) -- 出力処理 end, on_exit = function() M.active_processes[bufnr] = nil end })
重要なポイント:
pty = trueを使用することで、Marp CLIのカラー出力を適切に処理stdout_buffered = falseでリアルタイム出力を実現- バッファ番号をキーにしてプロセスを管理
2. 自動クリーンアップの実装
vim.api.nvim_create_autocmd({"BufDelete", "BufWipeout"}, { buffer = bufnr, once = true, callback = function() M.stop(bufnr) end })
VSCode拡張機能では当たり前の機能だが、Neovimでは自前実装が必要。バッファのライフサイクルに合わせてプロセスを管理。
3. ウォッチモード vs サーバーモード
if M.config.server_mode then cmd = string.format("%s -s '%s'", marp_cmd, file) else -- デフォルトは --watch モード cmd = string.format("%s --watch '%s'", marp_cmd, file) end
2つのモードをサポート:
- ウォッチモード(デフォルト): HTMLファイルを生成し、変更を監視
- サーバーモード: HTTPサーバーを起動(ポート競合の可能性あり)
4. ANSIエスケープシーケンスの処理
local function clean_ansi(str) return str:gsub("\27%[[%d;]*m", ""):gsub("\27%[[%d;]*[A-Za-z]", "") end
Marp CLIの美しいカラー出力をNeovimの通知システムで扱うための処理。これがないと文字化けする。
実装で工夫した点
1. 初回HTML生成の最適化
-- ウォッチモード開始前に初回HTMLを生成 if not M.config.server_mode then local init_cmd = string.format("%s '%s' -o '%s'", marp_cmd, file, html_file) vim.fn.system(init_cmd) if vim.fn.filereadable(html_file) == 1 then -- 即座にブラウザを開く M.open_browser("file://" .. html_file) end end
--watchモードは初回生成が遅いため、事前に生成してUXを改善。
2. クロスプラットフォーム対応
function M.open_browser(url) local cmd if vim.fn.has("mac") == 1 then cmd = "open " .. url elseif vim.fn.has("unix") == 1 then cmd = "xdg-open " .. url elseif vim.fn.has("win32") == 1 then cmd = "start " .. url end vim.fn.jobstart(cmd, {detach = true}) end
3. デバッグモード
M.config = { debug = true, -- 詳細ログを有効化 } -- :MarpDebug コマンドで診断 function M.debug() local test_cmd = string.format("%s --version", marp_cmd) -- Marp CLIの動作確認 end
トラブルシューティングを容易にするため、詳細なログ出力機能を実装。
VSCode拡張機能との機能比較
| 機能 | Marp for VS Code | marp.nvim |
|---|---|---|
| ライブプレビュー | ✅ | ✅ |
| 自動リロード(書き込みイベント時) | ✅ | ✅ |
| テーマ切り替え | GUI | :MarpTheme |
| エクスポート | GUI | :MarpExport |
| スライドナビゲーション | ✅ | ❌(開発中) |
| スニペット | ✅ | ✅ |
| 複数ファイル同時編集 | ✅ | ✅ |
使用方法
インストール
-- lazy.nvim { "nwiizo/marp.nvim", ft = "markdown", config = function() require("marp").setup({ marp_command = "npx @marp-team/marp-cli@latest", debug = false, server_mode = false, -- ウォッチモードを使用 }) end, }
基本的なワークフロー
:e presentation.md :MarpWatch " プレビュー開始(ファイル名をClipboardに書き込みもしている) :MarpTheme uncover " テーマ変更 :MarpExport pdf " PDF出力 :q " バッファを閉じると自動でサーバー停止
トラブルシューティング
:MarpDebug " Marp CLIの動作確認 :MarpList " アクティブなサーバー一覧 :MarpStopAll " 全サーバー停止
パフォーマンスと制限事項
メモリ使用量
既知の制限
まとめ
marp.nvimの開発により、Marpプレゼンテーション作成のためだけにCursorを起動する必要がなくなった。Neovimのjob APIを活用することで、VSCode拡張機能と似た体験を実現できることを証明できた。
重要なのは、完璧を求めすぎないこと。VSCode拡張機能のすべての機能を再現する必要はない。ターミナルでの開発に必要十分な機能を、シンプルに実装することが大切だ。
Claude Codeとの組み合わせで、プレゼンテーション作成もAIアシスト付きで行える。これで本当にすべての開発作業をNeovimで完結できるようになった。
vimmer村への完全帰還、達成。