Display total characters count on vim statusline - vim

I want to add a function to my statusline by which I can display the total characters count of the current file.
:help statusline showed me that F referes to Full path to the file in the buffer, and through a bit searching I got to know how I can display the output of a shell command. So i currently have these in .vimrc:
set statusline+=%#lite#\ %o/%{DisplayTotalChars()}\
function! DisplayTotalChars()
let current_file = expand("%:F")
let total_chars = system('wc -c ' . current_file)
return total_chars
endfunction
This is what it displays now in the status line, whereas I only need the characters count not the file path to be displayed:
36/29488 /home/nino/scripts/gfp.py^#

As both system() and wordcount() being called repeatedly do a lot of unnecessary work and burn extra CPU time that may even result in slowing down your machine (especially while working on a huge file), you're strongly advised to use line2byte() instead:
set statusline=%o/%{line2byte(line('$')+1)-1}
Or even better, press gCtrl-g whenever you really want to get this info and keep your statusline clean.

set statusline+=%#lite#\ %o/%{DisplayTotalChars()}\
That part is correct, minus the \ at the end which serves no purpose.
let current_file = expand("%:F")
That part is incorrect because the F you found under :help 'statusline' means something when used directly in the value of &statusline but it is meaningless in :help expand(), where you are supposed to use :help filename-modifiers. The correct line would be:
let current_file = expand("%:p")
And a working function:
function! DisplayTotalChars()
let current_file = expand("%:p")
let total_chars = system('wc -c ' . current_file)
return total_chars
endfunction
But your status line is potentially refreshed several times per second so calling an external program each time seems expensive.
Instead, you should scrap your whole function and use :help wordcount() directly:
set statusline+=%#lite#\ %o/%{wordcount().bytes}
which doesn't care about filenames or calling external programs.

Related

Using Vim in a GTD way

