vim-lspのCallHierarchyをツリーっぽく表示する

リファクタリングしたりコードを調べたりする時、呼び出し元を探すのにLspReferencesLspCallHierarchyIncomingを使っていた。
ただ、どちらも1階層分しか表示してくれず、呼び出し元が遠いと影響範囲が把握しにくかったのでquickfixに結果をマージして表示するコマンドを作ってみた。

command! AppendCallTree call s:append_tree(':LspCallHierarchyIncoming')
command! AppendRefTree call s:append_tree(':LspReferences')

augroup AppendTree
    autocmd!
augroup END

function! s:append_tree(cmd) abort
    autocmd AppendTree BufWinEnter quickfix let s:lsp_done = 1

    copen                            " quickfixに移動し、
    let l:pos = line('.')            " 現在の行番号と、
    let l:parent_tree = getqflist()  " 内容を取得し、
    call setqflist([])               " いったん空する
    let l:level = count(l:parent_tree[l:pos-1].text, '⬅️  ')
    wincmd p

    " 元のバッファで指定したコマンドを実行し、
    let s:lsp_done = 0
    execute a:cmd

    " 完了するかある程度時間が経過するまで待つ
    let l:cnt = 0
    while !s:lsp_done && l:cnt < 100
        sleep 10m
        let l:cnt += 1
    endwhile

    let l:child = getqflist()
    if len(l:child) != 0
        " 新たに取得した分は先頭に⬅️を付けて元の位置の下に挿入する
        call extend(l:parent_tree, map(l:child, 'extend(v:val, {"text": repeat("⬅️  ", l:level+1) . v:val.text})'), l:pos)
    endif

    " 結果(取得できなかった場合は元の内容)をquickfixに表示し、
    " 次の場所にジャンプする
    call setqflist(l:parent_tree)
    execute 'cc ' . string(l:pos + 1)

    autocmd! AppendTree
endfunction

AppendCallTree実行後は@:などで繰り返せるので調査が楽になった。

f:id:daisuzu:20210312165925g:plain
例: goplsのCallHierarchy

  • LSPを直接呼ぶのは面倒なのでコマンドを実行する形式にした
    • 特にCallHierarchy...
  • 専用バッファよりquickfixの方が何かと扱いやすいのでやらなかった
    • 何も考えずにジャンプできるし
    • フィルタも簡単だし