🐧

TypeScript 4.7のGoToSourceDefinitionをneovim builtin-lspから呼び出す

2023/09/09に公開

Go to Source Definitionとは

TypeScriptにおいて従来のGo to Definitionでは、型宣言ファイル(d.ts)がある場合はそちらにジャンプしていました。しかしTypeScript4.7でexperimentalとして登場したGo to Source Definitionでは、d.tsに隠蔽されている実装(.js or .ts)を探してその定義にジャンプすることができます。

Microsoft謹製エディタVisual Studio Codeには既にこのGo to Source Definitionが組み込まれています。しかしneovimユーザには悲しいことに、build-in LSPにはGo to Defenition(=vim.lsp.buf.definition)のような組み込み関数がGo to Source Definitionに対しては用意されていないようです。

したがってluaなりvimscriptを書いて自前でコマンドを定義してやる必要があります。

想定環境

  • neovim
  • lua
  • neovim built-in LSP
  • typescript-language-server
  • Mason

実装

私の環境は init.lua 1ファイルにすべての設定を詰めこんでいるので、そこに書き込んでいきます。
function goto_source_definition関数以外は、適宜自分の環境と読み替えて使ってください。

-- go to source definition for TypeScript(~4.7)
function goto_source_definition()
    -- 関数が呼ばれた時点でのファイルパスを取得する
	local bufnr = 0
	local uri = vim.uri_from_bufnr(bufnr)

    -- 関数が呼ばれた時点でのファイル上での座標(row, col)を取得する
	local pos = vim.api.nvim_win_get_cursor(0)
	local row, col = pos[1] - 1, pos[2]

    -- typsecript-language-server が要求するinterfaceに合わせる
    -- SEE: https://github.com/typescript-language-server/typescript-language-server#go-to-source-definition
	local position = { line = row, character = col }
	local params = {
		command = "_typescript.goToSourceDefinition",
		arguments = {
			uri,
			position,
		},
	}

    -- goToSourceDefinition実行後のcallback関数
	local handle_response = function(err, result, ctx, config)
		if result then
			if #result == 1 then
				vim.lsp.util.jump_to_location(result[1])
			else
				locations_to_items = vim.lsp.util.locations_to_items(result)
                -- qflistにセットして、quickfix windowを開く
				vim.fn.setqflist({}, " ", { title = "GoToSourceDefinition", items = locations_to_items })
				vim.cmd("copen")
				vim.cmd("wincmd p")
			end
		end
	end

	vim.lsp.buf_request_all(bufnr, "workspace/executeCommand", params, handle_response)
end


local on_attach = function(client, bufnr)
	local set = vim.keymap.set

	-- ...その他色々なコマンド設定...
	set("n", "gs", "<cmd>lua goto_source_definition()<CR>")

    -- ...

end

-- mason の設定
require("mason").setup()
require("mason-lspconfig").setup()
require("mason-lspconfig").setup_handlers({
	function(server_name) -- default handler (optional)
		require("lspconfig")[server_name].setup({
			on_attach = on_attach,
		})
	end,
})

参考資料

Discussion