VIM line count in status bar with thousands separator? - vim

Is it possible to display the line count in the VIM status bar with thousands separators, preferably custom thousands separators?
Example:
set statusline=%L
should lead to "1,234,567" instead of "1234567".

I've found a way but it looks a bit crazy:
set statusline=%{substitute(line('$')\,'\\d\\zs\\ze\\%(\\d\\d\\d\\)\\+$'\,'\,'\,'g')}
The first round of backslashes is just for set (I have to escape , and \ itself).
What I'm actually setting the option to is this string:
%{substitute(line('$'),'\d\zs\ze\%(\d\d\d\)\+$',',','g')}
As a format string, this line contains one formatting code, which is %{...}. Everything in ... is evaluated as an expression and the result substituted back in.
The expression I'm evaluating is (spaces added (if I had added them to the real code, I would've had to escape them for set again, forcing yet more backslashes)):
substitute(line('$'), '\d\zs\ze\%(\d\d\d\)\+$', ',', 'g')
This is a call to the substitute function. The arguments are the source string, the regex, the replacement string, and a list of flags.
The string we're starting with is line('$'). This call returns the number of lines in the current buffer (or rather the number of the last line in the buffer). This is what %L normally shows.
The search pattern we're looking for is \d(\d\d\d)+$ (special vim craziness removed), i.e. a digit followed by 1 or more groups of 3 digits, followed by the end of the string. Grouping is spelled \%( \) in vim, and "1 or more" is \+, which gives us \d\%(\d\d\d\)\+$. The last bit of magic is \zs\ze. \zs sets the start of the matched string; \ze sets the end. This works as if everything before \zs were a look-behind pattern and everything after \ze were a look-ahead pattern.
What this amounts to is: We're looking for every position in the source string that is preceded by a digit and followed by exactly N digits (where N is a multiple of 3). This works like starting at the right and going left, skipping 3 digits each time. These are the positions where we need to insert a comma.
That's what the replacement string is: ',' (a comma). Because we're matching a string of length 0, we're effectively inserting into the source string (by replacing '' with ',').
Finally, the g flag says to do this with all matches, not just the first one.
TL;DR:
line('$') gives us the number of lines
substitute(..., '\d\zs\ze\%(\d\d\d\)\+$', ',', 'g') adds commas where we want them
%{ } lets us embed arbitrary expressions into statusline

Related

What do you understand by this RegEx?

