This plugin is a predecessor to much more capable stitch.nvim. Please check that out instead
Edit the current quickfix or location list in a scratch buffer. Write the buffer to push the updates back into the original files and the list.
- Opens a quickfix or location list in an editable buffer (
[csub],filetype=csub) - Works on whichever list (quickfix or loclist) you invoke
:Csubfrom - Shows a plain
path:line:collabel beside each entry in the[csub]buffer (virtual text, aligned to one gutter) - Marks edited lines with a
~sign in the sign column so changes are visible at a glance - Applies changes to the underlying files and the list on write
- Run
:Csubto switch back and forth between the list window and the csub buffer - Use a range —
:'<,'>Csubor:3,7Csub— to scope editing to a slice of the list; entries outside the range round-trip unchanged - Supports different modes based on quickfix source (text replacement, buffer management)
Example: Find and Replace
- Use as a replacement for
:cfdoand:cdo(Find and replace across multiple files)- Unlike
:cfdoor:cdo, you can make arbitrary changes and see the results before applying them.
- Unlike
- Edit the
Csubbuffer as if you would any other buffer and all changes will be applied when you write the buffer. - Saving(
:w) the Csub buffer will switch back to the updated quickfix list.
Example: Buffer Management
- Configure csub to close buffers when using a buffer picker that populates the quickfix list
- Delete lines in the csub buffer to close the corresponding buffers
Example: Location lists
:lvimgrep /pattern/ %, open the loclist with:lopen, then:Csubfrom inside the loclist window.- Edits apply to the owner window's location list; the quickfix list is left untouched.
Example: Scoped editing
:5,10Csubopens only entries 5–10 in the csub buffer. The other entries stay put.- Visual-line select rows in the list window and run
:Csub— same effect with the visual range.
- Neovim 0.12 or higher
- Verified with Neovim 0.12.1
The plugin works out of the box — no setup() call required. The :Csub command and the highlight groups are registered automatically at startup by plugin/csub.lua; the metadata column appears inside the [csub] buffer when you run :Csub (csub does not modify the real quickfix/loclist window). By default every list opens in "replace" mode; call setup() only to configure per-title handlers or change the fallback.
vim.pack example (Neovim 0.12+):
vim.pack.add({"https://github.com/anoopkcn/csub.nvim"})Lazy.nvim example (zero-config):
{ "https://github.com/anoopkcn/csub.nvim" }Lazy.nvim example (deferred until the first :Csub — recommended for true lazy loading):
{ "https://github.com/anoopkcn/csub.nvim", cmd = { "Csub" } }csub does nothing until :Csub is invoked, so loading on the command is enough — there is no startup behavior that needs to run earlier.
Packer.nvim example:
use({ "https://github.com/anoopkcn/csub.nvim" })require("csub").setup(opts) is optional. Out of the box, every list opens in "replace" mode. Call setup() to register per-title handlers (so buffer pickers get "buffers" mode, diagnostics get disabled, etc.) or to change the fallback used for unmatched lists.
require("csub").setup({
-- Per-list-title handlers. The first match wins; non-matching lists
-- fall back to default_mode.
handlers = {
{ match = "qfbuffers", mode = "buffers" }, -- buffer picker → close on delete
{ match = "vimgrep", mode = "replace" }, -- :vimgrep results
{ match = "Diagnostics", mode = nil }, -- explicitly disable for diagnostics
},
-- Fallback for lists that no handler matched. Default: "replace".
-- Set to nil to disable csub on every unmatched list.
default_mode = "replace",
})With lazy.nvim:
{
"https://github.com/anoopkcn/csub.nvim",
config = function()
require("csub").setup({
handlers = {
{ match = "qfbuffers", mode = "buffers" },
},
})
end,
}Each handler is a { match, mode } table:
match: substring to look for in the list's title (plain text, case-sensitive). Get the title withvim.fn.getqflist({ title = 1 }).title(orgetloclist(0, ...)for a loclist) after running the command that built the list.mode: one of"replace","buffers", ornil.nilexplicitly disables csub for that title (useful for diagnostic lists you never want to edit).
Handlers are checked in order; the first match wins. The same handler list is consulted for both quickfix and location lists, so a single handler entry covers :grep/:lgrep, :vimgrep/:lvimgrep, etc. when their titles share a substring.
If no handler matches, csub falls back to default_mode (default: "replace").
| Mode | Delete line | Edit text | Add line | Use case |
|---|---|---|---|---|
"replace" |
Remove from list | Replace line in file | Rejected | Grep results, compiler errors |
"buffers" |
Close buffer (:bdelete) |
Ignored | Rejected | Buffer pickers |
nil |
- | - | - | Disable csub for this list |
Notes:
- In
"buffers"mode, use:w!to force-close modified buffers - The mode is detected from the list title when
:Csubis invoked - The same handler list is checked for both quickfix and location lists
- Populate a quickfix or location list (e.g.
:make,:grep,:lvimgrep, diagnostics, a file picker). - Run
:Csubfrom inside the list window (quickfix or loclist) to open it for editing in place.- Add a range to scope the edit:
:5,10Csubor:'<,'>Csubopens only those entries; the rest round-trip unchanged on write.
- Add a range to scope the edit:
- Edit the lines directly. Edited rows are marked with a
~sign in the sign column. Deleting a line removes that entry. Adding lines is rejected. - Run
:Csubagain at any point to toggle back to the list window without discarding unsaved edits. - Write (
:w) to apply changes back to the underlying files and the list; the view jumps back to the list window.
NOTE: closing the csub buffer without writing discards all changes.
Useful keymap:
vim.keymap.set("n", "<leader>s", "<cmd>Csub<cr>", { desc = "Csub the current list" })
vim.keymap.set("x", "<leader>s", ":Csub<cr>", { desc = "Csub the selected entries" })If you wan't to only activate the Csub command when the quickfix list is open and focused, then use the following autocommand:
vim.api.nvim_create_autocmd("FileType", {
pattern = { "qf", "csub" },
group = vim.api.nvim_create_augroup("CsubQfMap", { clear = true }),
callback = function(args)
map("n", "<leader>s", "<CMD>Csub<CR>",
{ buffer = args.buf, silent = true, desc = "Substitute in quickfix (Csub)" })
end,
})| Group | Default link | Purpose |
|---|---|---|
CsubMeta |
Comment |
The path:line:col metadata label |
CsubDirtyLine |
DiffChange |
~ sign on edited lines |
All are default = true, so user overrides take precedence.
- In the
[csub]buffer, metadata is virtual text and line wrapping is disabled locally. The real quickfix/loclist window is left untouched. - Dirty-line
~signs appear in the sign column for every row whose text differs from its originating entry. The signcolumn auto-shows. - If the target line changed since the list was built and differs from your edit, the plug-in reports an error and leaves that entry untouched.
- Running
:Csubwith a different range (or no range vs. ranged) on the same list while you have unsaved edits is treated as a context switch and warns; finish or discard the existing buffer first.
Run :help csub after installing (requires :helptags doc).