https://github.com/shatur/neovim-tasks
A statefull task manager focused on integration with build systems.
https://github.com/shatur/neovim-tasks
c cargo cmake cpp debug quickfix rust
Last synced: 3 months ago
JSON representation
A statefull task manager focused on integration with build systems.
- Host: GitHub
- URL: https://github.com/shatur/neovim-tasks
- Owner: Shatur
- License: gpl-3.0
- Created: 2022-09-10T14:46:27.000Z (over 3 years ago)
- Default Branch: master
- Last Pushed: 2024-07-15T14:32:52.000Z (over 1 year ago)
- Last Synced: 2024-10-19T02:56:51.266Z (about 1 year ago)
- Topics: c, cargo, cmake, cpp, debug, quickfix, rust
- Language: Lua
- Homepage:
- Size: 40 KB
- Stars: 107
- Watchers: 3
- Forks: 10
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- License: COPYING
Awesome Lists containing this project
README
Neovim Tasks
===============
A Neovim plugin that provides a stateful task system focused on integration with build systems.
Tasks in this plugin are provided by modules that implement functionality for a specific build system. Modules can have custom parameters which user can set via `:Task set_module_param` (like current target or build type). Tasks consists of one or more commands and have `args` and `env` parameters to set arguments and environment variable respectively. All this settings are serializable and will be stored in configuration file in your project directory.
# Dependencies
- Necessary
- [plenary.nvim](https://github.com/nvim-lua/plenary.nvim) for internal helpers.
- Optional
- [nvim-dap](https://github.com/mfussenegger/nvim-dap) - for debugging.
# Features
- Output directly into quickfix for fast navigation.
- Tasks provided by modules which can have custom parameters.
- Modules are lazy loaded.
- Module for a task name could be determined automatically based on its condition.
- Tasks can run through debugger.
- Tasks can be chained and react on the previous output.
- Task and module parameters are serializable and specific to the current working directly.
- Tasks arguments could be read from parameters and / or extended via additional temporary arguments passed to `:Task` command.
# Available modules
- [CMake](https://cmake.org) via [cmake-file-api](https://cmake.org/cmake/help/latest/manual/cmake-file-api.7.html#codemodel-version-2).
- [Cargo](https://doc.rust-lang.org/cargo).
- [Bazel](https://bazel.build)
- [GNU Make](https://www.gnu.org/software/make/)
- [Zig](https://ziglang.org/learn/build-system/)
- [NPM](https://www.npmjs.com/)
You can also write [your own module](#modules-creation-and-configuration).
# Commands
Use the command `:Task` with one of the following arguments:
| Argument(s) | Description |
| ---------------------------------------- | ---------------------------------------------------------------------------- |
| `start ` | Starting a task from a module. |
| `set_module_param ` | Set parameter for a module. All parameters are module-specific. |
| `set_task_param ` | Set parameter for a task from a module. The parameter can be `arg` or `env`. |
| `cancel` | Cancel currently running task. |
Modules and tasks will be autocompleted.
Module name can be `auto`, in which case the first module that satisfies the condition will be used.
# Configuration
To configure the plugin, you can call `require('tasks').setup(values)`, where `values` is a dictionary with the parameters you want to override. Here are the defaults:
```lua
local Path = require('plenary.path')
require('tasks').setup({
default_params = { -- Default module parameters with which `neovim.json` will be created.
cmake = {
cmd = 'cmake', -- CMake executable to use, can be changed using `:Task set_module_param cmake cmd`.
build_type = 'Debug', -- Build type, can be changed using `:Task set_module_param cmake build_type`.
build_kit = 'default', -- default build kit, can be changed using `:Task set_module_param cmake build_kit`.
dap_name = 'codelldb', -- DAP configuration name from `require('dap').configurations`. If there is no such configuration, a new one with this name as `type` will be created.
build_dir = tostring(Path:new('{cwd}', 'build', '{build_kit}', '{build_type}')), -- Build directory. The expressions `{cwd}`, `{build_kit}` and `{build_type}` will be expanded with the corresponding text values. Could be a function that return the path to the build directory.
cmake_kits_file = nil, -- set path to JSON file containing cmake kits
cmake_build_types_file = nil, -- set path to JSON file containing cmake kits
clangd_cmdline = {
'clangd',
'--background-index',
'--clang-tidy',
'--header-insertion=never',
'--completion-style=detailed',
'--offset-encoding=utf-8',
'-j=4',
}, -- command line for invoking clangd - this array will be extended with --compile-commands-dir and --query-driver after each cmake configure with parameters inferred from build_kit, build_type and build_dir
ignore_presets = false, -- if true, cmake presets will not be used, build_kit and build_type will be used instead even if CMakePresets.json or CMakeUserPresets.json files are present in the project root.
restart_clangd_after_configure = true, -- if true, clangd will be restarted after each cmake configure with updated compile_commands.json file and --query-driver parameters mattching current cmake build_kit and build_type or build_preset.
},
bazel = {
cmd = 'bazel',
dap_name = 'codelldb',
},
zig = {
cmd = 'zig', -- zig command which will be invoked
dap_name = 'codelldb', -- DAP configuration name from `require('dap').configurations`. If there is no such configuration, a new one with this name as `type` will be created.
build_type = 'Debug', -- build type, can be changed using `:Task set_module_param zig build_type`
build_step = 'install', -- build step, cah be changed using `:Task set_module_param zig build_step`
},
cargo = {
dap_name = 'codelldb', -- DAP configuration name from `require('dap').configurations`. If there is no such configuration, a new one with this name as `type` will be created.
},
npm = {
cmd = 'npm', -- npm command which will be invoked. If using yarn or pnpm, change here.
working_directory = vim.loop.cwd(), -- working directory in which NPM will be invoked
},
make = {
cmd = 'make', -- make command which will be invoked
args = {
all = { '-j10', 'all' }, -- :Task start make all → make -j10 all
build = {}, -- :Task start make build → make
nuke = { 'clean' }, -- :Task start make nuke → make clean
},
},
},
save_before_run = true,
params_file = 'neovim.json',
quickfix = {
pos = 'botright',
height = 12,
only_on_error = false,
},
dap_open_command = function() return require('dap').repl.open() end,
```
# Usage examples
## CMake
1. Open a CMake project.
2. Run `configuration` task using `:Task start cmake configure`.
3. Select a target by specifying module parameter with `:Task set_module_param cmake target`. All module parameters are specific to modules. Since CMake can't run targets like Cargo, we introduced a parameter to select the same target for building (appropriate arguments will be passed to CMake automatically) and running.
4. Optionally set arguments using `:Task set_task_param cmake run`.
5. Build and run the project via `:Task start cmake run` or build and debug using `:Task start cmake debug`. You can pass additional arguments to these commands, which will be temporarily added to the arguments from the previous step.
### CMake build types
You can define your custom build types in a JSON file, path to which needs to be specified in the `default_params` object as described above. An example JSON file with CMake build types looks like this:
```json
{
"debug" : {
"build_type" : "Debug",
"cmake_usr_args" : {
"MY_TREAT_WARNINGS_AS_ERRORS": "OFF"
}
},
"dev-release": {
"build_type" : "RelWithDebInfo",
"cmake_usr_args": {
"MY_TREAT_WARNINGS_AS_ERRORS": "ON"
}
},
"release": {
"build_type" : "Release",
"cmake_usr_args": {
"MY_TREAT_WARNINGS_AS_ERRORS": "ON"
}
}
}
```
The structure of the JSON is in the form:
```json
{
"build_type_name": {
"build_type": "this will be passed to -DCMAKE_BUILD_TYPE",
"cmake_usr_args": { // optional, will be passed to cmake configure whenever build_type_name is active
"cmake_variable1": "value1",
"cmake_variable2": "value2",
...
}
},
...
}
```
This allows for custom cmake user arguments for each build type that will always be applied, regardless of selected CMake kit.
### Cmake build kits
You can define your custom build kits (or toolchains) in a JSON file, path to which needs to be specified in the `default_params` object as described above. An example JSON file with CMake kits looks like this:
```json
{
"xcode-clang": {
"compilers": {
"C": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/cc",
"CXX": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++"
},
"query_driver" : "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++",
"generator": "Xcode",
"build_type_aware": false
},
"clang": {
"compilers": {
"C": "/usr/bin/clang",
"CXX": "/usr/bin/clang++"
},
"query_driver" : "/usr/bin/clang++",
"generator": "Ninja"
},
"clang-xcode-13.4.1": {
"compilers": {
"C": "/usr/bin/clang",
"CXX": "/usr/bin/clang++"
},
"query_driver" : "/usr/bin/clang++",
"environment_variables": {
"DEVELOPER_DIR": "/Applications/xcode-versions/Xcode-13.4.1.app/Contents/Developer"
},
"generator": "Ninja"
},
"emscripten-3.1.17": {
"toolchain_file": "~/.conan/data/emsdk_installer/3.1.17/microblink/stable/package/3c9d8fb1b383ce27cf8b0942dcbdbb6add8cbee9/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake",
"environment_variables": {
"EM_CONFIG": "/Users/dodo/.conan/data/emsdk_installer/3.1.17/microblink/stable/package/3c9d8fb1b383ce27cf8b0942dcbdbb6add8cbee9/.emscripten",
"EM_CACHE": "/Users/dodo/.conan/data/emsdk_installer/3.1.17/microblink/stable/package/3c9d8fb1b383ce27cf8b0942dcbdbb6add8cbee9/.emscripten_cache"
},
"cmake_usr_args": {
"MY_EMSCRIPTEN_EMRUN_BROWSER": "chrome",
"MY_EMSCRIPTEN_EMRUN_SILENCE_TIMEOUT": "300",
"MY_EMSCRIPTEN_EMRUN_SERVE_AFTER_CLOSE": "ON",
"MY_EMSCRIPTEN_EMRUN_BROWSER_ARGS": "--remote-debugging-port=9622"
},
"query_driver" : "~/.conan/data/emsdk_installer/3.1.17/microblink/stable/package/3c9d8fb1b383ce27cf8b0942dcbdbb6add8cbee9/upstream/emscripten/em++",
"generator": "Ninja"
}
}
```
The structure of the JSON is in the form:
```json
{
"build_kit_name": {
"compilers": { // optional, if provided it will pass -DCMAKE_C_COMPILER and -DCMAKE_CXX_COMPILER to cmake during configuration, not needed when toolchain_file is provided or default compiler is used
"C": "/path/to/c/compiler/to/be/used",
"CXX": "/path/to/c++/compiler/to/be/used",
},
"toolchain_file": "/path/to/cmake/toolchain/file", // also optional, if provided, this will be passed to -DCMAKE_TOOLCHAIN_FILE
"environment_variables": { // optional, if provided, given environment variables will be set for every invocation of any cmake task
"VAR1": "value1",
"VAR2": "value2",
...
},
"cmake_usr_args": { // optional, will be passed to cmake configure whenever build_kit_name is active
"cmake_variable1": "value1",
"cmake_variable2": "value2",
...
},
"query_driver": "/path/to/query_driver/compiler", // optional, if provied, it will be passed as --query-driver` parameter to clangd when build_kit_name is active
"generator": "cmake-generator", // will be passed as -G option to cmake. If not provided, Ninja will be used. Keep in mind that not all generators support creating compile_commands.json file, which is needed for clangd to work. However, you can still use those generators for quickly creating CMake build directories (e.g. for Xcode)
"build_type_aware": true|false, // optional, indicates whether generator is aware of CMAKE_BUILD_TYPE. For example, IDE generators need to have this set to false to prevent invoking cmake with -DCMAKE_BUILD_TYPE=active_build_type
},
...
}
```
### Tasks
CMake with Kits task module will generate following tasks:
- `configure` — invokes cmake with parameters for active `build_kit` and `build_type`, or active `configure_preset` if presets are available
- `configureDebug` - same as `configure`, but starts the CMake debugging session
- `build` — builds the currenly selected target
- `build_all` — builds all targets
- `build_current_file` — builds target associated with now opened source file (Ninja generator only)
- `run` — builds and then runs the selected target
- `debug` — builds and then starts `nvim-dap` debug session for the selected target
- `clean` — cleans the build directory
- `ctest` — invokes `ctest`
- `purge` — deletes the cmake binary directory (unix only)
- `reconfigure` — equivalent to `purge` + `configure`
### More CMake examples
#### Example mappings
Here is convenient Lua code for configuring mapping for tasks generated by this plugin:
```lua
vim.keymap.set( "n", "cC", [[:Task start cmake configure]], { silent = true } )
vim.keymap.set( "n", "cD", [[:Task start cmake configureDebug]], { silent = true } )
vim.keymap.set( "n", "cP", [[:Task start cmake reconfigure]], { silent = true } )
vim.keymap.set( "n", "cT", [[:Task start cmake ctest]], { silent = true } )
vim.keymap.set( "n", "cK", [[:Task start cmake clean]], { silent = true } )
vim.keymap.set( "n", "ct", [[:Task set_module_param cmake target]], { silent = true } )
vim.keymap.set( "n", "", [[:Task cancel]], { silent = true } )
vim.keymap.set( "n", "cr", [[:Task start cmake run]], { silent = true } )
vim.keymap.set( "n", "", [[:Task start cmake debug]], { silent = true } )
vim.keymap.set( "n", "cb", [[:Task start cmake build]], { silent = true } )
vim.keymap.set( "n", "cB", [[:Task start cmake build_all]], { silent = true } )
-- open ccmake in embedded terminal
local function openCCMake()
local build_dir = tostring( require( 'tasks.cmake_utils.cmake_utils' ).getBuildDir() )
vim.cmd( [[bo sp term://ccmake ]] .. build_dir )
end
vim.keymap.set( "n", "cc", openCCMake, { silent = true } )
-- if project is using presets, provide preset selection for both cv and ck
-- if not, provide build type (cv) and kit (ck) selection
local function selectPreset()
local availablePresets = cmake_presets.parse( 'buildPresets' )
vim.ui.select( availablePresets, { prompt = 'Select build preset' }, function( choice, idx )
if not idx then
return
end
local projectConfig = ProjectConfig:new()
if not projectConfig[ 'cmake' ] then
projectConfig[ 'cmake' ] = {}
end
projectConfig[ 'cmake' ][ 'build_preset' ] = choice
-- autoselect will invoke projectConfig:write()
cmake_utils.autoselectConfigurePresetFromCurrentBuildPreset( projectConfig )
end)
end
local function selectBuildKitOrPreset()
if cmake_utils.shouldUsePresets() then
selectPreset()
else
tasks.set_module_param( 'cmake', 'build_kit' )
end
end
vim.keymap.set( "n", "ck", selectBuildKitOrPreset, { silent = true } )
local function selectBuildTypeOrPreset()
if cmake_utils.shouldUsePresets() then
selectPreset()
else
tasks.set_module_param( 'cmake', 'build_type' )
end
end
vim.keymap.set( "n", "cv", selectBuildTypeOrPreset, { silent = true } )
```
#### Correct LSP `clangd` config on NeoVim startup
To correctly boot the `clangd` LSP on NeoVim startup, you can configure LSP to immediately use correct `clangd` arguments with following code:
```lua
vim.lsp.config('clangd', {
cmd = require( 'tasks.cmake_utils.cmake_utils' ).currentClangdArgs(),
})
```
#### Active CMake kit, target and build type or preset in [lualine](https://github.com/nvim-lualine/lualine.nvim) status
```lua
local Path = require('plenary.path')
local lualine = require( 'lualine' )
local ProjectConfig = require( 'tasks.project_config' )
local function cmakeStatus()
local cmake_config = ProjectConfig:new()[ 'cmake' ]
local cmakelists_dir = cmake_config.source_dir and cmake_config.source_dir or vim.loop.cwd()
if ( Path:new( cmakelists_dir ) / 'CMakeLists.txt' ):exists() then
local cmake_utils = require( 'tasks.cmake_utils.cmake_utils' )
if cmake_utils.shouldUsePresets( cmake_config ) then
local preset = cmake_config.build_preset or 'not selected'
local cmakeTarget = cmake_config.target and cmake_config.target or 'all'
return 'CMake preset: ' .. preset .. ', target: ' .. cmakeTarget
else
local cmakeBuildType = cmake_config.build_type or 'not selected'
local cmakeKit = cmake_config.build_kit or 'not selected'
local cmakeTarget = cmake_config.target or 'all'
return 'CMake variant: ' .. cmakeBuildType .. ', kit: ' .. cmakeKit .. ', target: ' .. cmakeTarget
end
else
return ''
end
end
lualine.setup({
sections = {
lualine_c = { { 'filename', path = 1 } },
lualine_x = { 'encoding', 'fileformat', 'filetype', cmakeStatus }
}
})
```
## Bazel
1. Open a Bazel project.
2. Optionally set compilation mode with `:Task set_module_param bazel build_type`.
3. Optionally set bazel target with `:Task set_module_param bazel target`.
4. Optionally set target arguments using `:Task set_task_param bazel run args`.
5. Optionally set other bazel arguments using `:Task set_module_param bazel bazel_args`.
- For example, you can set `--copt="-g" --strip=never --spawn_strategy=local --disk_cache=false` to enable debugging on MacOS, regardless of the compilation mode.
- there is also `global_bazel_args` module parameter that will be added before any `bazel_args` to every bazel command. Use it to tweak the default bazel output.
5. Build and run the project via `:Task start bazel run` or build and debug using `:Task start bazel debug`.
### Clangd compile commands JSON generation
#### Using bazel tools, such as [bazel-compile-commands-extractor](https://github.com/hedronvision/bazel-compile-commands-extractor)
Bazel does not natively support generating `compile_commands.json` file, which is required for `clangd` to work properly. However, you can use the [bazel-compile-commands-extractor](https://github.com/hedronvision/bazel-compile-commands-extractor) to create a Bazel target that will generate the `compile_commands.json` file for you. You can also define your own target to do that. Once you have the target, run `:Task set_module_param bazel compile_commands_refresh_target` to set the name of the target that will be invoked to refresh the `compile_commands.json` file. The default value is `@hedron_compile_commands//:refresh_all`.
Once the refresh target is configured, you can run `:Task start bazel refresh_compile_commands` to regenerate the `compile_commands.json` file. This will also restart the `clangd` language server to pick up the changes.
#### Using [bazel-compile-commands tool](https://github.com/kiron1/bazel-compile-commands)
Alternatively, if you don't want to pollute your Bazel workspace with special targets for extracting compile commands, you can use the [bazel-compile-commands](https://github.com/kiron1/bazel-compile-commands) tool. Follow the installation instructions in the tool's README. Once installed, you can define the `bazel_compile_commands_tool` module parameter to point to the executable of this tool. The default value is `bazel-compile-commands`. Additionally, set the `bazel_compile_commands_tool_args` module parameter to define any arguments that will be given to the tool. You can use the `%target%` placeholder to put a currently selected bazel target in this place. This may be useful if you want to avoid generating large `compile_commands.json` files that may clog your clangd process. After setting this parameter, you can run `:Task start bazel external_refresh_compile_commands` to regenerate the `compile_commands.json` file and restart the `clangd` language server. All bazel arguments set with `:Task set_module_param bazel bazel_args` will be passed to the tool as well, but every argument will be prefixed with `bazel_compile_commands_tool_bazel_prefix` module parameter, which defaults to `--bazelopt=`.
## Cargo
1. Open a Cargo project.
2. Optionally set arguments using `:Task set_task_param cargo run`.
3. Optionally set global cargo arguments using `:Task set_task_param cargo global_cargo_args`.
4. Build and run the project via `:Task start cargo run` or build and debug using `:Task start cargo debug`.
Cargo module doesn't have a `target` param which specific to CMake because `cargo run` automatically pick the binary. If there is multiple binaries, you can set which one you want to run using `--bin` or `--project` in step 2 as you do in CLI.
## GNU Make
1. Open a Make project.
2. Run a Make target `` with `:Task start make `.
To override targets or add custom `make` options, configure the appropriate task:
```lua
require('tasks').setup({
default_params = {
...
make = {
cmd = 'make',
args = {
all = { '-j10', 'all' }, -- :Task start make all → make -j10 all
build = {}, -- :Task start make build → make
nuke = { 'clean' }, -- :Task start make nuke → make clean
},
},
...
}
})
```
## Zig
### Quick start with a zig file
1. Open zig file that contains `main`
2. Run `:Task start zig run_file`
3. To start debugging the file, run `:Task start zig debug_file`
### Quick start with a project that contains `build.zig` file
1. Open Zig project
2. Select a build step with `:Task set_module_param zig build_step`.
3. Select a build type with `:Task set_module_param zig build_type`
4. Run selected build step with `:Task start zig build`
### All available zig tasks
- `build` - invokes `zig build` with step configured as `build_step` with build type configured as `build_type`
- `clean` - deletes the `zig-out` folder (works only on Unix shells at the moment)
- `clean_cache` - deletes the `.zig-cache` folder (works only on Unix shells at the moment)
- `clean_all` - invokes forst `clean`, then `clean_cache`
- `run_file` - invokes `zig run` for currently open buffer. Obeys `build_type`
- `debug_file` - starts debugger for currently open buffer (effectively as `run_file`, but under debugger)
- `test_file` - invokes `zig test` for currently open buffer. Obeys `build_type`.
- `debug_test_file` -same as `test_file`, but under debugger
- `run_current_test` - invokes `zig test` for currently open buffer with test filter set to a first test above current cursor location. If test filter cannot be calculated, all tests in current file are run (behavior same as `test_file`).
- `debug_current_test` - same as `run_current_test`, but under debugger
## NPM
1. Open a NPM project
2. Run `:Task start npm install`
3. You can also run any NPM script using `:Task start npm run `
For example, imagine that your `package.json` contains lines like these:
```json
{
"scripts": {
"clean": "rimraf build dist",
"lint": "eslint --ext ts -c .eslintrc.json src",
"start": "NODE_PATH=$(pwd)/node_modules node $(pwd)/../core/scripts/https-serve.js dist",
"rollup": "rollup -c rollup.config.js",
}
}
```
You can map then those commands with code like this:
```lua
vim.keymap.set( "n", "<leader>ni", [[:Task start npm install<cr>]] )
vim.keymap.set( "n", "<leader>nl", [[:Task start npm run lint<cr>]] )
vim.keymap.set( "n", "<leader>nr", [[:Task start npm run rollup<cr>]] )
vim.keymap.set( "n", "<leader>ns", [[:Task start npm run clean<cr>]] )
vim.keymap.set( "n", "<leader>ns", [[:Task start npm run start<cr>]] )
```
# Modules creation and configuration
To create a module just put a lua file under `lua/tasks/module` in your configuration or submit your module as a PR. In this module you need to return a table with the following fields:
```lua
{
params = {
-- A table of parameter names. Possible values:
'parameter_name1', -- A string parameter, on setting user will be prompted with vim.ui.input.
parameter_name2 = { 'one', 'two' }, -- A table with possible values, on setting user will be prompted with vim.ui.select to pick one of these values.
parameter_name3 = func, -- A function that generates a string or a table.
}
condition = function() return Path:new('file'):exists() end -- A function that returns `true` if this module could be applied to this directory. Used when `auto` is used as module name.
tasks = {
-- A table of module tasks. Possible values:
task_name1 = {
-- Required parameters:
cmd = 'command' -- Command to execute.
-- Optional parameters:
cwd = 'directory' -- Command working directory. Default to current working directory.
after_success = callback -- A callback to execute on success.
dap_name = 'dap_name' -- A debug adapter name. If exists, the task will be launched through the adapter. Usually taken from a module parameter. Implies ignoring all streams below.
-- Disable a stream output to quickfix. If both are disabled, quickfix will not show up. If you want to capture output of a stream in a next task, you need to disable it.
ignore_stdout = true,
ignore_stderr = true,
},
task_name2 = func1, -- A function that returns a table as above. Accepts configuration for this module and previous job.
task_name3 = { func2, func3 }, -- A list of functions as above. Tasks will be executed in chain.
}
}
```
For a more complex example take a look at [cargo.lua](lua/tasks/module/cargo.lua).
You can also edit existing modules in right in your config. Just import a module using `require('tasks.module.module_name')` and add/remove/modify any fields from the above.