Ariunbayar
Ariunbayar

Reputation: 81

Efficient vim regex to toggle multiline function call

As my experience with vim is quite limited I'm having difficulties writing a regex and editing my vimrc. I have this function call which would be very long:

dothis(123, [1, 2], [2, [7,8]], 'text (), []', [], fn([123], 'abc, def', [[], [123]]));

I often need to change to following, or vice versa.

dothis(
    123,
    [1, 2],
    [2, [7,8]],
    'text (), []',
    [],
    fn([123], 'abc, def', [[], [123]])
);

Would I be able to do this with :%s/search/replace/ syntax?

Or should this be a ToggleFnCall function in my vimrc to be called as :call ToggleFnCall()?

Or should this better be as a recorded macro rather than regex?

Does everyone write their efficiency scripts from scratch? Or is there something that I don't know that almost every vim users extend on?

Upvotes: 0

Views: 261

Answers (3)

romainl
romainl

Reputation: 196546

I don't think the time needed to come up with the right substitution/macro is worth it.

Without an external formatter, here's how I would deal with this task:

  1. place the cursor on the first parenthese,

    f(
    
  2. put the content of the parentheses on its own line,

    ci(
    <CR>
    <C-r>"
    <CR>
    <Esc>
    k
    
  3. change some comma+space into comma+cr with an interactive substitution,

    :.,/);/s/, /,\r/gc<CR>
    
  4. and then format the whole thing properly,

    :'{,'}norm ==<CR>
    

You could put all of that in a dedicated function:

function! Foo()
    normal! 0f(
    " ^M is obtained with <C-v><CR> and ^R with <C-v><C-r>
    normal! ci(^M^R"^M
    normal! k
    .,/);/s/, /,\r/gc
    '{,'}normal! ==
endfunction

and assign a mapping to it:

nnoremap <key> :call Foo()<CR>

example


To revert to the one-line version, select the block and press J.


As for your last question, a seasoned vimmer never looks for the one and only silver bullet that will fit every possible scenario. Instead, he/she builds small, specialized, solutions from a vast array of smaller parts made available by Vim. Some ad-hoc solutions can easily and quikly become generic and reusable like the one above.

I recommend this short and awesome screencast by Gary Bernhardt that demonstrates how to turn a repetitive task into a specialized macro first, then into a generic mapping.

I followed the same method for this answer.

Upvotes: 0

Amadan
Amadan

Reputation: 198324

It's hardly nice, and I don't promise it won't break, or that it will work in all cases ("not well tested" is an understatement; and I am not nowhere near a VimL expert), but this might work:

function! SplayFnCall()
  let magic = &magic
  set magic
  exe "normal _f(vi(\<Esc>a\<CR>\<Esc>gvo\<Esc>i\<CR>\<Esc>"
  while 1
    norm "zyl
    if (@z =~ "[a-zA-Z0-9_]")
      exe "norm /[^a-zA-Z0-9_ ]\<CR>\"zyl``"
    endif
    if (@z == "[" || @z == "(" || @z == "{" || @z == "'" || @z == '"')
      exe "norm va".@z."\<Esc>"
    endif
    norm f,
    norm "zyl
    if (@z == ",")
      exe "norm a\<CR>\<Esc>_"
    else
      let &magic = magic
      return
    endif
  endwhile
endfunction

nmap <leader>fs :call SplayFnCall()<CR>
nmap <leader>fu va(J%l"_x

Try not to be inside sub-parentheses, and hit \fs to splay, and \fu to unsplay (unless you redefined your leader, then adjust accordingly). Obviously, you can change the mapping.

As Ingo says, it is impossible to write this as regexp (because of nested delimiters), and it is impossible to do it in a simple macro (because of the branching logic). You really need the full power of a programming language.

Also, I didn't do it as a toggle because I wasn't too sure what would be the criterion for when you want to splay, and when to unsplay, so two different mappings.

Upvotes: 1

Ingo Karkat
Ingo Karkat

Reputation: 172540

Matching (nested) parentheses requires regular expression extensions that Vim doesn't have, see match parenthesised block using regular expressions in vim.

Instead, you'd better rely on the built-in % command to go to the matching parenthesis. Also, the argtextobj.vim - Text-object like motion for arguments (my improved fork here) works pretty well on your example.

With that, I would write a function (as the logic to proceed to the next argument, and checks where to stop will be rather complex) that is then invoked by a mapping. But if you're new to Vimscript, be aware that isn't trivial, and you may be better off to just use the mentioned plugin to speed up your manual reformatting. (On the other hand, if you manage to turn this into a plugin and post it on vim.org, it would certainly be well received by other Vim users.)

Upvotes: 0

Related Questions