Neovimを「IDE」の先へ:Luaによるタスクランナー自作で開発ループを極限まで加速する
巷には数多のプラグインが溢れているが、真に生産性を高めるのは「自分の脳の拡張」として機能するツールだけだ。汎用的なプラグインは、汎用的な分だけ「あなたの今のワークフロー」という文脈を理解しない。
本稿では、NeovimのLua APIを直接叩き、外部依存を排した自分専用のタスクランナーを構築する。単なるコマンド実行ではない。浮動ウィンドウ(Floating Window)による非同期実行と、CI/CDパイプラインとのシームレスな統合を目指す。
—
1. なぜ「自作」なのか:アーキテクチャの本質
多くのエンジニアは `vim-dispatch` や `asyncrun.vim` を使うが、それらはブラックボックスだ。メモリ消費量、イベントループの制御、さらには出力バッファの競合など、大規模プロジェクトでは「他人の書いた非同期処理」がボトルネックになる。
Neovimの `vim.loop` (libuvのラッパー) を直接扱うことで、以下のメリットを享受できる。
- 完全なメモリ制御: 不要なバッファを生成せず、必要なデータだけをメモリ上に保持。
- イベントループの分離: 重い処理をメインスレッドから完全に切り離し、UIのフリーズを皆無にする。
- CI/CDとの直接対話: ローカルのタスク結果を、そのまま環境変数としてパイプラインへ流し込むフックの構築。
—
2. 構築ステップ:Floating Window x Job API
まずは、モダンなUIを実現するために浮動ウィンドウを生成し、そこに `vim.fn.jobstart` でバックグラウンドタスクを流し込むコアロジックを設計する。
実装:`task_runner.lua`
local M = {}
— 浮動ウィンドウのパラメータ設定(再利用性を考慮)
local function create_floating_window()
local buf = vim.api.nvim_create_buf(false, true) — 非リストバッファを作成
local width = math.floor(vim.o.columns 0.8)
local height = math.floor(vim.o.lines 0.6)
— ウィンドウ設定:中央配置
local opts = {
relative = ‘editor’,
width = width, height = height,
row = (vim.o.lines – height) / 2,
col = (vim.o.columns – width) / 2,
style = ‘minimal’, — 不要な装飾を排除し、パフォーマンス向上
border = ‘rounded’
}
local win = vim.api.nvim_open_win(buf, true, opts)
return buf, win
end
— 非同期実行のコアロジック
function M.run_task(cmd)
local buf, win = create_floating_window()
— Jobの実行:stdout/stderrをバッファに逐次流し込む
vim.fn.jobstart(cmd, {
stdout_buffered = false,
stderr_buffered = false,
on_stdout = function(_, data)
if data then vim.api.nvim_buf_set_lines(buf, -1, -1, false, data) end
end,
on_exit = function(_, code)
— 終了時の処理:成功・失敗を色分けするなどの拡張が可能
vim.api.nvim_buf_set_lines(buf, -1, -1, false, {“”, “— Task Finished with code: ” .. code .. ” —“})
end
})
end
—
3. DevOps現場への実装:CI/CDとの連携ハック
単にコマンドを叩くだけでは、それは「端末」と同じだ。真の強みは、「現在編集中のファイルのコンテキストをCIツールに渡す」ことにある。
Makefileとの強力な融合
プロジェクトルートに `Makefile` を置き、Neovimからそれを叩く。その際、Neovimの `expand(‘%:p’)`(現在ファイルのフルパス)を引数として渡すことで、CIで実行するテストを即座にローカルで実行できる。
— キーマッピング例:現在ファイルのテストを即座に実行
vim.keymap.set(‘n’, ‘
local current_file = vim.fn.expand(‘%:p’)
require(‘task_runner’).run_task({“make”, “test”, “FILE=” .. current_file})
end)
なぜこれが最強なのか?
CI/CDパイプライン(GitHub Actions等)が実行するのと同じ環境を、ローカルで、かつ浮動ウィンドウ内で再現できるからだ。「ローカルでは動くがCIで落ちる」という絶望的なデバッグ時間を、このツールで極小化できる。
—
4. パフォーマンス最適化の深淵
Neovimで独自ツールを作る際、避けて通れないのが「ガベージコレクション」と「イベントループのブロッキング」だ。
- 不要な `autocmd` を書くな:
タスク実行中に `CursorMoved` イベントを監視するような実装はメモリを食いつぶす。必要な時だけ登録し、`job` 終了時に `vim.api.nvim_del_autocmd` で破棄する設計を徹底すること。
- バッファの肥大化対策:
ログが数万行になる場合、`vim.api.nvim_buf_set_lines` は極めて低速になる。上限行数を超えたら古い行を削除する `ring buffer` 的な実装をLuaで書くことが、長時間稼働環境では必須となる。
—
結論:ツールを所有せよ
既製のプラグインを使うことは楽だが、それは「他人の設計思想の奴隷」になることと同義だ。今回紹介したアーキテクチャをベースに、自分のプロジェクト特有のタスク(Dockerのビルド、k8sへのデプロイ確認、DBのマイグレーション)をNeovimの中に統合せよ。
エディタから指を離さず、全てのインフラを制御する。それが、我々エンジニアが目指すべき「開発体験の極致」である。次は、この `job` の出力をパーズして、エラー箇所へ即座に `quickfix` リストへジャンプさせる機能を実装してみることを強く推奨する。
あなたのワークフローは、あなたのコードでしか最適化できない。さあ、今すぐ `init.lua` を開き、自分だけの神ツールを作り上げろ。