8

One of my friend told me that I can use the trick below to remove a pair of curly brackets.

Put the cursor inside the curly bracket pair you want to remove.

y i { to yank the text in braces , then v a { then paste p.

It works well like:

for(int i = 0; i < s.length(); i++) {
        int index = s[i] - c;
        if(root->next[index] == NULL)
            root->next[index] = new TrieNode;
        root = root->next[index];
}

After execute the command:

for(int i = 0; i < s.length(); i++)
    int index = s[i] - c;
    if(root->next[index] == NULL)
        root->next[index] = new TrieNode;
    root = root->next[index];

until I met this situation(The space before the first curly bracket is missing).

for(int i = 0; i < s.length(); i++){
        int index = s[i] - c;
        if(root->next[index] == NULL)
            root->next[index] = new TrieNode;
        root = root->next[index];
}

After executing those commands the text block becomes

for(int i = 0; i < s.length(); i++
        int index = s[i] - c;
        if(root->next[index] == NULL)
            root->next[index] = new TrieNode;
        root = root->next[index];
)

The area I select in the visual mode seems good, but why does the parentheses goes down?

10
  • Are you sure of the code sample you posted? I don't understand how the return -1; comes from. Also could you precise where you put your cursor before executing each sequence of key?
    – statox
    Jun 24, 2015 at 14:16
  • @statox Sorry about that...I've corrected the latter block, and added a description about where did I put the cursor. Thanks for your recommendation :-)
    – MrDwZ
    Jun 24, 2015 at 14:50
  • I can't replicate this. yi{ yanks between the curly brackets and vap visually selects the paragraph (the entire text in this case) but I don't see how this changes the text at all. Are you sure you're not doing yi{ + va{ + p?
    – jjaderberg
    Jun 24, 2015 at 18:15
  • 2
    I just tried yi{ + va{ + p on your text in a clean session (no vimrc, -u NONE) and that does reproduce your result. I'm not sure why, for instance p and P both give this result, despite i{ and a{ moving characterwise. It would help if you could a) give an example text on which your technique does work and b) confirm where in your technique the 'changing' happens. @Ben indicates a good workaround in his answer (I use that plugin) but I wouldn't mind learning why this happens in the first place.
    – jjaderberg
    Jun 24, 2015 at 18:37
  • I'm just like @jjaderberg I can't reproduce the behavior you describe. Giving the details asked in his comments would be a really good idea.
    – statox
    Jun 24, 2015 at 19:09

3 Answers 3

6

Install the "surround" plugin. Then you can just do d s B .

From the project page:

Surround.vim is all about "surroundings": parentheses, brackets, quotes, XML tags, and more. The plugin provides mappings to easily delete, change and add such surroundings in pairs.

The command ds followed by any text object (B in this case, for "Block", as in the aB or iB text object) will delete the surrounding characters that define the text object. Thus, d s B in your case accomplishes what you wanted.

Other commands include c s B b, if you wanted to change the surrounding {...} into (...), or y s {any cursor motion or text object} B to add surrounding {...} to the text selected by the cursor.

See the plugin page and documentation for more details.

3
  • This is a good workaround. Would you consider adding a sentence or two explaining how this plugin works and why the particular mapping accomplishes the task?
    – jjaderberg
    Jun 24, 2015 at 18:39
  • Is there any elegant way to remove those braces without a plugin? : (
    – MrDwZ
    Jun 25, 2015 at 3:22
  • Maybe. But surround is really light-weight and one of the "essential" plugins. So may as well install it...
    – Ben
    Jun 25, 2015 at 3:29
6

Since you aren't asking for the cursor to be returned to its original position, [{x]}x does the job. It's 6 characters long with two presses of Shift but the principle is straightforward so it's easy to remember.

If you want to go back to the original position, you can use [{x``]}x``

6

Summary

It's a bug and there's a patch (743). The bug affects both your examples, but you don't see it in one case because the character that is moved down is a (whitespace). The solution is to install the patch, but there are also many workarounds or alternative methods, not affected by the bug.

NB: {, } and B all refer to the same text object: Block (:help aB, :help iB) . If this or other answers use a different sign than the one used in the question, that may be why.

The bug

I don't understand it entirely, but here is towards an explanation, if someone is interested.

The behaviour you see is due to a bug mentioned by Yukihiro Nakadaira on vim_dev and his patch is released as 7.4.743: "p" in Visual mode causes an unexpected line split.

Visual mode put (:help v_p) is supposed to replace the visually selected text with text from a register. The bug happens when a) the register is of linewise type (:help linewise-register) but the visual selection is of characterwise type and b) the visual selection ends at the last column of a line. In your case, if you yank inner Block and then visually select a Block and paste, the yanked register will be linewise and the visual selection will be characterwise. We can yank both inner Block and a Block to named registers and inspect:

On

for(int i = 0; i < s.length(); i++){  
    int index = s[i] - c;  
    if(root->next[index] == NULL)  
        root->next[index] = new TrieNode;  
    root = root->next[index];  
}

we do

"ayi{
"bya{
:registers ab

which shows

--- Registers ---
"a       int index = s[i] - c;^J    if(root->next[index] == NULL)^J        root->next[index] = new TrieNode;^J    root = root->next[index];^J
"b   {^J    int index = s[i] - c;^J    if(root->next[index] == NULL)^J        root->next[index] = new TrieNode;^J    root = root->next[index];^J}

and

:echo getregtype('a') " displays 'V' for linewise
:echo getregtype('b') " displays 'v' for characterwise

To replace a characterwise visual selection with a linewise register, the lines that hold the beginning and the end of the visual selection must be split in two, so that the text can be inserted in between. This usually works just fine:

Running vim as vim -u NONE -U NONE and typing
(a)

iaaa
bbb
ccc
ddd<Esc>ggYjlvp

results in

aaa
b
aaa
b
ccc

and (b)

iaaa
bbb
ccc
ddd<Esc>ggYjvjp

in

aaa

aaa
cc
ddd

But when the characterwise visual selection ends at the last column of a line (and does not include the newline /n/^J) something goes wrong. It has to do with the cursor position, which is suddenly off by -1 and must be specially incremented, which is what the patch does. Compare (a) with the command <Esc>ggYjllvp, that is, the same command except move one more column to the right so the cursor is on the last "b" on the line. The result is the same as before!

Now take the following text:

if (TIRED){
    goto bed;
} else {
    goto work;
}

With the cursor on the second line, your yi{va{p works just fine and gives

if (TIRED)
    goto bed;
 else {
    goto work;
}

The yank register is still linewise and the visual selection characterwise, but the visual selection doesn't end in the last column anymore, and everything is fine.

Example texts from the question

For the two example texts, where the only difference is a blank space between closing parenthesis and opening curly bracket in the first line, it may seem like your command behaves differently, but it really doesn't. In the first case, the one which looks successful, there ought to be a trailing whitespace on the first line once the brackets are removed. That trailing whitespace is instead on the last line. With yi{va{p

for(int i = 0; i < s.length(); i++) {
    int index = s[i] - c;
    if(root->next[index] == NULL)
        root->next[index] = new TrieNode;
    root = root->next[index];
}

becomes

for(int i = 0; i < s.length(); i++)
    int index = s[i] - c;
    if(root->next[index] == NULL)
        root->next[index] = new TrieNode;
    root = root->next[index];
␣

just like

for(int i = 0; i < s.length(); i++){
    int index = s[i] - c;
    if(root->next[index] == NULL)
        root->next[index] = new TrieNode;
    root = root->next[index];
}

becomes

for(int i = 0; i < s.length(); i++
    int index = s[i] - c;
    if(root->next[index] == NULL)
        root->next[index] = new TrieNode;
    root = root->next[index];
)

Solutions

The solution would be to install the patch, but there are many other ways to remove the surrounding brackets which don't suffer from the bug. The best workaround is @Gilles answer but the easiest may be to visually select before you yank, to keep the register characterwise.

Tim Pope's surround plugin is excellent, but (at least for me) it does more than remove the brackets: it joins the second line to the first, it removes indentation and it moves the cursor to the beginning of the first line. This leaves some non-trivial cleanup to do. Ingo Karkat and Luc Hermitte have plugins that deal with registers and pasting (I'm sure there are many others) and that should be able to do this. I'm not very familiar with them, but I believe with lh-brackets you might do <M-b>x to delete surrounding brackets and for more powerful putting (especially in visual mode) you could look at ReplaceWithRegister and UnconditionalPaste.

The best way is that given in @Gilles answer, [{x]}x. It is fast, handles nested Blocks well and doesn't join lines inappropriately or mess with indentation. If there is a white space before the opening bracket you can easily add a x to the command to remove it: [{xx]}x

Other vanilla commands

Again, the most appropriate command I can think of is the one in @Gilles' answer, [{x]}x or [{xx]}x, but here are two alternatives specifically in light of this bug.

Yank characterwise

Since the bug occurs only for visual put linewise register over characterwise selection, and since it is only accidental to your inner Block yank that it is linewise, you can choose to yank it characterwise instead. One easy way is to visually select the inner Block before yanking it, and to make sure that visual selection is characterwise (i.e., use v not V): vi{yva{p. This may be the easiest way because it is very similar to how you currently do the operation.

Don't use visual put

Other commands, like change and delete, are not affected by this bug. You can yank as before, but then delete a Block into the black hole register (:help quote_) and put, or delete a Block and put from the 0 register (:help quote0): yi{"_da{p or yi{da{"0p

Generality

Finally, there are ways similar to @Gilles answer–move to beginning of a Block, delete character, move to end of a Block, delete character–but that are more generic. The only reason to use these would be if the "block" you are deleting around is eccentric and does not have associated motions that work as well as [{ does for a block delimited by curly brackets. There is a chance that the vim-surround plugin can handle these eccentric blocks well, but two vanilla ways would be

  1. Visually select the eccentric block and leave visual mode. The cursor is on the last character of the selection. Delete it, then go to the beginning of the last selection (:help `<) and delete character. va{<Esc>x`<x
  2. Search backwards for the beginning of the eccentric block and delete character, then search forward for the end of the eccentric block and delete character. ?{<CR>x/}<CR>x This may not work well with nested blocks.

Character count

+----+------------------------+----------------------------+------------+
|    | method                 |          command           | characters |
+----+------------------------+----------------------------+------------+
| 1. | vim-surround           | ds{                        |          3 |
| 2. | @Gilles answer         | [{x]}x                     |          6 |
| 3. | Characterwise yank     | vi{yva{p                   |          8 |
| 4. | Delete, not visual put | yi{"_da{p or yi{da{"0p     |          9 |
| 5. | Visual mark            | va{<Esc>x`<x               |          8 |
| 6. | Search                 | ?{<CR>x/}<CR>x             |          8 |
+----+------------------------+----------------------------+------------+

Your Answer

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

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