The surround feature brings the power of nvim-surround (the Lua successor to tpope’s vim-surround) to Obsidian. It allows you to add, change, and delete surrounding delimiters like brackets, quotes, and HTML tags. This implementation includes native support for Markdown formatting marks, function wrapping, and custom surround pairs.

Info

For the best experience, disable Obsidian’s built-in Vim mode in Settings → Editor → Vim key bindings. This enables the plugin’s bundled fork mode, which provides full surround support and Neovim-correct behavior.

Keybindings

Surround

Add, change, or delete surrounding delimiters like brackets, quotes, and tags.

KeybindingDescription
ds{target}Delete surrounding (ds" on "hello"hello)
dstDelete surrounding tag
cs{target}{replacement}Change surrounding (cs"''hello')
cst{replacement}Change surrounding tag
ys{motion}{replacement}Add surround (ysiw) on hello(hello))
ys{motion}<tag>Surround with HTML tag (ysiw<em><em>hello</em>)
ysiwf + name + EnterSurround with function call (print(hello))
ysiwF + name + EnterSurround with spaced function call (print( hello ))
dsfDelete surrounding function call (print(hello)hello)
csf + name + EnterChange surrounding function name (foo(bar)baz(bar))
yss{replacement}Surround entire line (yss""line content")
cS / yS / ySSNewline surround variants (delimiters on separate lines)
S{replacement}Surround visual selection (visual mode)
S<tag>Surround selection with tag (visual mode)
gSNewline surround selection (visual mode)
2ds), 2cs)Count: delete/change 2nd-level surrounding bracket
2ysiw*Count: repeat delimiter (**hello** for Markdown bold)
2ds*Count: delete repeated delimiter (unbold **hello**)
<C-G>s{char}Insert mode: inserts both delimiters, type inside them
Link to original

Targets

Surround operations work with a wide range of targets.

  • Quotes: ", ', `
  • Brackets: (, ), [, ], {, }, <, >
  • Tags: t (HTML tags)
  • Aliases: b for ), B for }, r for ], a for >

Bracket spacing

Opening and closing brackets behave differently regarding whitespace:

  • Opening brackets ((, [, {): Add a space inside the delimiters.
  • Closing brackets (), ], }): Do not add spaces.

Tag surround

You can wrap text in HTML tags or change existing tags.

  • Add tag: ys{motion}t<tagname> or ys{motion}< (triggers a prompt).
  • Change tag: cst<newtag>.
  • Delete tag: dst.

When prompted for a tag, typing < allows you to enter the tag name.

Function surround

The f and F targets in replacement position allow you to wrap text in a function call.

  • ysiwf + name + Enter: Wraps the target in functionName().
  • ysiwF + name + Enter: Wraps the target in functionName( ) with internal spacing.
  • dsf: Deletes the surrounding function call, keeping the arguments (print(hello)hello).
  • csf + name + Enter: Changes the surrounding function name (foo(bar)baz(bar)).

dsf and csf use findSurroundingFunction which scans the current line for identifier( patterns. Nested calls, method chains (obj.method()), and no-arg functions (func()) are supported. Multi-line function calls are not detected (single-line only).

Count-prefix

Markdown formatting marks use a count-prefix to distinguish between single and double delimiters.

  • Bold: 2ysiw* surrounds a word with **.
  • Strikethrough: 2ysiw~ surrounds a word with ~~.
  • Highlight: 2ysiw= surrounds a word with ==.

To delete these repeated delimiters, use a count with the delete command, such as 2ds*.

Doubled symmetric delimiters also work with single-character ds/csds$ on $$example$$ deletes the innermost $ pair to produce $example$, and cs$) changes it to $(example)$. This applies to all symmetric surround characters ($, ", ', `, etc.).

Tip

Use the count-prefix for fast Markdown formatting. It’s often quicker than typing the marks manually.

Newline variants

Several commands allow you to place delimiters on their own lines.

  • yS: Adds surroundings on new lines and indents the content.
  • ySS: Surrounds the current line on new lines.
  • cS: Changes surroundings and moves them to new lines.
  • gS: In visual mode, surrounds the selection on new lines.

Insert mode

You can add surroundings while typing in insert mode using <C-G>s{char}. This inserts the pair and places the cursor inside. Pressing Esc leaves the cursor on the last typed character, matching vim-surround’s behavior. If no text was typed, the cursor rests on the opening delimiter.

Insert-mode surround supports full dot-repeat: pressing . after i<C-G>s)hello<Esc> replays (hello) at the new cursor position. Counted dot-repeat (2.) repeats the typed text inside one set of delimiters (e.g., (hellohello)). This exceeds both vim-surround and nvim-surround, where insert-mode surround dot-repeat is broken.

<C-G>S{char} is the newline variant — places delimiters on separate lines with indentation.

Custom surround pairs

Define your own single-character triggers that map to arbitrary delimiters — including multi-character ones like [[wikilinks]] or $$math$$.

Lua

vim.obsidian.surround.set("l", { left = "[[", right = "]]" })
vim.obsidian.surround.set("m", { left = "$$", right = "$$" })
 
-- Or batch:
vim.obsidian.surround.add({
    { "l", left = "[[", right = "]]" },
    { "m", left = "$$", right = "$$" },
    { "e", left = "\\begin{equation}", right = "\\end{equation}" },
})
 
-- Remove:
vim.obsidian.surround.del("l")

Vimrc

surroundmap l [[ ]]
surroundmap m $$ $$
surroundunmap l

After registration, all surround operations work with the custom pair:

  • ysiw l wraps a word → [[word]]
  • ds l deletes surrounding [[...]]
  • cs l m changes [[...]] to $$...$$
  • Visual S l wraps the selection

Built-in surround characters ((, ), [, ], {, }, <, >, b, B, r, a, t, T, f, F, ", ', `) are reserved and cannot be overridden.

Fork mode required

Custom surround pairs require the plugin’s bundled fork mode. Disable Obsidian’s built-in Vim mode in Settings → Editor → Vim key bindings for full support.

See lua-config > Custom surround pairs for the full API reference.

Dot-repeat

All surround commands support the . command — including insert-mode surround (<C-G>s). You can repeat your last add, change, delete, or insert-mode surround operation across different parts of your document.

nvim-surround parity

The surround implementation is verified against nvim-surround via 74 Neovim golden comparison tests. 54 tests pass, 20 deviations are tracked for future improvement.

Working features (verified against nvim-surround):

  • All ds/cs/ys/yss/visual S with quotes, brackets, parens, braces, backticks, angle brackets
  • Opening vs closing bracket distinction: ds( strips inner spaces, ds) preserves them; ysiw( adds spaces, ysiw) doesn’t
  • Cursor position after surround-add operations (on the opening delimiter)
  • Tag operations: dst (delete tag), cst" (change tag to quotes)
  • Count-prefixed Markdown formatting: 2ysiw* for bold, 2ds* to delete
  • Dot-repeat for ys, ds, and visual S
  • Nested and multiline bracket operations
  • Empty content surround (dsb on (), surround empty lines)
  • Aliases: b), B}, r], a>
  • Custom surround pairs via Lua/vimrc
  • Arbitrary delimiter characters (|, ^, etc.)

Known gaps (tracked as deviations):

  • cst<tag> / ysiwtdiv — tag input via golden test dispatch needs re-verification
  • ds< semantic difference — fork treats < as angle bracket; nvim-surround treats it as tag prompt

See known-limitations > Surround nvim-surround parity gaps for the full deviation list.