J-Win
J-Win

Reputation: 1492

Vim Multi-Line If-Statements into Single-Line If-Statements

Is it possible to have a vim binding that turns multi-line if-statements into single-line if-statements and vice-versa?

turn this

if () {
    statement_1;
    statement_2;
} else if () {
    statement_3; statement_4;
} else if () {
    statement_5;
} else {

}

into this

if ()      { statement_1; statement_2 }
else if () { statement_3; statement_4; }
else if () { statement_5; }
else       { }

or anything close to the above behavior? I was thinking of having the command execute upon visually selecting the block to convert and then use searches for else if and entering new lines, etc. But my problem was determining how many else if were in the code

Upvotes: 1

Views: 1425

Answers (1)

Martin Tournoij
Martin Tournoij

Reputation: 27822

Join all the lines to a single line by selecting them in visual mode V and pressing J; then add a newline before every else with :s/else/\relse/. You'll end up with:

if () { statement_1; statement_2; } 
else if () { statement_3; statement_4; }
else if () { statement_5; } 
else { }

The \r in the replacement pattern is a newline (you need to use \n and search and \r in replace; don't ask me why).

Next step is to put all the start braces in the same column. I'd use the tabular plugin for this, which makes that very easy:

:%Tabularize /{/

With % we operate on the entire buffer, in a "real" file you'll probably want to use a more restrictive range or visual mode. There are some other plugins which do something similar as well.

You should now have the output you want.


If you don't want to use a plugin, you can use the column command:

:%!column -ts{ -o{

If you want a "Vim-only" solution, then it's a bit more complex:

:let b:column = 10
:%s/^\(.\{-}\) {/\=submatch(1) . repeat(' ', b:column - len(submatch(1))) . ' {'/

Breaking that down:

  • I used the b:column variable to specify the column to align to. You don't need this but it make it a bit easier to edit this number later on.

  • ^\(.\{-}\) { puts everything before { in a subgroup.

  • In the replace we used an expression (as indicated with \=). See :help sub-replace-\=.
  • First we put the if ... back with submatch(1)
  • Then we insert as many spaces as we need with repeat(' ', b:column - len(submatch(1)))
  • Finally we insert the literal { back.

I told you it was a bit more complex ;-) If you don't want tabular. Personally, I'd just start insert mode to insert spaces, which will be a lot faster than writing & debugging this (relevant xkcd).


Note that I didn't make some "magic" command which re-arranges all the text with just a stroke of a key. I don't think such a command would be a good idea. In practice there will be a lot of edge cases that such a command won't handle. Fully "parsing" a programming language with ad-hoc editing commands and/or regexps doesn't really work all that great.

Where Vim really shines is giving the user powerful text editing commands, which can be applied and combined with minimal effort, which is exactly what I did above. There are several other ways one can use to get the same effect.

But if you really want to, you can of course combine all of the above in a command:

fun! s:reformat(line1, line2)
    " Remember number of lines for later
    let l:before = line('$')

    " Join the lines
    execute 'normal! ' . (a:line2 - a:line1 + 1) . 'J'

    " Put newline before else
    :s/else/\relse/

    " Run tabular; since the number of lines change we need to calculate the range.
    " You could also use one of the other methods here, if you want.
    let l:line2 = a:line2 - (l:before - line('$'))
    execute a:line1 . ',' . l:line2 . 'Tabularize /{/'
endfun

command! -range Reformat :call s:reformat(<line1>, <line2>)

Upvotes: 3

Related Questions