4

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?

  • 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 – Christian Brabandt Jan 1 '16 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 Jan 2 '16 at 12:02
  • 2
    Yeah, errorformat is a little bit like black vodoo ;) – Christian Brabandt Jan 2 '16 at 12:55
3

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
2

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.

1

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, privacy policy and cookie policy

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