Vim surround plugin tutorial

Video

Anyone who uses Vim extensively knows a lot of it's power is being able to manipulate text as a set of components. We do this using text objects and motions. There are a lot of useful vim plugins that extend these capabilities.

Tim Pope's vim surround plugin is one of the most popular [1] plugins. It provides text objects to deal with pairs of things surrounding something else.

This tutorial covers how to install the plugin, how to use the major capabilities and provides a set examples for each of the operations. For a visual overview I've created a video (3:15 mins) linked on the left. It demonstrates a few of the capabilities that this post covers while marking up a text document with HTML, including visual selection and the repeat operator.

The first thing any plugin has to answer, is whether it's truly useful. Surround.vim comes into play when you want to alter brackets, a common requirement in development. It's also useful when marking up a document to HTML since it lets you surround text with mark-up very quickly. Vim's built in capabilities for handling brackets as text objects are useful, but here's two situations where the additional capabilities of this plugin help:

# A python list
# di] removes the 'i'nner, or da] removes the whole thing
# but we want to remove the square brackets only
['one', 'two']

# We want to put some quote marks around the text
# We could do i, move 3 words and i again - not very efficient!
print(Hit the button)

Installing surround.vim

To install it use the plugin manager of your choice and add to your .vimrc. I'm using NeoBundle:

NeoBundleLazy 'tpope/vim-surround', {'autoload': {'filetypes': ['clojure','rst','python']} }
NeoBundleLazy 'tpope/vim-repeat', {'autoload': {'filetypes': ['clojure', 'rst', 'python']} }

The first line makes vim-surround available and loads it if we're editing a Clojure, reStructuredText or Python file. The second line does the same thing for the vim-repeat plugin which we'll cover later. The reason for using NeoBundleLazy is that with lazy loading Vim still starts up quickly, but can load these plugins when required. After a restart and load, we can check it's available with :NeoBundleList within Vim.

When the plugin is loaded a new text object for surround is available, it's called with s. We use it with the normal mode commands specifying a surrounding text object to act on. Taking our examples from above:

# remove the brackets
# ds[ -  'd'elete 's'urrounding [
['one', 'two']

# add quote marks
# ys3w" - Add (y) 's'urrounding 3 words with "
print("Hit the button")

A bit more formally the structure is:

<command><surround object>[count]<surround target>[replacement]

Where <command> is one of the standard Vim ones such as delete (d), change (c) or visual (v). It also adds a new one add (y) for where you want to add something around the object you're operating on. The <surround object> is called with 's' which tells Vim we're trying to operate on something surrounding some text. The next parameter is the <surround target>, such as a bracket or a quote mark. The last parameter is the <replacement> which is required when you're changing (with c) or adding (with y) something. As with other Vim commands it's possible to use a [count] when specifying the surrounding object to operate on, e.g. above we acted on three words.

The easiest form is the delete command so lets look at that first.

Delete surroundings

To delete we use ds, the next character determines the target to delete. Some examples:

Objective Original text Command Edited text
Delete quotes surrounding a sentence
"Hello world!"
  [] <--- cursor
ds"
Hello world!
[] <--- cursor now
Delete surrounding tags
<p>Start here</p>
       [] <--- cursor
dst
Start here
[] <--- cursor now
Delete surrounding parentheses in some code
(var1, var2)
ds(
var1, var2

The cursor can be anywhere within the surrounding text object we're operating on, when the command is complete the cursor moves to the start of the object we operated on. Also, unlike other text objects there's no i or a option for changing how it selects the area.

Change surrounding

To change a surrounding we use cs, this deletes the supplied text-object and replaces it with the second argument. When used in this way the cursor does not enter Insert mode at the end of the command. There are two ways to use it:

Examples of how to use it are:

Objective Original text Command Edited text
Change surrounding quotes from double to single quote marks
"My kingdom for a horse"
      [] <--- cursor here
cs"'
'My kingdom for a horse'
[] <--- cursor here now
Change to HTML paragraph with tags on separate lines
'Look, to the East'
cS'<p>
<p>
Look, to the East
</p>

Add surrounding

Often, rather than altering some existing mark-up or brackets, we want to add some additional surroundings. Add a surrounding uses ys, the following item is the additional mark-up, brackets or text that will placed either side. The ways it can be used are:

Some examples of using these methods:

Objective Original text Command Edited text
Put some quotes around some text at the end of a line.
he said, Boo! scary huh
         [] <--- cursor
ys$"
he said, "Boo! scary huh"
Put some square brackets around some variables. Note use of repetition in the motion.
print var1, var2
      [] <--- cursor
ys3w)
print (var1, var2)
      [] <--- cursor
Put some squiggly brackets around some code. This acts on the whole line with the cursor anywhere on the line
34, 21, 7
  [] <-- cursor
