Vim - count nonblank nonwhitespace lines - vim

Using GVIM, I'd like to have something similar to the line count MSExcel offers - an on-the-fly line count that shows me how many lines I've selected so far that are non-blank (i.e. don't contain only whitespaces).
Up until now I've always used y for yank and then it shows on the bottom how many lines yanked, but:
this is not on-the-fly
this counts also blank/whitespace lines.
What's the best way to achieve this?

The downside of :substitute//n is that is clobbers the last search pattern and search history, and its output contains additional text and is difficult to capture. Alternatively, you can filter() the entire buffer, and count the matches:
:echo len(filter(getline(1, '$'), 'v:val =~# "\\S"'))
This can be easily turned into a custom mapping or command. If the performance is acceptable, you can even add this to your 'statusline':
:let &statusline .= ' %{len(filter(getline(1, "$"), ''v:val =~# "\\S"''))} lines'
Note: The statusline update won't work during a visual selection, because the marks '< and '> are only set after you've left the selection.

:%s/\S//n
3 matches on 3 lines
This combines a no-op :substitute (with the /n flag) that only counts the matching lines with the \S atom, which matches non-whitespace. As long as there is any such in a line, it is counted.
For the visual selection, just trigger it from there; it'll automatically use :'<,'> range instead of :%.

To get the number of blank lines you could use
:%s/^.\+//n
of course, instead of % you can use any other range command.
However, this approach will count only non-blank lines (without whitespace) not starting with a whitespace. Some hints on counting search results can be found here.
To allow for whitespace recognition you could use something like
:%s/^.*[^ ]\+//n

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.

Highlight line(s) containing search word

I am aware that it is possible to configure vim to highlight all matches for the searched word.
But, is it possible to highlight the entire line(s) on which the searched for word is found? If so, how?
If you generally want the current line highlighted:
:set cursorline
If you just want the searches highlighted, the only easy way is by extending the search pattern to cover the entire line:
:set hlsearch
:let #/ = '.*'.#/.'.*'
Note that on n / N, the cursor will now jump to the beginning of the line, not the matched word. Also, you won't be able to do :%s//... substitutions of the matched word (without repeating the search pattern) any more.
The exact solution depends probably on your goal:
Do you want to make the matched lines stand out a little more?
Do you want to know the line numbers for further use?
Do you want to act directly on those lines?
You could use the quickfix window to list the lines containing a match:
:vim! foo . | copen
You could use the :global command to list or act on every line containing a match:
:g/foo<CR> " list the lines
:g/foo/<Ex command> " act on each line
:set hlsearch
will highlight the searched-for word.
Note also that you can highlight your current line i.e. the line your cursor is on. So when you move between matches, the complete line you move to will be highlighted.
You may install this plugin highlight.vim. One of the shortcut key <C-h><C-j> allowed you to highlight the line contains the previous search pattern.
Please use this:
To highlight all the search pattern matches in a file set the following option:
:set hlsearch
To disable the highlighting temporarily, use:
:nohlsearch

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.

vim: delete the first 2 spaces for multiple lines

What's the easiest way to delete the first 2 spaces for each line using VIM? Basically it's repeating "2x" for each line.
Clarification: here the assumption is the first 2 characters are spaces. So the question is about doing indentation for multiple lines together.
Enter visual block mode with Ctrl-V (or Ctrl-Q if you use Ctrl-V for paste);
Select the area to delete with the arrows;
Then press d to delete the selected area.
Press Esc
Some more options. You can decided which is the "easiest way".
Remove the first 2 characters of every line:
:%normal 2x
Remove first 2 characters of every line, only if they're spaces:
:%s/^ /
Note that the last slash is optional, and is only here so that you can see the two spaces. Without the slash, it's only 7 characters, including the :.
Move indentation to left for every line:
:%normal <<
You could also use a search and replace (in the ex editor, accessed via the : character):
Remove first two characters no matter what:
%s/^.\{2}//
Remove first two white space characters (must be at the beginning and both must be whitespace... any line not matching that criteria will be skipped):
%s/^\s\{2}//
Assuming a shiftwidth=2, then using shift with a range of %
:%<
Two spaces, or two characters? (2x does the latter.)
:[range]s/^ //
deletes two blanks at the beginning of each line; use % (equivalent to 1,$) as [range] do to this for the entire file.
:[range]s/^..//
deletes the first two characters of each line, whatever they are. (Note that it deletes two characters, not necessarily two columns; a tab character counts as one character).
If what you're really doing is changing indentation, you can use the < command to decrease it, or the > command to increase it. Set shiftwidth to control how far it shifts, e.g.
:set shiftwidth=2
I'd try one of two approaches:
Do column editing on the block to delete using Ctrl+V (often mapped to Ctrl+Q).
Record a macro on the first row using q1 (or any other number/letter you want to denote the recording register), then replay that macro multiple times using #1 (to use my previous example. Even better, use a preceding number to tell it how many times to run - 10#1 to run that macro 10 times, for example. It does, however, depends on what you recorded - make sure to rewind the cursor 0 or drop one line j, if that's relevant.
I'd also add: learn how to configure indentation for vim. Then a simple gg=G will do the trick.

How to fill a line with character x up to column y using Vim

How can I fill the remainder of a line with the specified character up to a certain column using Vim? For example, imagine that the cursor is on column four and I want to fill the remainder of the current line with dashes up to column 80. How would I do that?
You can do 80Ax<Esc>d80| for a simpler solution.
Here's a function to implement what you ask, and slightly more.
It fills the line from its current end of line, rather than the cursor position
It forces a single space between what's currently on the line and the repeated chars
It allows you to specify any string to fill the rest of the line with
It uses vim's textwidth setting to decide how long the line should be
(rather than just assuming 80 chars)
The function is defined as follows:
" fill rest of line with characters
function! FillLine( str )
" set tw to the desired total length
let tw = &textwidth
if tw==0 | let tw = 80 | endif
" strip trailing spaces first
.s/[[:space:]]*$//
" calculate total number of 'str's to insert
let reps = (tw - col("$")) / len(a:str)
" insert them, if there's room, removing trailing spaces (though forcing
" there to be one)
if reps > 0
.s/$/\=(' '.repeat(a:str, reps))/
endif
endfunction
Insert that into your .vimrc, and make a mapping to it, e.g.
map <F12> :call FillLine( '-' )
Then you can press F12 to apply the hyphens to the current line
Note: this could probably be easily extended to act on a selection in VISUAL mode, but currently works for single lines only.*
If I understand the question correctly, this can be accomplished like this: in normal mode subtract the cursor's current column position from the desired ending column, then type the result followed by 'i' to enter insert mode, then the character you want to fill the space with. End by returning to normal mode. For example, with the cursor at column four in normal mode, if you wanted to fill the rest of the line up to column 80 with dashes, type 76i- then Esc or Ctrl-[ to return to normal mode. This should result in 76 dashes starting in column 4 and ending in column 79.
If you have the virtualedit option set to block or all, you can create a visual selection (even over empty space) up to the desired column:
v80| (if virtualedit=all) or
<c-v>80| (if virtualedit=block)
Then replace the selected area with dashes:
r-
It's probably helpful to start visual mode after the last character in the line by hitting l to avoid overwriting the last character in the line. If you are not using virtualedit=all, then you need to set virtualedit+=onemore so you can move one character beyond the end of line in normal mode.
One of the other answers here is: 80Ax<Esc>d80|. I initially started using it as a key mapping like this:
nnoremap <leader>- 80A-<Esc>d80<bar>
...but I didn't like how it leaves the cursor at the end of the line. Also, for narrow windows (e.g. just a little wider than 80 cells), it causes the entire window to scroll horizontally because the cursor briefly jumps to the end of the long line before it's trimmed back to 80. This is partially resolved by returning to the beginning of the line:
nnoremap <leader>- 80A-<Esc>d80<bar>0
...but then the screen will "flash" briefly while the cursor jumps offscreen and back (thus scrolling the window briefly to the right and back). To prevent this, we can temporarily use reverse insert mode (:h revins or :h ri) to keep the cursor onscreen while appending. Here's the full command as a key mapping:
nnoremap <leader>- :set ri<cr>80A-<esc>81<bar>d$0:set nori<cr>
This answer answers your question. Just replace len computation with your desired column number (+/- 1 may be, I never remember), and remove the enclosing double-quotes added by the substitution.
Using the textwidth value is also possible. It allows for different maximum line widths depending on filetype (check :h 'tw'). Here is what I now use, it appends a space after existing line content if present and will prompt for the string to use for the pattern:
function! FillLine() abort
if &textwidth
let l:str = input('FillLine>')
.s/\m\(\S\+\)$/\1 /e " Add space after content (if present).
" Calculate how many repetitions will fit.
let l:lastcol = col('$')-1 " See :h col().
if l:lastcol > 1
let l:numstr = float2nr(floor((&textwidth-l:lastcol)/len(l:str)))
else
let l:numstr = float2nr(floor(&textwidth/len(l:str)))
endif
if l:numstr > 0
.s/\m$/\=(repeat(l:str, l:numstr))/ " Append repeated pattern.
endif
else
echohl WarningMsg
echom "FillLine requires nonzero textwidth setting"
echohl None
endif
endfunction
You can map it for quick access of course. I like:
nnoremap <Leader>' :call FillLine()<Cr>
Note that the calculation assumes simple ASCII characters are being inserted. For more complicated strings, len(l:str) might not work. From :h strlen():
If you want to count the number of multi-byte characters use strchars().
Also see len(), strdisplaywidth() and strwidth().
You can insert first your dashes and then go to the first character and enter your text in replace mode: 80i-Esc0R
if you don't want to type the text, first delete the line with 0D, use 80i-Esc to insert the dashes and 0RCTRL+r " to paste the contents of the unamed register in replace mode.
I actually stumbled across this looking to align columns. Just in case anyone else does the same, this thread might be useful: How to insert spaces up to column X to line up things in columns?

Resources