- Custom tasks
- Actions
- Custom components
- Customizing built-in tasks
- Parsing output
- Running tasks sequentially
- VS Code tasks
There are two ways to define a task for overseer.
1) directly registering
overseer.register_template({
-- Template definition (see below)
})
2) as a module
Similar to custom components, templates can be lazy-loaded from a module in the overseer.template
namespace. It is recommended that you namespace your tasks inside of a folder (e.g. overseer/template/myplugin/first_task.lua
, referenced as myplugin.first_task
). To load them, you would pass the require path in setup:
overseer.setup({
templates = { "builtin", "myplugin.first_task" },
})
-- You can also load them separately from setup
overseer.load_template("myplugin.second_task")
If you have multiple templates that you would like to expose as a bundle, you can create an alias module. For example, put the following into overseer/template/myplugin/init.lua
:
return { "first_task", "second_task" }
This is how builtin
references all of the different built-in templates.
The definition of a template looks like this:
{
-- Required fields
name = "Some Task",
builder = function(params)
-- This must return an overseer.TaskDefinition
return {
-- cmd is the only required field
cmd = {'echo'},
-- additional arguments for the cmd
args = {"hello", "world"},
-- the name of the task (defaults to the cmd of the task)
name = "Greet",
-- set the working directory for the task
cwd = "/tmp",
-- additional environment variables
env = {
VAR = "FOO",
},
-- the list of components or component aliases to add to the task
components = {"my_custom_component", "default"},
-- arbitrary table of data for your own personal use
metadata = {
foo = "bar",
},
}
end,
-- Optional fields
desc = "Optional description of task",
-- Tags can be used in overseer.run_template()
tags = {overseer.TAG.BUILD},
params = {
-- See :help overseer-params
},
-- Determines sort order when choosing tasks. Lower comes first.
priority = 50,
-- Add requirements for this template. If they are not met, the template will not be visible.
-- All fields are optional.
condition = {
-- A string or list of strings
-- Only matches when current buffer is one of the listed filetypes
filetype = {"c", "cpp"},
-- A string or list of strings
-- Only matches when cwd is inside one of the listed dirs
dir = "/home/user/my_project",
-- Arbitrary logic for determining if task is available
callback = function(search)
print(vim.inspect(search))
return true
end,
},
}
Template providers are used to generate multiple templates dynamically. The main use case is generating one task per target (e.g. for a makefile), but can be used for any situation where you want the templates themselves to be generated at runtime.
Providers are created the same way templates are (with overseer.register_template
, or by putting them in a module). The structure is as follows:
{
generator = function(search, cb)
-- Pass a list of templates to the callback
-- See the built-in providers for make or npm for an example
cb({...})
end,
-- Optional. Same as template.condition
condition = {
callback = function(search)
return true
end,
},
-- Optional. Overrides the default cache key of `opts.dir`
-- Additionally, if the returned value is an absolute file path,
-- whenever that file is written overseer will automatically clear the cache
cache_key = function(opts)
return vim.fs.find('Makefile', { upward = true, type = "file", path = opts.dir })[1]
end,
}
Actions can be performed on tasks by using the RunAction
keybinding in the task list, or by the OverseerQuickAction
and OverseerTaskAction
commands. They are simply a custom function that will do something to or with a task.
Browse the set of built-in actions at lua/overseer/task_list/actions.lua
You can define your own or disable any of the built-in actions in the call to setup():
overseer.setup({
actions = {
["My custom action"] = {
desc = "This is an optional description. It may be omitted.",
-- Optional function that will determine when this action is available
condition = function(task)
if task.name == "foobar" then
return true
else
return false
end
end,
run = function(task)
-- Your custom logic here
end,
},
-- Disable built-in actions by setting them to 'false'
watch = false,
},
-- You can optionally add keymaps to run your action in the task list
-- It will always be available in the "RunAction" menu, but it may be
-- worth mapping it directly if you use it often.
task_list = {
bindings = {
["P"] = "<CMD>OverseerQuickAction My custom action<CR>",
},
},
})
When components are passed as an argument, they can be specified as either a raw string (e.g. "on_complete_dispose"
) or a table with configuration parameters (e.g. {"on_complete_dispose", timeout = 10}
).
Components are lazy-loaded via requiring in the overseer.component
namespace. For example, the timeout
component is loaded from lua/overseer/component/timeout.lua
. It is recommended that for plugins or personal use, you namespace your own components behind an additional directory. For example, place your component in lua/overseer/component/myplugin/mycomponent.lua
, and reference it as myplugin.mycomponent
.
Paths given are all relative to any runtimepath (:help rtp
), so in practice it's probably easiest to put it in ~/.config/nvim
. The full path to your custom component would then become ~/.config/nvim/lua/overseer/component/myplugin/mycomponent.lua
.
The component definition should look like the following example:
return {
desc = "Include a description of your component",
-- Define parameters that can be passed in to the component
params = {
-- See :help overseer-params
},
-- Optional, default true. Set to false to disallow editing this component in the task editor
editable = true,
-- Optional, default true. When false, don't serialize this component when saving a task to disk
serializable = true,
-- The params passed in will match the params defined above
constructor = function(params)
-- You may optionally define any of the methods below
return {
on_init = function(self, task)
-- Called when the task is created
-- This is a good place to initialize resources, if needed
end,
---@return nil|boolean
on_pre_start = function(self, task)
-- Return false to prevent task from starting
end,
on_start = function(self, task)
-- Called when the task is started
end,
on_reset = function(self, task)
-- Called when the task is reset to run again
end,
---@return table
on_pre_result = function(self, task)
-- Called when the task is finalizing.
-- Return a map-like table value here to merge it into the task result.
return { foo = { "bar", "baz" } }
end,
---@param result table A result table.
on_preprocess_result = function(self, task, result)
-- Called right before on_result. Intended for logic that needs to preprocess the result table and update it in-place.
end,
---@param result table A result table.
on_result = function(self, task, result)
-- Called when a component has results to set. Usually this is after the command has completed, but certain types of tasks may wish to set a result while still running.
end,
---@param status overseer.Status Can be CANCELED, FAILURE, or SUCCESS
---@param result table A result table.
on_complete = function(self, task, status, result)
-- Called when the task has reached a completed state.
end,
---@param status overseer.Status
on_status = function(self, task, status)
-- Called when the task status changes
end,
---@param data string[] Output of process. See :help channel-lines
on_output = function(self, task, data)
-- Called when there is output from the task
end,
---@param lines string[] Completed lines of output, with ansi codes removed.
on_output_lines = function(self, task, lines)
-- Called when there is output from the task
-- Usually easier to deal with than using on_output directly.
end,
---@param code number The process exit code
on_exit = function(self, task, code)
-- Called when the task command has completed
end,
on_dispose = function(self, task)
-- Called when the task is disposed
-- Will be called IFF on_init was called, and will be called exactly once.
-- This is a good place to free resources (e.g. timers, files, etc)
end,
---@param lines string[] The list of lines to render into
---@param highlights table[] List of highlights to apply after rendering
---@param detail number The detail level of the task. Ranges from 1 to 3.
render = function(self, task, lines, highlights, detail)
-- Called from the task list. This can be used to display information there.
table.insert(lines, "Here is a line of output")
-- The format is {highlight_group, lnum, col_start, col_end}
table.insert(highlights, { "Title", #lines, 0, -1 })
end,
}
end,
}
A component alias is just a simple string you can use as a component that resolves to a list of components. These are configured via the component_aliases option in setup()
. The two built-in aliases are default
, which is used for all tasks when no components are specified, and default_vscode
which is the same but for tasks specifically from the VS Code task integration. You can define and use your own component aliases using the same format. Aliases can include other aliases; for example the default_vscode
alias includes the default
alias in addition to some other components.
NOTE: When components are added to a task, it will be a no-op if the component already exists on the task. This is meaningful if you intend to change the parameters on a component that is in the default
alias. As a best practice, always list any component aliases after specific components.
local task = require("overseer").new_task({
cmd = "g++ " .. vim.fn.expand("%"),
components = {
-- Add on_complete_notify first with a customized 'statuses' parameter
{ "on_complete_notify", statuses = { "SUCCESS" } },
-- The default group also adds on_complete_notify,
-- but since it appears second it will be ignored.
"default"
}
})
A note on the Task result table: there is technically no schema for it, as the only things that interact with it are components and actions. However, there are a couple of built-in uses for specific keys of the table:
diagnostics: This key is used for diagnostics. It should be a list of quickfix items (see :help setqflist
)
error: This key will be set when there is an internal overseer error when running the task
You may wish to customize the built-in task definitions, or tasks from another plugin. The simplest way to do this is using the add_template_hook function. This allows you to run a function on the task definition (the arguments passed to new_task) and process it however you like. A common use case would be to add a component or modify the environment variables while in a specific project:
overseer.add_template_hook({
dir = "/path/to/my/project",
module = "^cargo$",
}, function(task_defn, util)
util.add_component(task_defn, { "on_output_quickfix", open = true })
end)
The primary way of parsing output with overseer is the on_output_parse
component.
-- Definition of a component that parses output in the form of:
-- /path/to/file.txt:123: This is a message
-- You would typically use this in the components list of a task definition returned by a template
{"on_output_parse", parser = {
-- Put the parser results into the 'diagnostics' field on the task result
diagnostics = {
-- Extract fields using lua patterns
-- To integrate with other components, items in the "diagnostics" result should match
-- vim's quickfix item format (:help setqflist)
{ "extract", "^([^%s].+):(%d+): (.+)$", "filename", "lnum", "text" },
}
}}
This is a simple example, but the parser library is flexible enough to parse nearly any output format. See more detailed documentation in the parsers doc.
You can of course create your own components to parse output leveraging the on_output
or on_output_lines
methods. The integration should be straightforward; see on_output_parse.lua to see how the built-in component leverages these methods.
There are currently two ways to get tasks to run sequentially. The first is by using the dependencies component. For example, if you wanted to create a npm serve
task that runs npm build
first, you could create it like so:
overseer.run_template({ name = "npm serve", autostart = false }, function(task)
if task then
task:add_component({
"dependencies",
task_names = {
"npm build",
-- You can also pass in params to the task
{ "shell", cmd = "sleep 10" },
},
sequential = true,
})
task:start()
end
end)
Another approach to running tasks in a specific order is to use the orchestrator strategy. This creates a single "orchestration" task that is responsible for running the other tasks in the correct order. You can create it like so:
local task = overseer.new_task({
name = "Build and serve app",
strategy = {
"orchestrator",
tasks = {
"make clean", -- Step 1: clean
{ -- Step 2: build js and css in parallel
"npm build",
{ "shell", cmd = "lessc styles.less styles.css" },
},
"npm serve", -- Step 3: serve
},
},
})
task:start()
Lastly, you can always leverage the .vscode/tasks.json
format to specify task dependencies using the dependsOn
keyword. It will use one of the two above methods under the hood.
Overseer can read VS Code's tasks.json file. By default, VS Code tasks will show up when you :OverseerRun
. Overseer is nearly at feature parity, but it's not quite (nor will it ever be) at 100%.
Some VS Code extensions add additional tasks, task types, or problem matchers. You can't install those extensions for neovim, but there are ways to similarly extend the functionality of overseer. See Extending VS Code tasks for more information.
Supported features:
- Task types: process, shell, typescript, node
- Standard variables
- Input variables (e.g.
${input:variableID}
) - Problem matchers
- Built-in library of problem matchers and patterns (e.g.
$tsc
and$jshint-stylish
) - Compound tasks (including
dependsOrder = sequence
) - Background tasks
group
(sets template tag; supportsBUILD
,RUN
,TEST
, andCLEAN
) andisDefault
(sets priority)- Operating system specific properties
- Integration with launch.json (see DAP)
- Output behavior (with some tweaked defaults)
Unsupported features:
- task types: gulp, grunt, jake
- Specifying a custom shell to use
${workspacefolder:*}
variables${config:*}
variables${command:*}
variables- The
${defaultBuildTask}
variable - Custom problem matcher patterns may fail due to differences between JS and vim regex (notably vim regex uses a different syntax for non-capturing groups
(?:.*)
and doesn't support character classes inside of brackets[\d\s]
). Built-in matchers have already been translated. - Run behavior (probably not going to support this)