A Neovim plugin that provides a simple way to run and visualize code actions.
Preview • Installation • Options • Buffer Picker Options • FAQ
Supported pickers:
buffer (a minimal picker that uses buffer)vim.ui.selecttelescope.nvimsnacks.nvimfzf-luaThe code action protocol is nearly fully implemented in this plugin, so you can use it with any language server, even with, like in the preview, Omnisharp which uses partial code actions.
[!WARNING] I have not tested on all LSP, so do not hesitate to open an issue if it doesn't work for you.
[!NOTE] This plugins comes with NerdFonts icons by default. If you don't want to use them, you can remove them from the
signsoption.
With Lazy.nvim:
{
"rachartier/tiny-code-action.nvim",
dependencies = {
{"nvim-lua/plenary.nvim"},
-- optional picker via telescope
{"nvim-telescope/telescope.nvim"},
-- optional picker via fzf-lua
{"ibhagwan/fzf-lua"},
-- .. or via snacks
{
"folke/snacks.nvim",
opts = {
terminal = {},
}
}
},
event = "LspAttach",
opts = {},
}
And add the following snippet to your keymaps:
vim.keymap.set({ "n", "x" }, "<leader>ca", function()
require("tiny-code-action").code_action()
end, { noremap = true, silent = true })
[!NOTE] To use the delta backend you must install delta
[!WARNING] Due to some limitations, the
deltabackend can be slow if the action is really big. If you want optimal performance, use thevimbackend.
{
"rachartier/tiny-code-action.nvim",
dependencies = {
{"nvim-lua/plenary.nvim"},
},
event = "LspAttach",
opts = {
--- The backend to use, currently only "vim", "delta", "difftastic", "diffsofancy" are supported
backend = "vim",
-- The picker to use, "telescope", "snacks", "select", "buffer", "fzf-lua" are supported
-- And it's opts that will be passed at the picker's creation, optional
--
-- You can also set `picker = "<picker>"` without any opts.
picker = "telescope",
backend_opts = {
delta = {
-- Header from delta can be quite large.
-- You can remove them by setting this to the number of lines to remove
header_lines_to_remove = 4,
-- The arguments to pass to delta
-- If you have a custom configuration file, you can set the path to it like so:
-- args = {
-- "--config" .. os.getenv("HOME") .. "/.config/delta/config.yml",
-- }
args = {
"--line-numbers",
},
},
difftastic = {
header_lines_to_remove = 1,
-- The arguments to pass to difftastic
args = {
"--color=always",
"--display=inline",
"--syntax-highlight=on",
},
},
diffsofancy = {
header_lines_to_remove = 4,
}
},
resolve_timeout = 100, -- Timeout in milliseconds to resolve code actions
-- Notification settings
notify = {
enabled = true, -- Enable/disable all notifications
on_empty = true, -- Show notification when no code actions are found
},
-- The icons to use for the code actions
-- You can add your own icons, you just need to set the exact action's kind of the code action
-- You can set the highlight like so: { link = "DiagnosticError" } or like nvim_set_hl ({ fg ..., bg..., bold..., ...})
signs = {
quickfix = { "î©¡", { link = "DiagnosticWarning" } },
others = { "î©¡", { link = "DiagnosticWarning" } },
refactor = { "", { link = "DiagnosticInfo" } },
["refactor.move"] = { "󰪹", { link = "DiagnosticInfo" } },
["refactor.extract"] = { "ï‚", { link = "DiagnosticError" } },
["source.organizeImports"] = { "", { link = "DiagnosticWarning" } },
["source.fixAll"] = { "󰃢", { link = "DiagnosticError" } },
["source"] = { "ï„¡", { link = "DiagnosticError" } },
["rename"] = { "ó°‘•", { link = "DiagnosticWarning" } },
["codeAction"] = { "î©¡", { link = "DiagnosticWarning" } },
},
}
}
The buffer picker provides compact and customizable options for displaying and managing code actions.
Below is an example configuration for the buffer picker:
require("tiny-code-action").setup({
picker = {
"buffer",
opts = {
hotkeys = true, -- Enable hotkeys for quick selection of actions
hotkeys_mode = "text_diff_based", -- Modes for generating hotkeys
auto_preview = false, -- Enable or disable automatic preview
auto_accept = false, -- Automatically accept the selected action (with hotkeys)
position = "cursor", -- Position of the picker window
winborder = "single", -- Border style for picker and preview windows
keymaps = {
preview = "K", -- Key to show preview
close = { "q", "<Esc>" }, -- Keys to close the window (can be string or table)
select = "<CR>", -- Keys to select action (can be string or table)
},
custom_keys = {
{ key = 'm', pattern = 'Fill match arms' },
{ key = 'r', pattern = 'Rename.*' }, -- Lua pattern matching
},
group_icon = " â””",
},
},
})
sequential: Generates sequential hotkeys like a, b, c, etc.text_based: Assigns hotkeys based on the first unique character in the action title.text_diff_based: Generates smarter hotkeys based on title differences.hotkeys_mode to fully control hotkey generation. The function receives (titles, used_hotkeys) and must return a list of hotkey strings. Example for numeric hotkeys:hotkeys_mode = function(titles, used_hotkeys)
local t = {}
for i = 1, #titles do t[i] = tostring(i) end
return t
end
vim.o.winborder or "rounded".preview: Key to show/toggle preview (default: "K")close: Key(s) to close the picker window (default: "q", can be string or table)select: Key(s) to select/apply an action (default: "<CR>", can be string or table)"â–¶ ")The custom_keys option allows you to define specific hotkeys for actions. It supports two formats:
custom_keys = {
{ key = 'm', pattern = 'Fill match arms' },
{ key = 'm', pattern = 'Consider making this binding mutable: mut' },
{ key = 'r', pattern = 'Rename.*' }, -- Lua pattern matching
{ key = 'e', pattern = 'Extract Method' },
}
This format allows:
'Rename.*') for flexible matchingcustom_keys = {
['e'] = "Extract Method", -- Assigning 'e' for the 'Extract Method' action
['r'] = "Rename", -- Assigning 'r' for the 'Rename' action
}
Note: This format doesn't allow duplicate keys and only supports exact string matching.
The array format is recommended as it provides more flexibility for complex use cases while maintaining backward compatibility.
buffer picker)The plugin provides autocmds that are triggered when code action windows are opened. You can listen to these events to customize behavior or integrate with other plugins.
TinyCodeActionWindowEnterMain: Triggered when the main code action picker window is openedTinyCodeActionWindowEnterPreview: Triggered when the preview window is openedBoth autocmds provide the following data:
buf: Buffer ID of the opened windowwin: Window ID of the opened window-- Listen for main window opening
vim.api.nvim_create_autocmd("User", {
pattern = "TinyCodeActionWindowEnterMain",
callback = function(event)
local buf = event.data.buf
local win = event.data.win
vim.notify("Code action main window opened: buf=" .. buf .. ", win=" .. win)
end,
})
-- Listen for preview window opening
vim.api.nvim_create_autocmd("User", {
pattern = "TinyCodeActionWindowEnterPreview",
callback = function(event)
local buf = event.data.buf
local win = event.data.win
-- Custom logic for preview window
end,
})
Filters can be provided via the filters option, the context.only option (LSP standard), or a user-supplied function. All filters are combined and applied in sequence.
context.only)If you pass an opts.context = { only = ... } object, only code actions matching the specified LSP CodeActionKind will be included. Matching follows the LSP hierarchy rules: a filter like "refactor" will include actions with kind exactly "refactor" or any child such as "refactor.extract", "refactor.inline", etc.
require("tiny-code-action").code_action({
context = { only = "refactor" },
})
filters option)The filters table allows you to filter code actions by specific criteria. Supported filter keys:
str: String or Lua pattern to match in the action titlekind: Code action kind (e.g., "refactor", "source.organizeImports")client: Name of the LSP clientline: Line number the action applies toYou may combine multiple filters; all must match for an action to be included.
Example:
require("tiny-code-action").code_action({
filters = {
kind = "refactor",
str = "Wrap",
client = "omnisharp",
line = 10,
}
})
filter option)You can supply a custom filter function, which will be called for each action. The function receives the action and client objects and should return true to include it.
Example:
require("tiny-code-action").code_action({
filter = function(action, client)
-- Only show actions that have "Rename" in the title and are preferred
return action.title:find("Rename") and action.isPreferred
end,
})
You can use all filtering mechanisms together; they are applied in the following order:
context.onlyfilters tablefilter functionOnly code actions that pass all enabled filters will be shown.
You can customize the order in which code actions appear using the sort option. By default, actions marked as isPreferred by the LSP are placed at the top. You can override this with a custom sorting function.
The sort function receives two result objects (a and b) and should return true if a should appear before b. Each result object contains:
action: The code action object (with title, kind, isPreferred, etc.)client: The LSP client object (with name, etc.)context: The context in which the action was requestedSet a default sort order in your setup:
require("tiny-code-action").setup({
sort = function(a, b)
-- Prioritize actions from rust_analyzer
if a.client.name == "rust_analyzer" and b.client.name ~= "rust_analyzer" then
return true
elseif a.client.name ~= "rust_analyzer" and b.client.name == "rust_analyzer" then
return false
end
-- Sort by action kind alphabetically
local a_kind = a.action.kind or ""
local b_kind = b.action.kind or ""
return a_kind < b_kind
end
})
Override the sort order for a specific invocation:
require("tiny-code-action").code_action({
sort = function(a, b)
-- Prioritize "Disable" actions
local a_is_disable = string.match(a.action.title, "Disable") ~= nil
local b_is_disable = string.match(b.action.title, "Disable") ~= nil
if a_is_disable and not b_is_disable then
return true
elseif not a_is_disable and b_is_disable then
return false
end
return false
end,
})
-- Prioritize quickfix actions, then refactoring, then everything else
require("tiny-code-action").code_action({
sort = function(a, b)
local function get_priority(kind)
if string.match(kind or "", "^quickfix") then return 1 end
if string.match(kind or "", "^refactor") then return 2 end
return 3
end
local a_priority = get_priority(a.action.kind)
local b_priority = get_priority(b.action.kind)
return a_priority < b_priority
end,
})
[!NOTE] The custom sort function is applied after the default
isPreferredsorting, so preferred actions will still be prioritized unless you explicitly override this behavior in your sort function.
How to look like the preview?
delta configuration here: config_path to the path of your configuration file.How can I find the kinds of actions?
require("tiny-code-action").code_action({
filter = function(action, client)
local client_name = client.name
local kind = action.kind
local title = action.title
vim.print(client_name, kind, title)
end,
})