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
- 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
- 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 |
+----+------------------------+----------------------------+------------+
return -1;
comes from. Also could you precise where you put your cursor before executing each sequence of key?yi{
yanks between the curly brackets andvap
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 doingyi{
+va{
+p
?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 instancep
andP
both give this result, despitei{
anda{
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.