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-lua
The 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 signs option.
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 })Warning
Due to some limitations, the delta backend can be slow if the action is really big.
If you want optimal performance, use the vim backend.
{
"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 = " └",
},
},
})- hotkeys: Enables hotkeys for selecting actions efficiently.
- hotkeys_mode: Defines the mode for generating hotkeys:
sequential: Generates sequential hotkeys likea,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.- Custom function: You can also provide a function for
hotkeys_modeto 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- auto_preview: Automatically previews the selected action.
- auto_accept: Automatically accepts the selected action without confirmation.
- position: Sets the position of the picker window.
- winborder: Style for window borders; falls back to
vim.o.winborderor"rounded". - keymaps: Customizable key mappings for picker interactions:
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)
- custom_keys: Allows users to assign custom hotkeys to specific actions.
- group_icon: Sets the string used for grouped keymaps (default:
"▶ ")
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:
- Multiple same keys: You can use the same key for different actions that never appear together
- Lua pattern matching: Use Lua patterns (e.g.,
'Rename.*') for flexible matching - Exact string matching: Plain strings without pattern characters work as exact matches
custom_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.
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 opened
Both 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.
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" },
})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 to
You 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,
}
})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.onlyfilterstablefilterfunction
Only 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 (withtitle,kind,isPreferred, etc.)client: The LSP client object (withname, etc.)context: The context in which the action was requested
Set 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 isPreferred sorting, so preferred actions will still be prioritized unless you explicitly override this behavior in your sort function.
-
How to look like the preview?
-
How can I find the kinds of actions?
- Use a temporary filter function to print the available 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, })
- Use a temporary filter function to print the available actions:



