*nvim-surround.txt* A plugin for adding/deleting/changing delimiter pairs. Author: Kyle Chui License: MIT License ================================================================================ CONTENTS *nvim-surround.contents* 1. Introduction ......................................... |nvim-surround| 2. Usage .......................................... |nvim-surround.usage| 2.1. The Basics .............................. |nvim-surround.basics| 2.2. More Mappings .................... |nvim-surround.more_mappings| 2.3. Default Pairs .................... |nvim-surround.default_pairs| 2.4. Aliasing .............................. |nvim-surround.aliasing| 2.5. Jumps .................................... |nvim-surround.jumps| 3. Configuration .......................... |nvim-surround.configuration| 3.1. Keymaps ......................... |nvim-surround.config.keymaps| 3.2. Surrounds ..................... |nvim-surround.config.surrounds| 3.3. Helper Functions ................ |nvim-surround.config.helpers| 3.4. Aliases ......................... |nvim-surround.config.aliases| 3.5. Highlights .................... |nvim-surround.config.highlight| 3.6. Cursor ...................... |nvim-surround.config.move_cursor| 3.7. Indentation ................ |nvim-surround.config.indent_lines| 4. Resources .................................. |nvim-surround.resources| ================================================================================ 1. Introduction *nvim-surround.introduction* |nvim-surround| is a plugin for efficiently manipulating surrounding left/right delimiter pairs, e.g. parentheses, quotes, HTML tags, and more. This plugin provides keymaps for "adding" (through insert/normal/visual mode) and "modifying" (deleting/changing) delimiter pairs. ================================================================================ 2. Usage *nvim-surround.usage* |nvim-surround| has three main operations: * Adding a delimiter pair to the buffer * Deleting a delimiter pair from the buffer * Changing a delimiter pair to a different pair -------------------------------------------------------------------------------- 2.1. The Basics *nvim-surround.basics* The primary way of adding a new pair to the buffer is via the normal-mode *ys* operator, which stands for "you surround". It can be used via `ys{motion}{char}`, which surrounds a given |motion| or |text-object| with a delimiter pair associated with {char}. For example, `ysa")` means "you surround around quotes with parentheses". In all of the following examples, the `*` denotes the cursor position: Old text Command New text ~ local str = H*ello ysiw" local str = "Hello" require"nvim-surroun*d" ysa") require("nvim-surround") char c = *x; ysl' char c = 'x'; int a[] = *32; yst;} int a[] = {32}; Furthermore, there are insert-mode *s* and visual-mode *S* mappings, that add the delimiter pair around the cursor and visual selection, respectively. In all of the following examples, we will use `|` to demarcate the start and end of a visual selection: Old text Command New text ~ local str = * s" local str = "*" local tab = * s} local str = {*} local str = |some text| S' local str = 'some text' |div id="test"| S>
To delete a delimiter pair, use the *ds* operator, which stands for "delete surround". It is used via `ds[char]`, deleting the surrounding pair associated with {char}. For example, `ds)` means "delete surrounding parentheses". Old text Command New text ~ local x = ({ *32 }) ds) local x = { 32 } See ':h h*elp' ds' See :h help local str = [[Hell*o]] ds] local str = [Hello] To change a delimiter pair, use the *cs* operator, which stands for "change surround". It is used via `cs{target}{replacement}`, changing the surrounding pair associated with {target} to a pair associated with {replacement}. For example, `cs'"` means "change the surrounding single quotes to double quotes". Old text Command New text ~ '*some string' cs'" "some string" use<*"hello"> cs>) use("hello") `some text*` cs`} {some text} -------------------------------------------------------------------------------- 2.2. More Mappings *nvim-surround.more_mappings* For each of the additive mappings, there are a few variants: The *yss* operator is a special case for |ys|, and operates on the current line via `yss[char]`, ignoring leading and trailing whitespace. The *yS* and *ySS* operators are analogous to |ys| and |yss|, but add the delimiter pair on new lines. Old text Command New text ~ hel*lo world yss" "hello world" some content ySStp

some content

The *S* insert-mode operator is analogous to |s|, but adds the delimiter pair on new lines. Old text Command New text ~ func_name* S) func( * ) The *cS* normal-mode operator is analogous to |cs|, but adds the replacement delimiter pair on new lines. Old text Command New text ~ func(a*rgs) cS)) func( args ) For the visual-mode `S` map, the type of visual mode determines how the delimiter pair is added * |charwise-visual|: Adds the pair around the selection * |linewise-visual|: Adds the pair around the selection, on new lines * |blockwise-visual|: Adds the pair around the visual block, once per line Note: For surrounds that add a delimiter pair on new lines, |nvim-surround| re-indents the text inside the pair (which may be dependent on filetype!). For example, the contents of an HTML tag pair may not be indented in a text file, but might be in an HTML file. See |nvim-surround.config.indent_lines| for details. -------------------------------------------------------------------------------- 2.3. Default Pairs *nvim-surround.default_pairs* We've looked at a few of the built-in surround actions, but here we'll go a bit more in-depth. For all open/close pairs, e.g. `()`, adding a surround using the closing character will surround the selection with just the pair itself, whereas using the opening character will add a whitespace gap between the selection and delimiter pair. Old text Command New text ~ *sample_text ysiw} {sample_text} *sample_text ysiw{ { sample_text } This applies for `()`, `{}`, `[]`, and `<>` pairs. When deleting or changing open/close pairs, the closing character always leaves whitespace intact, while the opening character will try to remove a whitespace character (if it exists). Old text Command New text ~ {( sa*mple_text )} ds( {sample_text} {(sa*mple_text)} ds( {sample_text} {(sa*mple_text )} ds( {sample_text} {( sa*mple_text )} ds) { sample_text } Note: Deleting with the opening character is possible when there is only whitespace on one side, or even when it is missing altogether. Changing surrounding pairs exhibits similar behaviors. Old text Command New text ~ (sa*mple_text) cs)} {sample_text} (sa*mple_text) cs(} {sample_text} (sa*mple_text) cs(( ( sample_text ) ( sa*mple_text ) cs() (sample_text) There are also a handful of built-in surrounds that are more complex. First up we have HTML tags, which are triggered with either `t` or `T`. The user is then queried for the tag type/attributes, after which they hit to create the surrounding pair. Old text Command New text ~ div cont*ents ysstdiv
div contents
h1 *contents yssth1 id="x"

h1 contents

d*iv contents
dst div contents

t*ext

cstdiv

text*

csTdiv
text
The keys `t` and `T` are identical, except for the fact that `cst` will only change the surrounding tag's type (leaving the attributes alone), while `csT` will change the entirety of the surrounding tag's contents. Note: When changing surrounding tags via `cst` or `csT`, a second replacement character is unnecessary. See |nvim-surround.config.surrounds|. Next are function calls, denoted by the key `f`. By default, they match a Lua pattern consisting of some non-whitespace characters, followed by a balanced pair of parentheses. Old text Command New text ~ arg*s ysiwffunc func(args) f*unc_name(a, b, x) dsf a, b, x f*unc_name(a, b, x) csfnew_name new_name(a, b, x) Note: Just like with HTML tags, a replacement character is unnecessary when changing (via `csf`). Finally, we have a "insert" key, denoted by `i`. It queries the user for what should go on the left and right hand sides of a selection, and adds the delimiter pair to the buffer. Currently, deletions and changes are not supported. Old text Command New text ~ inner text yssi/\ /inner text\ -------------------------------------------------------------------------------- 2.4. Aliasing *nvim-surround.aliasing* In |nvim-surround|, one can alias a character to "stand in for" one or more other characters. If the alias is just a single character, then they can be used interchangeably anywhere. For example, the default configuration aliases `b` to represent `)` and `r` to represent `]`. Old text Command New text ~ sample* text yssb (sample text) [more stuff] dsr more stuff use["nvim*-surround"] csrb use("nvim-surround") Note: While `ysabB` is a valid surround action, `ysarB` is not, since `ar` is not a valid Vim motion. This can be side-stepped by creating the following operator-mode maps: >lua vim.keymap.set("o", "ir", "i[") vim.keymap.set("o", "ar", "a[") vim.keymap.set("o", "ia", "i<") vim.keymap.set("o", "aa", "a<") < The other type of alias is a "tabular alias", where each alias character represents some set of characters. Tabular aliases are only used when modifying existing delimiter pairs. For example, the default configuration uses `q` as an alias for the set { ', ", ` }. Modifications with tabular aliases modify the nearest such pair. Old text Command New text ~ "Nested '*quotes'" dsq "Nested quotes" "Nes*ted 'quotes'" dsq Nested 'quotes' "Nes*ted 'quotes'" csqb (Nested 'quotes') Note: Tabular aliases cannot be used to add surrounding pairs, e.g. `ysa)q` is invalid, since it's ambiguous which pair should be added. -------------------------------------------------------------------------------- 2.5. Jumps *nvim-surround.jumps* If the cursor is not located inside a surrounding pair, it can jump to the "nearest" pair. When jumping, |nvim-surround| always prefers * pairs that surround the cursor, before * pairs that occur after the cursor, before * pairs that occur before the cursor. Old text Command New text ~ "hello"* 'world' dsq "hello" world "hello" world* csqB {hello} world *some "'nested' quotes" dsq some 'nested' quotes "'nested' quotes" t*ext dsq 'nested' quotes text Note: |nvim-surround| jumps across lines to find the next/previous surrounding pair, except for quote characters. This is done to match existing behavior for the `a"` text-object. ================================================================================ 3. Configuration *nvim-surround.configuration* To configure this plugin, call `require("nvim-surround").setup()` or `require("nvim-surround").buffer_setup()`. The former configures "global" options that are present in every buffer, while the latter configures buffer-local options that override the global options. This is particularly useful for setting up filetype-specific surrounds, i.e. by calling `require("nvim-surround").buffer_setup()` in a `ftplugin/` file. Both functions take a table as an argument, full of configuration options. >lua require("nvim-surround").setup({ keymaps = -- Defines plugin keymaps surrounds = -- Defines surround keys and behavior aliases = -- Defines aliases highlight = -- Defines highlight behavior move_cursor = -- Defines cursor behavior indent_lines = -- Defines line indentation behavior }) < The main setup and buffer setup functions are strictly additive, so only the keys that are to be modified need to be provided. To disable any default behavior, set the corresponding value to `false`. For example, to disable the `(` surround, call: >lua require("nvim-surround").setup({ surrounds = { ["("] = false, } }) < -------------------------------------------------------------------------------- 3.1. Keymaps *nvim-surround.config.keymaps* The `keymaps` table defines the keymaps used to perform surround actions. The general rule is that if the key ends in "_line", the delimiter pair is added on new lines. If the key ends in "_cur", the surround is performed around the current line. The default configuration is as follows: >lua keymaps = { insert = "s", insert_line = "S", normal = "ys", normal_cur = "yss", normal_line = "yS", normal_cur_line = "ySS", visual = "S", visual_line = "gS", delete = "ds", change = "cs", change_line = "cS", }, < Additionally, there is a corresponding set of || mappings that one may use to set up their own custom keymaps: >vim (nvim-surround-insert) (nvim-surround-insert-line) (nvim-surround-normal) (nvim-surround-normal-cur) (nvim-surround-normal-line) (nvim-surround-normal-cur-line) (nvim-surround-visual) (nvim-surround-visual-line) (nvim-surround-delete) (nvim-surround-change) (nvim-surround-change-line) < -------------------------------------------------------------------------------- 3.2. Surrounds *nvim-surround.config.surrounds* The `surrounds` table associates each key with a "surround", which is a table containing the following keys: *nvim-surround.config.add* add: ~ A function that returns the delimiter pair to be added to the buffer. For example, consider the function for adding parentheses: >lua [")"] = { add = function() return { { "(" }, { ")" } } end, } < The function must return pairs of multi-line strings. Anything can be called in the functions; consider the default for `f`: >lua add = function() local config = require("nvim-surround.config") local result = config.get_input("Enter the function name: ") if result then return { { result .. "(" }, { ")" } } end end, < The user is queried for the function name, and that is used to create the surrounding pair at runtime. For "static" delimiter pairs that are not evaluated at runtime, the function can be omitted and the `add` key can directly take on the value of the delimiter pair. Furthermore, tables with just one string can be replaced by the string itself. For example, the following configuration also adds parentheses: >lua [")"] = { add = { "(", ")" }, } < *nvim-surround.config.find* find: ~ A function that returns the "parent selection" for a surrounding pair. The selection returned should contain the surrounding pair and little else, as it is used by the `delete` and `change` keys to determine what to modify. For example, HTML tags are found via the `at` text-object: >lua ["t"] = { find = function() local config = require("nvim-surround.config") return config.get_selection({ motion = "at" }) end, } < See |nvim-surround.config.get_selection()| for more information. For convenience, this key can also take a string as a value, which is then interpreted as a Lua pattern that represents the selection. *nvim-surround.config.delete* delete: ~ A function that returns the pair of selections to remove from the buffer when deleting the surrounding pair. For convenience, this key can also take a string as a value, which is then interpreted as a Lua pattern whose match groups represent the left/right delimiter pair. For example, function calls are deleted via >lua delete = "^(.-%()().-(%))()$", < The Lua pattern matches the function name and opening parenthesis for the left delimiter, and the closing parenthesis for the right delimiter. See |nvim-surround.config.get_selections()| for more information. *nvim-surround.config.change* change: ~ A table of two keys: `target` and `replacement`. target: ~ A function that returns the pair of selections to be replaced in the buffer when changing the surrounding pair. Similar to the `delete` key. For simplicity, this key can also take a string as a value, which is then interpreted as a Lua pattern whose match groups represent the left/right delimiter pair. For example, the default `T` surround's `change.target` key: >lua target = "^<([^>]*)().-([^%/]*)()>$", < The Lua pattern matches the contents of the opening tag (including attributes) for the left delimiter, and the contents of the closing tag for the right delimiter. See |nvim-surround.config.get_selections()| for more information. replacement: ~ A function that returns the surrounding pair to replace the target selections. This is kept separate from the `add` key because it is often the case that replacing a delimiter pair is slightly different from adding a new one. For example, when changing HTML tags you only want the inner contents to be changed, without including the angle brackets. Note: When the `change.replacement` key is provided, the user is not queried for a replacement character; the results of `change.replacement` are directly used as the replacement pair. For example, when changing HTML tag types, only `cst` is needed, instead of `cstt`. There is a special key in the `surrounds` table, `invalid_key_behavior`, that defines how |nvim-surround| behaves when a given key in the delimiter table is omitted. It takes the same form as all other surrounds. The default behavior is to treat the character literally, and add/modify the surrounding pair: >lua invalid_key_behavior = { add = function(char) if not char or char:find("%c") then return nil end return { { char }, { char } } end, find = function(char) if not char or char:find("%c") then return nil end return M.get_selection({ pattern = vim.pesc(char) .. ".-" .. vim.pesc(char), }) end, delete = function(char) if not char or char:find("%c") then return nil end return M.get_selections({ char = char, pattern = "^(.)().-(.)()$", }) end, }, < For example, `ysiw*` would surround a word with asterisks, and `ds*` would delete those same asterisks, despite the `*` surround not being configured by default. Note that control characters are valid inputs, with the exception of , which cancels the input (same as ). -------------------------------------------------------------------------------- 3.3. Helper Functions *nvim-surround.config.helpers* |nvim-surround| exposes a few helper functions to assist in finding selection(s). For the sake of brevity we assume >lua local config = require("nvim-surround.config") < *nvim-surround.config.get_input()* config.get_input({prompt}) Queries the user for input using {prompt}. Properly handles keyboard interrupts (i.e. ) and for cancelling the input. *nvim-surround.config.get_selection()* config.get_selection({args}) Retrieves a selection from the buffer using {args}. The {args} table currently takes one of two keys: motion: ~ A string that represents any Neovim motion. For example, the default `find` key for `(` is: >lua find = function() return config.get_selection({ motion = "a(" }) end, < pattern: ~ A Lua pattern to be found in the buffer. For example, the default `find` key for `invalid_key_behavior` is: >lua find = function(char) return config.get_selection({ pattern = vim.pesc(char) .. ".-" .. vim.pesc(char), }) end, < The Lua pattern escapes the provided character, and |nvim-surround| finds a selection that starts and ends with that character. node: ~ A Tree-sitter node type to be found in the buffer. For example, the default `find` key for `f` in Lua files is: >lua find = function() return config.get_selection({ node = "function_call" }) end, < *nvim-surround.config.get_selections()* config.get_selections({args}) Retrieves a pair of selections from the buffer using {args}. The {args} table takes a {char} key and {pattern} key: char: ~ The character associated with the current surround. pattern: ~ A Lua pattern that narrows down a parent selection to the left/right pair to be modified. It must contain four match groups: * The left selection to be modified * An empty capture group * The right selection to be modified * An empty capture group Note: The empty capture groups must come immediately after the left and right selections. For example, the default `delete` key for `t` is: >lua delete = function() return config.get_selections({ char = "t", pattern = "^(%b<>)().-(%b<>)()$", }) end, < The left selection to be deleted is a balanced pair of angle brackets `%b<>`, immediately followed by an empty capture group. The right selection is a second balanced pair of angle brackets `%b<>`, followed by an empty capture group. Another example is the `change.target` key for `t`: >lua target = function() return config.get_selections({ char = "t", pattern = "^<([^%s<>]*)().-([^/]*)()>$", }) end, < The Lua pattern matches just the HTML tag type in both the beginning and end. -------------------------------------------------------------------------------- 3.4. Aliases *nvim-surround.config.aliases* The aliases table maps characters to other characters, or lists of characters. Its behavior is described in |nvim-surround.aliasing|. The default configuration is >lua aliases = { ["a"] = ">", ["b"] = ")", ["B"] = "}", ["r"] = "]", ["q"] = { '"', "'", "`" }, ["s"] = { "}", "]", ")", ">", '"', "'", "`" }, }, < -------------------------------------------------------------------------------- 3.5. Highlights *nvim-surround.config.highlight* When adding a new delimiter pair to the buffer in normal mode, one can optionally highlight the selection to be surrounded. Similarly, when changing a surrounding pair, one can optionally highlight the pair to be replaced. The `highlight` table takes the following keys: duration: ~ The amount of time (in ms) to highlight the selection(s) before clearing them. If the value is zero, then the highlights persist until the surround action is completed. The relevant highlight group is `NvimSurroundHighlight`, which can be configured separately. The default highlight group used is `Visual`: >vim highlight default link NvimSurroundHighlight Visual < -------------------------------------------------------------------------------- 3.6. Cursor *nvim-surround.config.move_cursor* By default, when a surround action is performed, the cursor moves to the beginning of the action. Old text Command New text ~ some_t*ext ysiw[ *[ some_text ] another { sample *} ds{ another *sample (hello* world) csbB *{hello world} This behavior can be disabled by setting `move_cursor = false` in one of the setup functions. -------------------------------------------------------------------------------- 3.7. Indentation *nvim-surround.config.indent_lines* By default, when a surround action is performed, |nvim-surround| tries to indent things "appropriately", i.e. when it is enabled by a Vim option. The `indent_lines` key corresponds to a function with parameters {start} and {stop}, which represent line numbers (inclusive). This indentation can be disabled by setting `indent_lines = false` in one of the setup functions. By default, |nvim-surround| uses the following function for indentation: >lua indent_lines = function(start, stop) local b = vim.bo -- Only re-indent the selection if a formatter is set up already if start < stop and (b.equalprg ~= "" or b.indentexpr ~= "" or b.cindent or b.smartindent or b.lisp) then vim.cmd(string.format("silent normal! %dG=%dG", start, stop)) end end, < ================================================================================ 4. Resources *nvim-surround.resources* Here are a few links to visit to get help/inspiration: * Showcase: https://github.com/kylechui/nvim-surround/discussions/53 * A place where people show off their configurations and custom surrounds * Wiki: https://github.com/kylechui/nvim-surround/wiki * A repository of helpful information about |nvim-surround| vim:tw=78:ts=8:ft=help:norl:conceallevel=0: