14

I would like to join lines together only for lines which have certain pattern (such as ;), however when using g/;/j it doesn't work as expected unless called couple of times.

For example the following content:

a
1;
2;
3;
4;
5;
b
6;
7;
8;
9;
c

when using: :g/;/j the output is:

a
1; 2;
3; 4;
5; b
6; 7;
8; 9;
c

or :g/;/-j gives:

a 1; 2; 3; 4; 5;
b 6; 7; 8; 9;
c

similar with: :g/;\_.\{-};/j.

My expected output is:

a 
1; 2; 3; 4; 5;
b
6; 7; 8; 9;
c

or something similar, so all lines containing the pattern are joined together.

How this can be achieved?

1
  • 3
    FWIW, :g/;/j doesn't work because it is done in two passes: first the buffer is scanned, then the command is applied to the matching lines.
    – romainl
    Dec 31, 2015 at 6:59

2 Answers 2

16

Possible explanation of the problem

I think the reason why :g/;/j doesn't work is because the :g command operates with a 2-pass algorithm:

  • during the first pass it marks the lines containing the pattern ;
  • during the second pass it operates on the marked lines

During the second pass, :g joins the line 1; with line 2; because 1; was marked during the first pass. However I suspect (not sure) that it doesn't join 1; 2; with 3; because the line 2; doesn't exist anymore, its content has been merged with the line 1; which has already been processed.

So :g looks for the next line which was marked during first pass (3;) and joins it with the following one (4;). After that the problem repeats, it can't join 3; 4; with 5; because the line 4; doesn't exist anymore.

Solution 1 (with vimscript)

Maybe you could call a function whenever a line containing ; is found to check whether the previous line also contains a semicolon:

function! JoinLines()
    if getline(line('.')-1) =~ ';'
        .-1join
    endif
endfunction

Then use the following global command:

:g/;/call JoinLines()

Or without a function:

:g/;/if getline(line('.')-1) =~ ';' | -j | endif

Solution 2 (without vimscript)

:g/;/.,/^[^;]*$/-1j

Whenever the global command :g finds the pattern ; it executes the command: .,/^[^;]*$/-1j

It can be broken down like this:

:g/pattern/a,bj

Where :

pattern = ;
a       = .           = number of current line
b       = /^[^;]*$/-1 = number of next line without any semicolon minus one

b can be broken down further like this:

/    = look for the number of the next line matching the following pattern
^    = a beginning of line
[^;] = then any character except a semicolon
 *   = the last character can be repeated 0 or more times
 $   = an end of line
 /   = end of pattern
 -1  = removes one to the number you just got

j is the abbreviated form of the Ex command :join which like most other Ex commands can be preceded by a range.
Here it's preceded by the range: .,/^[^;]*$/-1 (a,b)
A range follows the form a,b where a and b are generally 2 line numbers, and allows you to operate on a group of lines whose number is between a and b, instead of just one.

So the j command joins all the lines between the current one (a) and the next one which doesn't contain any semicolon minus one (b).

For more information, see:

:help :global
:help :join
:help :range
1
  • I can't tell you how many times I visited your answer. When I think "ok this time I got it and can do it myself" then somehow I find myself in this answer :) Thank you.
    – caltuntas
    Sep 2, 2022 at 6:56
3

I do similar joining all the time with a global search and replace:

s/;\n/;/

\n matches newline.

To find and delete blank lines:

s/^$\n//

I am not sure why, but if want to insert a new line you have to use \r

4
  • s alone will work only for one line, to make it global, you need to use %s, but then it'll join almost all the lines, including non ; lines
    – kenorb
    Jan 1, 2016 at 20:39
  • 2
    @kenorb Ehm no, I think you can use the :s command exactly for what you want. I think this %s/;\n\(.*;\)\@=/;/ does what you need. Jan 1, 2016 at 21:01
  • @Christian Brabandt Your solution %s/;\n\(.*;\)\@=/;/ works perfectly, but what is \@= ?
    – Mark
    Jun 19, 2020 at 16:19
  • @Mark I just converted your answer to a comment; in doing so, I thought I would point out that you can use :help followed by almost anything to get more advice. Even just plain :help is very useful.
    – D. Ben Knoble
    Jun 19, 2020 at 19:36

Your Answer

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

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