Join groups of N lines - vim

I have a text file generated from an old database that consists of one line per field, with no delimiter between records other than knowing how many fields there are. What I'd like to do is join the first N lines, and then the next N, and so on. Is there any way to do this within Vim? Is there were a way to select lines to apply a command to based on an arbitrary VimL expression (like line(".")%5==0) instead of just a regex?

There are multiple ways of solving this. First that comes to my mind is recording a macro, say in register w:
qw5Jjq
This essentially uses the J normal command to join 5 lines and moves one down. Then you can repeat the macro for 20 times with a simple 20#w or keep repeating afterwards with ##.
Another, maybe more "proper" way is using the :join ex command, which is the same as the J normal command, but can be abbreviated to :j and used in conjunction with the :g to operate in various lines. For example:
:g/./j5
This will match every line non-empty line and in each one of them, join the next 5 lines (inclusive). Then move to the next line and join more 5 and so on.

Related

Join every 3 lines spearated with tab (vim)

I'd like to join every 3 lines of a file with a tab character as separator. How can this be done using Vim?
I'm aware of the macro mechanism, but I?m looking for something more elegant.
It turns out that this works:
:g/\n/,+1s//\t
The :global will match every line in the buffer (or in the range, if you pass it a range.)
The /\n/ is being used as a regex that will match every line, in this case, on the line break itself. We could have used something like /^/ (or perhaps /./ or /\S/ to match non-empty or non-blank lines), here we're using /\n/ since we want to use that pattern in the following :s, so we can omit it there to use the same pattern.
Then, for each line processed by :g, we use a range of that line up to line +1. That means two lines, in this case, current line and next one. Since we want to join three lines, we want to replace the line break on two lines, so from current line up to line +1. (You could generalize that to using + the number of lines in the blocks you want to join, minus two.)
Finally, we perform the substitution s//\t, which is equivalent to s/\n/\t/ (using an empty pattern will match the previously used on, in this case the one passed to :g.) This :substitute will replace the matched line breaks with a tab character, effectively joining lines where it matches. Since we're using ranges of two lines, it will only do so two lines at a time, effectively replacing two line breaks, which will join three lines.
This works because the way :global works when there are edits on the affected lines. It first "marks" the lines that should be acted on, but then if the line is no longer there, it will skip it. So while it will first mark every line, when the :s joins every second and third line to the first in a block, the marks on them will no longer be there, so the end result is that :g will not try to process this line again and will move on to the next "marked" line, which will then become the start of the next block.
I would go with two :help :normal commands…
Append a tab to every line:
:%normal A^i
with the literal ^i being obtained with <C-v><Tab>.
Join every group of three lines:
:%normal 3J
I would recommend using macro, doing the process manually once and bind it into one key, but if you want to use it regularly I would recommend you to add a mapped command in your .vimrc

Using gvim, to copy few lines and paste them at the start of each line [duplicate]

