23

I'm writing a Vim plugin which will need to surround the word directly under your cursor with a quotes. I have tried using simple solutions which use commands like "b" and "e" executed in normal mode, in combination with other cursor position commands, though those felt clunky and non-robust.

I wondered if there was perhaps, a builtin way to surround text, or a more expedient method of doing so.

2
  • 2
    Welcome to Vi and Vim! As it stands, your question asks for answers for which it is hard to judge the best—words like simplest and fastest can be subjective and solicit answers based on opinion rather than fact and experience. That doesnt work well with the StackExchange mode. Please edit your question to clarify your goals and make them objective. [side note: have a look at tpope’s surround.vim on github]
    – D. Ben Knoble
    Sep 4, 2019 at 13:55
  • I don't think this is quite right obe ben kenobe: if a solution requires a long mapping in the vimrc or is a built in feature with 2 or 3 key-presses, then "simple" and "fast" is readily objective. Also opinions are informed by expertise and experience and can be quite useful when elaborated and explained. I think SO has got to correct it's culture of knee-jerk kill questions when certain keywords come up. You could use a bot to do that if that's what you really want, and then you wouldn't need curators.
    – NeilG
    Sep 9, 2021 at 1:49

6 Answers 6

38

You can surround the word currently under the cursor in quotes with the following normal mode commands:

ciw""EscP

Replace iw with any other Vim motion/text object to surround other things with quotes*. Or "" with any other pair of characters to surround the object with different things.

* Or use c from visual mode to surround text that is hard to describe with a single motion.

If you want to surround the object with a longer piece of text, such as an HTML <p> tag, you can use Ctrl-R instead of the P put command:

ciw<p>Ctrl-R"</p>Esc

See :help i_CTRL-R for more details.

Objects smaller than a line can also be surrounded using the small delete register, and as of Vim 8.2.2189, this is repeatable via the dot command, making it easy to apply the edit quickly in several different places.

So e.g. to go from this:

one two three four

to this:

(one two) (three four)

type:

c2w(Ctrl-R-)Escw.

Thanks to Christian Brabandt for letting us know about the fix, and for implementing it!

However, as dicussed in the comments, if you attempt to use Ctrl-R to surround an object larger than a single line (or prior to v8.2.2189), when you repeat this with the dot command, it will enter the text from the original change command. As @user938271 explains, you can workaround this by using Ctrl-RCtrl-O or Ctrl-RCtrl-P instead of a plain Ctrl-R when inserting the contents of the register.

So to go from:

<li>one
<li>two

<li>three
<li>four

to:

<ul>
<li>one
<li>two
</ul>

<ul>
<li>three
<li>four
</ul>

You can type:

c2c<ul>ReturnCtrl-RCtrl-O"</ul>Escjj.

