以下の内容はhttps://syu-m-5151.hatenablog.com/entry/2025/05/31/105405より取得しました。


marp.nvimを開発してCursorから完全移行した話

なぜmarp.nvimが必要だったのか

前回の記事でClaude Codeに移行し、Neovimに完全回帰することを決めた。コーディング、ドキュメント作成、設定ファイルの編集――すべてが再びターミナルで完結するようになった。

しかし、一つだけ問題があった。Marpでのプレゼンテーション作成だ。

Marpは素晴らしいツールだが、公式のNeovimサポートは存在しない。プレゼンテーションを作るたびに、仕方なくVSCodeやCursorを起動していた。せっかくNeovimに完全回帰したのに、プレゼン作成のためだけに別のエディタを立ち上げる。この矛盾が許せなかった。

marp.app

既存のソリューションを探したが、満足できるものはなかった。ならば答えは一つ――自作するしかない

こうしてmarp.nvimは生まれた。Neovimですべてを完結させるという理想を、妥協なく追求した結果だ。

github.com

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 CLIプロセス: 約50-100MB/インスタンス
  • 複数ファイル同時編集時は線形に増加

既知の制限

  1. ホットリロードの遅延: ファイル保存からブラウザ更新まで約100-200ms
  2. 大規模ファイル: 100スライド以上でパフォーマンス低下
  3. 画像の相対パス: 作業ディレクトリに依存

まとめ

marp.nvimの開発により、Marpプレゼンテーション作成のためだけにCursorを起動する必要がなくなった。Neovimのjob APIを活用することで、VSCode拡張機能と似た体験を実現できることを証明できた。

重要なのは、完璧を求めすぎないことVSCode拡張機能のすべての機能を再現する必要はない。ターミナルでの開発に必要十分な機能を、シンプルに実装することが大切だ。

Claude Codeとの組み合わせで、プレゼンテーション作成もAIアシスト付きで行える。これで本当にすべての開発作業をNeovimで完結できるようになった。

vimmer村への完全帰還、達成。




以上の内容はhttps://syu-m-5151.hatenablog.com/entry/2025/05/31/105405より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14