Skip to content

Features

kylechui edited this page Jul 19, 2022 · 2 revisions

Table of Contents


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.

Cursor Anchoring

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.

Example 1

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 the a in example.

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.

Forward/Reverse Searching

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

Example 1

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.

Example 2

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.

Aliasing

Characters can be used to represent other characters via aliasing. There are two types of aliases:

  • Single character aliases
  • Tabular aliases

Single Character 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 and ysi)} are the same
  • dsB and ds} are the same
  • csB" and cs}" 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<")

Tabular Aliases

With tabular aliases, each character corresponds to a table of characters it could possibly represent for modification.

Example 1

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 around another 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.


Example 2

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

Function-evaluated Surrounds

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.

Example 1

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.

Example 2

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.

Dot-repeating

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

Example 1

Consider the buffer:

here
are
some
words

If the cursor is on the h:

  • ysiwffunc<CR> surrounds here in a function call, yielding func(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)

Visual Surrounds

  • TODO: Show the difference between charwise/linewise/blockwise
Clone this wiki locally