doc/lib.nvim-selection.txt — rendered from the plugin's own vimdoc
*lib.nvim-selection.txt* Reselect a Visual range after a mapping mutates it lib.nvim.selection *lib.nvim-selection* Neovim drops the Visual selection the instant a mapped function returns, which forces every "act on the selection, then keep it selected" mapping to hand-roll the same feedkeys dance.keep_lines/keep_charsdo that dance once: capture the current selection's extent, run the caller's mutation, then restore an equivalent selection over the (rewritten) same rows or same byte-column span.
CONTENTS
1. Design ................................... |lib.nvim-selection-design| 2. Usage ..................................... |lib.nvim-selection-usage| 3. Functions .............................. |lib.nvim-selection-functions| lines ................................. |lib.nvim-selection-lines| reselect_lines ............... |lib.nvim-selection-reselect_lines| keep_lines ....................... |lib.nvim-selection-keep_lines| chars ................................. |lib.nvim-selection-chars| reselect_chars ............... |lib.nvim-selection-reselect_chars| keep_chars ....................... |lib.nvim-selection-keep_chars| chars_multiline ............ |lib.nvim-selection-chars_multiline| reselect_chars_multiline |lib.nvim-selection-reselect_chars_multiline| keep_chars_multiline |lib.nvim-selection-keep_chars_multiline|
1. DESIGN
Two shapes are supported, matching the two patterns real callers need:
lines a linewise (V) row range. For actions that rewrite whole lines
in place but never add or remove any (bullet/checkbox toggles,
sort/reverse/rotate, indent, ...).
chars a same-line charwise (v) byte-column range. For actions that
rewrite part of a single line without changing its total
length (swap-with-neighbor, inline transforms, ...).
chars_multiline
a charwise (v) selection spanning more than one line.
Complements chars, which only covers the same-line case.
gv is deliberately not used: the '< / '> marks it reads are only set once
Visual mode actually ENDS, so calling gv from inside a mapping that is
still conceptually "in" Visual mode reselects the PREVIOUS selection, not
the current one. Reselection instead uses an explicit <Esc> followed by
pure normal-mode motions — never a : command: entering Visual mode
auto-prefixes a typed : with '<,'>, which would corrupt any :call ...
sequence queued mid-selection.
2. USAGE
A visual-mode ("x") keymap that toggles something on every selected line and
should leave the same lines selected afterwards:
local selection = require("lib.nvim.selection")
vim.keymap.set("x", "<A-->", function()
local bufnr = vim.api.nvim_get_current_buf()
selection.keep_lines(function(srow, erow)
my_toggle_range(bufnr, srow, erow)
end)
end)
A visual-mode keymap that swaps a same-line selection with its right
neighbor char, falling back to gv when the selection isn't same-line
charwise:
vim.keymap.set("x", "<leader><Right>", function()
local _, applicable = selection.keep_chars(function(row, scol, ecol)
my_swap_right(row, scol, ecol)
end)
if not applicable then
vim.api.nvim_feedkeys(vim.keycode("gv"), "n", false)
end
end)
3. FUNCTIONS
lines()
0-based inclusivesrow, erowrow range of the active Visual selection (any submode). Readsline("v")/line("."), which stay live during Visual mode — unlike the '< / '> marksgvrelies on.
reselect_lines({srow}, {erow})
Restore a linewise (V) selection over [srow, erow] (0-based inclusive), queued to run once the current mapping returns.
keep_lines({fn})
Capture the current row range, runfn(srow, erow), then reselect the same rows linewise. Returnsfn's return value. Use for actions that rewrite line CONTENTS in place without changing the line count.
chars()
0-based row, scol, ecol (inclusive byte columns) of the active selection,
if (and only if) it is charwise and confined to one line; otherwise nil.
reselect_chars({row}, {scol}, {ecol})
Restore a charwise (v) selection spanning byte columns [scol, ecol] onrow. Byte columns are converted to character offsets first, so multibyte text still lands on the right boundary (lmotions move per character, not per byte).
keep_chars({fn})
Capture the current same-line charwise selection, runfn(row, scol, ecol), then reselect it. Returnsret, applicable;applicableis false (andfnis not called) when the current selection is not a same-line charwise selection — fall back to your own handling (e.g.gv) in that case.
chars_multiline()
0-basedsrow, scol, erow, ecol(inclusive byte columns) of the active selection, if (and only if) it is charwise and spans MORE THAN ONE line; otherwise nil. Complementschars(), which only covers the same-line case.srow/scolis always the earlier point in the buffer, regardless of which end the cursor is on.
reselect_chars_multiline({srow}, {scol}, {erow}, {ecol})
Restore a charwise (v) selection running from byte columnscolonsrowto byte columnecolonerow(0-based inclusive), covering every full line in between. Byte columns are converted to character offsets first, same asreselect_chars.
keep_chars_multiline({fn})
Capture the current multi-line charwise selection, runfn(srow, scol, erow, ecol), then reselect it. Returnsret, applicable;applicableis false (andfnis not called) when the current selection is not multi-line charwise. Only correct whenfndoesn't shift where the first/last line's selected span starts or ends — a mutation that changes that width (e.g.9.->10.) should reselect explicit new bounds itself viareselect_chars_multilineinstead of this wrapper.