Skip to content

Capture backtrace #18604

@albfan

Description

@albfan

In the middle of a debug session can be nice to see current line: (this is for function but something for scripts can be done too)

function! ListFuncLines(func, lnum, context = 10) abort
  " Capture verbose output
  redir => l:out
  silent execute 'verbose function ' . a:func
  redir END

  if empty(l:out)
    echoerr "ListFuncLines: no output from ':verbose function " . a:func . "'"
    return
  endif

  let l:lines = split(l:out, "\n")
  let llines = len(l:lines)
  let l:res   = []

  let start = max([a:lnum - a:context, 0])
  let end = min([llines, a:lnum + a:context])
  for l in range(start, end )
    if l == a:lnum + 1
      echohl ErrorMsg
    endif
    echo l:lines[l]
    if l == a:lnum + 1
      echohl None
    endif
  endfor

endfunction

this provides similar feature as list on gdb.

Image

to ease usage, get current context and apply directly can be helpful. for that you need to inspect where or bt

>let out=''
>redir => out
>where 
  2 BufRead Auto-órdenes para "*"
  1 function <SNR>85_maybe_build_folds[9]
->0 <SNR>85_build_fold_data
line 64: where | redir END
>redir END 

>echo out

  2 BufRead Auto-órdenes para "*"
  1 function <SNR>85_maybe_build_folds[9]
->0 <SNR>85_build_fold_data
line 64: where | redir END
>

but you cannot retrieve this output outside debug scope: like in a function.

The simplest way to show the error:

>let out = '' | redir => out | where | redir END | echo out
Error detected while processing BufRead Autocommands for "*"..function <SNR>85_maybe_build_folds[9]..<SNR>85_build_fold_data:
line   64:
E492: Not an editor command:  where | redir END | echo out
>

once output is captured, you can parse it:

function! s:script_path_by_number(num) abort
  for line in split(execute('scriptnames'), "\n")
    let m = matchlist(line, '^\s*' . a:num . ':\s\+\(.*\)$')
    if len(m)
      return m[1]
    endif
  endfor
  return ''
endfunction

function! GetWhereCurrentFrame(bt) abort
  " Capture :where output
  let l:out = a:bt
  "redir => l:out
  "  debug where
  "redir END


  if empty(l:out)
    echo "empty lines"
    return {}
  endif

  let lines = split(l:out, "\n")
  let n = len(lines)

  " Find the line starting with '->' (current frame)
  let idx = -1
  for i in range(0, n - 1)
    if lines[i] =~# '^\s*->'
      let idx = i
      break
    endif
  endfor
  if idx == -1
    return {}
  endif

  let funcline = lines[idx]

  " Try to extract script-local <SNR>NN_name
  let m = matchlist(funcline, '<SNR>\(\d\+\)_\(\S\+\)')
  if len(m)
    let scriptnum = str2nr(m[1])
    let funcname = '<SNR>' . m[1] . '_' . m[2]
  else
    " fallback: extract the token after the arrow (->0 NAME) or after 'function'
    let m2 = matchlist(funcline, '->\s*\d\+\s\+\(\S\+\)')
    if len(m2)
      let funcname = m2[1]
    else
      " last resort: trim arrow and whitespace
      let funcname = substitute(funcline, '^\s*->\s*', '', '')
    endif
    let scriptnum = -1
  endif

  " Look for a following "line N: ..." or 'line N of "file"' entry within a few lines
  let lnum = -1
  let code = ''
  let fname = ''
  for j in range(idx + 1, min([n - 1, idx + 6]))
    let l = lines[j]
    let m1 = matchlist(l, '^\s*line\s\+\(\d\+\):\s*\(.*\)$')
    if len(m1)
      let lnum = str2nr(m1[1])
      let code = m1[2]
      break
    endif
    let m2 = matchlist(l, 'line\s\+\(\d\+\)\s\+of\s\+"\([^"]\+\)"')
    if len(m2)
      let lnum = str2nr(m2[1])
      let fname = m2[2]
      break
    endif
  endfor

  " If we have a script number but no filename, map scriptnum -> path via :scriptnames
  if fname ==# '' && scriptnum > 0
    let fname = s:script_path_by_number(scriptnum)
  endif

  return {
        \ 'func': funcname,
        \ 'scriptnum': scriptnum,
        \ 'fname': fname,
        \ 'lnum': lnum,
        \ 'code': code,
        \ 'funcline': funcline,
        \ 'verbose_lines': lines,
        \ }
endfunction

getting as result:

>echo GetWhereCurrentFrame(out)
{'lnum': 64, 'scriptnum': 85, 'func': '<SNR>85_build_fold_data', 'code': 'where | redir END', 'verbose_lines': ['  2 BufRead Autocommands for "*"', '  1 function <SNR>85_maybe_build_folds[9]', '->0 <SNR>85_build_fold_data', 'line 64: where
 | redir END'], 'fname': '~/.vim/plugged/vim-tree-sitter/plugin/ts_helper.vim', 'funcline': '->0 <SNR>85_build_fold_data'}

output indented for better view:

{
   'lnum': 64,
   'scriptnum': 85,
   'func': '<SNR>85_build_fold_data',
   'code': 'while !empty(stack)',
   'verbose_lines': [
     '  2 BufRead Autocommands for "*"',
     '  1 function <SNR>85_maybe_build_folds[9]',
     '->0 <SNR>85_build_fold_data',
     'line 64: while !empty(stack)'
   ],
   'fname': '~/.vim/plugged/vim-tree-sitter/plugin/ts_helper.vim',
   'funcline': '->0 <SNR>85_build_fold_data'
}

Is it possible to provide output from do_backtrace in a function call (getcallstack get_backtrace) avoiding too parsing

or at least allow where or backtrace to detect where they are inside a debug session to avoid error E492?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions