A vim newbie here.

Would like to know an efficient way of converting

self.mat = material
self.x = x


self.mat, self.epsilon, self.x = material, epsilon, x

Andrew Radev's splitjoin plugin is designed specifically for this (and many similar) use cases.

After installation, simply place your cursor on the first line and type gJ to convert from a multiple assignments to a single one.

If you need to go in the other direction, you can type gS on a single-line multiple assignment.


I'm not sure this is what you want, but you could try these global commands:

g/^self\./let l=[]|s/\s*=\s*\(\w*\)/\=add(l,submatch(1))[-1][-1]/g|s/\s/, /g|s/$/\=' = '.join(l,', ')

The first command should turn this block:

self.mat = material
self.x = x

into this line:

self.mat = material self.epsilon=epsilon self.x = x

The second command should turn the latter line into:

self.mat, self.epsilon, self.x = material, epsilon, x

Note that it doesn't work if the last line of the buffer starts with self.. In that case, maybe you could add an empty line at the end, before running the commands.

If this is a frequent task, and you don't want to remember these commands, you can wrap them inside a custom command. As an example:

com! -bar -range=% AssignmentMulti2Single call s:assignment_multi2single(<line1>,<line2>)
fu! s:assignment_multi2single(lnum1,lnum2) abort
    if getline('$') =~# '^self\.' | $put='' | endif
    exe a:lnum1 . 'ka'
    exe a:lnum2 . 'kb'
    sil 'a,'bg/^self\./,/^\(self\.\)\@!/-j
    sil 'a,'bg/^self\./let l=[]|s/\s*=\s*\(\w*\)/\=add(l,submatch(1))[-1][-1]/g|s/\s/, /g|s/$/\=' = '.join(l,', ')

You can use this :AssignmentMulti2Single custom command on an arbitrary range, like :12,34 to target the lines from the address 12 to 34, or like '<,'> to target the last lines which were visually selected.

Note that this time, the command works even if the last line starts with self\..

I haven't tested the code a lot; only on this text file:

self.mat = material
self.x = x

some text

self.one = abc
self.three = ghi

some other text

self.four = jkl
self.six = pqr

Which :AssignmentMulti2Single turns into:

self.mat, self.epsilon, self.x = material, epsilon, x

some text

self.one, self.two, self.three = abc, def, ghi

some other text

self.four, self.five, self.six = jkl, mno, pqr

enter image description here

So there are probably special cases that it doesn't handle well.

For more information, see:

:h :g
:h :j
:h :s
:h :s\=
:h :com
:h :command-bar
:h :command-range
:h <line1>
:h :put
:h :k
:h :silent
:h add()
:h submatch()
:h join()
:h getline()
:h =~#
:h /^
:h /$
:h /\(
:h /\@!
:h \s
:h \w

There might be plug-ins that do this particular transformation, or you might be able to use two fairly long :s/// commands to isolate each side.

But personally I find that using Vim's Visual Block feature is the best way to handle column data, which is the main kind of transformation you have here.

You can start by aligning the =s so you really have three columns (the variables to be assigned, the =s and the expressions.) I previously wrote an answer on how to do that using visual block mode.

Once you have that, you can again use visual block mode to cut the right column and paste it into its own lines. Go to the start of material, then use CTRL-V to start visual block mode, then 2j to select the three lines and finally $ to select to the end. Use d or x to cut that text.

Navigate to a blank line (for example, use o at the last line of the file to create a new bla m one and then <Esc> to go back to normal mode.) Then paste with :put (use :put instead of P or p to force pasting in likewise mode.) You'll have your three expressions in different lines.

Visually select the first two again: CTRL-V, j, $ and add commas. You can use A,<Esc> which will add commas to the end of every line in the visual block selection.

Finally join them with 3J.

With the variable names, first you'll need to clean up the whitespace at the end, together with the =, which you can do with 3:s/ *= *$//. At that point, you can use the same procedure to add commas to the end of the first few lines (except the last one) and join them together. (Also possible is 3:norm! f D to jump to the first space and delete til the end of the line, or 3:norm! f C, to add a comma already.)

Here all you need to do is insert an = back to the line with the list of variables, and paste back the list of expressions to the end of that.

All done!

It's a bit of work, but it's a really flexible method that you can use in many similar situations, visual block mode is a tool in Vim I definitely recommend that everyone who uses Vim should really learn! It can be really useful for this kind of text transformation.

UPDATE: @user938271 was kind enough to record this into a video for a nice visual reference!

Video recording of demonstration

  • Just to illustrate: i.imgur.com/AwZSRcu.jpg – user938271 Sep 11 at 10:24
  • 2
    I think pasting with :pu instead of P is slightly better; with P there is still the risk that the deleted text is mixed with some pre-existing text lines, because it's pasted blockwise. OTOH, with :pu the text is pasted linewise, even if the unnamed register contains a blockwise text. And, fwiw, I always append a bang to :norm, to prevent custom mappings from interfering (which, in my case, happens frequently). – user938271 Sep 11 at 10:24
  • Thank you so much @user938271! Updated the answer to include your awesome video. You're clearly raising the bar here! – filbranden Sep 11 at 12:36
  • 1
    Thank you, guys. Visual Blocks are awesome!. Appreciate video very much. – plasmon360 Sep 11 at 17:22

In terms of keystroke efficiency, it's hard to beat a recorded macro.

Starting with your cursor on the first self., typing the following will create a macro in your "q register which add the next line that begins with self. to the current assignment:

Emaqq/^self\.<CR>d3ei <Esc>`aa, <Esc>pma``d2wywdd`aA, <Esc>"0pq

You can then replay this once with @q, multiple times with N@q (so, e.g. for @user938271's example file, you would type 7@q), or for every matching line, you can just switch off 'wrapscan' and use a large number:

:set nows

The macro playback will then abort when there are no more matching lines in the file.

How it works

Let's break it down into smaller pieces:

  • Ema First, move to the end of the first variable, and set a mark 'a
  • qq Start recording a macro into register "q.
  • /^self\.<CR> Search for the next line that begins with self.
  • d3e Delete the variable name
  • i<Space><Esc> In order to be sure that the macro will work on all lines, we add a space character here, so that there is always whitespace on the line before the equals sign
  • `a Jump to the position we marked earlier
  • a,<Space><Esc>p Append a comma, a space, and then paste the variable we deleted
  • ma Reset the mark
  • `` Jump back to our previous position (on the line we're merging from)
  • d2wywdd Delete the equals sign and surrounding whitespace (d2w), yank the rhs of the assignment (yw), and then delete the line (dd)
  • `a Jump back to the position we just marked
  • A,<Space><Esc>"0p Append a comma and a space to the end of the line, and then paste in the value that we yanked
  • q End the recording

We could shave off a few keystrokes here and there by using some more advanced Vim features (e.g. :help registers, :help i_CTRL-R), but this version offers a nice balance between simplicity and speed of recording.

We could also make some tweaks to make it more robust (e.g. to ignore lines like self.do_something()), but this works for the examples supplied by you and @user938271).

As the other answers on this page have videos, here's a screen recording of this answer in action (sadly without a keyboard overlay):


For more information


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.