yssB
{ 34, 21, 7 }
[] <-- cursor moves
Convert some speech into a HTML blockquote. Uses Vims search motion and surrounds ability to then put tags around that.
Jack said, "Done it!"
           [] <--- cursor
ySf"t
Jack said, <blockquote>
    "Done it!"
</blockquote>
Put some brackets around a procedure and indent the text
defn -proc1
    [] <--- cursor
ySSb
(
     defn -proc1
)

Visual surround

In visual mode, the surround object is called with S, and then an argument that wraps the visual selection. The keyboard usage is something like this:

v                         # Enter visual mode
<visually select>         # Use the keyboard to select the section of text
S                         # Press upper case S
"                         # Specify what you want to surround the visual selection with
Objective Original text Command Edited text
Select a word and put rST italic marks around it
Add two eggs
   [] <--- cursor
viwS*
Add *two* eggs
[] <--- cursor now

Put HTML paragraph around some lines.

Uses linewise visual mode

Consider this, when
we create an object
we take more memory.
Shift-v,
<highlight the text>
S<p>
<p>
Consider this, when
we create an object
we take more memory.
</p>

Put HTML list item on each line to create a list.

Uses block wise visual mode

6 eggs
4 apples
2 pints of milk
Ctrl-v
<highlight the text>
S<li>
<li>6 eggs</li>
<li>4 apples</li>
<li>2 pints of milk</li>

Surround targets

A surround target is something where the plugin recognises that it can operate on it. With the ds and cs commands the parameter is a surround target to operate on. As we've seen the targets are often the first letter of a standard vim motion or text-object. The most common ones are:

Target Explanation
( or ) Bracket. Using an open bracket adds additional spaces. Using a closed bracket doesn't add space.
{ or } Squiggly brackets. Using an open one ({) adds additional spaces. Using a closed one doesn't add space.
[ or ] Square brackets. Using an open bracket ie [ adds additional spaces. A closed one doesn't add spaces.
< or > Diamond bracket. Using an open one (<) adds additional spaces. A closed one doesn't add spaces.
b Bracket, alternative to (
B Bracket, alternative to )
r Alias for }
a Alias for ]
` A backtick
" A quote
' A single quote
t A pair of HTML or XML tags
w A word
s A sentence. I avoid as it's a bit confusing, it only works with cs and it mentally conflicts with yss meaning to work on the current line.
p A paragraph.

Customising vim surround

Many of the examples for vim-surround are marking up HTML - the plugin is aware of HTML and XML tags. We can imagine other situations where it would be valuable for the plugin to recognise how to add a tag around some text: for example being able to mark-up a Sphinx or LaTeX document with defined tag shortcuts.

As an example, the simplest case is adding a short-cut for a reStructuredText file where we want to add bold (which is two **'s around some text). We'll assign it to the o key, because b is is a vim default text-object for brackets and we don't want to over-ride that. We're only going to have this mapping for reST files, so we open a rST file and check the filetype with :set filetype? which confirms it is rst. Also in vim, we can check the hex for the o character with :echo char2nr('o') which returns 111. Then in our .vimrc we do:

" put ** around the text, when used with ysso, or v<highlight>So
" These two lines do the same thing
autocmd Filetype rst let b:surround_111 = "**\r**"
autocmd FileType rst let b:surround_{char2nr('o')} = "**\r**"

Now, when a reStructuredText file is opened and we do something like ysso it will place rst bold mark-up around the line.

The constraint in vim-surround is that we can't use complex calls like "<leader>b" to call a surrounding, we can only define single letter shortcuts. Complex shortcuts may be possible with the vim-sandwich plugin, but I'm not totally clear on it. If this idea is attractive, there's a utility plugin called vim-surround_custom_mapping, and an answer on Stackoverflow which is worth looking at.

Repeat plugin

The repeat plugin extends Vims repeat command (.) so that it works with plugins - in this case enabling vim surround to work with the repeat command. It lets us repeat ds, cs and ys. The one wrinkle is if you do a visual selection then it will repeat the size of the visual selection.

Objective Original text Command Edited text
Put brackets around the multiple lines of a text. Converting to three Python tuples.
'Bob', 22, 'Paris'
'Sarah', 34, 'New York'
'Karen', 54, 'London'
yss)
.
.
('Bob', 22, 'Paris')
('Sarah', 34, 'New York')
('Karen', 54, 'London')

Parting thoughts

I find the surround plugin adds a nice boost to editing code, I just need to get the muscle memory really embedded. If you've used the plugin, or have any thoughts on how to use it, please leave a comment!


[1]Listed by Vim Awesome in the top 10 of most popular plugins.

Posted in Tech Saturday 19 March 2016
Tagged with vim tech