Calling autoloaded dictionary functions from other autoloaded dictionary functions in VimL (vimscript) - vim

Is it possible to invoke an autoloaded dictionary function from within another autoloaded dictionary function in Vim script?
I want to have something like this in autoload/foo.vim:
function! foo#Initialize()
return 1
endfunction
let foo#MyDict = {}
function! foo#MyDict.say_hi() dict
echo "hi"
endfunction
let foo#OtherDict = {}
function! foo#OtherDict.call_hi() dict
call foo#MyDict.say_hi()
endfunction
And I want to use it like this from another file/interactively/whatever:
call foo#Initialize()
call foo#OtherDict.call_hi()
Unfortunately, that gets me an error:
E121: Undefined variable: foo#MyDict
The call to foo#Initialize() is necessary due to a bug/limitation in Vim related to dictionary functions not triggering an autoload. There's a Google Groups thread about this where Bram confirmed the problem.
I'm not sure that's the root of this problem, however, because once the file is autoloaded (via foo#Initialize()), invoking dictionary functions works in general. It's the nested call example above that's giving me an error.
To clarify, this works fine:
function! foo#SayHello()
echo "hello"
endfunction
function! foo#OtherDict.say_hello() dict
call foo#SayHello()
end
It's only nested calls to autoloaded dictionary functions that fail.

The same happens when the script is put in the plugin/ directory or explicitly :runtime'd before use. But there's no error when instead of foo#MyDict a script-local s:MyDict is used. This is unexpected for me, too. Please submit a bug on the vim_dev mailing list.

Related

Various ways to execute a function in vim

I've been doing some trial-and-error on how functions can be called, and it seems like the following is my understanding:
From the command line, typing in :call MyFunction()
From the command line, typing in :call execute('call MyFunction'), where execute essentially performs a string escape (if that's the correct term?) to pass back to the first call param.
From within a function or vim file, typing in call MyFunction(). In other words, each line in a vim function/file acts like the command-line.
From within a function or vim file, typing in call execute('call MyFunction')
Is that a correct understanding of the various ways to call a function? Are there any other possible ways to do it?
I don't really understand what you are doing, but if you ask if there are other ways to call a function, yes, there are.
For example,
the eval(...) can call another function
echo getline('.') or something like this
:s/../\=getline(...)
in expr mappings
...
Simply put, in almost any place when a vimscript can be evaluated, a function can be called.

Vim: report error on a single line

When an error is raised inside a Vimscript function, Vim reports an error in the following format:
Error detected while processing function <SNR>My_Function:
line X:
EXXX: Error Message Here
This is very distracting. Ideally, I would like this to be formatted in some way to fit on a single line, but if that's not possible, then I just want the last EXXX line with the actual error. Is it possible to change this?
I don't like that behavior neither, but it is how it is, and cannot be changed.
Your functions need to :catch any errors, and convert this into the desired one-line error message:
function! s:My_Function()
try
" commands here
catch /^Vim\%((\a\+)\)\=:/
echohl ErrorMsg
echomsg substitute(v:exception, '^\CVim\%((\a\+)\)\=:', '', '')
echohl None
endtry
endfunction
This is how most plugins handle it. The behavior isn't completely like that of built-in commands, though: The error is effectively "swallowed", and subsequent commands (e.g. in a sequence cmd1 | call <SID>My_Function() | cmd3) will be executed despite the error. For most uses this is okay, and most users probably are even unaware of this.
better alternative
To make the custom command behave like a built-in one, the error message needs to be returned to the custom command or mapping, and :echoerr'd there. In my ingo-library plugin, I have corresponding helper functions:
function! s:My_Function()
try
" commands here
return 1 " Success
catch /^Vim\%((\a\+)\)\=:/
call ingo#err#SetVimException()
return 0 " Failure, use stored error message.
endif
endfunction
if ! <SID>My_Function() | echoerr ingo#err#Get() | endif
There is another approach to the problem: error messages can be decoded.
In my lh-vim-lib, I've defined a function that decodes the last error message and displays the result in the qf-window.
Given lh-vim-lib is installed, you've to add something like
command! WTF call lh#exception#say_what()
to your .vimrc.
The feature by itself has been inspired from https://github.com/tweekmonster/exception.vim, the difference is that I've used stuff I've already implemented in my library plugin in order to:
support localized messages
support autoloaded functions, even when # isn't in &isk (which is possible depending on the settings associated to the current filetype/buffer)
have as few loops as possible
factorize code with my logging, and unit testing, and DbC frameworks.
Near the end of the screencast I've recorded to demonstrate my Dbc framework, I've used :WTF to display the last error in a human friendly manner.

Function already exists VIM

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.

What does the sharp (#) sign mean in a vim script function definition?

So I was looking for ways to get some object oriented stuff in vimcript when I found this page
So for instance:
function gnat#Make () dict
...
return
endfunction gnat#Make
What does the '#' mean?
Does it have to do with ending the function explicitly like that?
(usually one just endfu[nction] without the function name)
Thanks!
The # is for autoload scripts. Try :h autoload for more info.
I don't think that "explicit" function ending is allowed, as written in the vim help:
:endf[unction] The end of a function definition. Must be on a line
by its own, without other commands.
But it appears that when you try to put something after :endf, even if it's not the name of the function, no error occurs.

how to understand these vim scripts

I have two question about understand those vim script. please give some help,
Question 1:
I download a.vim plugin, and i try to read this plugin, how to understand the below variable definition? the first line I can understand, but the second line, I don't know exactly "g:alternateExtensions_{'aspx.cs'}" means.
" E.g. let g:alternateExtensions_CPP = "inc,h,H,HPP,hpp"
" let g:alternateExtensions_{'aspx.cs'} = "aspx"
Question 2:
how to understand "SID" before the function name, using like below function definition and function call.
function! <SID>AddAlternateExtensionMapping(extension, alternates)
//omit define body
call <SID>AddAlternateExtensionMapping('h',"c,cpp,cxx,cc,CC")
call <SID>AddAlternateExtensionMapping('H',"C,CPP,CXX,CC")
thanks for you kindly help.
let g:alternateExtensions_{'aspx.cs'} = "aspx"
That is an inline expansion of a Vimscript expression into a variable name, a rather obscure feature that is rarely used since Vim version 7. See :help curly-braces-names for details. It is usually used to interpolate a variable, not a string literal like here ('aspx.cs'). Furthermore, this here yields an error, because periods are forbidden in variable names. Newer plugins would use a List or Dictionary variable, but those data types weren't available when a.vim was written.
To avoid polluting the function namespace, plugin-internal functions should be script-local, i.e. have the prefix s:. To invoke these from a mapping, the special <SID> prefix has to be used instead of s:, because <SID> internally gets translated into something that keeps the script's ID, whereas the pure s:, when executed as part of the mapping, has lost its association to the script that defined it.
Some plugin authors don't fully understand this unfortunate and accidental complexity of Vim's scoping implementation either, and they put the <SID> prefix also in front of the function name (which works, too). Though it's slightly more correct and recommended to write it like this:
" Define and invoke script-local function.
function! s:AddAlternateExtensionMapping(extension, alternates)
...
call s:AddAlternateExtensionMapping('h',"c,cpp,cxx,cc,CC")
" Only in a mapping, the special <SID> prefix is actually necessary.
nmap <Leader>a :call <SID>AddAlternateExtensionMapping('h',"c,cpp,cxx,cc,CC")
<SID> is explained in :help <SID>:
When defining a function in a script, "s:" can be prepended to the name to
make it local to the script. But when a mapping is executed from outside of
the script, it doesn't know in which script the function was defined. To
avoid this problem, use "<SID>" instead of "s:". The same translation is done
as for mappings. This makes it possible to define a call to the function in
a mapping.
When a local function is executed, it runs in the context of the script it was
defined in. This means that new functions and mappings it defines can also
use "s:" or "<SID>" and it will use the same unique number as when the
function itself was defined. Also, the "s:var" local script variables can be
used.
That number is the one you see on the left when you do :scriptnames, IIRC.

Resources