Neovim+LSP 환경에서의 Codelens integration


해냈다

요약

CodeLens는 대개 편집 문서와 관련된 부가기능을 제공하는 데 유용하게 사용할 수 있다. 예를 들면 Rust 코드를 편집할 때 테스트 코드 상단에 Run test를 표시하고, 그걸 누르면 바로 테스트가 실행되는데 이것이 바로 CodeLens를 이용한 기능이다.

이번 글에서는 Neovim에서 CodeLens를 활용해서 새로운 기능을 추가하는 방법에 대해 설명한다.

목표: CodeLens로 뭘 하겠다고?

연관문서 검색 기능을 원래 Neovim의 custom command로 구현하고 있었다. 이를테면 노트 위에 커서를 두고 :MyS 명령을 실행하면 연관문서를 보여주는 방식.

이 부분을 CodeLens로 바꾸면 유저 입장에서 해당 기능이 존재한다는 것을 좀 더 직관적으로 파악할 수 있어서 편할 것 같다고 생각했다.

다만 CodeLens로 바꾸는 과정에서 핵심 문제는 Custom command에서 구현했던 여러 custom handler를 CodeLens 구현에서 어떻게 적용하냐는 점이었다. 즉 연관 문서 목록을 서버에서 받아다가 화면에 표시하는 로직을 어떻게 설정하냐는 것.

가정

CodeLens에 대한 구체적인 구현이나 설명은 이 문서에서 다루고자 하는 범위 밖이라 과감하게 스킵한다. 즉, 이미 CodeLens를 제공하는 language server는 이미 주어져 있다고 가정한다. 이 글에서의 CodeLens는 단순히 서버에서 ‘이 CodeLens는 XYZ Command를 ABC Arguments로 실행하는 거랍니다’ 라고 간주해도 충분하다.

1단계: CodeLens 표시하기

Neovim의 LspAttach 이벤트에 빌붙어서, Codelens를 표시하고 업데이트하도록 구현한다.

vim.api.nvim_create_autocmd('LspAttach', {
  callback = function(ev)
    -- Show codelens
    vim.api.nvim_create_autocmd({'TextChanged', 'InsertLeave'}, {
      callback = function(_)
        vim.lsp.codelens.refresh()
      end
    })
    vim.lsp.codelens.refresh()
  end,
})

2단계: command 실행을 client에서 낚아채기

Neovim의 codelens 구현 에 의하면 codelens의 실행은 client._exec_cmd 함수에서 담당한다. (참고로 master branch이기 때문에 stable branch의 구현은 약간 다를 수 있지만 의미는 동일했다)

Neovim의 해당 함수 구현 에 의하면 가장 간편한 방법은 client.commands[cmdname]에 내가 원하는 구현을 끼워넣는 것이다. 즉, 다음과 같이 구현하면 된다.

local configs = require("lspconfig.configs")

-- My own lsp server for markdown
if not configs.lsp_md then
  configs.lsp_md = {
    default_config = {
      -- cmd_cwd, cmd, filetypes, root_dir, single_file_support 등등 설정
      commands = {
        ['cmd1'] = function(args, client_info) -- cmd1은 server에서 전달한 command 이름
          -- args는 server에서 전달한 arguments, client_info는 client_id와 buf number 등이 들어감
          local client = vim.lsp.get_active_clients()[client_info.client_id]
          client.request('workspace/executeCommand',
            args,
            function (_, resp, ctx, _)
              -- 내가 원하는 callback을 여기서 구현. 이를테면 lopen이라던가, telescope 창을 연다던가
            end
          )
        end,
        -- 다른 command들도 여기에 이어서 선언한다.
      }
    },
  }
end

3단계: CodeLens 실행하기

아… 지금은 아직 귀찮아서 그냥 해당 CodeLens 위에서 :lua vim.lsp.codelens.run()을 실행한다. 단축키 설정을 하기만 하면 된다.

결론

기존의 Custom command와 비교하면, Language Server에서 바로 어떤 기능이 있는지 편집기 위에서 보여줄 수 있으니까 더 좋은것 같기도?