Using Vim in a GTD way - vim

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

Related

Display total characters count on vim statusline

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.

Quickly switching between a file and a test file in vim

Suppose I'm editing
src/a/b/c/d.c
and I expect a test file for this file to be in
test/a/b/c/d.c.c
how can I alternate between files following this pattern quickly?
a.vim and my alternate-lite fork support a searchpath option where you could specify how we can (quickly) switch between directories.
They're more tuned to jump between a header file and a definition file, but it should be possible to add test files as well -- I don't know how it'd behave with .c.c VS .c actually.
Given the pattern you've given us, the vanilla (non scalable) approach would be something like (untested):
function! s:alt_name(name) abort
if a:name =~ '\.c\.c$'
return substitute(a:name, '\v<test>/(.*)\.c', 'src/\1', '')
elseif a:name =~ '\.c$'
return substitute(a:name, '\v<src>/(.*\.c)', 'test/\1.c', '')
else
return a:name
endif
endfunction
command! -nargs=0 Switch :exe ':e '.s:alt_name(expand('%'))
Of course, if you need to jump to a window where the buffer is already opened, or split, or... well. That's why there are plugins.

Trigger file completion from vimscript

probably the answer to my question is obvious but even after a straight our of searching I cannot find anything useful.
I'm currently writing a small vim latex auto-completion plugin that suggests completions based on the editing context. The relevant part of the code looks like this:
function! Complete_latex(findstart, base)
if a:findstart
" locate the start of the base
"....
else
if s:envname_required()
return s:env_complete(a:base)
endif
if s:citation_required()
return s:cite_complete(a:base)
endif
if s:filename_required()
" TODO: Trigger filename completion
endif
endif
endfunction
set omnifunc=Complete_latex
The *_required() functions basically throw a bunch of regexps at the current line I'm editing to figure out what I'm doing right now. So if I am in INSERT mode at a position like ...\input{|... I'd like my omnifunc to call the same completion I can trigger with C-X C-F in INSERT mode.
As I also use the YouCompleteMe plugin and set { as a trigger for semantic completion in *.tex files, the triggering is being take care of.
I know that I can get a list of files and fill the popup menu myself, but I was nevertheless wondering If I can use a builtin function of vim.
Thank you.
I'm not entirely sure if that is the best way to go, but I came up with
let l:glob_pattern = a:base . '*'
let l:files_pre = globpath('.', l:glob_pattern, 0, 1)
let l:files_post = []
for l:file in l:files_pre
call add(l:files_post, substitute(l:file, '^\./', '', ''))
endfor
return l:files_post
Which basically gets all files in the current directory matching "base*" and returns a list of them. The post processing part just removes the './' at the beginning of each filename returned by globpath

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

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