Neovim surround examples

The final ) in saiw) is a mini.surround identifier for a parentheses pair: insert ( on the left and ) on the right. The final x in saiwx uses the plugin's literal fallback: insert x on both sides.

Both characters are interpreted by mini.surround in that position. They are not ordinary Vim commands such as x for deleting a character.

sa | iw | )
add | inner word | parentheses pair

sa | iw | x
add | inner word | literal x on both sides

All examples use this config's default mini.surround mappings. Start in normal mode with the cursor on the h in hello, unless stated otherwise. Spaces and | separators in breakdowns are explanatory: do not type them. No leader key is involved.

Which part comes from where?

LayerExamplesJobDocumentation
Vim's visual modesv, VStart selecting characters or whole linesVisual mode
Vim's text objectsiw, i(, a(Describe a region of textText objects
Vim's motions$, {, 3lDescribe movement from the cursorMotions and operators
mini.aiEnhances objects such as i( and a(Improves how regions are found; configured separately from mini.surroundmini.ai
mini.surroundsa, sd, srAdd, delete, or replace wrappersSurround actions

For example, va( is Vim's visual selection plus the "around parentheses" text object, enhanced here by mini.ai. It does not require mini.surround.

The selection language is reusable: viw selects an inner word, diw deletes one, yiw copies one, and saiw) adds parentheses around one.

Step 1: choose the action

KeysMeaningNext inputFinal input
saSurround addWhich text?What new wrapper?
sdSurround deleteWhich existing wrapper?Nothing else
srSurround replaceWhich existing wrapper?What replacement wrapper?

These actions come from mini.surround. Add needs a region because its wrapper does not exist yet. Delete and replace find an existing enclosing wrapper around the cursor.

Step 2 for add: choose the text

These selectors are called text objects:

KeysMeaningWith the cursor on hello in (hello world)Documentation
iwInner wordhelloVim: iw
awWord plus adjacent whitespacehello Vim: aw
i(Inside parentheseshello worldVim: i(
a(Parentheses and their contents(hello world)Vim: a(
i{ / a{Inside / including bracesSame distinction for {…}Vim text objects
i" / a"Inside / including double quotesSame distinction for "…"Vim text objects
ipInner paragraphThe paragraph containing the cursorVim: ip

A motion is another way to supply text to an action:

MotionText covered after an actionDocumentation
$From cursor to end of lineVim motions
{Backward to a paragraph boundaryVim paragraphs
3lThree characters moving rightVim: l

Bare { and a{ are different: { moves backward by paragraph; a{ identifies an existing brace-delimited region. Similarly, bare ( is a backward-sentence motion; it does not select parentheses.

Step 3 for add: choose the new wrapper

This table describes mini.surround's interpretation, not Vim's normal-mode meaning for the same characters.

Final identifierInterpretationLeft insertedRight insertedSelected hello becomes
)Special: parentheses pair()(hello)
(Special: padded parentheses( )( hello )
]Special: brackets pair[][hello]
[Special: padded brackets[ ][ hello ]
}Special: braces pair{}{hello}
{Special: padded braces{ }{ hello }
"Literal character on both sides"""hello"
*Literal character on both sides***hello*
xLiteral character on both sidesxxxhellox
iLiteral character on both sidesiiihelloi
?Special: prompt for both stringsFirst answerSecond answerWhatever you specify

Source: mini.surround's built-in surroundings. There are other special identifiers, including f for function calls and t for tags. Letters are not universally literal: x and i specifically use the literal fallback in this default configuration.

Add: full before/after table

Full commandAction and targetFinal character's meaningBefore → after
saiw)sa + iw: add around inner wordPair ()hello → (hello)
saiw(sa + iw: add around inner wordPadded parentheseshello → ( hello )
saiw}sa + iw: add around inner wordPair {}hello → {hello}
saiw{sa + iw: add around inner wordPadded braceshello → { hello }
saiw]sa + iw: add around inner wordPair []hello → [hello]
saiw"sa + iw: add around inner wordLiteral " on each sidehello → "hello"
saiw*sa + iw: add around inner wordLiteral * on each sidehello → *hello*
saiwxsa + iw: add around inner wordLiteral x on each sidehello → xhellox
saa(isa + a(: including parenthesesLiteral i on each side(hello) → i(hello)i
sai(isa + i(: inside parenthesesLiteral i on each side(hello) → (ihelloi)
saa(}sa + a(: including parenthesesPair {}(hello) → {(hello)}
sai(}sa + i(: inside parenthesesPair {}(hello) → ({hello})
saa(*sa + a(: including parenthesesLiteral * on each side(hello) → *(hello)*
sai(*sa + i(: inside parenthesesLiteral * on each side(hello) → (*hello*)

There is no separate saa command. In saa(i, one a finishes sa, and the next starts a(. The final i is a literal wrapper, not an insert-mode command:

sa | a( | i
add | select including parentheses | insert literal i on both sides

sa | i( | i
add | select inside parentheses | insert literal i on both sides

For the note (capital is across all files), saa(i produces i(capital is across all files)i. sa(i instead uses the backward-sentence motion and can reach back into preceding prose.

Delete and replace: find an existing wrapper

After sd or sr, ) means find an enclosing parentheses pair, not insert one. A wrapper identifier describes the pair; the action determines what happens to it. The documentation calls this an input surrounding, while the new wrapper is an output surrounding.

Source: mini.surround input and output surroundings.

CommandBreakdownBefore → after
sd)Delete the existing () pair(hello) → hello
sd"Delete the existing quote pair"hello" → hello
sdxDelete existing literal x…x wrappingxhellox → hello
sr)"Replace old () pair with literal quotes(hello) → "hello"
sr)]Replace old () pair with new [] pair(hello) → [hello]
sr")Replace old quotes with new () pair"hello" → (hello)
sr)iReplace old () pair with literal letters(hello) → ihelloi

There is no iw or a( step after sd: the enclosing wrapper already defines the boundaries. Only its two ends are removed; its contents remain.

Visual selection: choose the text first

v and V come from Vim's Visual mode. Once text is selected, sa needs only the new wrapper. Pause after the selection step to inspect the highlight.

Full commandVim selection firstmini.surround partBefore → after
vsa}v: current character onlysa}: add {}hello → {h}ello
viwsa}v + iw: inner wordsa}: add {}hello → {hello}
vi(sa}v + i(: inside parenthesessa}: add {}(hello) → ({hello})
va(sa}v + a(: including parenthesessa}: add {}(hello) → {(hello)}
VsaxV: whole linesax: add literal x'shello world → xhello worldx

Typing just v initially selects one character. That is why vsa{ wraps just that character (with padded braces), rather than finding a word or parentheses.

Why not saVx for the whole line?

The meaning of V depends on the mode:

  • Before sa: V starts whole-line visual selection. Vsax works.
  • After sa: Vim is waiting for a motion. V forces a following motion to be linewise; it does not finish the selection by itself. saVx is incomplete as a surround operation because x is not that motion.

See Vim's forced-motion rules. An alternative without visual selection is sa_x: _ supplies a linewise motion over the current line.

What does sa{x select?

sa{x means sa + backward-paragraph motion { + literal wrapper x. It wraps the text traversed by the motion, with the starting cursor position providing one boundary. It does not search for an opening brace.

For actual brace-delimited text, use saa{x: {hello} becomes x{hello}x.

Multi-character wrappers

For more selected text, choose a larger text object, motion, or visual selection. For a wrapper containing more than one character, use ?.

Type saiw?, then answer the prompts:

Left surrounding:  BEGIN  [Enter]
Right surrounding: END    [Enter]

hello → BEGINhelloEND

For Markdown bold, enter ** at both prompts. Alternatively, 2saiw* repeats the wrapper twice and produces **hello**. The leading 2 repeats the wrapper; it does not select two words.

See mini.surround examples and counts.

Practice and further examples

Put (hello world) on a scratch line, with the cursor on hello:

  1. Type va(, inspect the highlight, then sa}: {(hello world)}.
  2. Undo with u, then type sd): hello world.
  3. Undo with u, then type sr)]: [hello world].

This compares adding another layer, removing a layer, and replacing a layer on the same text.

Tutorials for other surround plugins often use ys, ds, and cs. This config uses mini.surround's sa, sd, and sr; the example commands above match those bindings.