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.
Related
A simple example:
function! Foo()
echom 'Starting Foo'
let x = Bar(123) " Function does not exist so E117 occurs.
echom 'Nonetheless, this executes.'
endfunction
Does Vim have a mechanism to abort a function or script if any error
occurs -- in other words, something similar in spirit to this setting in bash?
set -o errexit
To clarify, I understand how to wrap error-prone code in try-catch. I'm
looking for a general mechanism to get Vim scripts to behave similar
to languages like Python, Ruby, Perl, and so forth, where an unhandled error
causes execution to screech to a halt.
TL;DR
Always tag functions with abort
Your question has a recent duplicate on vi.SE: https://vi.stackexchange.com/questions/29038/how-do-i-make-a-function-stop-when-an-error-occurs/29039#29039
or, on the same topic: https://vi.stackexchange.com/questions/15905/when-should-a-function-not-be-defined-with-abort
on SO, there are some more explanations regarding abort: Vimscript: what's the point of 'abort' in a function definition?
Update
Submitted the issue, has been fixed in Vim 8.1.1061.
I'm writing a short script for adding delay to .srt files in Vim.
The core functionality could practically be a one-liner substitute command searching for timecodes and referring to a helper function which converts its argument to millisecs, do the math, then converts the result back to timecode format.
However, I'd like to do an additional check - if any of the new values are negative, then throw an exception, which I'd like to handle. Given a well-formed subtitle file with the timecode lines in the right order, we can assume the exception will be thrown right at the very first match, if at all, so no state change to worry about - at least that's what I thought.
fun! s:DelayTimecodes(delay)
try
let saved_view = winsaveview()
let timecode = '\v\d{2}:\d{2}:\d{2},\d{3}'
exe 'keepjumps keeppatterns %s/' . timecode . '/'
\ . '\=s:DelayedTimecode(submatch(0), a:delay)/g'
catch 'illegal timecode value'
redraw | echo 'Cannot apply: the given delay time would result'
\ . ' in negative timecode value(s)'
finally
call winrestview(saved_view)
endtry
endfun
But when an exception is thrown by DelayedTimecode inside the \= expression, the matches on the first matching line are still replaced, by an empty string. As a temporary solution, I've written a loop, doing the substitution line by line, creating the whole delayed line first (that might throw an exception), and passing that to the substitute command. This works fine, but feels ridiculously over-engineered.
What is the reason behind this? Is there any way to circumvent this behavior, without undoing the substitution command & clearing the history, which is kinda messy?
Exceptions don't work well with sub-replace-expression. You could raise an issue at Vim's bug tracker, or directly discuss this on the vim_dev mailing list, but chances are this is classified as implementation-specific behavior, and put on the back burner.
The easiest workaround would be to just return the original match (submatch(0)) instead of throwing the exception (making the :substitute a no-op), and setting an internal flag that makes the expression do the same for each following invocation of the expression within the :substitute. Then, afterwards, you can check that flag and print the error message, just as you're now doing in :catch.
If you want to avoid any buffer modification (also marking it as modified), you'd have to :call search() to locate the first timecode instance, extract it (getline(), matchstr()), and do the check before the actual :substitute.
I added some set commands to my _vimrc file, how can I check whether the set command is executed successfully or not? I care about its status because there may be some different handling based on the status of previous set command.
You could try to raise the value of the verbose option until you find a value that allows you to catch every error thrown by every option (and set verbosefile=somefile as well because your life will be miserable if you don't) but I suspect what you want can't really be done in a manageable manner and probably not worth the hassle.
Here are a couple of idioms that could help you, though:
try something and catch errors
try
set option=value
catch /^Vim\%((\a\+)\)\=:EXXX/
set option=othervalue
endtry
See :help :try and :help :catch.
Note that raising verbose to its maximum value didn't allow me to catch the E596 error supposed to be thrown for invalid font so my point remains valid: it will be hard to find a silver bullet solution.
Also, guifont can take a coma-separated string as value so you can give it n fonts and let Vim use the first one that works.
do something only if a feature is present
if has('mouse_sgr')
set ttymouse=sgr
endif
See :help has().
If you gave us a concrete example of what you have in mind we could probably help you further.
You can actually handle exception in Vim.
In your case, this is an example:
for font in ['fonta:h10', 'fontb:h10', 'fontc:h10']
if !get(s:, 'font_set')
try
execute 'set guifont='.font
let s:font_set = 1
catch
endtry
else
break
endif
endfor
For more details, refer help :try.
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.
... or in any mode for that matter.
I just want to prevent some extensions from loading when that is the case, something like:
if ! currentmode('restricted')
Bundle('some-extension')
endif
You're right; a special variable like v:vimmode would be helpful, but I don't think such a thing currently exists. Why not raise this on the vim_dev mailing list?!
In the meantime, you have to detect the mode through the result of invoking something that is forbidden in restricted mode. My best idea that is the least intrusive on success is invoking writefile() with an empty file name:
silent! call writefile([], '')
" In restricted mode, this fails with E145: Shell commands not allowed in rvim
" In non-restricted mode, this fails with E482: Can't create file <empty>
let isRestricted = (v:errmsg =~# '^E145:')
I am not sure if this is a good idea:
restricted-mode disabled external commands (also some related functions). If we call external command or some certain functions in a rvim, we get Error E145.
So maybe you could just call some dummy external command via system(), then catch the Exception E145. to distinguish if it is in restricted mode. e.g.
try
call system("echo x") "or even call system("")
catch /E145/
"your codes
endtry
hope it helps