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.
Link to original
Keybinding Description 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)onhello→(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
Targets
Surround operations work with a wide range of targets.
- Quotes:
",',` - Brackets:
(,),[,],{,},<,> - Tags:
t(HTML tags) - Aliases:
bfor),Bfor},rfor],afor>
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>orys{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 infunctionName().ysiwF+ name + Enter: Wraps the target infunctionName( )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/cs — ds$ 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 lAfter registration, all surround operations work with the custom pair:
ysiw lwraps a word →[[word]]ds ldeletes surrounding[[...]]cs l mchanges[[...]]to$$...$$- Visual
S lwraps 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/visualSwith 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 visualS - Nested and multiline bracket operations
- Empty content surround (
dsbon(), 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-verificationds<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.