I'd like to change my habit in the way I take notes.
I want add files named YYYYmmddHHiiss.txt in a directory and start them this way:
=Call somebody (title of my note)
#work (context of my note)
!todo (type of the note, I'll use !inbox, !todo, !waiting, !someday and !reference, each one his habit)
#project_name
#call
#Name of the person
#other tags if needed...
Details...
What I'd like is:
Using Vim (no plugins, just built-in features; no external programs; just a few autocmd, mappings and functions in my personnal vimrc)
Saving all my notes in a single directory and trust Vim and my tags to find what I need.
Start using this system with one command of this kind :GtdGrep and think in a while if I need more.
Model
:GtdGrep !todo #work
:GtdGrep !inbox
:GtdGrep #waiting #home
:GtdGrep !todo #work #this_project
:GtdGrep #name_of_a_co-worker #this_project
Now that I introduced you my need, I can start describing my problem ^^ I want to create the function behind the :GtdGrep command but there is a lot of things I don't manage to gather... Here is my draft.
let gtd_dir=expand($HOME)."/Desktop/notes"
function! GtdGrep(...)
execute "silent! vimgrep /\%<10l".join(a:000, "\\_.*")."/j ".gtd_dir."/**"
execute "copen"
endfunction
command! -nargs=* GtdGrep call GtdGrep(<f-args>)
How to restrain the search before the first empty line? I managed to look for my tags in the first 9 lines with the regexp \%<10l but that's it.
How to look for my tags regardless of their positions in the file? I just succeeded to do the grep on several lines with the \_.* regexp which is for the line returns.
The icing on the cake will be that the display on the quickfix window focus on the title part of my note (after /^=). I think it is possible with a ^=\zs.*\ze but it is too much for me in a single vimgrep!
EDIT
I solve my "AND" vimgrep issue by doing successive vimgrep on the previous results. Is it a good solution?
let gtd_dir=expand($HOME)."/Desktop/notes"
function! GtdGrep(...)
let dest = g:gtd_dir."/**"
for arg in a:000
execute "silent! vimgrep /^".arg."$/j ".dest
let dest = []
let results = getqflist()
if empty(results)
break
else
for res in results
call add(dest, bufname(res.bufnr))
endfor
let dest = join(dest, ' ')
endif
endfor
" Last vimgrep to focus on titles before displaying results
let results = getqflist()
if !empty(results)
echom dest
execute "silent! vimgrep /\\%<10l^=.*/j ".dest
execute "copen"
else
echom "No results"
endif
endfunction
command! -nargs=* GtdGrep call GtdGrep(<f-args>)
I'd like to restrain my vimgrep on the lines before the first blank line but I didn't succeed to do this. Any idea?
First of all, you should know the risk if you use dynamic string as pattern. E.g. your project_name contains [].*()...
What you can try is, building this command:
vimgrep '/\%<10l\(foo\|bar\|blah\|whatever\)' path/*

Comment / uncomment multiple fixed lines in vim

In my code I have multiple scattered lines which help me to debug my program and show me what is going on during execution. Is there an easy and fast way to comment and uncomment (toggle) these fixed lines in vim? I thought about marking these lines with a special sign (e.g. //) like this in python:
print "Debug!" # //
and everytime a sepcific shortcut is pressed all lines which end with a "# 'some optional descriptive text' //" are commented or uncommented, respectively.
I've looked at NERD Commenter, but from what I read the lines to be commented / uncommented have to be selected each time?
First, find a pattern that selects the right lines. If you have :set hls, it will help spot the matches. I think something like /#.*\/\/$ is what you want.
Next, comment out the selected lines with
:g/<pattern>/s/^/# /
if # will comment out the line, and un-comment them with
:g/<pattern>/s/^# //
Now, you want a single command to toggle. You can either use a variable to keep track of the toggle state, or you can try to figure out the current state by examining the lines that match. Using a variable seems simpler.
The variable could be global, local to the buffer, or local to the script. I like using script-local variables in order to avoid cluttering the namespace. In this case, using a script-local variable might mean that vim will get confused when you switch buffers, so let's use a buffer-local variable, say b:commentToggle.
The first time the function is called, it notices that the variable is not set, so use search() to look for a line that starts with # (There is a space there!) and ends with the pattern we already have. The n flag means not to move the cursor, and w means to wrap around the end of the file (like searching with 'wrapscan' set). The search() function returns the line number (1-based!) if the pattern is found, 0 if not. See :help search().
This seems to work in a small test:
fun! CommentToggle()
if !exists('b:commentToggle')
let b:commentToggle = !search('^# .*#.*\/\/$', 'nw')
endif
if b:commentToggle == 1
g/#.*\/\/$/s/^/# /
else
g/#.*\/\/$/s/^# //e
endif
let b:commentToggle = !b:commentToggle
endfun
nnoremap <F4> :call CommentToggle()<CR>
If you want to put # in front of the first non-blank, then use ^\s*# in the search() command; s/\ze\S/# / or s/\S/\1# / in the first :g line; and s/^\s*\zs# // in the second :g line. See :help /\zs, :help /\ze, and :help sub-replace-special.

How to diff two lines in an open file in vim?

I occasionally see very long lines in my code that I need to check if they are the same. Is there a way in vim to select two lines and diff them to show any differences between the two?
For example, given the two lines in vim:
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *lists);
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(That *is, Overloaded *with, Multiple *different, Parameter *1ists);
I would like vim to tell me that the two lines are in fact different because each spells "lists" differently. Is this possible, and if so, how do I do it?
A quick and dirty solution is to just select both lines and sort them while removing duplicates:
select lines
":sort u"
if only one line remains, both were equal
if both remain, there most be some difference
An undo recovers everything again.
An alternative to #sehe's approach would not require the use of temp files:
funct! DiffTwoTexts(text1, text2)
new
put =a:text1
normal ggdd
diffthis
new
put =a:text2
normal ggdd
diffthis
endfunct
funct! DiffTwoLines(line1, line2)
let text1 = getline(a:line1)
let text2 = getline(a:line2)
call DiffTwoTexts(text1, text2)
endfunct
comma! DiffWithNext call DiffTwoLines('.', line('.') + 1)
This will still be pretty hard to read, since it keeps everything on a single line, so I came up with this modification:
funct! EvalTextPreprocessor(expr, text)
let text = a:text
return eval(a:expr)
endfunct
comma! -nargs=1 DiffWithNextPre call DiffTwoTexts(
\ EvalTextPreprocessor(<q-args>, getline('.')),
\ EvalTextPreprocessor(<q-args>, getline(line('.') + 1)))
This new command takes a vimscript expression as its argument, wherein the variable text refers to whichever line is being preprocessed. So you can call, e.g.
DiffWithNextPre split(text, '[(,)]\zs')
For your sample data, this gives the two buffers
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *lists)
;
and
AVeryLongReturnType* MyLongClassName:hasAnEvenLongerFunction(
That *is,
Overloaded *with,
Multiple *different,
Parameter *1ists)
;
Only the lines that start with Parameter are highlighted.
You can even build up from there, creating a command
comma! DiffTwoCFunctionSigs DiffWithNextPre split(text, '[(,)]\s*\zs')
Notice that I modified the regexp a bit so that it will keep trailing spaces at the end of lines. You could get it to ignore them entirely by moving the \s* to after the \zs. See :help /\zs if you're unfamiliar with what that vim-specific RE atom does.
A nicety would be to make the command take a range (see :help command-range), which you could use by diffing the first line of the range with the last line. So then you just visual-select from the first line to the second and call the command.
I used linediff.vim.
This plugin provides a simple command, ":Linediff", which is used to diff two separate blocks of text.
That is not a feature, however it is easily scripted, e.g. in your vimrc:
function! DiffLineWithNext()
let f1=tempname()
let f2=tempname()
exec ".write " . f1
exec ".+1write " . f2
exec "tabedit " . f1
exec "vert diffsplit " . f2
endfunction
This will open the current and next lines in vertical split in another tab.
Note that this code is a sample
it doesn't check whether next line exists (there are any following lines)
it doesn't cleanup the tempfiles created
a nice improvement would be to take a range, or use the '' mark to select the other line
You can leave off the 'vert' in order to have a horizontal split
Map it to something fancy so you don't have to :call it manually:
:nnoremap <F10> :call DiffLineWithNext()^M
you could also just create a new empty window buffer and copy line, then make command:
:windo diffthis
this should open a new window showing the differences of those 2 lines

Vim search and highlighting control from a script

I'm writing a script in which I want to control searches programmatically, and get them highlighted. The search() function results are not highlighted (I think), so using that function is not of use to me.
What I want to do is use the 'normal /' command to search for a variable, but that doesn't seem to be straightforward. I can script the command:
execute 'normal /' . my_variable . '\<CR>'
(or other variations as suggested in the vim tip here: http://vim.wikia.com/wiki/Using_normal_command_in_a_script_for_searching )
but it doesn't do anything. I can see the correct search term down in the command line after execution of the script line, but focus is in the document, the search register has not been altered, and the cursor has not done any search. (It seems as though the < CR > isn't getting entered, although no error is thrown -- and yes, I have tried using the literal ^M too.)
I can at least control the search register by doing this:
execute 'let #/ ="' . a:term .'"'
and then the obvious thing seems to be to do a:
normal n
But that 'normal n' doesn't do anything if I run it in a script. Setting the search register does work, if I manually press 'n' after the scrip terminates the search happens (and highlighting appears, since hlsearch is on). I don't even care if the cursor is positioned, I just want the register pattern to be highlighted. But various combinations of 'set hlsearch' in the script don't work either.
I know I could use 'match()', but I want to get it working with regular search highlighting, and I wonder what I'm doing wrong. It must be something simple but I'm not seeing it. Thanks for any help.
run:
let #/ = a:searchStr
from inside your function then run
normal n
from outside your function (inside it does nothing) eg.
command -nargs=* Hs call MySearch() | normal n
or you can use:
set hlsearch
instead of normal n if you don't want the cursor to move
(I cannot work out another way of doing this without having something outside the function.)
If your script is using functions, then this quote from :help function-search-undo is relevant:
The last used search pattern and the redo command "."
will not be changed by the function. This also
implies that the effect of :nohlsearch is undone
when the function returns.
Vim usually tries to reset the search pattern (and a few other things) when a function ends, often you can get around this by adding the n (next search) to the end of a mapping, or using :map <expr> and having your function return the key sequence to be executed.
On closer inspection, it seems \<CR> is not picked up inside single quotes. Try using this instead:
execute 'normal /' . my_variable . "\<CR>"

Variable that holds "list of all open buffers" in Vim?

:vimgrep looks like a really useful thing.
Here's how to use it:
:vim[grep][!] /{pattern}/[g][j] {file} ...
:help says that you can essentially glob {file} to name, say, *.c for the current directory. I may have started Vim with a list of files that is complicated enough that I don't want to manually type it in for {file}, and besides Vim already knows what those files are.
What I would like to do is vimgrep over any of:
:args
:files
:buffers
What variable(s) would I use in place of {file} to name, respectively, any of those lists in a vimgrep command?
Can't you catch the result in these commands into a register (:h :redir), and insert it back into :vimgrep call (with a :exe).
Something like:
:exe "vimgrep/pattern/ " . lh#askvim#Exe(':args')
Notes:
lh#askvim#Exe is just a wrapper around :redir ; nothing really complex
some of these results may need some processing (see :args that adds square brackets)
Sometimes there is a function that returns exactly what you are looking for, see join(argv(), ' ') in :args case
Regarding :buffers, may be something like:
.
function BuffersList()
let all = range(0, bufnr('$'))
let res = []
for b in all
if buflisted(b)
call add(res, bufname(b))
endif
endfor
return res
endfunction
:exe 'vimgrep/pattern/ '.join(BuffersList(),' ')
You can do this:
:bufdo vimgrep /pattern/ %
% substitutes the buffer name.
To [vim]grep the list of files in the argument list, you may use ## (see :help cmdline-special).
:vimgrep /re/ ##
I am unaware of a similar shorthand for the buffer list, but you may be able to do something like:
:argdelete ##
:bufdo argadd %
... and then use ##. Or use :n to open new files (which will be added to the arg list) instead of :e.
Here is a slightly refined version of one of the answers. The following command searches for the pattern in all opened tabs and remembers results in quickfix list:
:cex [] | tabdo vimgrepa /pattern/ %
cex [] sets contents of quickfix list to empty list. You need to call it first because vimgrepa accumulates search results from all the tabs. Also, you can replace tabdo with argdo, bufdo and windo.
To view search results execute:
:cope
This method, however, has limitation: it can only search in tabs which already have file names assigned to them (% would not expand in a new tab).
EDIT:
You can also shortcut the command into function in your ~/.vimrc like this:
function TS(text)
exe "cex [] | tabdo vimgrepa /" . a:text . "/ %"
endfunction
command -nargs=1 TS call TS(<q-args>)
cnoreabbrev ts TS
With last line you can call your function like this:
:ts from game import
where words after ts is a search pattern. Without last line you have to type function name in upper case.
Very helpful script !
A minor fix: The search finds one of the buffers twice - first time as the numbered buffer, second as buffer #0 => alternate buffer.
Hence, we shall change the line to "range(1, bufnr('$'))" to skip the alternate buffer and show the search results once.

Resources