I’d like to merge two blocks of lines in Vim, i.e., take lines k through l and append them to lines m through n. If you prefer a pseudocode explanation: [line[k+i] + line[m+i] for i in range(min(l-k, n-m)+1)].
For example,
abc
def
...
123
45
...
should become
abc123
def45
Is there a nice way to do this without copying and pasting manually line by line?
You can certainly do all this with a single copy/paste (using block-mode selection), but I'm guessing that's not what you want.
If you want to do this with just Ex commands
:5,8del | let l=split(#") | 1,4s/$/\=remove(l,0)/
will transform
work it
make it
do it
makes us
harder
better
faster
stronger
~
into
work it harder
make it better
do it faster
makes us stronger
~
UPDATE: An answer with this many upvotes deserves a more thorough explanation.
In Vim, you can use the pipe character (|) to chain multiple Ex commands, so the above is equivalent to
:5,8del
:let l=split(#")
:1,4s/$/\=remove(l,0)/
Many Ex commands accept a range of lines as a prefix argument - in the above case the 5,8 before the del and the 1,4 before the s/// specify which lines the commands operate on.
del deletes the given lines. It can take a register argument, but when one is not given, it dumps the lines to the unnamed register, #", just like deleting in normal mode does. let l=split(#") then splits the deleted lines into a list, using the default delimiter: whitespace. To work properly on input that had whitespace in the deleted lines, like:
more than
hour
our
never
ever
after
work is
over
~
we'd need to specify a different delimiter, to prevent "work is" from being split into two list elements: let l=split(#","\n").
Finally, in the substitution s/$/\=remove(l,0)/, we replace the end of each line ($) with the value of the expression remove(l,0). remove(l,0) alters the list l, deleting and returning its first element. This lets us replace the deleted lines in the order in which we read them. We could instead replace the deleted lines in reverse order by using remove(l,-1).
An elegant and concise Ex command solving the issue can be obtained by
combining the :global, :move, and :join commands. Assuming that
the first block of lines starts on the first line of the buffer, and
that the cursor is located on the line immediately preceding the first
line of the second block, the command is as follows.
:1,g/^/''+m.|-j!
For detailed explanation of this technique, see my answer to
an essentially the same question “How to achieve the “paste -d '␣'”
behavior out of the box in Vim?”.
To join blocks of line, you have to do the following steps:
Go to the third line: jj
Enter visual block mode: CTRL-v
Anchor the cursor to the end of the line (important for lines of differing length): $
Go to the end: CTRL-END
Cut the block: x
Go to the end of the first line: kk$
Paste the block here: p
The movement is not the best one (I'm not an expert), but it works like you wanted. Hope there will be a shorter version of it.
Here are the prerequisits so this technique works well:
All lines of the starting block (in the example in the question abc and def) have the same length XOR
the first line of the starting block is the longest, and you don't care about the additional spaces in between) XOR
The first line of the starting block is not the longest, and you additional spaces to the end.
Here's how I'd do it (with the cursor on the first line):
qama:5<CR>y$'a$p:5<CR>dd'ajq3#a
You need to know two things:
The line number on which the first line of the second group starts (5 in my case), and
the number of lines in each group (3 in my example).
Here's what's going on:
qa records everything up to the next q into a "buffer" in a.
ma creates a mark on the current line.
:5<CR> goes to the next group.
y$ yanks the rest of the line.
'a returns to the mark, set earlier.
$p pastes at the end of the line.
:5<CR> returns to the second group's first line.
dd deletes it.
'a returns to the mark.
jq goes down one line, and stops recording.
3#a repeats the action for each line (3 in my case)
As mentioned elsewhere, block selection is the way to go. But you can also use any variant of:
:!tail -n -6 % | paste -d '\0' % - | head -n 5
This method relies on the UNIX command line. The paste utility was created to handle this sort of line merging.
PASTE(1) BSD General Commands Manual PASTE(1)
NAME
paste -- merge corresponding or subsequent lines of files
SYNOPSIS
paste [-s] [-d list] file ...
DESCRIPTION
The paste utility concatenates the corresponding lines of the given input files, replacing all but the last file's newline characters with a single tab character,
and writes the resulting lines to standard output. If end-of-file is reached on an input file while other input files still contain data, the file is treated as if
it were an endless source of empty lines.
Sample data is the same as rampion's.
:1,4s/$/\=getline(line('.')+4)/ | 5,8d
I wouldn't think make it too complicated.
I would just set virtualedit on
(:set virtualedit=all)
Select block 123 and all below.
Put it after the first column:
abc 123
def 45
... ...
and remove the multiple space between to 1 space:
:%s/\s\{2,}/ /g
I would use complex repeats :)
Given this:
aaa
bbb
ccc
AAA
BBB
CCC
With the cursor on the first line, press the following:
qa}jdd''pkJxjq
and then press #a (and you may subsequently use ##) as many times as needed.
You should end up with:
aaaAAA
bbbBBB
cccCCC
(Plus a newline.)
Explaination:
qa starts recording a complex repeat in a
} jumps to the next empty line
jdd deletes the next line
'' goes back to the position before the last jump
p paste the deleted line under the current one
kJ append the current line to the end of the previous one
x delete the space that J adds between the combined lines; you can omit this if you want the space
j go to the next line
q end the complex repeat recording
After that you'd use #a to run the complex repeat stored in a, and then you can use ## to rerun the last ran complex repeat.
There can be many number of ways to accomplish this. I will merge two blocks of text using any of the following two methods.
suppose first block is at line 1 and 2nd block starts from line 10 with the cursor's initial position at line number 1.
(\n means pressing the enter key.)
1. abc
def
ghi
10. 123
456
789
with a macro using the commands: copy,paste and join.
qaqqa:+9y\npkJjq2#a10G3dd
with a macro using the commands move a line at nth line number and join.
qcqqc:10m .\nkJjq2#c