I´m working with VBA and trying to split a string into three columns, almost all strings are like Company Name 3567782 Agent Name.pdf
With this pattern I want to match all the text before a space and digits (1st group), the digits (2nd group) and all the text after the space and before the .pdf (3rd group).
strPattern = "^(.+)\n(\d{4,10})\n(.+).pdf"
I recall spaces in python are \s but saw in VBA are \n.
Can you help me find the right pattern for what I´m looking for?
As I put in my comment, I use the https://regex101.com site. There are others but I find this one the most helpful to me.
When I put in your regex
^(.+)\n(\d{4,10})\n(.+).pdf
and test string
Company Name 3567782 Agent Name.pdf
the first thing I notice is that the regex does not match the test string (see right side under MATCH INFORMATION).
Here are a couple things that I saw:
\n is newline, not space. In regex, space is " ".
Your last "." in ".pdf" is not registering as a literal period, it's a token that matches any character. To match a literal period, you need \.
If we change those two things it returns three groups that seem to match what you are looking for.
^(.+) (\d{4,10}) (.+)\.pdf
It looks like for the digits, you are looking for between 4 and 10 digits. If that's correct, it looks like your regex is good. You could put in a handful of example strings into the TEST STRING area and make sure that it works in all cases.
I'd use either of these:
(?:(?:([a-zA-Z]+\.?)|(\d+)))
capture a-Z greedy with a possible . to allow for the .pdf or capture digits
this version excludes the space [ ] or \s
or keep the search structured so you can control what goes in and out of each column
^(\w+\s\w+)|(\d+)|(\w+\s\w+\.\w+$)
\b or ^ - word boundary or start of string
(\w+\s\w+) - 1st capture \w+ - any alpha numeric char greedily, followed by 1 x space (use \s* or \s+ for more), followed again by alpha numeric greedily
|(\d+) - alteration - \d+ - capture just digits
`|(\w+\s\w+.\w+$) - similar to 1st group but allows for the '.' of pdf and bounds to the end of string (\G or $).
you could optionally build the '.' into the 1st group like my top answer, but for neatness and better control I prefer the 2nd.

Vim - Insert something between every letter

In vim I have a line of text like this:
abcdef
Now I want to add an underscore or something else between every letter, so this would be the result:
a_b_c_d_e_f
The only way I know of doing this wold be to record a macro like this:
qqa_<esc>lq4#q
Is there a better, easier way to do this?
:%s/\(\p\)\p\#=/\1_/g
The : starts a command.
The % searches the whole document.
The \(\p\) will match and capture a printable symbol. You could replace \p with \w if you only wanted to match characters, for example.
The \p\#= does a lookahead check to make sure that the matched (first) \p is followed by another \p. This second one, i.e., \p\#= does not form part of the match. This is important.
In the replacement part, \1 fills in the matched (first) \p value, and the _ is a literal.
The last flag, g is the standard do them all flag.
If you want to add _ only between letters you can do it like this:
:%s/\a\zs\ze\a/_/g
Replace \a with some other pattern if you want more than ASCII letters.
To understand how this is supposed to work: :help \a, :help \zs, :help \ze.
Here's a quick and a little more interactive way of doing this, all in normal mode.
With the cursor at the beginning of the line, press:
i_<Esc>x to insert and delete the separator character. (We do this for the side effect.)
gp to put the separator back.
., hold it down until the job is done.
Unfortunately we can't use a count with . here, because it would just paste the separator 'count' times on the spot.
Use positive lookahead and substitute:
:%s/\(.\(.\)\#=\)/\1_/g
This will match any character followed by any character except line break.
:%s/../&:/g
This will add ":" after every two characters, for the whole line.
The first two periods signify the number of characters to be skipped.
The "&" (from what I gathered) is interpreted by vim to identify what character is going to be added.
Simply indicate that character right after "&"
"/g" makes the change globally.
I haven't figured out how to exclude the end of the line though, with the result being that the characters inserted get tagged onto the end...so that something like:
"c400ad4db63b"
Becomes "c4:00:ad:4d:b6:3b:"

Vim: word vs WORD

I'm learning Vim and can't wrap my head around the difference between word and WORD.
I got the following from the Vim manual.
A word consists of a sequence of letters, digits and underscores, or a
sequence of other non-blank characters, separated with white space
(spaces, tabs, ). This can be changed with the 'iskeyword'
option. An empty line is also considered to be a word.
A WORD consists of a sequence of non-blank characters, separated with
white space. An empty line is also considered to be a WORD.
I feel word and WORD are just the same thing. They are both a sequence of non-blank chars separated with white spaces. An empty line can be considered as both word and WORD.
Question:
What's the difference between them?
And why/when would someone use WORD over word?
I've already done Google and SO search, but their search-engine interpret WORD as just word so it's like I'm searching for Vim word vs word and of course won't find anything useful.
A WORD is always delimited by whitespace.
A word is delimited by non-keyword characters, which are configurable. Whitespace characters aren't keywords, and usually other characters (like ()[],-) aren't, neither. Therefore, a word usually is smaller than a WORD; the word-navigation is more fine-grained.
Example
This "stuff" is not-so difficult!
wwww wwwww ww www ww wwwwwwwww " (key)words, delimiters are non-keywords: "-! and whitespace
WWWW WWWWWWW WW WWWWWW WWWWWWWWWW " WORDS, delimiters are whitespace only
To supplement the previous answers... I visualise it like this; WORD is bigger than word, it encompasses more...
If I do viw ("select inner word") while my cursor is on app in the following line, it selects app:
app/views/layouts/admin.blade.php
If I do viW (WORD) while my cursor is at the same place, it selects the whole sequence of characters. A WORD includes characters that words, which are like English words, do not, such as asterisks, slashes, parentheses, brackets, etc.
According to Vim documentation ( :h 03.1 )
A word ends at a non-word character, such as a ".", "-" or ")".
A WORD ends strictly with a white-space. This may not be a word in normal sense, hence the uppercase.
eg.
ge b w e
<- <- ---> --->
This is-a line, with special/separated/words (and some more). ~
<----- <----- --------------------> ----->
gE B W E
If your cursor is at m (of more above)
a word would mean 'more' (i.e delimited by ')' non-word character)
whereas a WORD would mean 'more).' (i.e. delimited by white-space only)
similarly, If your cursor is at p (of special)
a word would mean 'special'
whereas a WORD would mean 'special/separated/words'
That's a grammar problem while understanding the definition of "word".
I get stuck at first in Chinese version of this definition (could be miss-translation).
The definition is definitely correct, but it should be read like that:
A word consists of:
[(a sequence of letters,digits and underscores),
or (a sequence of other non-blank characters)],
separated with white space (spaces, tabs, <EOL>).
Whitespace characters were only needed when delimiting two same types of 'word'
More examples in brackets as follow:
(example^&$%^Example) three "word" :(example), (^&$%^) and (Example)
(^&^&^^ &&^&^) two "word" : (^&^&^^) and (&&^&^)
(we're in stackoverflow) five "word" :(we), ('), (re), (in) and (stackoverflow)
Another way to say it. If ur coding, and want to move thru the line stopping at delimiters and things line that "() . [] , :" use w.
if you want to bypass those and just jump to words lets say like a novel or short story has, use W.
For coding the small w is probably the one used most often. Depends where you are in the code.

Tell vim to add commas to a number, e.g. change 31415926 to 31,415,926

I have a very large number (a couple hundred digits long), and I'd like to use vim to add commas to the number in the appropriate manner, i.e. after each group of three digits, moving from right to left. How can I do this efficiently?
Taken from here
Substitue command that adds commas in the right spot.
:%s/\(\d\)\(\(\d\d\d\)\+\d\#!\)\#=/\1,/g
This uses a zero width lookahead to match any number that isn't followed by groups of three numbers followed by one number. (or 3n+1 numbers)
So the numbers that match in are marked with ^. These are then replaced with a comma after it the match.
31415926
^ ^
Which replaces to
31,415,926
A friend of mine suggests using the printf program: ciw<C-r>=system("printf \"%'d\" ".shellescape(#"))<CR>.
This is one way of doing it:
s/\d\{-1,}\ze\(\d\{3}\)\+\s/&,/g
Notes:
\{-1,} is saying match at least 1 but in a non-greedy way (Vim doesn't seem to support the usual \+\? syntax; also, for quantifiers, you just need to escape the opening curly brace)
\ze is saying match the pattern behind this but don't store the match in & (equivalent to positive look-ahead)
\(\d\{3}\)\+\> matches groups of 3 digits that ends with word-nonword boundary (word in this sense means alphanumerical + underscore).
Alternatively, you can use \s for space/tab, or \D for non-digit instead of \>, whichever fits your needs better
The way that I used is to create a macro that adds one single comma, and then invoke the macro a whole bunch of times, like qahhi,<ESC>hq#a#a#a#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