Skip to content

Repository files navigation

dev-tools.nvim Main status

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.

Features

  • 🚀 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

Installation and setup

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.

Integration with nvim-lightbulb and lsp-saga

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" } }
})

Usage

  • 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 keymap table of the action_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.

Code Actions UI

  • <C-b> will cycle through categories filter

Code Actions Filter

  • If opts.ui.group_actions is set to true, the actions will be grouped by group name.
    Use <C-l> to open the group and <C-h> to close.

Code Actions Groups

  • You can also call require("dev-tools.actions").open(opts) ---@type ActionOpts to 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 available

Note

Useful to set a keymap for QuickFix actions, e.g. require("dev-tools.actions").open { kind = "quickfix" }

Adding code actions

  • Custom actions can be added to the opts.actions table in your configuration
    or registered via require('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 buffer

Note

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.

Contributing

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,
},
},
}

Available actions

Lua

Refactoring

  • Extract variable
  • Extract function

Editing

  • Split/join function/table/conditional
  • Convert JSON to Lua table

Specs

  • Run/watch all specs
  • Run/watch current spec

Specs

  • Switch between code and spec files
  • Toggle pending
  • Toggle #wip tag

Debugging

  • Log variable under cursor
  • Log with trace
  • Log on condition
  • Log in spec
  • Clear logs

Go, Javascript, Lua, Python, Typescript, C/C++, Java, PHP, Ruby, C#

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

General

  • Todo open/add

License

MIT

About

Friendly developer tools for Nvim - Code Actions LSP server and library

Topics

Resources

Stars

87 stars

Watchers

3 watching

Forks

Releases

Used by

Contributors

Languages