Vim -- Double Inclusion Guard - vim

Id'd like to be able to repeatedly insource my ~/.vimrc.local file (I'm using the sp13-vim distro), but I get error messages in the part of that file when I have Bundle/UnBundle statements. How can I prevent that part to be double executed.
That is, how do I write the following in Vimscript?:
if guard_global_not_defined
define_guard_global
do stuff
endif

The canonical structure is
if !exists('g:didBundle')
let g:didBundle = 1
Bundle ...
...
endif
As bundles are globally scoped, the g: prefix makes it a global guard. You can do the same with other scopes (e.g. b: for buffer-local stuff).

The local_vimrc files I'm using have the following kind of include guards: https://github.com/LucHermitte/Rasende/blob/master/_vimrc_local.vim
The latest templates I'm using are a little bit different (they are meant to support projects having global definitions that need to be set before the local settings (like a project name, where to find sources, build configurations and their related build directories), and local tunings).
When expanded, the result look like this (you certainly don't need everything):
let s:k_version = 42
" Always loaded {{{1
" Buffer-local Definitions {{{1
" Avoid local reinclusion {{{2
if &cp || (exists("b:loaded_tests_lh_vimrc_local")
\ && (b:loaded_tests_lh_vimrc_local >= s:k_version)
\ && !exists('g:force_reload_tests_lh_vimrc_local'))
finish
endif
let b:loaded_tests_lh_vimrc_local = s:k_version
let s:cpo_save=&cpo
set cpo&vim
" ======================[ Project config {{{2
if ! (exists("g:loaded_tests_lh_vimrc_local")
\ && (g:loaded_tests_lh_vimrc_local >= s:k_version)
\ && !exists('g:force_reload_tests_lh_vimrc_local'))
source <sfile>:p:h/_vimrc_local_global_defs.vim
endif
" ======================[ Local settings {{{2
.... <- here go your local settings
"--------------------------------------------------------------------
" Global Definitions {{{1
" Avoid global reinclusion {{{2
if &cp || (exists("g:loaded_tests_lh_vimrc_local")
\ && (g:loaded_tests_lh_vimrc_local >= s:k_version)
\ && !exists('g:force_reload_tests_lh_vimrc_local'))
finish
endif
let g:loaded_tests_lh_vimrc_local = s:k_version
" ======================[ Functions {{{2
.... <- here go some more global stuff like functions
" }}}1
"--------------------------------------------------------------------
let &cpo=s:cpo_save
"====================================================================
" vim600: set fdm=marker:
PS: I found quite odd that you have a local_vimrc at your very $HOME directory. They are meant to be at the root of project trees. And moreover they shall not be loaded by a plugin manager but by a local-vimrc plugin.

Related

VIM thesaurus file

I have been poking around for a good solution for a vim thesaurus. The capability is built-in, obviously, but the file everyone seems to use is the mthesaur.txt. While it 'works' in the sense that the commands in insert mode bring up a list, it seems to me the results are programatically correct but not super useful. The vim online thesaurus plugin works very well, but the latency over the wire and necessity of using a split for the returned buffer is less than ideal. Anyone have an opinion about this?
I have written a plugin that can address the two issues you raised here.
Multi-language Thesaurus Query plugin for Vim
It improves the using experience in two regards: more sensible synonym choosing
mechanism; and better and more flexible synonym source(s).
Thesaurus_query.vim screen cast
By default, the plugin uses vim's messagebox for candidate display, with each
synonym labeled by a number. And it let user choose the suitable one to replace
the word under cursor by typing in its number. It works similar to vim's
default spell correction prompt. And drastically reduced the operation time for
choosing proper synonym from a long list of candidates.
To improve the quality of synonym candidates, multiple query backends were
used. For English user, two are note worthy.
thesaurus_com Backend using Thesaurus.com as synonym source
mthesaur_txt Backend using mthesaur.txt as synonym source
thesaurus_com Backend will work straight away. For Local Query Backend to work,
you will need to download mthesaur.txt and tell the plugin where it is
located either by setting variable thesaurus or specifying
variable g:tq_mthesaur_file. Or else only Online Backend will be
functional.
By default, Online Query Backend will be used first. But if internet is not
available or too slow, future query in the current vim session will be handled
by Local Query Backend first to reduce latency time. Priority of these two
backends can also be manually altered(see documentation).
To address the latency issue(which usually stands out when the word is not found), I have introduced a timeout mechanism. You may set
let g:tq_online_backends_timeout = 0.6
if your internet is reasonably fast. So that the latency could be reduced to
under 0.6 second.
The plugin is written in Python, though. So you might want to use it with Vim compiled with Python and/or Python3 support.
If your system is unix-like and if you have awk installed, then I have a
simple solution to your problem that gives you access to thesauri in
multiple languages without internet connection and without a split
window either.
First download LibreOffice thesauri from:
https://cgit.freedesktop.org/libreoffice/dictionaries/tree/
for example.
(Look after th_*.dat files, these are the ones you need, not the .aff and
.dic files which work only for spellchecking with Hunspell.) Download
the *.dat thesauri of your liking and copy them to a subdirectory of the
folder where you will put your plugin; this subdirectory should be
called, "thes."
Now create a new file in your plugin folder (the folder where you should
have the "thes" subdirectory with the *.dat thesauri inside) and put the
following in this file:
" offer choice among installed thesauri
" ==================================================
let s:thesaurusPath = expand("<sfile>:p:h") . "/thes"
function! s:PickThesaurus()
" 1, 1: glob does not ignore any pattern, returns a list
let thesaurusList = glob(s:thesaurusPath . "/*", 1, 1)
if len(thesaurusList) == 0
echo "Nothing found in " . s:thesaurusPath
return
endif
let index = 0
let optionList = []
for name in thesaurusList
let index = index + 1
let shortName = fnamemodify(name, ":t:r")
let optionList += [index . ". " . shortName]
endfor
let choice = inputlist(["Select thesaurus:"] + optionList)
let indexFromZero = choice - 1
if (indexFromZero >= 0) && (indexFromZero < len(thesaurusList))
let b:thesaurus = thesaurusList[indexFromZero]
endif
endfunction
command! Thesaurus call s:PickThesaurus()
This will allow you to pick the thesaurus of your choice by typing
:Thesaurus in Vim's command mode.
(Actually, if you plan to use only one thesaurus then you don't need any
of this; just assign the full name of your thesaurus file to the
buffer-local variable, b:thesaurus).
Finally, add the following to your plugin file:
" run awk on external thesaurus to find synonyms
" ==================================================
function! OmniComplete(findstart, base)
if ! exists("b:thesaurus")
return
endif
if a:findstart
" first, must find word
let line = getline('.')
let wordStart = col('.') - 1
" check backward, accepting only non-white space
while wordStart > 0 && line[wordStart - 1] =~ '\S'
let wordStart -= 1
endwhile
return wordStart
else
" a word with single quotes would produce a shell error
if match(a:base, "'") >= 0
return
endif
let searchPattern = '/^' . tolower(a:base) . '\|/'
" search pattern is single-quoted
let thesaurusMatch = system('awk'
\ . " '" . searchPattern . ' {printf "%s", NR ":" $0}' . "'"
\ . " '" . b:thesaurus . "'"
\)
if thesaurusMatch == ''
return
endif
" line info was returned by awk
let matchingLine = substitute(thesaurusMatch, ':.*$', '', '')
" entry count was in the thesaurus itself, right of |
let entryCount = substitute(thesaurusMatch, '^.*|', '', '')
let firstEntry = matchingLine + 1
let lastEntry = matchingLine + entryCount
let rawOutput = system('awk'
\ . " '" . ' NR == ' . firstEntry . ', NR == ' . lastEntry
\ . ' {printf "%s", $0}' . "'"
\ . " '" . b:thesaurus . "'"
\)
" remove dash tags if any
let rawOutput = substitute(rawOutput, '^-|', '', '')
let rawOutput = substitute(rawOutput, '-|', '|', 'g')
" remove grammatical tags if any
let rawOutput = substitute(rawOutput, '(.\{-})', '', 'g')
" clean spaces left by tag removal
let rawOutput = substitute(rawOutput, '^ *|', '', '')
let rawOutput = substitute(rawOutput, '| *|', '|', 'g')
let listing = split(rawOutput, '|')
return listing
endif
endfunction
" configure completion
" ==================================================
set omnifunc=OmniComplete
set completeopt=menuone
This will allow you to get the synonyms of any word you type in insert
mode. While still in insert mode, press Ctrl-X Ctrl-O (or any key
combination you mapped on omnicompletion) and a popup menu will show up
with the synonym list.
This solution is very crude as compared to Chong's powerful plugin (see above), but it is lightweight and works well enough for me. I use it with thesauri in four different languages.
Script for ~/.vimrc, it needs the file thesaurii.txt (merged dictionaries from https://github.com/moshahmed/vim/blob/master/thesaurus/thesaurii.txt) and perl.exe in path for searching for synonyms. Script tested on win7 and cygwin perl.
Calls aspell to do spell correction, if no synonyms are found. See https://stackoverflow.com/a/53825144/476175 on how to call this function on pressing [tab].
set thesaurus=thesaurii.txt
let s:thesaurus_pat = "thesaurii.txt"
set completeopt+=menuone
set omnifunc=MoshThesaurusOmniCompleter
function! MoshThesaurusOmniCompleter(findstart, base)
" == First call: find-space-backwards, see :help omnifunc
if a:findstart
let s:line = getline('.')
let s:wordStart = col('.') - 1
" Check backward, accepting only non-white space
while s:wordStart > 0 && s:line[s:wordStart - 1] =~ '\S'
let s:wordStart -= 1
endwhile
return s:wordStart
else
" == Second call: perl grep thesaurus for word_before_cursor, output: comma separated wordlist
" == Test: so % and altitude[press <C-x><C-o>]
let a:word_before_cursor = substitute(a:base,'\W','.','g')
let s:cmd='perl -ne ''chomp; '
\.'next if m/^[;#]/;'
\.'print qq/$_,/ if '
\.'/\b'.a:word_before_cursor.'\b/io; '' '
\.s:thesaurus_pat
" == To: Debug perl grep cmd, redir to file and echom till redir END.
" redir >> c:/tmp/vim.log
" echom s:cmd
let s:rawOutput = substitute(system(s:cmd), '\n\+$', '', '')
" echom s:rawOutput
let s:listing = split(s:rawOutput, ',')
" echom join(s:listing,',')
" redir END
if len(s:listing) > 0
return s:listing
endif
" Try spell correction with aspell: echo mispeltword | aspell -a
let s:cmd2 ='echo '.a:word_before_cursor
\.'|aspell -a'
\.'|perl -lne ''chomp; next unless s/^[&]\s.*?:\s*//; print '' '
let s:rawOutput2 = substitute(system(s:cmd2), '\n\+$', '', '')
let s:listing2 = split(s:rawOutput2, ',\s*')
if len(s:listing2) > 0
return s:listing2
endif
" Search dictionary without word delimiters.
let s:cmd3='perl -ne ''chomp; '
\.'next if m/^[;#]/;'
\.'print qq/$_,/ if '
\.'/'.a:word_before_cursor.'/io; '' '
\.&dictionary
let s:rawOutput3 = substitute(system(s:cmd3), '\n\+$', '', '')
let s:listing3 = split(s:rawOutput3, ',\s*')
if len(s:listing3) > 0
return s:listing3
endif
" Don't return empty list
return [a:word_before_cursor, '(no synonyms or spell correction)']
endif
endfunction

Which error format should be used for Vim and Cppcheck?

I use the following script to integrate Cppcheck with gVim:
" vimcppcheck.vim
" ===================================================================
" Code Checking with cppcheck (1)
" ===================================================================
function! Cppcheck_1()
set makeprg=cppcheck\ --enable=all\ %
setlocal errorformat=[%f:%l]:%m
let curr_dir = expand('%:h')
if curr_dir == ''
let curr_dir = '.'
endif
echo curr_dir
execute 'lcd ' . curr_dir
execute 'make'
execute 'lcd -'
exe ":botright cwindow"
:copen
endfunction
:menu Build.Code\ Checking.cppcheck :cclose<CR>:update<CR>:call Cppcheck_1() <cr>
Normally this is very good, but this script sometimes creates trouble when checking wrong pointers with Cppcheck.
For example, I have the following C code:
/* test_cppcheck.c */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *ptr01;
*ptr01 = (int *)malloc((size_t)10 * sizeof(int)); /* FIXME: I intensionally written *ptr01 instead of ptr01 */
if(ptr01==NULL) {
fprintf(stderr, "\ndynamic memory allocation failed\n");
exit(EXIT_FAILURE);
}
free(ptr01);
ptr01 = NULL;
}
The quickfix list shows:
|| Checking test_cppcheck.c...
H:\codes\test_cppcheck.c:11] -> [test_cppcheck.c|12| (warning) Possible null pointer dereference: ptr01 - otherwise it is redundant to check it against null.
H:\codes\test_cppcheck.c|11| (error) Uninitialized variable: ptr01
H:\codes\test_cppcheck.c|16| (error) Uninitialized variable: ptr01
H:\codes\test_cppcheck.c|12| (error) Uninitialized variable: ptr01
|| Checking usage of global functions..
|| (information) Cppcheck cannot find all the include files (use --check-config for details)
After a lot of Vim errors, a new file '11] -> [test_cppcheck.c' is created in a new buffer. When I double-click the first error, nothing can be done from the quickfix window. It is because of the errorformat as much as I know.
The -> instead of : is creating all the trouble, although I know minor tweaking of this script will fix this issue, but I am tired of doing so.
Please try this first. How can I deal with this?
Without the original format of the error this is guesswork, but I think you need to add an alternative to the 'errorformat' definition (these are comma-separated):
setlocal errorformat=[%f:%l]\ ->\ %m,[%f:%l]:%m
PS: You should also use :setlocal for the 'makeprg' option to restrict it to the current buffer, too.
Now I'm using the script below, and it's working perfectly as I expected.
This can be a general solution to everyone who is interested to integrate Cppcheck with Vim.
Of course, this script can be improved a lot. But it's a starting point for them.
" vimcppcheck.vim
" ===================================================================
" Code Checking with cppcheck (1)
" Thanks to Mr. Ingo Karkat
" http://stackoverflow.com/questions/19157270/vim-cppcheck-which-errorformat-to-use
" ===================================================================
function! Cppcheck_1()
setlocal makeprg=cppcheck\ --enable=all\ %
" earlier it was: " setlocal errorformat=[%f:%l]:%m
" fixed by an advise by Mr. Ingo Karkat
setlocal errorformat+=[%f:%l]\ ->\ %m,[%f:%l]:%m
let curr_dir = expand('%:h')
if curr_dir == ''
let curr_dir = '.'
endif
echo curr_dir
execute 'lcd ' . curr_dir
execute 'make'
execute 'lcd -'
exe ":botright cwindow"
:copen
endfunction
:menu Build.Code\ Checking.cppcheck :cclose<CR>:update<CR>:call Cppcheck_1() <cr>

Add space after colon zencoding-vim for .scss files

I am using the ZenCoding plugin for vim and I want it to expand CSS abbreviations by adding a space after every colon, which is not the current default behaviour for SCSS files.
I checked both Emmet's and zenconding-vim's documentation and I couldn't apply Emmet's custom property:
css.valueSeparator: ": "
to Vim. Which I tried by adding inside my .vimrc file.
let g:user_zen_settings = {
\ 'css' : {
\ 'valueSeparator' : ': '
\ }
\}
I am not sure whether I am missing something in ZenCoding's documentation or I am trying something that can't be done in ZenCoding.
I finally found the answer among zencoding-vim's issues:
https://github.com/mattn/zencoding-vim/issues/94
You must add this to to your .vimrc file:
let g:user_zen_settings = {
\ 'scss' : {
\ 'filters' : 'fc',
\ }
\}
Important! Don't miss the leading backslashes. They are required.

Configure vimrc based on environment variables

Is it possible to configure vimrc command mapping based on env variables?
For example, I'm using vim-ruby-test plugin which provides the following config:
let g:rubytest_cmd_test = "ruby %p"
I want to set the command differently based on whether i'm running tests using spork or not:
With Spork:
$ TESTDRB=y mvim .
let g:rubytest_cmd_test = "testdrb %p"
Without Spork:
$ mvim .
let g:rubytest_cmd_test = "ruby %p"
if $TESTDRB == 'y'
let g:rubytest_cmd_test = 'ruby %p'
endif
" etc

VIM: Check if a file is open in current tab? window? (and activate it)

In vim, you can check if a file is open in the current buffer with bufexists. For a short filename (not full path), you can check if it's open using bufexists(bufname('filename')).
Is there any way to check if a file is open in a tab?
My closest workaround is to do something like:
:tabdo if bufnr(bufname('filename')) in tabpagebuflist(): echo "Yes"
However, that's sort of pythonic pseudocode... I'm not sure how to get that to work in vim. My goal is for an external applescript to check if a file is already open and if so go to a line in that file.
Ideally, I'd like to be able to search through different GUI windows too, but I've gathered (e.g. Open vim tab in new (GUI) window?) that working with different GUI windows is very challenging / impossible in VIM.
My impatience and good documentation got the better of me... here's the solution (greatly aided by Check if current tab is empty in vim and Open vim tab in new (GUI) window?). The source is at https://github.com/keflavich/macvim-skim
function! WhichTab(filename)
" Try to determine whether file is open in any tab.
" Return number of tab it's open in
let buffername = bufname(a:filename)
if buffername == ""
return 0
endif
let buffernumber = bufnr(buffername)
" tabdo will loop through pages and leave you on the last one;
" this is to make sure we don't leave the current page
let currenttab = tabpagenr()
let tab_arr = []
tabdo let tab_arr += tabpagebuflist()
" return to current page
exec "tabnext ".currenttab
" Start checking tab numbers for matches
let i = 0
for tnum in tab_arr
let i += 1
echo "tnum: ".tnum." buff: ".buffernumber." i: ".i
if tnum == buffernumber
return i
endif
endfor
endfunction
function! WhichWindow(filename)
" Try to determine whether the file is open in any GVIM *window*
let serverlist = split(serverlist(),"\n")
"let currentserver = ????
for server in serverlist
let remotetabnum = remote_expr(server,
\"WhichTab('".a:filename."')")
if remotetabnum != 0
return server
endif
endfor
endfunction
then use like so:
exec "tabnext ".WhichTab('my_filename')
echo remote_foreground( WhichWindow('my_filename') )
or, from the command line, here's a script to go to a particular line of a file using WhichTab:
#!/bin/bash
file="$1"
line="$2"
for server in `mvim --serverlist`
do
foundfile=`mvim --servername $server --remote-expr "WhichTab('$file')"`
if [[ $foundfile > 0 ]]
then
mvim --servername $server --remote-expr "foreground()"
mvim --servername $server --remote-send ":exec \"tabnext $foundfile\" <CR>"
mvim --servername $server --remote-send ":$line <CR>"
fi
done
I'd reply to keflavich, but I can't yet...
I was working on a similar problem where I wanted to mimic the behavior of gvim --remote-tab-silent when opening files inside of gvim. I found this WhichTab script of yours, but ran into problems when there is more than one window open in any given tab. If you split windows inside of tabs, then you will have more than one buffer returned by tabpagebuflist(), so your method of using the buffer number's position in the List doesn't work. Here's my solution that accounts for that possibility.
" Note: returns a list of tabnos where the buf is found or 0 for none.
" tabnos start at 1, so 0 is always invalid
function! WhichTabNo(bufNo)
let tabNos = []
for tabNo in range(1, tabpagenr("$"))
for bufInTab in tabpagebuflist(tabNo)
if (bufInTab == a:bufNo)
call add(tabNos, tabNo)
endif
endfor
endfor
let numBufsFound = len(tabNos)
return (numBufsFound == 0) ? 0 : tabNos
endfunction
I think I can just return tabNos which will be an empty list that gets evaluated as a scalar 0, but I just learned vimscript and am not that comfortable with the particulars of its dynamic typing behavior yet, so I'm leaving it like that for now.

Resources