5

In Vim, I keep finding myself desiring a keystroke to rewrite the rest of some parameter list. For example, in the following Python function:

def myfun(a, b=12, c=(1,2,3), d=15):
    pass

I wish to replace the c=(1,2,3), d=15 with e=12. The keystroke ci( allows me to replace everything inside the entire parameter list, but I find that I often want to retain some prefix of the Vim text-object. In general, I'd assume this keystroke I'm searching for would be useful in the context of replacing final parameters of function calls as well as definitions.

A desirable answer to this question would apply to quoted strings, [] blocks and other text objects too. Note that I understand all about text-objects as answered in "How to select between brackets (or quotes or ...) in Vim?".

Both @pb2q and @romainl give good search shortcuts, but they require me to visually find the end of the enclosing block to devise a search which is unambiguous in terms of any other garbage which is in the block (e.g. think nested function calls). In particular, I often find myself wanting this when I have nested parenthesis inside the parenthesis set I want to manipulate. The answer I really want is analogous to ci) or ca) which is entirely conceptually based on the nearest enclosing bracketing ) and deals entirely gracefully with other nested ) blocks.

Community
  • 1
  • 1
jbmohler
  • 308
  • 2
  • 10

5 Answers5

10

There are a bunch of commands starting with ] that go to the end of some kind of structure, and respect nesting. ]) to go to the end of a parenthesized block, ]} to go to the end of a braced block, ]/ to go to the end of a C comment (/*...*/ style). Use [ in place of ] to go to the beginning instead of the end.

So to do the replacement of your c=(1,2,3), d=15 type c]).

The complete list of these commands is listed under :help various-motions. Unfortunately there isn't one for blocks delimited by brackets, because [[ and ]] already had a different meaning in classical vi, and vim has defined ][ and [] to fit nicely with those.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Alan Curry
  • 14,255
  • 3
  • 32
  • 33
  • I believe this is the answer I'm looking for as far as native vim goes. It's not as complete as I wish, but I suspect it's about as good as it gets with current vim. I've wanted this in terms of replace to closing `)` (this answer achieves elegantly) and replace to closing `"` (this answer does not appear to achieve). – jbmohler Jul 26 '12 at 17:46
  • Since quotes aren't nestable, you can just use `/` to search for the next one. If this is the first you've heard of it, it may be a little surprising, but `/` is a motion command, therefore valid as an argument to the `c` command. Just type `c/"` and hit Enter. – Alan Curry Jul 26 '12 at 18:02
  • Actually they're sort of nestable with `\"` aren't they? I guess I don't have a good answer for that. Submit a wishlist bug to vim for a `["` and `]"` command and maybe it'll appear in the next version – Alan Curry Jul 26 '12 at 18:06
  • I like this answer best because the key sequence is shorter. But note the technique I suggested can easily be updated to work with `]`. – darcyparker Jul 26 '12 at 19:06
  • @darcyparker yes yours is nice too. And you taught me about the visual-mode `o` command which I didn't know before but I'll probably use soon. – Alan Curry Jul 26 '12 at 19:21
5

Create a map with this

mavi)o`a
  • Mark the current position a
  • visually select inside () to select the full set of characters inside the () that you are in
  • go to the other end of the highlighted text :h v_o for more info
  • move to the previously marked position

Now you can perform an operation such as c or d...

Edit: I still like @Alan-Curry's solution best because the key sequence is short. However this technique has the benefit of also working with ].

I also discovered a simplification for my solution:

vi)o``

`` jumps to the last position. So you don't need to create a mark.

darcyparker
  • 1,199
  • 9
  • 13
1

If you don't mind using a plugin, I believe vim-ninja-feet does exactly what you are looking for.

  • c]i) will change the rest of the parameter list,
  • d[i" will delete backwards to the beginning of a quote, and
  • z]i] will put you in insert mode at the end of a [] block.
pagrick
  • 73
  • 5
0

ct: in this case will change everything up to, but excluding, the :. Preceding the t with a numeric argument will allow you to comfortably change nested braces/parens/quotes, e.g. c2t).

If the cursor is already on the line that you want to change, fc will put you on the character where you want to begin your change with the commands above, or use a search, e.g.: /c= for the more general case, i.e. if you have c characters earlier on the line, or the cursor is currently on a preceding line.

pb2q
  • 58,613
  • 19
  • 146
  • 147
  • Yes, both true and both useful, but I'm going to have to say I'm pretty picky about the details. For example, I might have a function call "... f(a, b, g(c, d)) ..." and wish to replace "b, g(c, d)" with "e" to get the result "... f(a, e) ...". – jbmohler Jul 26 '12 at 16:40
  • @jbmohler, you should be fine with `fb` then `cf)e`. Use `fc` followed by `cf5e=12` or `c2t)e=12` for the example in your question. – romainl Jul 26 '12 at 16:54
0

I hacked together a small solution. It may have some edge cases and may have some off-by-one errors but I don't have anymore time to test today. I'll revisit when I have some time.

function! UntilTextObject(op)
    " Save marks
    let save_a = getpos("'a")
    let save_b = getpos("'b")

    " Read in the object type and set '< and '> to the ends of the
    " range of the text-object and 'a to the current position
    let object_type = nr2char(getchar())
    execute "normal! mavi" . object_type . "\<esc>"

    " Adjust end position (off by one)
    let pos = getpos("'>")
    let pos[2] += 1
    call setpos("'b", pos)

    " Operate on the range `a to `b
    execute "normal! `a" . a:op . "`b"

    " Restore marks
    call setpos("'a", save_a)
    call setpos("'b", save_b)
endfunction

onoremap u :call UntilTextObject(v:operator)<cr>

This allows you to do du) to delete-until-the-end-of-the-)-text-object. Works for other text-objects as well.

I added a few changes that make this a little more polite by not stomping on your marks. It's still a little quirky if you give it a text-object that doesn't select anything. I'll revisit this at a later date and see if I can figure that out. It's intentionally over-commented for demonstration purposes.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Randy Morris
  • 39,631
  • 8
  • 69
  • 76