vim change newline into space but not every nth-line

I have a file.txt of 1000 lines and every first 10 lines need to be on one line.
The eleventh line starts on a new line and is added with lines 12-20.
How can I do this in Vim!
I'm not 100% sure I grok what you're after, but I'll give you a few hints and perhaps they'll help.
To join lines together you can use J (that's capital J or Shift-J). You can precede this with a number, such as 10, and join 10 lines together. For example, typing 10J will give you:
Now, what you really need is a macro. This sounds fancy but it's just a way or recording a set of commands. So for example you could take 10J and record it to a macro. Repeat that macro 10 times and then you do that to 100 lines.
I think that the solution to your problem would therefore be something like this:
qa10Jjjq99#a
This does this (see below for an explanation):
Explanation:
q starts recording a macro. Everything that you type afterwards, until you type q again will be recorded. The next character a records the macro to the character a. Thus, qa of qa10Jjjq99#arecords 10Jjj to a. Now, to use the macro that you have stored to a you use the # symbol followed by the letter of where you stored the macro (in this case a, because we typed qa). The 99 means repeat this 99 times, thus, 99#a means repeat what's stored in macro a 99 times.
All together qa10Jjjq99#a means: record a macro q and store it in a. Then, join 10 lines 10J and move down two lines jj before stopping the recording q. Then repeat the macro stored 99 times 99#a.

How to merge two multi-line blocks of text in Vim?

I’d like to merge two blocks of lines in Vim, i.e., take lines k through l and append them to lines m through n. If you prefer a pseudocode explanation: [line[k+i] + line[m+i] for i in range(min(l-k, n-m)+1)].
For example,
abc
def
...
123
45
...
should become
abc123
def45
Is there a nice way to do this without copying and pasting manually line by line?
You can certainly do all this with a single copy/paste (using block-mode selection), but I'm guessing that's not what you want.
If you want to do this with just Ex commands
:5,8del | let l=split(#") | 1,4s/$/\=remove(l,0)/
will transform
work it
make it
do it
makes us
harder
better
faster
stronger
~
into
work it harder
make it better
do it faster
makes us stronger
~
UPDATE: An answer with this many upvotes deserves a more thorough explanation.
In Vim, you can use the pipe character (|) to chain multiple Ex commands, so the above is equivalent to
:5,8del
:let l=split(#")
:1,4s/$/\=remove(l,0)/
Many Ex commands accept a range of lines as a prefix argument - in the above case the 5,8 before the del and the 1,4 before the s/// specify which lines the commands operate on.
del deletes the given lines. It can take a register argument, but when one is not given, it dumps the lines to the unnamed register, #", just like deleting in normal mode does. let l=split(#") then splits the deleted lines into a list, using the default delimiter: whitespace. To work properly on input that had whitespace in the deleted lines, like:
more than
hour
our
never
ever
after
work is
over
~
we'd need to specify a different delimiter, to prevent "work is" from being split into two list elements: let l=split(#","\n").
Finally, in the substitution s/$/\=remove(l,0)/, we replace the end of each line ($) with the value of the expression remove(l,0). remove(l,0) alters the list l, deleting and returning its first element. This lets us replace the deleted lines in the order in which we read them. We could instead replace the deleted lines in reverse order by using remove(l,-1).
An elegant and concise Ex command solving the issue can be obtained by
combining the :global, :move, and :join commands. Assuming that
the first block of lines starts on the first line of the buffer, and
that the cursor is located on the line immediately preceding the first
line of the second block, the command is as follows.
:1,g/^/''+m.|-j!
For detailed explanation of this technique, see my answer to
an essentially the same question “How to achieve the “paste -d '␣'”
behavior out of the box in Vim?”.
To join blocks of line, you have to do the following steps:
Go to the third line: jj
Enter visual block mode: CTRL-v
Anchor the cursor to the end of the line (important for lines of differing length): $
Go to the end: CTRL-END
Cut the block: x
Go to the end of the first line: kk$
Paste the block here: p
The movement is not the best one (I'm not an expert), but it works like you wanted. Hope there will be a shorter version of it.
Here are the prerequisits so this technique works well:
All lines of the starting block (in the example in the question abc and def) have the same length XOR
the first line of the starting block is the longest, and you don't care about the additional spaces in between) XOR
The first line of the starting block is not the longest, and you additional spaces to the end.
Here's how I'd do it (with the cursor on the first line):
qama:5<CR>y$'a$p:5<CR>dd'ajq3#a
You need to know two things:
The line number on which the first line of the second group starts (5 in my case), and
the number of lines in each group (3 in my example).
Here's what's going on:
qa records everything up to the next q into a "buffer" in a.
ma creates a mark on the current line.
:5<CR> goes to the next group.
y$ yanks the rest of the line.
'a returns to the mark, set earlier.
$p pastes at the end of the line.
:5<CR> returns to the second group's first line.
dd deletes it.
'a returns to the mark.
jq goes down one line, and stops recording.
3#a repeats the action for each line (3 in my case)
As mentioned elsewhere, block selection is the way to go. But you can also use any variant of:
:!tail -n -6 % | paste -d '\0' % - | head -n 5
This method relies on the UNIX command line. The paste utility was created to handle this sort of line merging.
PASTE(1) BSD General Commands Manual PASTE(1)
NAME
paste -- merge corresponding or subsequent lines of files
SYNOPSIS
paste [-s] [-d list] file ...
DESCRIPTION
The paste utility concatenates the corresponding lines of the given input files, replacing all but the last file's newline characters with a single tab character,
and writes the resulting lines to standard output. If end-of-file is reached on an input file while other input files still contain data, the file is treated as if
it were an endless source of empty lines.
Sample data is the same as rampion's.
:1,4s/$/\=getline(line('.')+4)/ | 5,8d
I wouldn't think make it too complicated.
I would just set virtualedit on
(:set virtualedit=all)
Select block 123 and all below.
Put it after the first column:
abc 123
def 45
... ...
and remove the multiple space between to 1 space:
:%s/\s\{2,}/ /g
I would use complex repeats :)
Given this:
aaa
bbb
ccc
AAA
BBB
CCC
With the cursor on the first line, press the following:
qa}jdd''pkJxjq
and then press #a (and you may subsequently use ##) as many times as needed.
You should end up with:
aaaAAA
bbbBBB
cccCCC
(Plus a newline.)
Explaination:
qa starts recording a complex repeat in a
} jumps to the next empty line
jdd deletes the next line
'' goes back to the position before the last jump
p paste the deleted line under the current one
kJ append the current line to the end of the previous one
x delete the space that J adds between the combined lines; you can omit this if you want the space
j go to the next line
q end the complex repeat recording
After that you'd use #a to run the complex repeat stored in a, and then you can use ## to rerun the last ran complex repeat.
There can be many number of ways to accomplish this. I will merge two blocks of text using any of the following two methods.
suppose first block is at line 1 and 2nd block starts from line 10 with the cursor's initial position at line number 1.
(\n means pressing the enter key.)
1. abc
def
ghi
10. 123
456
789
with a macro using the commands: copy,paste and join.
qaqqa:+9y\npkJjq2#a10G3dd
with a macro using the commands move a line at nth line number and join.
qcqqc:10m .\nkJjq2#c

How to insert variable number of spaces necessary to align textual columns in Vim?

I’m a fan of Visual mode in Vim, as it allows to insert text before any given column.
For example, insertion of spaces after the quotation leaders below:
> one
> two
> three
can be done via <Ctrl-V>jjI <Esc>:
> one
> two
> three
as follows:
Start Visual mode with Ctrl-V.
Extend visual selection with jj.
Insert some spaces with I__.
Propagate the change to all the lines of the block selection with Esc.
Now I have a text file that needs some formatting. This is what it looks like:
start() -- xxx
initialize() -- xxx
go() -- xxx
Now I want to align part of this text to arrange it into columns like this:
start() -- xxx
initialize() -- xxx
go() -- xxx
The problem I have is that I cannot insert a different amount of indentation into each line and merely indenting a fixed amount of spaces/tabs is insufficient.
How can you do an indentation where all indented text will have to be aligned at the same column?
Update
I only figured out a rather verbose and unwieldy method:
Find the string position to indent from: \--.
Insert n (let's say 20) spaces before that: 20i <Esc>.
Delete a part of those spaces back to a certain column (let's say 15): d|15.
Save those steps as a macro and repeat the macro as often as necessary.
But this approach is very ugly, though!
I'm much better off without any vim plugins.
Here is my solution:
<Shift-V>jj:!column -ts --
Then insert -- into multiple lines just as you wrote in the question.
You can also append a number of comments at insertion time.
:set virtualedit=all
<Ctrl-V>jjA-- xxx<Esc>
You have to use a specific plugin, you can use either Tabular or Align plugin in this case.
They both allow you to align text on specific characters, like -- in your example. Their syntax is a bit different though. Pick the one that suit you the most.
Without plugin and if you have already entered your comments without emix's solution:
:,+2 s/--/ &
This will ensure all comments are to be shifted leftwise in order to align them properly.
Then blockwise select the column to which you want to align the text, and : 100<
An easy way to align text in columns is to use the Tabular or
Align plugin. If neither of these is ready at hand, one can use
the following somewhat tricky (and a little cumbersome looking) yet
perfectly working (for the case in question) commands.1,2
:let m=0|g/\ze -- /let m=max([m,searchpos(#/,'c')[1]])
:%s//\=repeat(' ',m-col('.'))
The purpose of the first command is to determine the width of the
column to the left of the separator (which I assume to be --
here). The width is calculated as a maximum of the lengths of the text
in the first column among all the lines. The :global command is used
to enumerate the lines containing the separator (the other lines do
not require aligning). The \ze atom located just after the beginning
of the pattern, sets the end of the match at the same position where
it starts (see :help \ze). Changing the borders of the match does
not affect the way :global command works, the pattern is written in
such a manner just to match the needs of the next substitution
command: Since these two commands could share the same pattern, it can
be omitted in the second one.
The command that is run on the matched lines,
:let m=max([m,searchpos(#/,'c')[1]])
calls the searchpos() function to search for the pattern used in the
parent :global command, and to get the column position of the match.
The pattern is referred to as #/ using the last search pattern
register (see :help "/). This takes advantage of the fact that the
:global command updates the / register as soon as it starts
executing. The c flag passed as the second argument in the
searchpos() call allows the match at the first character of a line
(:global positions the cursor at the very beginning of the line to
execute a command on), because it could be that there is no text to
the left of the separator. The searchpos() function returns a list,
the first element of which is the line number of the matched position,
and the second one is the column position. If the command is run on
a line, the line matches the pattern of the containing :global
command. As searchpos() is to look for the same pattern, there is
definitely a match on that line. Therefore, only the column starting
the match is in interest, so it gets extracted from the returning list
by the [1] subscript. This very position equals to the width of the
text in the first column of the line, plus one. Hence, the m variable
is set to the maximum of its current value and that column position.
The second command,
:%s//\=repeat(' ',m-col('.'))
pads the first occurrence of the separator on all of the lines that
contain it, with the number of spaces that is missing to make the text
before the separator to take m characters, minus one. This command
is a global substitution replacing an empty interval just before the
separator (see the comment about the :global command above) with the
result of evaluation of the expression (see :help sub-replace-\=)
repeat(' ',m-col('.'))
The repeat() function repeats its first argument (as string) the
number of times given in the second argument. Since on every
substitution the cursor is moved to the start of the pattern match,
m-col('.') equals exactly to the number of spaces needed to shift
the separator to the right to align columns (col('.') returns the
current column position of the cursor).
1 Below is a one-line version of this pair of commands.
:let m=0|exe'g/\ze -- /let m=max([m,searchpos(#/,"c")[1]])'|%s//\=repeat(' ',m-col('.'))
2 In previous revisions of the answer the commands used
to be as follows.
:let p=[0]|%s/^\ze\(.*\) -- /\=map(p,'max([v:val,len(submatch(1))+1])')[1:0]/
:exe'%s/\ze\%<'.p[0].'c -- /\=repeat(" ",'.p[0].'-col("."))'
Those who are interested in these particular commands can find their
detailed description in this answer’s edit history.
This is a modification on Benoit's answer that has two steps.
First step, block select text search and replace -- with lots of spaces.
'<,'>s/--/ --/
Now all the comments should have lots of spaces, but still be uneven.
Second step, block select the text again and use another regex to match all the characters you want to keep (say the first 20 characters or so) plus all the spaces following, and to replace it with a copy of those first 20 characters:
'<,'>s/\(.\{20}\)\s*/\1/
Not quite as easy as Benoit's, but I couldn't figure out how to make his second step work.

Resources