8

I have a common usecase when I transform some python expression the following way:

value 1
value 2
value 3

into

['value 1', 'value 2', 'value 3']

The easiest way may be to use a mapping, but I wanted to use a substitution for this task.

So far I got:

s/\(.*\n\)\+/[&]/g

Which result in

[value 1
value 2
value 3
]

This raise a question, because I want to be able to match the \(.*\), but not the \n and the use the result of the matched inside a '...'.

Do you know how to do this?

  • 2
    I don't know how to do it in a single substitution, but you could do it in 2 while in visual mode (after selecting the python expression): :'<,'>s/\v(.*)\n/'\1', / | s/\v(.*), /[\1]/ You could transform this into a visual mapping: xnoremap ,x :s/\v(.*)\n/'\1', / <Bar> s/\v(.*), /[\1]/<CR> and maybe into a normal mapping if the expression is inside a paragraph: nnoremap ,x :'{+1,'}-1s/\v(.*)\n/'\1', / <Bar> s/\v(.*), /[\1]/<CR> Here the mapping would be ,x. – user9433424 May 11 '16 at 16:36
  • 1
    couldn't do with regex, but using external commands :%! echo "[$(sed "s/.*/'&',/" % | tr '\n' ' ' | sed 's/, $//')]" – Sundeep May 13 '16 at 13:04
5

Edit

It is possible to do this in one expression if we use a "sub-replace-expression." See bottom for info on that.

/Edit

The problem here is that you want to do two different things.

  1. Operate on the match as a whole (i.e. surround it with [])

  2. Operate on each item in the match (i.e. surround them with '',)

You can easily do either one:

  1. :s/\(.\+\n\)\+/[&]/
  2. :%s/\(.\+\)\n/'\1', /

but as far as I know there's no way to do both in a single operation. I did try to get the proper output with something like:

:s/\(\(.\+\)\n\)\+/[\2]/

But of course the problem with this is that the \2 matches only the last match from the second set of memory parentheses \(\) and doesn't "remember" anything before that. So you end up with only the last line.

I would recommend doing some pre/post processing with an additional :s/// command to get rid of the newlines before/after the fact. Here's what I came up with

function! FormatExpression()
   .,/\n^$/s/\(.*\)\n/'\1', /
   s/\(.*\), /[\1]/
endfunction

1st line (Remove newlines)

  • .,/\n^$/ This is a range modifier for the search and replace. Without this, the command will go on to mutilate your entire file. Currently it goes from the current line ., to the next blank line \n^$. I'm not sure how you were intending to split things up, but you need some way to tell it to stop.
  • s/ The beginning of a search and replace command
  • \(.*\)\n Match the whole line, but only save the part without the newline.
  • '\1', Replace line with the match surrounded by single quotes and append a comma.

2nd line (Surround in brackets)

  • \(.*\), Match the whole line but not the last comma and space
  • [\1] Surround with brackets and also remove superfluous ending comma and space.

I'm going to keep looking into this, but at the moment I don't think it's possible with a single expression. :(

EDIT:

I have found a way to do this with one expression! Internally this is actually two substitutions, but it is technically one expression. Here's what I came up with:

:s/\v((.+\n)*.+)\n/\= "['" . substitute(submatch(1), '\n', "', '", 'g') . "']" /
  • :s///: Do a substitution
  • \v((.+\n)*.+)\n: Basically gathers up all the next non-blank lines and stores it all except for the final \n
  • \= Allows us to use an expression in the replacement (see :h sub-replace-expression)
  • substitute(submatch(1)...): Replaces all stored \n with ', '
  • "['" . ... . "']": Prepends [' and appends ']

This will start at the position of the cursor and go until it finds a blank line (^\n). Not grabbing the last \n is important as without that bit we're left with an additional ', that we don't want at the end.

Some might consider this more complex than the previous two-expression answer. But I thought I would go ahead and add this since it is in fact possible to do it with one expression. :)

2

Visually highlight, then:

:'<,'> s/.*/['&']/ | *j! | s/]\[/, /ge

It surrounds each line, to make e.g. ['value 1'], joins them all, then replaces adjacent ] and [ with comma-space.

The documentation for the * in *j! is at :help cpo-star, by the way. That one's a bit tricky to find.

  • Nice work around :) – nobe4 Jul 29 '16 at 5:34
  • Actually you can use :'<,'>s/\v(.*)(\_.)/['\1']/ and remove the joining. – nobe4 Jul 29 '16 at 11:31
  • Yeah, but it eats the final \n, that's why I used :join. I probably should have mentioned that. :-) – Antony Jul 29 '16 at 11:36
  • 1
    How about '<,'>s/.*/['&']/ | *s/]\_.\[/, / then? – nobe4 Jul 29 '16 at 11:45
  • 1
    Yeah, that's better. Though I'd probably write the second part as *s/]\n\[/, /e. – Antony Jul 29 '16 at 11:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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