Variable that holds "list of all open buffers" in Vim? - 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.

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/*

How to make a parameterized command map in vim editor

I m trying to make a custom command for block commenting, to avoid writing the whole search and replace sequence each time in vim for commenting lines.
What I m trying to do is make a key combination map to which I can pass line numbers as parameter and those should be passed to the .vimrc file and processed there. Is it possible?
For example, I have this in my .vimrc
map :pc :17,21s/^/#<CR>
Now whenver I will do :pc in vim, it will add a # infront of lines 17-21 (commenting them in python)
Now 17,18 is hard coded in command here but can I make this command parameterized so that I can pass line numbers specifically like :17,21pc and it will take them in map command?
If it is possible then I would love to make the '#' symbol parameterized too so that I can pass in language specific comment symbol, like // in JS.
Mappings can't have parameters, but it's typically a command's job (see :h :command).
command! -range -nargs=? Comment call CommentThis(<line1>, <line2>, <q-args>)
function! CommentThis(l1, l2, lead)
let l:lead = a:lead == '' ? '#' : a:lead
exe printf('%i,%is+^+%s', a:l1, a:l2, l:lead)
endf
You can use it like this: select some lines with V and arrows, then:
:'<,'>Comment //
Of course you can specify the line numbers by yourself : don't select anything, then type:
:17,21Comment //
:12,45Comment " '#' is the default
Note: the above code is far from perfect, it's just an example.
But there is really better if your goal is to comment some lines: use NERD Commenter; it automatically chooses the right comment leader depending of the filetype, it allows several kinds of comment styles, it can comment and uncomment...
Here is an example of its use: select some lines with V and arrows, then type <leader>cc, with <leader> as \ by default.

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.

Vim line completion with external file

Can line completion Ctrl+X Ctrl+L be used to show line completions from a specific external file instead of "only" from the current buffer?
Something like dictionaries, but for lines.
Update:
To test I did following:
created a file tt.txt with some test lines
placed the file in D:\t1\ (I'm on windows)
included the file with :set path+=D:\\t1\\tt.txt
:set complete ? returns complete =.,w,b,u,t,i
:set path ? returns path=.,,,D:\t1\tt.txt
checkpath returns: All included files were found
typing a line which should be completed with the matching content from tt.txt with Ctrl+X Ctrl+L returns pattern not found
What am I missing?
I think the only way to achieve what you want is with a custom complete-function. See help complete-functions for the (very useful!) documentation. Here's my attempt at a solution:
First you need a separate function to silently grep a file for a string (if you just call the naked vimgrep function you will get an ugly error if there are no matches).
function! SilentFileGrep( leader, file )
try
exe 'vimgrep /^\s*' . a:leader . '.*/j ' . a:file
catch /.*/
echo "no matches"
endtry
endfunction
Now, here's your completion function. Note that the path to the file you want to search is hard-coded in here, but you could change it to use a variable if you so wish. We call SilentFileGrep(), which dumps the results in the quickfix list. Then we extract the results from the qflist (trimming the leading whitespace) and clear the qflist before returning the results.
function! LineCompleteFromFile(findstart,base)
if a:findstart
" column to begin searching from (first non-whitespace column):
return match(getline("."),'\S')
else
" grep the file and build list of results:
let path = <path_to_file>
call SilentFileGrep( a:base, path )
let matches = []
for thismatch in getqflist()
" trim leading whitespace
call add(matches, matchstr(thismatch.text,'\S.*'))
endfor
call setqflist([])
return matches
endif
endfunction
To use this function, you need to set the completefunc option to point at it:
set completefunc=LineCompleteFromFile
Then you can use <C-X><C-U> to invoke the completion, which you could easily map to <C-X><C-L>.
This seems to work pretty well for me, but it is not exhaustively tested.
In Vim help for line completion it's written that it only works for loaded buffers. As a workaround, you may open your "dictionary" file in another buffer and Vim will suggest lines from this buffer.
'path' is supposed to be a list of directories so (assuming that's the correct syntax on Windows) :set path+=D:\\t1\\tt.txt should be :set path+=D:\\t1\\ or :set path+=D:\\t1.
The i in 'complete' means that the completion candidates are chosen from current and included files. You must include a file for completion to work: it won't if you don't explicitly include that file.
Say that you have created ~/test/testfile with this single line:
lorem ipsum dolor sit amet
You add it to Vim's 'path' with:
:set path+=~/test
To use it as completion source in a C++ file you would do:
#include <testfile>
and be able to do:
lore<C-x><C-f>
to get:
lorem ipsum dolor sit amet
As far as I know, it doesn't work with languages that don't have an include mechanism like C or C++ so you can forget about it for Markdown, JavaScript, plain text or, if my tests are any indication, even PHP which does have include.
If you want a more generic mechanism, just add that file to the arglist: it will automatically be used as a completion source:
:argadd ~/test/testfile
If you do
:set dictionary=<some file>
then you can use ctrl + x followed by ctrl + k to complete from <some file>
See
:help ins-completion
for more info.
As #black_wizard points out, the other file must be loaded in a buffer. With set hidden, you can use the following to load another file and return to the previous buffer:
command! -nargs=? XL silent edit <args> | silent bprevious
To load tt.txt into another buffer and return to the previous one:
:XL tt.txt

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

Resources