6

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.

beyarkay
  • 513
  • 3
  • 16
  • 2
    Related: https://stackoverflow.com/questions/26551430/vim-how-to-shrink-the-visual-area – T.J. Crowder Feb 01 '22 at 08:03
  • 2
    I suspect that not many people need the `A`/`I` in visual mode to do what they do. Maybe a plugin making it behave as you suggest would be appreciated. – Enlico Feb 01 '22 at 08:50
  • 3
    Based on your post, I decided to make the [vim-visual-history](https://github.com/Matt-A-Bennett/vim-visual-history) plugin that keeps a traversable record of previous visual selections with `[v`, `]v`, `[V` and `]V`. So in your case, you would just do `[v` to go back one selection. The plugin works, but is still under construction. – mattb Feb 04 '22 at 11:41
  • 2
    @Enlico I just made the [vim-visual-history plugin](https://github.com/Matt-A-Bennett/vim-visual-history) to do it. – mattb Feb 04 '22 at 17:05

2 Answers2

5

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.
romainl
  • 186,200
  • 21
  • 280
  • 313
  • 1
    Okay, I'm guessing you don't know of any plugins that already exist to solve this? Otherwise I might go do it myself and post here when I've got an MVP. – beyarkay Feb 01 '22 at 10:20
  • @beyarkay see my edit. – romainl Feb 04 '22 at 08:07
  • 2
    Based on the OP and on your post, I decided to make the [vim-visual-history](https://github.com/Matt-A-Bennett/vim-visual-history) plugin that keeps a traversable record of previous visual selections with `[v`, `]v`, `[V` and `]V`. The plugin works, but is still under construction. – mattb Feb 04 '22 at 11:41
2

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

mattb
  • 2,787
  • 2
  • 6
  • 20