Tsuquyomi で popup する

本記事は Vim advent calendar 21 日目の記事です。

例年は割と準備してたけど、もっとカジュアルに書いていい気がしたという完全な言い訳をして、自分の vimrc に書いてた設定をブログにコピペ。

その 1

TypeScript で変数とか関数のシグネチャーとかをポップアップで表示する。 組み込みの popup 関係の関数だと表示位置によって微妙にポップアップの位置をずらすとか微調整ができなかったので、自前で描画する場所を計算してやる。

let s:winid = -1
function! s:ts_hint_popup()
  if s:winid != -1
    call popup_close(s:winid)
    let s:winid = -1
  endif
  let l:content = tsuquyomi#hint()
  let l:contents = split(l:content, '\n')

  let l:border_size = 2 " both side of `|` and top and bottom `-`

  " col position
  let l:current_col = col('.')
  let l:max_width = strlen(sort(copy(l:contents), function('s:sort'))[0])

  let l:col = l:current_col
  if l:current_col + l:max_width > &columns
    " popup is overflowed
    if l:max_width > &columns
      let l:col = 0
    else
      " If popup is overflow from buffer window, popup like followings.
      "
      " +=========================+
      " |      +-----------------+|
      " |      |const foo: string||
      " |      +-----------------+|
      " |  { bar, baz, bazz, foo }|
      " |                    ^    |
      " |     cursor is here |    |
      " +=========================+
      "
      let l:col = l:current_col + (&columns - (l:current_col + l:max_width + l:border_size)) + 1
    endif
  endif

  " line position
  let l:current_line = winline()
  let l:popup_height = len(l:contents) + l:border_size

  " Calc popup overflow size
  for l:line in l:contents
    let l:width = strlen(l:line)
    if l:width + l:border_size >= &columns
      let l:popup_height += 1
    endif
  endfor

  let l:lnum = l:current_line < l:popup_height
    \ ? l:current_line + 1
    \ : l:current_line - l:popup_height

  let s:winid = popup_create(split(l:content, '\n'), {
    \ 'line': l:lnum,
    \ 'col': l:col,
    \ 'border': [1, 1, 1, 1],
    \ 'moved': 'WORD',
    \ })
  let bufnr = winbufnr(s:winid)
  call setbufvar(winbufnr(s:winid), '&filetype', &filetype)
  return s:winid
endfunction

command! TsHintPopup :call s:ts_hint_popup()
noremap <silent> <buffer> <Plug>(TsuHintPopup) :<c-u>TsuHintPopup<CR>

Tsuquyomi には変数とかシグネチャーを取得する関数があるので、それを利用して、Vim のポップアップに流し込んだだけ。

その 2

Angular を書いてて、html でも補完とか、定義ジャンプを使いたい(こっちがメイン)。 Angular は LSP を提供してるので、それを使う。 VSCode で Angular Language Service(angular.ng-template-0.802.3) の拡張を入れる(angular.ng-template-0.900.3 はうまく動いてなかったので要調査)。

function! s:is_angular()
  let current = expand('%:p:h')
  let path = findfile('angular.json', current . ';')
  if path == ''
    return 0
  endif
  return 1
endfunction

augroup Angular
  let s:server = expand('~/.vscode/extensions/angular.ng-template-0.802.3/server/server.js')
  let g:lsp_async_completion = 1
  autocmd User lsp_setup call lsp#register_server({
    \ 'name': 'Angular Language Service',
    \ 'cmd': {server_info -> [&shell, &shellcmdflag, printf('node %s --stdio', s:server)]},
    \ 'root_uri':{server_info -> lsp#utils#path_to_uri(lsp#utils#find_nearest_parent_file_directory(lsp#utils#get_buffer_path(), 'angular.json'))},
    \ 'whitelist': ['html'],
    \ })
augroup END
autocmd FileType html if s:is_angular() | setlocal omnifunc=lsp#complete | endif

なお Angular の TS なファイルは普通に Tsuquyomi で何の問題もなく動いている。

追記

最新 の angular.ng-template-0.900.3 の場合。

let base = expand(printf('~/.vscode/extensions/%s', 'angular.ng-template-0.900.3'))                  
let node_modules = printf('%s/node_modules/', base)
let ng = expand(printf('--ngProbeLocations %s/server', base))
let ts = expand(printf('--tsProbeLocations %s', node_modules))
let s:server = printf('%s/server %s %s', base, ng, ts)

とすればうまくいく。

また angular.ng-template-0.802.3 は適当に GitHub からダウンロードして、

yarn
yarn compile

して出来上がった /angular.ng-template-0.802.3/server/out/server.jslet s:server に指定すれば OK。