A Neovim plugin that provides a simple way to run and visualize code actions with Telescope.
๐ tiny-code-action.nvim
A Neovim plugin that provides a simple way to run and visualize code actions.Preview โข Installation โข Options โข Customizing Action Titles โข Buffer Picker Options โข FAQ
Supported pickers:
buffer(a minimal picker that uses buffer)vim.ui.selecttelescope.nvimsnacks.nvimfzf-lua
[!WARNING]
I have not tested on all LSP, so do not hesitate to open an issue if it doesn't work for you.
Preview
Delta
Vim
Difftastic
Buffer Picker (can be used with hotkeys)
Installation
[!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 = {
-- 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 })
Options
[!NOTE]
To use the delta backend you must install delta
[!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",
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 headerlinesto_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 = { headerlinesto_remove = 1,
-- The arguments to pass to difftastic args = { "--color=always", "--display=inline", "--syntax-highlight=on", }, }, diffsofancy = { headerlinesto_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 },
-- Customize how action titles are displayed in the picker -- Function receives (action, client) and returns a formatted string -- Default: action.title format_title = nil,
-- 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 nvimsethl ({ 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" } }, }, } }
Customizing Action Titles
You can customize how action titles are displayed in all pickers using the format_title option. This function receives the action and client objects and returns a formatted string.
Example: Show action.kind with the title
require("tiny-code-action").setup({
format_title = function(action, client)
if action.kind then
return string.format("%s (%s)", action.title, action.kind)
end
return action.title
end,
})
This will display actions like:
Add missing members (quickfix)Extract to function (refactor.extract)Organize imports (source.organizeImports)
format_title function can access: action.title: The action's title textaction.kind: The LSP CodeActionKind (e.g., "quickfix", "refactor.extract")action.isPreferred: Boolean indicating if the action is preferredclient.name: The name of the LSP client providing the action
Buffer Picker Options
The buffer picker provides compact and customizable options for displaying and managing code actions.
Configuration
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
hotkeysmode = "textdiff_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)
preview_close = { "q", "<Esc>" }, -- Keys to return from preview to main window (can be string or table)
},
custom_keys = {
{ key = 'm', pattern = 'Fill match arms' },
{ key = 'r', pattern = 'Rename.*' }, -- Lua pattern matching
},
group_icon = " โ",
},
},
})
Explanation of Options
- hotkeys: Enables hotkeys for selecting actions efficiently.
- hotkeys_mode: Defines the mode for generating hotkeys:
sequential: Generates sequential hotkeys like a, b, c, etc.
- text_based: Assigns hotkeys based on the first unique character in the action title.
- textdiffbased: Generates smarter hotkeys based on title differences.
- Custom function: You can also provide a function for hotkeysmode to fully control hotkey generation. The function receives (titles, usedhotkeys) and must return a list of hotkey strings. Example for numeric hotkeys:
hotkeysmode = function(titles, usedhotkeys)
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)
- preview_close: Key(s) to return from preview window to main window (default: { "q", "<Esc>" }, 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:
"โถ ")
Custom Keys
The custom_keys option allows you to define specific hotkeys for actions. It supports two formats:
Array Format (Recommended)
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
Table Format (Legacy)
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.
Autocmds (only for 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.
Available Autocmds
TinyCodeActionWindowEnterMain: Triggered when the main code action picker window is openedTinyCodeActionWindowEnterPreview: Triggered when the preview window is opened
buf: Buffer ID of the opened windowwin: Window ID of the opened window
Usage Examples
-- Listen for main window opening
vim.api.nvimcreateautocmd("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.nvimcreateautocmd("User", { pattern = "TinyCodeActionWindowEnterPreview", callback = function(event) local buf = event.data.buf local win = event.data.win -- Custom logic for preview window end, })
Filters
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.
1. LSP Kind Filter (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" },
})
2. Built-in Filters (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 to
Example:
require("tiny-code-action").code_action({ filters = { kind = "refactor", str = "Wrap", client = "omnisharp", line = 10, } })
3. Custom Filter Function (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, })
4. Combined Filtering
You can use all filtering mechanisms together; they are applied in the following order:
context.onlyfilterstablefilterfunction
Sorting
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
Global Configuration
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 == "rustanalyzer" and b.client.name ~= "rustanalyzer" then
return true
elseif a.client.name ~= "rustanalyzer" and b.client.name == "rustanalyzer" then
return false
end
-- Sort by action kind alphabetically local a_kind = a.action.kind or "" local b_kind = b.action.kind or "" return akind < bkind end })
Per-Call Sorting
Override the sort order for a specific invocation:
require("tiny-code-action").code_action({
sort = function(a, b)
-- Prioritize "Disable" actions
local aisdisable = string.match(a.action.title, "Disable") ~= nil
local bisdisable = string.match(b.action.title, "Disable") ~= nil
if aisdisable and not bisdisable then return true elseif not aisdisable and bisdisable then return false end
return false end, })
Example: Prioritize Specific Action Types
-- 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 apriority = getpriority(a.action.kind) local bpriority = getpriority(b.action.kind)
return apriority < bpriority 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.
FAQ:
- 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,
})