-
-
Notifications
You must be signed in to change notification settings - Fork 63
Features
- Cursor Anchoring
- Forward/Reverse Searching
- Aliasing
- Function-evaluated Surrounds
- Dot-repeating
- Visual Surrounds
A few terms to take note of:
- "Add" refers to any form of adding a surround pair to the buffer, whether it be in insert/normal/visual mode.
- "Modify" refers to either deleting or changing a surround pair in the buffer.
- A "surround action" refers to any of adding/deleting/changing a surrounding pair.
Users of vim-surround
might be familiar with the idea that the cursor always
"jumps" to the beginning of a surround action. In nvim-surround
, this is
optional, and one may choose to "anchor" the cursor to its current row/column
by setting the move_cursor
option to false
.
Let the move_cursor
option be false
. Consider the buffer:
local str = "This is an example"
If the cursor resides on the x
and ds"
is typed:
- The quotes are deleted, and the cursor stays where it is. Note that since
characters before the cursor have been deleted, the cursor no longer resides
on the
x
, now sitting on thea
inexample
.
If the cursor's old location is now invalid in the buffer (due to too many deletions before the cursor), it will "jump" to the nearest valid location in the buffer.
Note: For the rest of this section, move_cursor
is assumed to be its
default value of "begin"
, and jumps to the beginning of any surround action.
Like vim-surround
, if the cursor comes before a surrounding pair, it can
"jump" to it for modification. However, as an extension of this behavior, in
nvim-surround
the cursor can "reverse-search" for surround pairs behind the
cursor for modification.
The rule is that nvim-surround
always chooses the nearest surrounding pair,
and prefers
- Current surrounding pairs before
- Forward-searching before
- Reverse-searching
Consider the buffer:
local nil_value = function()
vim.ui.input({
prompt = "Enter some text: ",
}, function(input)
end)
end
When the cursor is put on top of the p
in prompt
, and ds(..
is typed:
- The parentheses for the
vim.ui.input
call get deleted first (surrounding pair). - The parentheses around the word
input
get deleted next (forward-search). - The parentheses on the first line get deleted last (reverse-search).
Forward/reverse searches can span multiple lines, except in the case of quote
characters ('
, "
, `
). In this case, forward/reverse searches only
occur on the current line.
Consider the buffer:
local str = "This string has 'quotes' in it!" -- Comment
local str2 = "Wow! Another string"
If the cursor is on the comment and ds".
is typed:
- The double quotes on the first line get deleted via reverse-search.
- Nothing happens. This is because
nvim-surround
only forward-searches on the current line.
Characters can be used to represent other characters via aliasing. There are two types of aliases:
- Single character aliases
- Tabular aliases
With single character aliases, each character corresponds to exactly one other character, and they can effectively be used interchangeably for any surround action. For example:
-
ysi)B
andysi)}
are the same -
dsB
andds}
are the same -
csB"
andcs}"
are the same
Note: For normal mode additions, the pattern is ys[object][char]
. The
text-object passed in must be a defined operator-mode mapping. Thus, ysarb
is invalid even though r
is aliased to ]
, since ar
is not a builtin
text-object. This can be circumvented by defining your own maps, e.g.:
vim.keymap.set("o", "ir", "i[")
vim.keymap.set("o", "ar", "a[")
vim.keymap.set("o", "ia", "i<")
vim.keymap.set("o", "aa", "a<")
With tabular aliases, each character corresponds to a table of characters it could possibly represent for modification.
By default, q
is aliased to { ', ", ` }
. Consider the buffer:
local str = "This is a 'test string' with some quotes" -- `another quote`
If the cursor is on the comment then:
-
dsq
deletes the backticks aroundanother quote
, then -
csqb
changes the double quotes to parentheses, then -
dsq
deletes the single quotes
Note: For the same reason noted in the section on single character aliases,
this does not work for adding surrounds, e.g. ysaqb
is invalid.
By default, s
is aliased to mean any surrounding pair. Consider the buffer:
local str = "this (will {have} a [lot of]) <'surrounding'> `pairs`"
If the cursor sits on the l
in lot
, and dss......
is typed:
- The square brackets are deleted
- The parentheses are deleted
- The double quotes are deleted
- The curly braces are deleted
- The angle brackets are deleted
- The single quotes are deleted
- The backticks are deleted
With basic surrounds, we mapped characters to tables of delimiters (left and right). In this section we introduce function-evaluated surrounds, where each character maps to a function that returns a table of delimiters.
The most basic example is a function that returns a static pair of text, e.g.
require("nvim-surround").buffer_setup({
delimiters = {
pairs = {
["f"] = { "function_name(", ")" },
},
},
})
Now when you type ysiwf
, you will surround the word with function_name(
on
the left, and )
on the right. However, this isn't particularly useful, since
we rarely ever have the same function name over and over again. Let's switch
things up by introducing user input.
One major benefit of using a function to return a surrounding pair instead of just providing the surrounding pair itself is that it unlocks the potential for dynamically created surrounding pairs. For example:
require("nvim-surround").buffer_setup({
delimiters = {
pairs = {
["f"] = function()
return {
vim.fn.input({
prompt = "Enter the function name: "
}) .. "(",
")",
}
end,
},
},
})
Now the user is queried every single time for the function name, and this generalizes the purpose of this surround. For inspiration/more examples on how to use function-evaluated surrounds, check out the Surrounds Showcase.
Warning: Since we are using vim.fn.input()
, keyboard interrupts are not
handled properly. It can be wise to use a protected call to allow for <C-c>
as
an input. See the default
config
for details.
Warning: Despite the similar name, vim.ui.input()
is very different from
vim.fn.input()
, namely it is non-blocking and asynchronous. This causes
problems with nvim-surround
and will not work.
We've already been using dot-repeating a bit throughout this wiki, so here we'll clarify some design choices that were made.
Dot-repeating function-evaluated surrounds does not query the user for input, and the user input that was used the first time gets used again
Consider the buffer:
here
are
some
words
If the cursor is on the h
:
-
ysiwffunc<CR>
surroundshere
in a function call, yieldingfunc(here)
-
j.j.j.
surrounds the next three words with the same function call
The resulting buffer is:
func(here)
func(are)
func(some)
func(words)
- TODO: Show the difference between charwise/linewise/blockwise