Neovimタスクランナーoverseer.nvimの使い方

Neovimのタスクランナープラグインoverseer.nvimの使い方・独自のタスクの追加方法などを解説します。

かなりボリュームのある記事です。

overseer.nvimとは

overseer.nvimはタスクランナーです。プロジェクトに応じたタスクを実行できます。
たとえばpnpmを使っているプロジェクトであればpackage.jsonscriptsがpnpmで実行できます。

タスク一覧

もちろん自分で設定したタスクも実行できます

非同期に実行できる

:lua vim.fn.system({"sleep","5s"})のように外部コマンドを実行すると、コマンドが終了するまで操作できません。

overseer.nvimを使えば、UIがブロックされずに非同期で実行できます

ステータスや履歴が分かる

実行中・実行済みのタスクの履歴を表示できます。

履歴の一覧

導入方法

リポジトリ名だけコピペしたい人向け
stevearc/overseer.nvim

各々が使っているプラグインマネージャーでインストールします。

インストール

lazy.nvimだと次のように書いてインストールできます。

lazy.nvimの例
return {
'stevearc/overseer.nvim',
opts = {},
}

依存プラグイン

必須のプラグインはありません
次のプラグインを入れると、そのプラグインが提供するUIに変化します。

最低限の設定

最低限のプラグインの設定はsetupを呼び出すだけで完了です。

require('overseer').setup()

lazy.nvimの場合、setupの中身はoptsに書くためこの記述は不要です。この書き方について知りたい人は別の記事「lazy.nvimの使い方から起動を爆速にする方法までを解説」を読んでください。

lazy.nvimでの遅延読み込み

lazy.nvimでの遅延読み込みの例も載せておきます。

return {
"stevearc/overseer.nvim",
keys = {
{ "<space>r", "<CMD>OverseerRun<CR>" },
{ "<space>R", "<CMD>OverseerToggle<CR>" },
},
opts = {},
}

筆者は2つのコマンドに遅延読み込みを設定しています。各コマンドの詳細は後で解説します。

ビルトインのタスク

デフォルトの設定でもさまざまなビルドツール・タスクランナーを実行できます。

2025年1月時点で対応されているビルトインのツールは次のとおりです。

  • make
  • shell
  • JavaScript関係:npm、pnpm、yarn、bun、deno
  • Composer
  • cargo
  • cargo-make
  • Rake
  • tox
  • mix
  • Mage
  • task
  • just

.vscode/tasks.jsonのタスクも実行できます。

pnpmyarnbunなどパッケージマネージャーはプロジェクトに合わせて表示してくれます。

yarnを表示してくれる例

タスクの実行方法

:OverseerRunでプロジェクトに応じたタスクの一覧が出ます。

タスク一覧

タスクを選んで<CR>を押すとバックグラウンドで実行してくれます。
完了すると通知がきます。

実行結果の確認

実行完了後、成功ならSUCCESS、失敗ならFAILUREのような通知が来ます。

コマンドの出力結果を見たい場合:OverseerToggleまたは:OverseerOpenを実行します。
すると、次のようにタスクの実行履歴が表示されます。

履歴の一覧

左がタスクの履歴一覧、右が現在カーソルのある行のタスクの実行画面(後述)です。

タスクに依存関係があればネストで表示されます。

ステータスは次の6つです。

  • RUNNING:実行中
  • PENDING:保留中
  • SUCCESS:成功
  • FAILURE:失敗
  • CANCELED:キャンセル済み
  • DISPOSED:廃棄済み

タスク履歴の操作

タスクの履歴画面ではデフォルトでキーマップが設定されており、?で確認できます。

次の表はよく使いそうなキーマップの一覧です。

key
?/g?デフォルトのキーバインドを確認
qウィンドウを閉じる
<CR>アクションメニューを開く
<C-F>タスクをfloating windowで開く
pタスクのプレビューをトグル
[ウィンドウを狭くする
]ウィンドウを広くする

バッファなのでjkなどで普通に移動できます。

アクションメニューの使い方

アクションメニューはターゲットとなるタスクに対して選んだアクションが実行されます。

