Join every 3 lines spearated with tab (vim) - 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

Related

How to append each line from 11-20 to each line from 1-10 in VIM

I have a file in which first 10 lines are the columns of a table and the rest 10 lines are the values of each column.
How can I use norm in VIM to append the values after each column names like this:
column1
...
column10
value1
...value10
-->
column1: value1
...
column10: value10
It is a little similar with this(Vim - Copy Nth word of each line, from line 10-100, to end of line), but I don't know how to go to line 1:10 and append the copied lines.
Any idea will be appreciated!
Fairly naive and crude way to do this, but:
:1,10norm! 10j0d$10kA: ^[p
Explanation:
1,10norm!: for lines 1 to 10, do the following (the ! means any custom mapping you have will be ignored, thanks to D. Ben Knoble for reminding of this):
10j: move down 10 lines
0d$: delete the whole line (not including newline)
10k: move back up 10 lines
A:: append (at the end of the line) ': ' (note the trailing space)
^[: input escape character, going back to normal mode. This (^[) is a single character and is inputted by typing Ctrl-v then escape, not by typing ^[.
p: paste the line deleted in step 3
Another (copy-pastable) way, (ab)using the substitute command:
:1,10s/\v(.*)\ze(.*\n){10}(.*)/\1: \3/ | 11,20d
which does:
1,10s/: for lines 1 to 10, execute the following substitution:
\v: use very-magic regex mode (see :help \v)
(.*): capture the entire current line (eg column1)
\ze: signal the end of the match. This way everything read (and captured) afterwards will not be affected (but can still be read)
(.*\n){10}: skip 10 (including current) lines, ie skip selector to 10 lines below
(.*): capture the line (eg value1)
/: end the 'select' part of the substitute command
\1: \3: replace with captured groups (eg column1: value1)
|: command separator
11,20d: delete lines 11 to 20
Use blockwise-visual mode to perform the operations.
You can enter visual block mode with Ctrl-V and it allows you to select and operate on columns. It also allows you to perform the same action on a block, which you can use to add the : to the lines with the column names.
I'll use normal Vim syntax for keystrokes in my examples, <C-v> means Ctrl-V.
Start by deleting the values into the default register, using a visual block:
11G<C-v>9j$d
Then Add the : to the column lines, also using a visual block:
1G<C-v>9j$A: <Esc>
Then add some more spaces to the first line, to ensure there's room for all the column names to fit:
A <Esc>
Finally, put the visual block at the end of the first line:
$p
It will actually put it on all lines all the way to the end.
This is slightly different from what you specified, since the values are all aligned on the same column. If you want different spacing, you can perhaps use a :s operation to fix spacing.
10:s/: */: /<cr>
Depending on where you pasted (if some column names had more trailing spaces than the first one), you might have some trailing spaces after the pasted values to fix as well, but that should be easy to do using a similar procedure.
Visual block operations are really powerful, it's a great feature to learn and keep in your "toolbox" in Vim. They're really handy with this kind of problem where thinking in "columns" makes the most sense.

Copy selected text n times using vim editor

How is it possible to copy multiple lines and paste them 'n' number of times using vi editor.
For example:
This is the first line.
This is the second line.
This is the third line.
Now I would like to copy the 3 lines as they are 100 times one after the other like:
#First copy
This is the first line.
This is the second line.
This is the third line.
#Second copy
This is the first line.
This is the second line.
This is the third line.
...
...
Vim has a concept called count that helps to iterate a lot of commands.
In this case, yanking and pasting can be used with count.
3y will yank 3 lines.
100p will paste those lines 100 times.
run :h count to learn more
An optional number that may precede the command to multiply
or iterate the command. If no number is given, a count of one
is used, unless otherwise noted.
Assuming you want to copy those three consecutive lines:
This is the first line.
This is the second line.
This is the third line.
and paste them three-by-three like this:
This is the first line.
This is the second line.
This is the third line.
This is the first line.
This is the second line.
This is the third line.
This is the first line.
This is the second line.
This is the third line.
...
and your cursor is on the first line, you can achieve your goal with a mix of Ex commands and normal mode commands like this:
3:y<CR>jj99p
which expands to:
:.,.+2y<CR>jj99p
or like this with a visual-line selection:
Vjjy99p
or like this if you know the line numbers:
:1,3y|3|normal! 99p<CR>
or like this if you want to use the "paragraph" text-object and motion:
yip}99p
or simply:
3yjj99p
And I'm sure you could find a dozen more ways.

How to remove all the empty new lines in a document with one command?

My document looks something like this:
Line number one
Line number two
Line number three
I want the whole document to look like this:
Line number one
Line number two
Line number three
In other words, to remove all the empty lines. How to accomplish this?
Try :g/^$/d, which will remove all blank lines. The g indicates global, the ^$ is a regular expression that basically means 'match lines that start and end with nothing in between', and the d means delete. You can mix and match as much as you need :)
Another space-related command that may come in handy if you have random whitespace is :%s/\s\+$//, which trims any trailing whitespace (as #Bernhard points out, the $ operator means that you have a max of one occurrence per line, so the g is unnecessary).
Per the update, possible that the lines already contain whitespace, in which case :g/^\s*$/d should work.
The command I use is
:v/./d
The v command matches the lines that do not match the given pattern.
It was inherited from ed.

Vim Multi-line search pattern

I have a substitute command that captures and displays submatch() values in the replacement string. But I have another line of information that I want to parse below this line. That line is always the first line after an empty line, though the number of lines TO that empty line varies. For example:
The first important line I want to capture is here
Stuff I don't want.
A few more lines of stuff I don't want...
Second line I want to capture.
This pattern repeats a hundred or so times in a document. I can substitute "The First Important Line" fine, but shouldn't that search pattern include a way to jump down to the first empty line and then pick up the next "Second line I want to capture." ?? I could then place the contents of that second line into submatch parenthesis and substitute them where needed (right?).
If so, I cannot discover the way to extend the first search pattern to capture the "Second line" Suggestions or correcting my approach would be greatly appreciated.
Someone has already dealt with a similar problem. Below I provide their solution and the detailed description.
/^\nF\d\_.\{-}\_^\n\zs.*/+
It means "Find a block of lines that start with F and a digit,
then scan forward to the next blank line and select the line after that."
Part of regex
Meaning
^\n
Matches the start of a line, followed by a newline - i.e a blank line
F\d
The next line starts with an F followed by a digit
\_.\{-}
\_. is like ., but also matches newline. \{-} matches the minimum number of the preceeding \_.. (If I were to use * instead of \{-}, it would match to near the end-of file.)
\_^\n
Matches a blank line. \_^ is like ^, but ^ only works at the start of a regular expression.
\zs
When the match is finished, set the start of match to this point. I use this because I don't want the preceding text to be highlighted.
.*
Matches the whole line.
The + after the regular expression tells Vim to put the cursor on the line after the selection.
I think I read about offsets, but I can't find the bit in the help that is relevant right now. As such, my other solution would be to record a macro to do what you want:
qa/[Your pattern]<CR>jddq
You could then execute this macro with #a and repeat with ##; or run it a lot of times (e.g., 999#a).

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