1

I'm looking for a minimal way to convert code that looks like this:

my_function(param1, param2=value2, param4=a_function_call())

into something like:

my_function(
    param1,
    param2=value2,
    param4=a_function_call(),
)

I'm sure there's a plugin that could work, but that seems pretty overkill for what can probably just be done with a function and a mapping, or possibly even just a clever regex. Also this often comes up in situations where I don't have a LSP/linter/formatter set up (like typing markdown snippets or in languages I don't use very often) so "just use formatting tool X" doesn't really cover the situations I care about. What if I've got some LaTeX citations in a similar format \cite{key1, key2, key3} and I want to get each key on a newline? I'd like something that doesn't care about the underlying language.

Some details:

  • Trailing whitespace should be removed,
  • Trailing commas should be added,
  • The solution should also work for function definitions:
def my_function(param1, param2=value2, param3=value3):

should become:

def my_function(
    param1,
    param2=value2,
    param3=value3,
):
  • Indentation should be consistent regardless of the starting indentation:
    my_function(param1, param2=value2, param4=a_function_call())

should become:

    my_function(
        param1,
        param2=value2,
        param4=a_function_call(),
    )

Edit: To clarify a comment, I'm not interested in a solution that works for language X or language Y. I want something that solves the generic formatting problem of converting a list of elements all on one line (separated by a delimiter and surrounded by some sort of brackets) and converts it into a list of elements where each element is on a different line. This happens most frequently in code, but also in LaTeX, markdown, little bash scripts, etc, so I'm not interested in something that only works in one particular language.

11
  • 2
    That last trailing comma kills me :( How can this be valid then? In general I would just use something like this: :s/, /,\r\t/g |:s/)/\r&/ and fix the remaining stuff manually, as this may not be hundert percent correct for all edge cases Commented Nov 24, 2023 at 9:57
  • 1
    What did you try?
    – romainl
    Commented Nov 24, 2023 at 10:25
  • 1
    What did you try to solve your problem?
    – romainl
    Commented Nov 24, 2023 at 10:43
  • 2
    Could you tell us which language are you using? Commented Nov 24, 2023 at 12:44
  • 1
    Sorry @romainl I don't think I'm understanding you, here's what I think is the answer to your question but please clarify if I was wrong. I am hoping someone here who knows more about vim than I do can point to a clever way of solving my problem (kinda like what happened here: vi.stackexchange.com/q/43531/43462). The only solutions I can think of is to use a substitution (although I can't get this to fully work) or to record a macro (this has been pretty fragile and breaks often though). But I'm hoping there's someone smarter than myself who knows something that I don't.
    – beyarkay
    Commented Nov 24, 2023 at 12:57

4 Answers 4

1

A first start might be using tpope’s surround plugin to do cS(( to make the parens multi-line. Then you could attempt one of the substitutions like :substitute/, /,\r/g and clean up. But this last will fall apart as soon as one argument contains commas (say, f(g(a, b), c)).

1

You could be interested to the following: vim-argwrap plugin.

It provides an :ArgWrap command that seems to do exactly what you want.

1

If you're happy to install a plugin, you could try Andrew Radev's splitjoin.

It works with a wide range of languages, and you can configure new ones if necessary. By default it uses gS to split a single line into multiple ones, or gJ to go back again, but this too is configurable.

1

To solve this without a plugin, the following pure Vim solution relying on :substitute and = could be used. It's hard to provide a "works for all languages" solution as syntax differs between languages and there will also be the occasional edge case that breaks it.

Having said all that, here's:

:s/(\zs.*\ze)/\r&\r/|-1s/,\s*/,\r/g|normal j=g'.

The first :substitute command will put everything between the outer parenthesis on a line of its own. Due to the greediness of * it will also match inner parentheses.

The cursor will now be on the line with the closing parenthesis and we want to modify the line above, that's why we give a range of -1 to the second :substitute. It replaces each comma (optionally followed by whitespace) with a comma followed by a newline.

In a final step, go down one line to the closing parenthesis and use =g'. to filter all the lines up to the last change through =. This will indent the arguments, assuming it's setup correctly for the current filetype.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.