アクションメニュー

たとえばタスクを停止するならstopを選びます。すると、そのタスクのステータスがCANCELEDになります。

disposeとは

disposeタスクを停止して履歴から消すというアクションです。依存関係のあるタスクが失敗したとき、その後に実行予定だったタスクはステータスPENDING(保留)になります。これを消すときに使います。

アクションメニューの呼び出し方

アクションメニューの呼び出しは次のとおりです。

  • タスク履歴画面で<CR>
  • :OverseerQuickAction:最新のタスクのアクションメニューを開く
  • :OverseerTaskAction:タスクを選んでからアクションメニューを開く

タスクを開いて何ができるというんだ

タスクを〇〇で開くというアクションがちょいちょい出てきます。これらはタスクの実行画面を指定した種類のウィンドウで開くアクションです。

タスクの実行画面はNeovimの:terminalと同じように操作できます。つまり、入力や実行結果のコピーができます

入力をしたい場合、最初はノーマルモードなのでaIなどで挿入モードに切り替えてから入力します。

動画の例ではvertical(左右分割)で開いています。
aで挿入モード切り替え後、1 2<CR>を入力すると、3が出力されています。

overseer.nvimの主要な概念

自作タスクを定義するときに登場する概念を紹介します。

タスクとは

タスクとは、実行される処理のことです。
たとえば、npm buildmake deployなどの外部コマンドです。

コンポーネントとは

コンポーネントはフックっぽいやつです。タスクに対し、依存関係の定義や出力・通知などを定義します。
たとえば、「実行結果はquickfixで表示するぞ」といったことを定義するのがコンポーネントの役割です。

テンプレートとは

テンプレートはタスクを組み立てるときに使われるものです。
つまり文字どおりタスクのテンプレートです。

タスクの内容はもちろん、タスクが有効になる条件やリスト化されたときの優先度を定義します。

また、外部コマンドの実行前にLuaの処理を挟むこともできます。

独自のタスクを作成する方法

自分でカスタマイズしたタスクを作成したい場合、いくつか手段があります。

  • テンプレートから作成:ほぼこれ
  • :OverseerBuild:その場限りのタスクで使う
  • APIの実行

この記事では一番よく使うであろう「テンプレートから作成」を解説します。

テンプレートの置き場所

自作タスクの元となる「テンプレート」はlua/overseer/templateディレクトリ以下に置きます。

ディレクトリ構成
.config/nvim/lua/overseer
└── template
├── blog
│ └── open-preview.lua
├── cpp
│ ├── gpp-build-only.lua
│ └── gpp-build-run.lua
└── test.lua

そして、setuptemplatesに次のようにディレクトリ.ファイル名またはファイル名を書いて登録します。

require("overseer").setup({
templates = {
"builtin",
"blog.open-preview",
"cpp.gpp-build-only",
"cpp.gpp-build-run",
"test",
},
})

ちなみにファイルに分けずAPIでテンプレートを作成する方法もありますが、結局処理が長くなってファイルを分けると思うので割愛します。

テンプレートの書き方

いよいよテンプレートの書き方です。まずは簡単な例をお見せしましょう。nameはタスクの名前です。

.config/nvim/lua/overseer/template/first-template.lua
---@type overseer.TemplateDefinition
return {
name = "First Template",
builder = function()
local file = vim.fn.expand("%")
---@type overseer.TaskDefinition
return {
cmd = { "echo" },
args = { "hello", file },
}
end,
}

テンプレートに必須なのはnamebuilderだけです。

-- templatesでの登録も忘れずに!
require("overseer").setup({
templates = {
"builtin",
"first-template",
},
})

builderの書き方

builderはそのタスクが呼び出されたときの処理です。実行したい外部コマンドをreturnします。returnされるのはoverseer.TaskDefinitionという型です。

cmdとargs

cmdは「文字列のテーブル」か「文字列」を渡します。引数はargsに渡します。

---@type overseer.TaskDefinition
return {
cmd = { "echo" },
args = { "hello", file },
}

