【テクニカル・上級編】Neovimで独自ツール開発:Lua APIを使って自分専用のタスクランナーを作る方法 – 軽量・高機能テキストエディタ生産性向上バイブル

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’, ‘tt’, function()
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` を開き、自分だけの神ツールを作り上げろ。

タイトルとURLをコピーしました