Reputation: 797
I have a text file with two words of different lengths on each line. For better reading, I want to have the second word on each line to be preceded with enough space characters to line up with the second words on all of the following lines.
For instance, given the text:
bla foo
barbla barfoo
foblaaaa Bablofoo
I want to have changed to:
bla foo
barbla barfoo
foblaaaa Bablofoo
Is it somehow possible via regex (e.g., s/…/…/g
) in Vim to format the file like that?
Something like the following,
:s/^\(\w\+\)\s*\(.*\)$/\1\t\t\t\2/g
but with the amount of necessary whitespace adjusted dynamically?
Upvotes: 3
Views: 225
Reputation: 29014
An easy way to align text in columns is to use the Tabular or Align plugin. As I have shown in response to the question “Inserting indentation for columns in Vim”, it is possible to solve the problem using only built-in Vim capacities with the help of the following 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 the end of
the first word, \>
). 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 such a manner just to match the needs of
the next substitution command: Since these two commands could share
the same pattern, the pattern 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 same 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 there is possibly 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, 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, the m
variable is set the
maximum of its 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 These pair of commands can be rewritten as a single one:
:let m=0|exe'g/\ze\>/let m=max([m,searchpos(@/,"c")[1]])'|%s//\=repeat(' ',m-col('.'))
2 The remaining text copies the detailed description given in the aforementioned answer of mine.
Upvotes: 1
Reputation: 59327
That's probably not a regex work, as you have to compute the highest width for the first word by first iterating trough the file to then start including spaces.
If you really want to avoid Tabular as @Prince Goulash suggested, here is an interesting and easy solution:
let n = system("awk '{ if (length($1) > L) { L = length($1) }}; END { print L }' ".expand("%:p"))
%s/\s\+/\=repeat(' ', n+1-system("awk '{ print length($1) }'", getline('.')))
In the first line, the variable n
will receive the output of a little awk program. It basically finds the largest width for the first field, that's the first work. Note the expand("%:p")
at the end: instead of passing simply your file's name we are expanding it to a full path so you can avoid confusions with the current directory.
The next line is the actual substitution. It substitutes the first set of white space found with an expression. The expression returns a string of spaces repeated a certain amount of times, which is n
(the maximum width) minus the length of the current line first word (awk help again!) plus one buffer space (you may use 2 or whatever number you want).
That will align everything.
If you want to install Tabular, then open your buffer and run:
:Tab /
Notice there is a space after the forward slash.
Now the one million dollar question: do you want to install Tabular?
Upvotes: 1