Search code examples
vimtraceautoload

Get filename and line number that called vim autoload function


I'm trying to populate the quickfix list using an autoload function, i.e.:

function! myplugin#myfunc(msg)

  " this doesn't work from *inside* an autoload function
  let filename = fnamemodify(resolve(expand('<sfile>:p')))

  " not sure if it's possible to get the line number from where
  " a function was called
  let linenum = '?#'

  " create qf dict object
  " set filename, line number, bufnr, text, etc
  " add dict to qflist
  " setqflist(qfdictlist)
endfunction!

The problem I've run into is I can't figure out a way to get the filename and line number from the code that called the autoload function. Any suggestions?


Solution

  • Out of the box. This is not possible.

    However, depending on the exact scenario, here are a few leads.

    As you see, none of these solutions is very good. But there are none other right now. The callstack isn't available, except from v:throwpoint in an exception context. The only other solution is to have callers inject their references (~__FILE__ + ~__LINE__) when calling. And the only way to automate this is to compile the caller script into another script that automatically injects the missing information.

    By right now, understand there had been a proposal on vim-dev mailing list this last month in order to permit to have access to the call stack, but alas, only during debugging sessions: https://github.com/vim/vim/pull/433 If this is accepted, may be it could be extended later to offer a viml function that'll export this information.


    EDIT: Your question inspired me to write a simplistic logging facility for vim:

    " Function: lh#log#new(where, kind) {{{3
    " - where: "vert"/""/...
    " - kind:  "qf"/"loc" for loclist
    " NOTE: In order to obtain the name of the calling function, an exception is
    " thrown and the backtrace is analysed.
    " In order to work, this trick requires:
    " - a reasonable callstack size (past a point, vim shortens the names returned
    "   by v:throwpoint
    " - named functions ; i.e. functions defined on dictionaries (and not attached
    "   to them) will have their names mangled (actually it'll be a number) and
    "   lh#exception#callstack() won't be able to decode them.
    "   i.e.
    "      function s:foo() dict abort
    "         logger.log("here I am");
    "      endfunction
    "      let dict.foo = function('s:foo')
    "   will work correctly fill the quicklist/loclist, but
    "      function dict.foo() abort
    "         logger.log("here I am");
    "      endfunction
    "   won't
    " TODO: add verbose levels
    function! lh#log#new(where, kind) abort
      let log = { 'winnr': bufwinnr('%'), 'kind': a:kind, 'where': a:where}
    
      " open loc/qf window {{{4
      function! s:open() abort dict
        try
          let buf = bufnr('%')
          exe 'silent! '.(self.where). ' '.(self.kind == 'loc' ? 'l' : 'c').'open'
        finally
          call lh#buffer#find(buf)
        endtry
      endfunction
    
      " add {{{4
      function! s:add_loc(msg) abort dict
        call setloclist(self.winnr, [a:msg], 'a')
      endfunction
      function! s:add_qf(msg) abort dict
        call setqflist([a:msg], 'a')
      endfunction
    
      " clear {{{4
      function! s:clear_loc() abort dict
        call setloclist(self.winnr, [])
      endfunction
      function! s:clear_qf() abort dict
        call setqflist([])
      endfunction
    
      " log {{{4
      function! s:log(msg) abort dict
        let data = { 'text': a:msg }
        try
          throw "dummy"
        catch /.*/
          let bt = lh#exception#callstack(v:throwpoint)
          if len(bt) > 1
            let data.filename = bt[1].script
            let data.lnum     = bt[1].pos
          endif
        endtry
        call self._add(data)
      endfunction
    
      " reset {{{4
      function! s:reset() dict abort
        call self.clear()
        call self.open()
        return self
      endfunction
    
      " register methods {{{4
      let log.open  = function('s:open')
      let log._add  = function('s:add_'.a:kind)
      let log.clear = function('s:clear_'.a:kind)
      let log.log   = function('s:log')
      let log.reset = function('s:reset')
    
      " open the window {{{4
      call log.reset()
      return log
    endfunction
    

    Which use this other function of mine that decodes the callstack.