8

I often write functions in my ~/.vimrc or in ~/.vim/autoload/myfunctions.vim, which when executed result in an error such as:

Error detected while processing function SomeFunction:
line   xx:

Those errors can be read in the output of the Ex command :messages.
Is there a way to populate the quickfix list with them, so that I can type :copen, navigate through them, hit Enter on one, and be automatically redirected to the file and line where an error has been raised?

3
  • 1
    it should be possible to set the 'errorformat' to the required formatstring, redir the messages command to a temporary file and use :cfile to read and parse the file into a quickfix list. I have never done that however Commented Jan 1, 2016 at 16:30
  • @ChristianBrabandt I thought about doing something like this but I don't know all the details of :help error-file-format. That's the solution that I would like the most. If one day I know how to do it, I'll post it here. Thank you for pointing me on the right direction ('errorformat') and confirming it's possible.
    – saginaw
    Commented Jan 2, 2016 at 12:02
  • 3
    Yeah, errorformat is a little bit like black vodoo ;) Commented Jan 2, 2016 at 12:55

4 Answers 4

9

A recent question on SO inspired me to develop a function that decodes v:throwpoint that I use in a new logger that fills the quickfix list, and my unit testing framework for vim.

This can be used to enhance romainl's answer in order to decode file+line from error messages.

function! Bar() abort
    bar
endfunction

function! Foo()
  try
    call Bar()
  catch /.*/
    let bt = lh#exception#callstack(v:throwpoint)
    let data = map(copy(bt), '{"filename": v:val.script, "text": "called from here", "lnum": v:val.pos}')
    let data[0].text = v:exception
    call setqflist(data)
  endtry
endfunction

Since my initial answer, I've improved the workflow to be able to decode the last error(s) sent as messages. A typical way to use it would be through a command like the following

command! -nargs=? WTF call lh#exception#say_what(<q-args>)

Note: my implementation tries to adapt automatically to the current locale (see :h :lang...) as error messages are localized, and to support some other minor issue (like '#' character not 'isk' list option).

5

I don't have a complete solution for you but most complex plugins have some kind of logging function for handling errors in a generic way.

A good approach would be to wrap some or all of your code in a try-catch block, parse the exception and populate the quickfix list with useful information.

Here is a very simplistic implementation of the idea:

function Foo()
    try
        bar
    catch
        call setqflist([{"bufnr":1,"lnum":1,"text":v:exception}])
    endtry
 endfunction

Pointing to the right file doesn't sound as easy, though.

See :help setqflist(), :help :try, :help v:exception.

4

enter image description hereI made a Vim plugin which is to jump to Vim script's last error or load errors to current loclist.

https://github.com/rbtnn/vim-vimscript_lasterror

If you would like, you can try it.

1
  • 1
    rbtnn, welcome to Vi and Vim! Btw, I noticed another account with the same name submitted an edit to your answer. Do you need a mod to merge the accounts (are they the same person)?
    – D. Ben Knoble
    Commented Feb 9, 2020 at 19:16
3

I've taken the liberty to build on both @romainl and @LucHermite's answers. I define the Stacktrace function, which can be called both manually and to get a trace for a specific error.

function! MyFunction(...)
  " Stacktrace can be called to get a manual stacktrace
  call Stacktrace()

  " It can also be called to get stacktrace for an error
  try
    " ...
  catch
    call Stacktrace()
  endtry
endfunction

function! Stacktrace()
  "
  " Get stack and exception
  "
  if empty(v:throwpoint)
    try
      throw 'dummy'
    catch
      let l:stack = reverse(split(v:throwpoint, '\.\.'))[1:]
      let l:exception = 'Manual stacktrace'
    endtry
  else
    let l:stack = reverse(split(v:throwpoint, '\.\.'))
    let l:exception = v:exception
  endif

  "
  " Build the quickfix entries
  "
  let l:qflist = []
  let l:files = {}
  for l:func in l:stack
    let [l:name, l:offset] = (l:func =~# '\S\+\[\d')
          \ ? matchlist(l:func, '\(\S\+\)\[\(\d\+\)\]')[1:2]
          \ : matchlist(l:func, '\(\S\+\), line \(\d\+\)')[1:2]

    if l:name =~# '\v(\<SNR\>|^)\d+_'
      let l:sid = matchstr(l:name, '\v(\<SNR\>|^)\zs\d+\ze_')
      let l:name  = substitute(l:name, '\v(\<SNR\>|^)\d+_', 's:', '')
      let l:filename = map(
            \ Command('scriptnames'),
            \ 'split(v:val, "\\v:=\\s+")[1]')[l:sid-1]
    else
      let l:name = l:name
      let l:filename = matchstr(
            \ Command('verbose function ' . l:name)[1],
            \ '.\{-}\s\+\zs\f\+$')
    endif

    let l:filename = fnamemodify(l:filename, ':p')
    if filereadable(l:filename)
      if !has_key(l:files, l:filename)
        let l:files[l:filename] = reverse(readfile(l:filename))
      endif

      let l:lnum = l:offset + len(l:files[l:filename])
            \ - match(l:files[l:filename], '^\s*fu\%[nction]!\=\s\+' . l:name)
      let l:text = len(l:qflist) == 0 ? l:exception : '#' . len(l:qflist)

      call add(l:qflist, {
            \ 'filename': l:filename,
            \ 'lnum': l:lnum,
            \ 'text': l:text,
            \})
    endif
  endfor

  call setqflist(l:qflist)
  execute 'copen' len(l:qflist) + 2
  wincmd p
endfunction

function! Command(cmd)
  let l:a = @a
  try
    silent! redir @a
    silent! execute a:cmd
    redir END
  finally
    let l:res = @a
    let @a = l:a
    return split(l:res, "\n")
  endtry
endfunction

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.