I have defined a function in my /.vim/ftplugin/python.vim file. The problem is that every time I open a .py file, I get the E122: Function MyFunction already exists, add ! to replace it.
I know that if I add ! then it will override the function (which is not a problem), but that means that it will replace it everytime, and it is a useless (and not very clean) supplementary action.
I guess that the problem come from the Python configuration file being sourced again and again every time I open a new .py file.
How can I tell VIM to source only once?
I would recommend putting the function in an autoload directory. (Read :help autoload it does a very good job explaining how this works). The quick version is below.
Edit the file ~/.vim/autoload/ftplugin/python.vim and add your function there. Everything after the autoload is part of the function signiture. (Instead of / use # between directories and leave off the .vim for the filename directory(s)#file#FunctionName)
function ftplugin#python#MyFunction()
...
endfunction
This function will automatically be loaded by vim the first time it is used.
Inside the filetype plugin you would just create the necessary mappings and commands.
command -buffer MyFunction call ftplugin#python#MyFunction()
nnoremap <buffer> <leader>m :call ftplugin#python#MyFunction()<CR>
and the function will automatically be loaded when it is called the first time. And other buffer that loads the ftplugin won't run into the redefinition problem.
One way: define a variable at the end of the file, check for its existence at the beginning (similar to a c include guard):
if exists('g:my_python')
finish
endif
fun MyFunction
...
endfun
" ... other stuff
let g:my_python = 1
Another way (if all you have is this function): check directly for the existence of its definition:
if !exists('*MyFunction')
fun MyFunction
...
endfun
endif
If you use ultisnips plugin would be great to have a snippet like:
snippet guard "add guard to functions" b
if !exists('*`!p
try:
func_name = re.search('\S+\s+(\S+)\(', snip.v.text.splitlines()[0]).group(1)
except AttributeError:
func_name = ''
snip.rv = func_name
`')
${VISUAL}
endif
${0:jump here <C-j>}
endsnippet
It allow us to select a function with vip, trigger the guard snippet and fix
any function with no effort. In the post quoted you can see a complete explanation about the code above
It came from a discussion on vim #stackexchange. Actually I already knew about !exists thing, so I was trying to create a snippet to make my snippets smarter.
Related
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
I’m trying to set up an abbreviation in my .vimrc that will insert a comment template for heading-level comments in my CSS files.
The comment I want to insert is:
/* ==========================================================================
#
========================================================================== */
I will then jump back to the # and add my title there (e.g. BUTTONS).
The abbreviation I have attempted to set up looks like this:
iab comsec·
\/* ==========================================================================
\<Cr>#
\<Cr>========================================================================== */
(Where · represents a trailing space.)
Right away this feels pretty crude, but the specific problem is that if try and drop a comsec in my CSS, it starts wrapping it in more comments. The output looks like this:
/* ==========================================================================
* #
* ========================================================================== */
Notice the two * at the beginnings of lines 2 and 3?
Is there a way to tell vim not to try and be clever and to just drop in exactly what I’ve told it? A way to prevent vim from trying to wrap comments around the comment?
I’m not a particularly hardcore vim user, so there’s every chance I’m overcomplicating things, or missing something obvious, or using the wrong tool for the job.
Thanks!
If you are the type of person who can keep track of your personal utilities, this isn't so fancy but works. You can import the output of an external command into your buffer, so I put a mapping like this in my .vimrc file:
"bc = block comment
map ,bc :read! python ~/my_personal_utils/insert_css_comment.py
So, I just have to type ",bc" to add the comment. On my Mac at least, this leaves me hanging in command mode, so that my cursor is after '.py' and I can quickly type an argument like BUTTONS (i.e. the python script takes an optional argument).
Here is a function to do that.
:function! Comment()
:normal! i/*
:normal! 80a=
:normal! o#
:normal! o
:normal! 80i=
:normal! a*/
:endfunction
You can put this in vimrc file and create a command or map for this.
Command
:cmap comsec call Comment()
You can keep the cursor on a line and then call this command.
Or an in insert mode mapping
:imap comsec <ESC>:comsec<CR>
As alternatives I'd suggest nerdcommenter for commenting/uncommenting with a few key strokes.
Or, even better, ultisnips. In which you can easily make your own template for those headings:
open a .css file
exec command : UltiSnipsEdit
create your own snip:
snippet heading "heading comments"
/* ===================================
* ${1}
* =================================== */
endsnippet
Here is a better and simple way to to insert your comment which check everytime if the line is surrounded by the comment template.
All you have to do is to write your comment on new line and then press ; during the insert mode. (you can change the character ; by
any combination you want.)
The comment template is set by the variable l:start, l:begin, l:end
so you can change the number of spaces or = as you like.
If you would like to change the template completely keep in mind that you need to change also the regular expressions for the variables l:matchprev, l:matchhier, l:matchnext .
inoremap <silent> ; <Esc>mx:call Comment()<cr>i<Right>
function! Comment()
let l:start= "/* ====="
let l:begin=" # "
let l:end= " ==== */"
let l:next=getline(line(".")+1)
let l:hier=getline(line("."))
let l:prev=getline(line(".")-1)
let l:matchnext= matchstr( l:next , '^\s*=\+\s*\*/\s*$')
let l:matchhier= matchstr( l:hier , '^\s*#\s*.*$')
let l:matchprev= matchstr( l:prev , '^\s*/\*\s*=\+\s*$')
if l:matchnext != '' && l:matchprev != '' && l:matchhier != ''
return 0
else
execute ":s:^.*$:".l:start."\r".l:begin."&\r".l:end."\r:"
"the number 3 is the length of the variable l:begin
normal! `xj3l
endif
endfunction
write this code in another file scriptname and then you can use the mapping in any css file by typing in the command mode first :so scriptname
Another alternative is to put all that simply in your .vimrc file
I was going through some code for latex_suite called vim_latex (http://vim-latex.sourceforge.net/) and I found few interesting lines in the file called "templates.vim":
" Back-Door to trojans !!!
function! <SID>Compute(what)
exe a:what
if exists('s:comTemp')
return s:comTemp.s:comTemp
else
return ''
endif
endfunction
Well, I'm not an expert on vim code, so I cannot interpret these lines except for the comment that freak me up a bit. Do you guys have an idea about what is happening ?
Edit:
The function seems to be called only by the following one:
" ProcessTemplate: processes the special characters in template file. {{{
" This implementation follows from Gergely Kontra's
" mu-template.vim
" http://vim.sourceforge.net/scripts/script.php?script_id=222
function! <SID>ProcessTemplate()
if exists('s:phsTemp') && s:phsTemp != ''
exec 'silent! %s/^'.s:comTemp.'\(\_.\{-}\)'.s:comTemp.'$/\=<SID>Compute(submatch(1))/ge'
exec 'silent! %s/'.s:exeTemp.'\(.\{-}\)'.s:exeTemp.'/\=<SID>Exec(submatch(1))/ge'
exec 'silent! g/'.s:comTemp.s:comTemp.'/d'
" A function only puts one item into the search history...
call Tex_CleanSearchHistory()
endif
endfunction
According to the header file description, the aim of these functions is to handle templates located into a specific directory.
I think the comment is intended as a warning. The function <SID>ProcessTemplate() goes through a template file, looks for certain (configurable) patterns, and calls <SID>Compute(what) where the argument what is text extracted from the template. Note the line :exe a:what.
If you install a template file from an untrusted source, then bad things can happen.
Of course, if you install a vim plugin from an untrusted source, equally bad things can happen. Putting malware in a template file adds a few levels of indirection, making it harder to implement and harder to diagnose.
It is possible that this code was written before the :sandbox command was added to vim, and that might be an easy way to make this code safer. I have not looked at what is allowed in the sandbox and compared it to the intended use of this template processing.
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
I'm currently modifying a plugin that I like, to suit my needs. I have come across the problem that I need to know in what buffer I'm in, within VimL's limitations.
I need to be able to refer to the buffer by name, specifically I need to know when I'm within Vim's own [Command Line] buffer. Which BTW, you can achieve using q:, q/ or q?.
The plugin I'm modifying is the following: https://github.com/jeffkreeftmeijer/vim-numbertoggle
And this is my fork: https://github.com/Greduan/vim-numbertoggle
What I want to do is know when I'm in this specific buffer, called [Command Line] and not use relative line numbers when I'm in it.
Thanks for any help you can provide!
Use vimscript function bufname('') with an empty string (for details, see :help bufname()). To get the number of the buffer, use bufnr('').
Also, you can get the name of file in the current buffer using register %:
let current_file = #%
You could check for some options set by Vim when it creates the command-line window.
create this file and directories:
~/after/ftpugin/vim.vim
What you put in this file is only executed by Vim when you edit a file with the corresponding filetype.
add the following self-explanatory code to that file:
if &buftype == "nofile"
setlocal number
endif
It works, here. I think that you can easily adapt it to your needs by adding &filetype == "vim".