echo helloのように引数を直接cmdに書きたい場合や、
|といったshellの機能を使ってまとめてコマンドを書きたいケースが出てきます。
そんなときはcmdを文字列にします。

---@type overseer.TaskDefinition
return {
cmd = "echo foo | sed 's/f/o/'",
-- cmd = { "echo foo | sed 's/f/o/'" }, これはエラーになる
}

複数のコマンドを使う場合、別々にタスク化して順番に実行させる方法もあります(後述)。

cppのビルドの例

ここからは実際に使える例をお見せします。まずはC++のビルドです。

---@type overseer.TemplateDefinition
return {
name = "g++ build only",
builder = function()
local file = vim.fn.expand("%:p")
local outfile = vim.fn.expand("%:p:r") .. ".out"
---@type overseer.TaskDefinition
return {
cmd = { "g++" },
args = { file, "-o", outfile },
components = {
{ "on_output_quickfix", open_on_exit = "failure" },
"default",
},
}
end,
condition = {
filetype = { "cpp" },
},
}

builderでNeovimのAPIを呼び出してファル名を取得していますね。
conditionで呼び出せるかどうかの条件を決めています。

quickfixで開く

コンパイルエラーになったらどの行が問題かを知りたいところです。
そこで、タスクが失敗したらquickfixで開くというコンポーネントを追加します。

components = {
{ "on_output_quickfix", open_on_exit = "failure" },
"default",
},
quickfixで開く

ヘルプ::help on_output_quickfix

defaultコンポーネントとは?

defaultはデフォルトのコンポーネント集です。タスク完了後の通知など、細かい設定をユーザーがやらなくてもいいように短くしてくれています。componentsがない場合はdefaultになります。

components = {
{ "on_output_quickfix", open_on_exit = "failure" },
"default",
},

componentsをカスタマイズするときはおまじないだと思ってセットで書いておくのが吉です。

詳しく知りたい人向けに後で解説します。

依存関係のある例

C++でビルドした実行ファイルを実行する例です。先ほど定義した「g++でビルドするタスク」を先に実行させます。

---@type overseer.TemplateDefinition
return {
name = "g++ build and run",
builder = function()
local outfile = vim.fn.expand("%:p:r") .. ".out"
---@type overseer.TaskDefinition
return {
cmd = { outfile },
components = {
{
"dependencies",
task_names = { "g++ build only" },
},
{ "open_output", focus = true, direction = "vertical" },
"default",
},
}
end,
condition = {
filetype = { "cpp" },
},
}

dependencies

依存関係のあるタスクではdependenciesコンポーネントを使います。task_namesのテーブルに他のタスクのnameを書きます。

{
"dependencies",
task_names = { "g++ build only" },
},

あるいは、task_namesの中に直接タスク内容を書いてもかまいません。

{
"dependencies",
task_names = {
{
cmd = { "g++" },
args = { file, "-o", outfile },
-- 省略
},
},
},
依存タスクを順番に実行したい

task_namesは複数のタスクを登録できます。通常だとテーブル内のタスクを一斉に実行します。

{
"dependencies",
task_names = {
{ cmd = "sleep 5" },
{ cmd = "sleep 3" },
{ cmd = "sleep 2" },
},
},

上記のタスクだとsleep 2slee 3sleep 5の順で実行が完了するということです。

これをリストどおりに順番に実行したい場合、次のように引数が必要です。

{
"dependencies",
task_names = {
{ cmd = "sleep 5" },
{ cmd = "sleep 3" },
{ cmd = "sleep 2" },
},
sequential = true,
},

上記のタスクだと「sleep 5が完了後にsleep 3を実行開始」というように順番に実行してくれます。

ヘルプ::help dependencies(名前被りありそうなので:help overseer-componentsのほうがいいかも?)

open_output

open_outputコンポーネントを使うと、実行画面を開けます。quickfixと違ってファイルの行に基づいた出力が必要なければopen_outputを使いましょう。

components = {
{
"dependencies",
task_names = { "g++ build only" },
},
{ "open_output", focus = true, direction = "vertical" },
"default",
},