8
  • 4
    @filbranden: If you want the dot command to use the last deleted word (and not always the first), during the first edition, you can press C-r C-o " or C-r C-p " (instead of just C-r ") to insert the deleted word back into the buffer.
    – user938271
    Sep 4, 2019 at 18:29
  • 2
    @filbranden: Reading the docs, it's unclear to me, too. But I think the difference is that C-r inserts the text "as if you typed it, but mappings and abbreviations are not used" - meaning that . doesn't "remember" the command C-r ", just the actual text that it expanded to (because that's what would have happened if you had typed it). But IMHO the docs really ought to call that out, if that's what is meant.
    – Kevin
    Sep 4, 2019 at 22:32
  • 2
    @Kevin: I don't think that's the relevant line from the help, because it's mentioned at :h i^r, and C-r doesn't make . behave like C-r C-o. I think the relevant line is "Does not replace characters!". If you insert the deleted word foo with C-r ", you'll see that the output of :echo @. is foo. But if you insert it with C-r C-o ", the output of :echo @. is ^R^O". In the dot register, the keys you pressed have not been replaced with the actual inserted characters. I think that's what "Does not replace characters!" refer to.
    – user938271
    Sep 5, 2019 at 10:53
  • 2
    I could be wrong though; the help is not very clear. Also, the dot command works as expected with C-r C-o and C-r C-p, but only if the deleted text is characterwise; not linewise.
    – user938271
    Sep 5, 2019 at 10:59
  • 2
    Sorry, the dot command does behave as expected; what I meant is that you can't use C-r C-o to surround a linewise text without breaking the undo sequence: i.imgur.com/rFGJXxy.gif Here, Up breaks the undo sequence (even if you prefix it with C-g U).
    – user938271
    Sep 5, 2019 at 11:13
20

Yes, a plug-in for surrounding with quotes exists! vim-surround is what you're looking for.

To surround the current word in double quotes, you can use ysiw" once you have the plug-in installed.

ys is the command to surround and object (there's also cs to replace one delmiter with another, ds to remove surrounding), then iw is a text-object defining what to surround and finally " is what to surround it with.

You might want to read the docs for vim-surround to see all that it can do. It's really a lot!

1
5

Starting with Vim 8.2.2189 you can easily surround a single word using the small delete register like this:

ciw"CtrlR-"

The whole operation is repeatable using the . command. So if you have a line like this:

foo bar foobar

With the cursor anywhere on the first word, simply type ciw'<C-R>-'. This will put single quotes around to first word. For the next two words type w.w. and you'll have successfully put single quotes around all 3 words:

'foo' 'bar' 'foobar'
2
  • 1
    Very nice! A bit of a shortcut over the usual Ctrl+R Ctrl+O ". Looking forward to the upgrade.
    – filbranden
    Dec 23, 2020 at 13:31
  • @filbranden thanks Dec 23, 2020 at 13:34
1
  1. string to double-quote: This is my dog
  2. put cursor on T
  3. 4cw""<ESC>P
2
  • 1
    Welcome to Vi and Vim!
    – filbranden
    May 3, 2021 at 22:36
  • 1
    Fortunately, adding a . to the end makes it a sentence, so cis""<esc>P also works (also cis"<C-R>""<esc>)
    – D. Ben Knoble
    May 4, 2021 at 1:41
0

To add to the previous answer, if it's a task you have to automate, you can use substitutions and some mapping.

For example:

nnoremap <LEADER>' :let col=col('.') <BAR> s/.*\zs\<.\{-}\%#.\{-}\>/'&'/ <BAR> call setpos('.', [0, line('.'), col+1, 0])<CR>

This will surround the current world with '' upon hitting <LEADER> '


To make it neater, you can use a vimscript function:

function! SurroundMe()
    let cara = escape(nr2char(getchar()), '/\\^$*.[~')
    let col = col('.')
    exe 's/.*\zs\<.\{-}\%#.\{-}\>/'.cara.'&'.cara.'/'
    call setpos('.', [0, line('.'), col + 1, 0])
endfunction

nnoremap <LEADER>' :call SurroundMe()<CR>

This function will actually wait for you to input a character, and surround the current word with that character!

0

In NVIM you can use this function and map <leader>sw or <leader>sW to use :lua Surround("w") and :lua Surround("W") respectively.

After pressing <leader>sw or <leader>sW in insert mode you will be asked for the character you want to surround with.

If you want to surround with characters whose closing character is different from the opening character, just type the opening one

Note that this is easily reproduced in VIM.

function Surround(w_or_W)
    local open_char = vim.fn.input("Surround with: ")
    local closed_char = nil
    if open_char == "(" then closed_char = ")" end
    if open_char == "[" then closed_char = "]" end
    if open_char == "{" then closed_char = "}" end
    if open_char == "<" then closed_char = ">" end
    if open_char == "'" then closed_char = "'" end
    if open_char == '"' then closed_char = '"' end
    if open_char == "`" then closed_char = "`" end
    if open_char == "/" then closed_char = "/" end
    if open_char == "|" then closed_char = "|" end

    if w_or_W == "w" then
        vim.cmd("normal! ciw" .. open_char)
    elseif w_or_W == "W" then
        vim.cmd([[normal! ciW]] .. open_char)
    end
    vim.cmd("normal! p")
    vim.cmd("normal! a" .. closed_char)
    vim.cmd("normal! a")
end

vim.api.nvim_set_keymap("n", "<leader>sw", ":lua Surround('w')<CR>", { noremap = true, silent = true })
vim.api.nvim_set_keymap("n", "<leader>sW", ":lua Surround('W')<CR>", { noremap = true, silent = true })
``

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.