Using Vim I'm really a fan of the visual mode that allows you to insert text before a column.
Insert some spacing after the arrows,
> one
> two
> three
can be done via <Ctrl-V>jjI <Esc>
:
> one
> two
> three
- go to visual mode
<Ctrl-V>
,
- extend visual selection
jj
,
- insert some spaces
I__
,
- propagate the change to all the lines of blockwise visual selection
<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?
EDIT: 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,
...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 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.
So, m
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 detailed
description in the history of edits.
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.