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.

Ryan Fredez
  • 545
  • 1
  • 3
  • 10
  • 2
    Welcome to [vi.se]! 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 04 '19 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 09 '21 at 01:49

6 Answers6

40

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.

Rich
  • 31,891
  • 3
  • 72
  • 139
  • 1
    +1 for the second answer using <C-r>" (also possible: <C-r>-, when you're changing less than a line.) Using that combination allows you to undo the operation with a single u. It also allows you to repeat with ., though in that case the repeat will insert the same text as the first time, which can be useful if you're quoting the same exact word over and over again. – filbranden Sep 04 '19 at 16:25
  • 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 04 '19 at 18:29
  • 1
    @user938271 That is just AMAZING!!! So glad I learned about this one today. I'm still reading the docs, trying to understand why this works. But it's so great that it does! Thanks for pointing this out. If you write this as an answer, I'll upvote it for sure, as far as I can tell that's the best way to execute this operation! – filbranden Sep 04 '19 at 19:07
  • 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 04 '19 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 05 '19 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 05 '19 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: https://i.imgur.com/rFGJXxy.gif Here, Up breaks the undo sequence (even if you prefix it with C-g U). – user938271 Sep 05 '19 at 11:13
  • If you want to quote Bash variables that start with $ you can use capital W, as in ciW""EscP, holding down Shift all the way from W to P. – enharmonic Feb 14 '20 at 18:11
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!

filbranden
  • 28,785
  • 3
  • 26
  • 71
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'
Christian Brabandt
  • 25,820
  • 1
  • 52
  • 77
1
  1. string to double-quote: This is my dog
  2. put cursor on T
  3. 4cw""<ESC>P
filbranden
  • 28,785
  • 3
  • 26
  • 71
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!

Zorzi
  • 1,111
  • 5
  • 7
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 == &quot;w&quot; then
    vim.cmd(&quot;normal! ciw&quot; .. open_char)
elseif w_or_W == &quot;W&quot; then
    vim.cmd([[normal! ciW]] .. open_char)
end
vim.cmd(&quot;normal! p&quot;)
vim.cmd(&quot;normal! a&quot; .. closed_char)
vim.cmd(&quot;normal! a&quot;)

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 }) ``