すでに説明しましたが、実行画面はNeovimの:terminalと同じように操作できます。

ヘルプ::help open_output

Luaの処理だけをタスクとして追加する方法

ここまでは全部外部コマンドでした。ここではLuaの処理をテンプレートとして定義します。

---@type overseer.TemplateDefinition
return {
name = "open blog preview",
builder = function()
local basename = vim.fn.expand("%:t:r")
vim.ui.open("http://localhost:4321/blog/" .. basename)
return {
cmd = { "echo" },
args = { basename },
}
end,
priority = 1,
condition = {
dir = "~/ghq/github.com/eetann/example",
},
}

筆者がブログを書くときに使っているテンプレートです。

Luaだけの処理をするときのbuilder

Luaの処理をbuilderreturn前に書きます

builder = function()
local basename = vim.fn.expand("%:t:r")
vim.ui.open("http://localhost:4321/blog/" .. basename)
return {
cmd = { "echo" },
args = { basename },
}
end,

cmdは必須パラメータであるため適当に書きます。空文字だと履歴が分かりづらくなるため、筆者は上記のように登場した変数を表示させています。

優先度の設定

:OverseerRunで表示されるタスクのリストはpriorityで並び替えられます。数字の小さいほうが優先されます

priority = 1,

特定のディレクトリだけで使うタスク

タスクが特定のディレクトリだけで使う場合、テンプレートのconditiondirで指定します。

condition = {
dir = "~/ghq/github.com/eetann/example",
},

他のプラグインとの連携

他のプラグインとの連携例を紹介します。

ステータスプラグインとの連携

lualine.nvimにタスクの実行状況を表示できます。

ステータスラインでの表示
require("lualine").setup({
sections = {
lualine_x = { "overseer" },
},
})

テンプレートの補完を効かせる

この記事では、---@typeのようなLua Language Serverのアノテーション機能がちょっと登場しました。
今回のreturn {}のような「require()を使わないけど補完させたい」というときに便利です。

具体的にはプラグインlazydev.nvimを使って次のように設定しています。

return {
"folke/lazydev.nvim",
ft = "lua",
opts = {
library = {
{ path = "${3rd}/luv/library", words = { "vim%.uv" } },
{ path = "overseer.nvim", words = { "overseer" } },
},
},
}

よくある問題とその解決

エンカウント率高めな問題と解決方法もまとめました。

登録したテンプレートがリストに表示されない

setupのtemplatesに追加しているか、名前が正しいかを確認してください。

require("overseer").setup({
templates = {
"builtin",
"cpp.build-only",
},
})

あるいはテンプレートのconditionとマッチしているか確認してください。

dependenciesのタスクが順番に実行されない

依存タスクを順番に実行したいで説明したとおり、sequential=trueを追加しましょう。

{
"dependencies",
task_names = {
{ cmd = "sleep 5" },
{ cmd = "sleep 3" },
{ cmd = "sleep 2" },
},
sequential = true,
},

挿入モードになってしまう

タスク実行時に挿入モードへ切り替わってしまう人がいるかもしれません。

次のようにstartinsertを定義しているとそうなります。

vim.api.nvim_create_autocmd({ "TermOpen" }, {
group = "my_nvim_rc",
command = "startinsert",
})

この設定を削除するか、overseer.nvimのときだけオフにします。

ここで、プラグイン作者stevearcさんの設定を見てみましょう。

vim.api.nvim_create_autocmd("TermOpen", {
desc = "Auto enter insert mode when opening a terminal",
pattern = "*",
group = aug,
callback = function()
-- Wait briefly just in case we immediately switch out of the buffer
vim.defer_fn(function()
if vim.bo.buftype == "terminal" and not vim.b.overseer_task then
vim.cmd.startinsert()
end
end, 100)
end,
})

変数vim.b.overseer_taskを使ってoverseerかどうか判定していますね。

あえてタスクを失敗させたい

あえてタスクを失敗させるときはfalseコマンドを使いましょう。Luaの処理で分岐させたいときに便利です。

たとえば筆者は「dependenciesを機械的に作成し、存在しなければタスクを失敗とする」というテンプレートを作りました。

