`vi{` increases the visual selection, but can I reduce the visual selection in a similar way?

Viewed 133

So if I've got text like:

if answer == 42 {                        #1
    if bugs == 'bunny' {                 #2
        if injury == 'but a scratch' {   #3
            // do the thing              #4
        }                                #5
    }                                    #6
}                                        #7

and I've got my cursor on // do the thing and do the va{ command, then I'll select the {} from lines 3 to 5, and if I do a{ again then I'll select {} from lines 2 to 6.

But oh no, I now realize that I actually only wanted to select the {} from lines 3 to 5. To my horror, the usual vim method of capitalisation => opposite doesn't work and A{ simply appends a { to the end of the line.

Can I go from a large selection to a smaller one without cancelling the selection, moving my cursor and doing it all over? In this example it would also be simple enough to use some of hjklwbeo to change the selection manually, but in practice the code blocks are long and non-trivial.

I'm aware that this would be ambiguous if there are multiple sibling blocks within one parent blocks, but I'd be happy with just selecting the first one since that rarely happens to me.

Thanks!

Edit: A plugin that does this would be a good alternative. If it looks like none exists, then I'll make a quick MVP and post a link here for comments.

2 Answers

Text objects always go outwards so, even if you try something like i{, which covers a smaller region than a{, the selection will inevitably be extended.

As you mentioned, you can change the geometry of the selection with o and various motions but that's cumbersome and the alternative, leaving visual mode, moving the cursor, and selecting again, is sadly just as cumbersome.

Building a custom "shrinking" pseudo text object might be possible, though, and something the community would probably gladly welcome.

--- EDIT ---

The quick and dirty code below seems to work reasonably well for a{, a(, a[, and a<:

function! InvertObject()
    call getline('.')[getcurpos()[2] - 1]->search('b')
endfunction
xnoremap A{ <Esc><Cmd>call InvertObject()<CR>va{
xnoremap A{ <Esc><Cmd>call InvertObject()<CR>va(
xnoremap A[ <Esc><Cmd>call InvertObject()<CR>va[
xnoremap A< <Esc><Cmd>call InvertObject()<CR>va<

See :help getcurpos(), :help getline(), and :help search().

Adding "inner" variants might be more involved because, depending on various parameters, the cursor might not always end up at the same position.

For several reasons, the snippet above can't be used as-is for the following text objects:

  • aw, aW, as, and ap, because they are not really delimited by specific tokens. as might be the easiest to add, though.
  • at, because the delimiter tokens are more complex than a single character.
  • a', a", and a`, because they don't expand in the same manner as their "parens" cousins, so they require a completely different logic.

Based on your post, I decided to make the vim-visual-history plugin that keeps a traversable record of previous visual selections with [v, ]v, [V and ]V. The lower case version of these commands also take a count. So in your case, you would just do [v to go back one selection.

[count][v : Reselect previous visual selection
[count]]v : Reselect next visual selection
[V : Reselect first visual selection
]V : Reselect last visual selection

enter image description here

Demo of the commands taking a count:

enter image description here

Related