A Neovim plugin that provides in-process LSP server, a community library and a convenient interface for customization and enhancement of your Neovim with custom code actions.
- 🚀 In-process LSP server for code actions
- 🧩 Simple, intuitive interface and helpers for managing and creating code actions
- 🔍 Enhanced code actions picker with live filtering, grouping, keymaps and extra info
- 📚 Community-driven library of useful code actions
With lazy.nvim, everything should work out of the box.
You may want to tweak a few options, notably global keymaps and perhaps choose which actions to include/exclude, to keep the picker clean and fast.
{
'yarospace/dev-tools.nvim',
dependencies= {
"nvim-treesitter/nvim-treesitter", -- code manipulation in buffer, required
{
"folke/snacks.nvim", -- optionalopts= {
picker= { enabled=true }, -- actions pickerterminal= { enabled=true }, -- terminal for running spec actions
},
},
{
"ThePrimeagen/refactoring.nvim", -- refactoring library, optionaldependencies= { "nvim-lua/plenary.nvim" },
},
},
opts= {
---@typeAction[]|fun():Action[]actions= {},
filetypes= { -- filetypes for which to attach the LSPinclude= {}, -- {} to include all, except for special buftypes, e.g. nofile|help|terminal|promptexclude= {},
},
}
}For other package managers, you may need to include dependencies and call require('dev-tools').setup({ ... }) in your config.
Minimal Config
{
'yarospace/dev-tools.nvim',
dependencies= { "nvim-treesitter/nvim-treesitter", -- code manipulation in buffer, required
{
"folke/snacks.nvim", -- optionalopts= {
picker= { enabled=true }, -- actions pickerterminal= { enabled=true }, -- terminal for running spec actions
},
},
{
"ThePrimeagen/refactoring.nvim", -- refactoring library, optionaldependencies= { "nvim-lua/plenary.nvim" },
},
},
opts= {
---@typeAction[]|fun():Action[]actions= {},
filetypes= { -- filetypes for which to attach the LSPinclude= {}, -- {} to include all, except for special buftypes, e.g. nofile|help|terminal|promptexclude= {},
},
builtin_actions= {
include= {}, -- filetype/group/name of actions to include or {} to include allexclude= {}, -- filetype/group/name of actions to exclude or "true" to exclude all
},
action_opts= { -- override options for actions
{
group="Debugging",
name="Log vars under cursor",
opts= {
keymap=nil, ---@typeKeymap action keymap spec, e.g. -- { -- global = "<leader>dl" | { "<leader>dl", mode = { "n", "x" } }, -- picker = "<M-l>",-- hide = true, -- hide the action from the picker-- }
},
},
},
ui= {
override=true, -- override vim.ui.select, requires `snacks.nvim` to be included in dependencies or installed separatelygroup_actions=true, -- group actions by group
},
}
}Full Config
localM= {
---@typeAction[]|fun():Action[]actions= {},
filetypes= { -- filetypes for which to attach the LSPinclude= {}, -- {} to include all, except for special buftypes, e.g. nofile|help|terminal|promptexclude= {},
},
builtin_actions= {
include= {}, -- filetype/group/name of actions to include or {} to include allexclude= {}, -- filetype/group/name of actions to exclude or "true" to exclude all
},
action_opts= { -- override default options for actions
{
group="Debugging",
name="Log vars under cursor",
opts= {
logger=nil, ---@typefunction to log debug info, default dev-tools.logkeymap=nil, ---@typeKeymap action keymap spec, e.g.-- {-- global = "<leader>dl"|{ "<leader>dl", mode = { "n", "x" } },-- picker = "<M-l>",-- hide = true, -- hide the action from the picker-- }
},
},
{
group="Specs",
name="Watch specs",
opts= {
tree_cmd=nil, ---@typestring command to run the file tree, default "git ls-files -cdmo --exclude-standard"test_cmd=nil, ---@typestring command to run tests, default "nvim -l tests/minit.lua tests --shuffle-tests -v"test_tag=nil, ---@typestring test tag, default "wip"terminal_cmd=nil, ---@typefunction to run the terminal, default is Snacks.terminal
},
},
{
group="Todo",
name="Open Todo",
opts= {
filename=nil, ---@typestring name of the todo file, default ".todo.md"template=nil, ---@typestring[] -- template for the todo file
},
},
},
ui= {
override=true, -- override vim.ui.select, requires `snacks.nvim` to be included in dependencies or installed separatelygroup_actions=true, -- group actions by groupkeymaps= { filter="<C-b>", open_group="<C-l>", close_group="<C-h>" },
},
debug=false, -- extra debug infocache=true, -- cache the actions on start
}Note
Dev-tools picker uses Snacks.nvim picker module, which should be included as a dependency or installed separately.
Some plugins, like lsp-saga or nvim-lighbulb show 💡 signs when there are code actions available.
Since dev-tools provides code actions for every line, you may want to disable it in your config. For example:
require('lspsaga').setup({
lightbulb= { ignore= { clients= { 'dev-tools' } } }
})
require("nvim-lightbulb").setup({
ignore= { clients= { "dev-tools" } }
})- Code actions are accessible via the default LSP keymaps, e.g.
gra,<leader>ca,<leader>la, etc. - Last action is dot-repeatable.
- You can add a global or a picker local keymap by specifying it in the
keymaptable of theaction_opts.
Dev-tools actions picker is an enhanced version of the default picker, which provides extra info about the actions, live filtering and actions keymaps.
<C-b>will cycle through categories filter
- If
opts.ui.group_actionsis set totrue, the actions will be grouped by group name.
Use<C-l>to open the group and<C-h>to close.
- You can also call
require("dev-tools.actions").open(opts) ---@type ActionOptsto open the actions picker with the specified options.
---@classActionOpts:vim.lsp.buf.code_action.Opts---@fieldgroup? string - only show actions matching this group---@fieldname? string - only show actions matching this name---@fieldkind? string - only show actions matching this kind---@fieldfilter? fun(x: lsp.CodeAction|lsp.Command):boolean - only show actions for which the function returns true---@fieldapply? boolean - apply the action without opening the picker if there is only one action availableNote
Useful to set a keymap for QuickFix actions, e.g. require("dev-tools.actions").open { kind = "quickfix" }
- Custom actions can be added to the
opts.actionstable in your configuration
or registered viarequire('dev-tools').register_action({})
---@classAction---@fieldnamestring - name of the action---@fieldgroupstring|nil - group of the action---@fieldconditionstring|nil|fun(action: ActionCtx): boolean - function or pattern to match against buffer name---@fieldfiletypestring[]|nil - filetype to limit the action to---@fieldfnfun(action: ActionCtx) - function to execute the action---@classActionCtx:Action---@fieldctxCtx - context of the action---@classCtx---@fieldbufnumber - buffer number---@fieldwinnumber - window number---@fieldrownumber - current line number---@fieldcolnumber - current column number---@fieldlinestring - current line---@fieldwordstring - word under cursor---@fieldts_nodeTSNode|nil - current TS node---@fieldts_typestring|nil - type of the current TS node---@fieldts_rangetable<number, number, number, number>|nil - range of the current TS node---@fieldbufnamestring - full path to file in buffer---@fieldrootstring - root directory of the file---@fieldfiletypestring - filetype---@fieldrangeRange|nil - range of the current selection---@fieldeditEdit - edititng functions---@classRange---@fieldstart{line: number, character: number} - start position of the range---@fieldend{line: number, character: number} - end position of the range---@fieldrctable<number, number, number, number> - row/col formatopts= {
---@typeAction[]|fun():Action[]actions= {
{
name="Extract variable",
filetype= { "lua" },
fn=function(action)
localctx=action.ctxvim.ui.input({ prompt="Variable name:", default="" }, function(var_name)
ifnotvar_namethenreturnendlocalvar_body= ("local %s = %s"):format(var_name, ctx.edit:get_range()[1])
ctx.edit:set_range { var_name }
ctx.edit:set_lines({ var_body }, ctx.range.rc[1], ctx.range.rc[1])
ctx.edit:indent(ctx.range.rc[1] -1, ctx.range.rc[3] +1)
ctx.edit:set_cursor(ctx.range.rc[1] +2, ctx.range.rc[2] +1)
end)
end,
},
}
}There are several helper functions to make it easier to create actions:
---@classEdit:Ctx---@fieldget_linesfun(self: Edit, l_start?: number, l_end?: number): string[] - get lines in the buffer---@fieldset_linesfun(self: Edit, lines: string[], l_start?: number, l_end?: number) - set lines in the buffer---@fieldget_rangefun(self: Edit, ls?: number, cs?: number, le?: number, ce?: number): string[] - get lines in the range of the buffer---@fieldset_rangefun(self: Edit, lines: string[], ls?: number, cs?: number, le?: number, ce?: number) - set lines in range of the buffer---@fieldget_nodefun(self: Edit, types: string|string[], node?: TSNode|nil, predicate?: fun(node: TSNode): boolean| nil): TSNode|nil, table <number, number, number, number>|nil - traverses up the tree to find the first TS node matching specified type/s---@fieldget_previous_nodefun(self: Edit, node: TSNode, allow_switch_parents?: boolean, allow_previous_parent?: boolean): TSNode|nil - get previous node with same parent---@fieldget_node_textfun(self: Edit, node?: TSNode): string|nil - get the text of the node---@fieldindentfun(self: Edit, l_start?: number, l_end?: number) - indent range in the buffer---@fieldset_cursorfun(self: Edit, row?: number, col?: number) - set the cursor in the buffer---@fieldwritefun() - write the bufferNote
Dev-tools actions API is slightly different from null-ls/none-ls API.
I may implement 100% compatibility if there is a demand for it. Let me know.
This project is originally thought out as community driven.
The goal is to provide a simple and intuitive interface for creating and managing code actions, as well as a collection of useful code actions that can be used out of the box. Your contributions are highly desired and appreciated!
All actions are stored in dev-tools.nvim/lua/dev-tools/actions/.
Actions specific to a language can be put under the relevant subdirectory.
---@classActions---@fieldgroupstring - group of actions---@fieldfiletypestring[]|nil - filetype to limit the actions group to---@fieldactionsAction[]|fun(): Action[] - list oe actions---@classAction---@fieldnamestring - name of the action---@fieldgroupstring|nil - group of the action---@fieldconditionstring|nil|fun(action: ActionCtx): boolean - function or pattern to match against buffer name---@fieldfiletypestring[]|nil - filetype to limit the action to---@fieldfnfun(action: ActionCtx) - function to execute the action---@typeActionsreturn {
group="Refactoring",
filetype= { "lua" },
actions= {
{
name="Extract variable",
condition="_spec",
fn=function(action)
---end,
},
{
name="Extract function",
condition=function(action) returnaction.ctx.root:match("project") end,
fn=function(action)
--end,
},
},
}- Extract variable
- Extract function
- Split/join function/table/conditional
- Convert JSON to Lua table
- Run/watch all specs
- Run/watch current spec
- Switch between code and spec files
- Toggle pending
- Toggle #wip tag
- Log variable under cursor
- Log with trace
- Log on condition
- Log in spec
- Clear logs
Refactoring ThePrimeagen/refactoring.nvim
The module should be included as a dependency or installed separately, and will be automatically detected.
Actions are available only for visually selected code.
- Extract function
- Inline function
- Extract variable
- Inline variable
- Todo open/add
MIT