builder = function()
local dependencies = create_tasks_before_foo()
if next(dependencies) == nil then
return {
cmd = "echo 'FAILED: foo' && false",
}
end
---@type overseer.TaskDefinition
return {
cmd = "echo 'SUCCESS: foo'",
components = {
{
"dependencies",
task_names = dependencies,
sequential = true,
},
"default",
},
}

記事を書いているときの画像変換のテンプレートで使っています。

on_exit_set_statusに関するエラーが出る

componentsをいじっていると次のようなエラーになることがあります。

[ERROR] Task false did not finalize during exit. Is it missing the on_exit_set_status component?

これはおそらくdefaultコンポーネントを使っていないからでしょう。次の項目に出てくるon_exit_set_statusを読んでください。

結局default componentとは?

途中で説明を省いたdefaultコンポーネントについて筆者が調べた内容を解説します。

defaultコンポーネントは次の5つのコンポーネントの集まりです。

default = {
{ "display_duration", detail_level = 2 },
"on_output_summarize",
"on_exit_set_status",
"on_complete_notify",
{ "on_complete_dispose", require_view = { "SUCCESS", "FAILURE" } },
},

設定を変更する機会が一番ありそうなon_complete_notifyから解説します。

on_complete_notify

on_complete_notifyはタスク終了時の通知設定です。

「defaultコンポーネントを使いたいけど通知はいらない」という場合はstatusesを空にします。

components = {
{ "on_complete_notify", statuses = {} },
"default",
},

ヘルプ:;help on_complete_notify

display_duration

display_durationはタスクの実行時間をどの詳細レベルで表示するか、という設定です。

タスク履歴でLを押すと詳細レベルが上がります。

たとえば次がデフォルト設定での詳細レベル1です。

詳細レベル1

次が詳細レベル2です。2秒のタスクでした。

詳細レベル2

デフォルトコンポーネントでは{ "display_duration", detail_level = 2 }と設定されているため、実行時間はレベル2以上で開示されます。

詳細レベル3だと、どんなコンポーネント設定だったのかも確認できます。

詳細レベル3

ヘルプ::help display_duration

on_output_summarize

on_output_summarizeは「詳細レベル2・3のときに表示する出力の行数」を指定します。デフォルトだと4行です。

詳細レベル2

ヘルプ::help on_output_summarize

on_exit_set_status

on_exit_set_statusは「タスクが成功したと見なす終了コード」を指定します。終了コード0は指定しなくても成功扱いです。

たとえば、次のタスクは終了コード1が返って失敗します。

---@type overseer.TaskDefinition
return {
cmd = "false",
components = {
"default",
},
}

次のように設定すると0だけでなく1も成功扱いにしてくれます。

---@type overseer.TaskDefinition
return {
cmd = "false",
components = {
{ "on_exit_set_status", success_codes = { 1 } },
"default",
},
}

defaultコンポーネントを使わない場合は必ずon_exit_set_statusを入れましょう。そうしないと次のようにエラーになります。

[ERROR] Task false did not finalize during exit. Is it missing the on_exit_set_status component?
defaultコンポーネントを使わない場合
---@type overseer.TaskDefinition
components = {
{ "on_exit_set_status" }, -- defaultを使わないなら必須
},

ヘルプ::help on_exit_set_status

on_complete_dispose

on_complete_disposeは「タスクが完了して何秒後に履歴を消すか」という設定です。

通常であれば「タスクを実行してから5分経過」「Neovimを終了する」のどちらかで履歴から消えます。
この設定を変えたい場合に活用します。

次の例では、タスク完了の1分後に履歴から消えるように設定しています。

components = {
{ "on_complete_dispose", timeout = 60 },
"default",
},

ヘルプ::help on_complete_dispose

公式ドキュメント

まだ紹介しきれていない機能やオプションがたくさんあります。詳しくは公式ドキュメントへどうぞ。


Neovimから離れずにいろいろ作業できるようになり、スーパーキノコを食べた気分です。

ちなみにoverseerは監督者って意味らしいです。