aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/lsp.txt13
-rw-r--r--runtime/doc/news.txt2
-rw-r--r--runtime/lua/vim/lsp.lua3
-rw-r--r--runtime/lua/vim/lsp/buf.lua84
-rw-r--r--runtime/lua/vim/lsp/handlers.lua39
-rw-r--r--test/functional/plugin/lsp_spec.lua436
6 files changed, 577 insertions, 0 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt
index df85b7a2a9..42ed31a186 100644
--- a/runtime/doc/lsp.txt
+++ b/runtime/doc/lsp.txt
@@ -182,6 +182,7 @@ won't run if your server doesn't support them.
- textDocument/hover
- textDocument/implementation*
- textDocument/inlayHint
+- textDocument/prepareTypeHierarchy
- textDocument/publishDiagnostics
- textDocument/rangeFormatting
- textDocument/references
@@ -190,6 +191,8 @@ won't run if your server doesn't support them.
- textDocument/semanticTokens/full/delta
- textDocument/signatureHelp
- textDocument/typeDefinition*
+- typeHierarchy/subtypes
+- typeHierarchy/supertypes
- window/logMessage
- window/showMessage
- window/showDocument
@@ -1428,6 +1431,16 @@ signature_help() *vim.lsp.buf.signature_help()*
Displays signature information about the symbol under the cursor in a
floating window.
+subtypes() *vim.lsp.buf.subtypes()*
+ Lists all the subtypes of the symbol under the cursor in the |quickfix|
+ window. If the symbol can resolve to multiple items, the user can pick one
+ using |vim.ui.select()|.
+
+supertypes() *vim.lsp.buf.supertypes()*
+ Lists all the supertypes of the symbol under the cursor in the |quickfix|
+ window. If the symbol can resolve to multiple items, the user can pick one
+ using |vim.ui.select()|.
+
type_definition({options}) *vim.lsp.buf.type_definition()*
Jumps to the definition of the type of the symbol under the cursor.
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 29b728b013..2c940887c0 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -215,6 +215,8 @@ The following new APIs and features were added.
https://microsoft.github.io/language-server-protocol/specification/#textDocument_inlayHint
• Implemented pull diagnostic textDocument/diagnostic: |vim.lsp.diagnostic.on_diagnostic()|
https://microsoft.github.io/language-server-protocol/specification/#textDocument_diagnostic
+ • Implemented LSP type hierarchy: |vim.lsp.buf.supertypes()| and |vim.lsp.buf.subtypes()|
+ https://microsoft.github.io/language-server-protocol/specification/#textDocument_prepareTypeHierarchy
• |vim.lsp.status()| consumes the last progress messages as a string.
• LSP client now always saves and restores named buffer marks when applying
text edits.
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index eb604caacd..ab22c5901a 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -43,6 +43,9 @@ lsp._request_name_to_capability = {
[ms.textDocument_prepareCallHierarchy] = { 'callHierarchyProvider' },
[ms.callHierarchy_incomingCalls] = { 'callHierarchyProvider' },
[ms.callHierarchy_outgoingCalls] = { 'callHierarchyProvider' },
+ [ms.textDocument_prepareTypeHierarchy] = { 'typeHierarchyProvider' },
+ [ms.typeHierarchy_subtypes] = { 'typeHierarchyProvider' },
+ [ms.typeHierarchy_supertypes] = { 'typeHierarchyProvider' },
[ms.textDocument_rename] = { 'renameProvider' },
[ms.textDocument_prepareRename] = { 'renameProvider', 'prepareProvider' },
[ms.textDocument_codeAction] = { 'codeActionProvider' },
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 17cc698b76..fa0cbab138 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -495,6 +495,90 @@ function M.outgoing_calls()
call_hierarchy(ms.callHierarchy_outgoingCalls)
end
+--- @param method string
+local function type_hierarchy(method)
+ --- Merge results from multiple clients into a single table. Client-ID is preserved.
+ ---
+ --- @param results table<integer, {error: lsp.ResponseError, result: lsp.TypeHierarchyItem[]?}>
+ local function merge_results(results)
+ local merged_results = {}
+ for client_id, client_result in pairs(results) do
+ if client_result.error then
+ vim.notify(client_result.error.message, vim.log.levels.WARN)
+ elseif client_result.result then
+ for _, item in pairs(client_result.result) do
+ table.insert(merged_results, { client_id, item })
+ end
+ end
+ end
+ return merged_results
+ end
+
+ local bufnr = api.nvim_get_current_buf()
+ local params = util.make_position_params()
+ --- @param results table<integer, {error: lsp.ResponseError, result: lsp.TypeHierarchyItem[]?}>
+ vim.lsp.buf_request_all(bufnr, ms.textDocument_prepareTypeHierarchy, params, function(results)
+ local merged_results = merge_results(results)
+ if #merged_results == 0 then
+ vim.notify('No items resolved', vim.log.levels.INFO)
+ return
+ end
+
+ if #merged_results == 1 then
+ --- @type {integer, lsp.TypeHierarchyItem}
+ local item = merged_results[1]
+ local client = vim.lsp.get_client_by_id(item[1])
+ if client then
+ --- @type lsp.TypeHierarchyItem
+ client.request(method, { item = item[2] }, nil, bufnr)
+ else
+ vim.notify(
+ string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
+ vim.log.levels.WARN
+ )
+ end
+ else
+ local opts = {
+ prompt = 'Select a type hierarchy item:',
+ kind = 'typehierarchy',
+ format_item = function(item)
+ if not item[2].detail or #item[2].detail == 0 then
+ return item[2].name
+ end
+ return string.format('%s %s', item[2].name, item[2].detail)
+ end,
+ }
+
+ vim.ui.select(merged_results, opts, function(item)
+ local client = vim.lsp.get_client_by_id(item[1])
+ if client then
+ --- @type lsp.TypeHierarchyItem
+ client.request(method, { item = item[2] }, nil, bufnr)
+ else
+ vim.notify(
+ string.format('Client with id=%d disappeared during call hierarchy request', item[1]),
+ vim.log.levels.WARN
+ )
+ end
+ end)
+ end
+ end)
+end
+
+--- Lists all the subtypes of the symbol under the
+--- cursor in the |quickfix| window. If the symbol can resolve to
+--- multiple items, the user can pick one using |vim.ui.select()|.
+function M.subtypes()
+ type_hierarchy(ms.typeHierarchy_subtypes)
+end
+
+--- Lists all the supertypes of the symbol under the
+--- cursor in the |quickfix| window. If the symbol can resolve to
+--- multiple items, the user can pick one using |vim.ui.select()|.
+function M.supertypes()
+ type_hierarchy(ms.typeHierarchy_supertypes)
+end
+
--- List workspace folders.
---
function M.list_workspace_folders()
diff --git a/runtime/lua/vim/lsp/handlers.lua b/runtime/lua/vim/lsp/handlers.lua
index 1c5291e7fd..a15096fdad 100644
--- a/runtime/lua/vim/lsp/handlers.lua
+++ b/runtime/lua/vim/lsp/handlers.lua
@@ -565,6 +565,45 @@ M[ms.callHierarchy_incomingCalls] = make_call_hierarchy_handler('from')
--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy_outgoingCalls
M[ms.callHierarchy_outgoingCalls] = make_call_hierarchy_handler('to')
+--- Displays type hierarchy in the quickfix window.
+local function make_type_hierarchy_handler()
+ --- @param result lsp.TypeHierarchyItem[]
+ return function(_, result, ctx, _)
+ if not result then
+ return
+ end
+ local function format_item(item)
+ if not item.detail or #item.detail == 0 then
+ return item.name
+ end
+ return string.format('%s %s', item.name, item.detail)
+ end
+ local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
+ local items = {}
+ for _, type_hierarchy_item in pairs(result) do
+ local col = util._get_line_byte_from_position(
+ ctx.bufnr,
+ type_hierarchy_item.range.start,
+ client.offset_encoding
+ )
+ table.insert(items, {
+ filename = assert(vim.uri_to_fname(type_hierarchy_item.uri)),
+ text = format_item(type_hierarchy_item),
+ lnum = type_hierarchy_item.range.start.line + 1,
+ col = col + 1,
+ })
+ end
+ vim.fn.setqflist({}, ' ', { title = 'LSP type hierarchy', items = items })
+ api.nvim_command('botright copen')
+ end
+end
+
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_incomingCalls
+M[ms.typeHierarchy_subtypes] = make_type_hierarchy_handler()
+
+--- @see # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#typeHierarchy_outgoingCalls
+M[ms.typeHierarchy_supertypes] = make_type_hierarchy_handler()
+
--- @see: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window_logMessage
--- @param result lsp.LogMessageParams
M[ms.window_logMessage] = function(_, result, ctx, _)
diff --git a/test/functional/plugin/lsp_spec.lua b/test/functional/plugin/lsp_spec.lua
index 5b90e6de6c..d471aadf9c 100644
--- a/test/functional/plugin/lsp_spec.lua
+++ b/test/functional/plugin/lsp_spec.lua
@@ -3461,6 +3461,442 @@ describe('LSP', function()
end)
end)
+ describe('vim.lsp.buf.subtypes', function()
+ it('does nothing for an empty response', function()
+ local qflist_count = exec_lua([=[
+ require'vim.lsp.handlers'['typeHierarchy/subtypes'](nil, nil, {})
+ return #vim.fn.getqflist()
+ ]=])
+ eq(0, qflist_count)
+ end)
+
+ it('opens the quickfix list with the right subtypes', function()
+ clear()
+ exec_lua(create_server_definition)
+ local qflist = exec_lua([=[
+ local clangd_response = { {
+ data = {
+ parents = { {
+ parents = { {
+ parents = { {
+ parents = {},
+ symbolID = "62B3D268A01B9978"
+ } },
+ symbolID = "DC9B0AD433B43BEC"
+ } },
+ symbolID = "06B5F6A19BA9F6A8"
+ } },
+ symbolID = "EDC336589C09ABB2"
+ },
+ kind = 5,
+ name = "D2",
+ range = {
+ ["end"] = {
+ character = 8,
+ line = 9
+ },
+ start = {
+ character = 6,
+ line = 9
+ }
+ },
+ selectionRange = {
+ ["end"] = {
+ character = 8,
+ line = 9
+ },
+ start = {
+ character = 6,
+ line = 9
+ }
+ },
+ uri = "file:///home/jiangyinzuo/hello.cpp"
+ }, {
+ data = {
+ parents = { {
+ parents = { {
+ parents = { {
+ parents = {},
+ symbolID = "62B3D268A01B9978"
+ } },
+ symbolID = "DC9B0AD433B43BEC"
+ } },
+ symbolID = "06B5F6A19BA9F6A8"
+ } },
+ symbolID = "AFFCAED15557EF08"
+ },
+ kind = 5,
+ name = "D1",
+ range = {
+ ["end"] = {
+ character = 8,
+ line = 8
+ },
+ start = {
+ character = 6,
+ line = 8
+ }
+ },
+ selectionRange = {
+ ["end"] = {
+ character = 8,
+ line = 8
+ },
+ start = {
+ character = 6,
+ line = 8
+ }
+ },
+ uri = "file:///home/jiangyinzuo/hello.cpp"
+ } }
+
+ local server = _create_server({
+ capabilities = {
+ positionEncoding = "utf-8"
+ },
+ })
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ local handler = require'vim.lsp.handlers'['typeHierarchy/subtypes']
+ handler(nil, clangd_response, { client_id = client_id, bufnr = 1 })
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = {
+ {
+ bufnr = 2,
+ col = 7,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 10,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'D2',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ {
+ bufnr = 2,
+ col = 7,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 9,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'D1',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ }
+
+ eq(expected, qflist)
+ end)
+
+ it('opens the quickfix list with the right subtypes and details', function()
+ clear()
+ exec_lua(create_server_definition)
+ local qflist = exec_lua([=[
+ local jdtls_response = {
+ {
+ data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' },
+ detail = '',
+ kind = 5,
+ name = 'A',
+ range = {
+ ['end'] = { character = 26, line = 3 },
+ start = { character = 1, line = 3 },
+ },
+ selectionRange = {
+ ['end'] = { character = 8, line = 3 },
+ start = { character = 7, line = 3 },
+ },
+ tags = {},
+ uri = 'file:///home/jiangyinzuo/hello-java/Main.java',
+ },
+ {
+ data = { element = '=hello-java_ed323c3c/_<mylist{MyList.java[MyList[Inner' },
+ detail = 'mylist',
+ kind = 5,
+ name = 'MyList$Inner',
+ range = {
+ ['end'] = { character = 37, line = 3 },
+ start = { character = 1, line = 3 },
+ },
+ selectionRange = {
+ ['end'] = { character = 19, line = 3 },
+ start = { character = 14, line = 3 },
+ },
+ tags = {},
+ uri = 'file:///home/jiangyinzuo/hello-java/mylist/MyList.java',
+ },
+ }
+
+ local server = _create_server({
+ capabilities = {
+ positionEncoding = "utf-8"
+ },
+ })
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ local handler = require'vim.lsp.handlers'['typeHierarchy/subtypes']
+ handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 })
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = {
+ {
+ bufnr = 2,
+ col = 2,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 4,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'A',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ {
+ bufnr = 3,
+ col = 2,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 4,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'MyList$Inner mylist',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ }
+ eq(expected, qflist)
+ end)
+ end)
+
+ describe('vim.lsp.buf.supertypes', function()
+ it('does nothing for an empty response', function()
+ local qflist_count = exec_lua([=[
+ require'vim.lsp.handlers'['typeHierarchy/supertypes'](nil, nil, {})
+ return #vim.fn.getqflist()
+ ]=])
+ eq(0, qflist_count)
+ end)
+
+ it('opens the quickfix list with the right supertypes', function()
+ clear()
+ exec_lua(create_server_definition)
+ local qflist = exec_lua([=[
+ local clangd_response = { {
+ data = {
+ parents = { {
+ parents = { {
+ parents = { {
+ parents = {},
+ symbolID = "62B3D268A01B9978"
+ } },
+ symbolID = "DC9B0AD433B43BEC"
+ } },
+ symbolID = "06B5F6A19BA9F6A8"
+ } },
+ symbolID = "EDC336589C09ABB2"
+ },
+ kind = 5,
+ name = "D2",
+ range = {
+ ["end"] = {
+ character = 8,
+ line = 9
+ },
+ start = {
+ character = 6,
+ line = 9
+ }
+ },
+ selectionRange = {
+ ["end"] = {
+ character = 8,
+ line = 9
+ },
+ start = {
+ character = 6,
+ line = 9
+ }
+ },
+ uri = "file:///home/jiangyinzuo/hello.cpp"
+ }, {
+ data = {
+ parents = { {
+ parents = { {
+ parents = { {
+ parents = {},
+ symbolID = "62B3D268A01B9978"
+ } },
+ symbolID = "DC9B0AD433B43BEC"
+ } },
+ symbolID = "06B5F6A19BA9F6A8"
+ } },
+ symbolID = "AFFCAED15557EF08"
+ },
+ kind = 5,
+ name = "D1",
+ range = {
+ ["end"] = {
+ character = 8,
+ line = 8
+ },
+ start = {
+ character = 6,
+ line = 8
+ }
+ },
+ selectionRange = {
+ ["end"] = {
+ character = 8,
+ line = 8
+ },
+ start = {
+ character = 6,
+ line = 8
+ }
+ },
+ uri = "file:///home/jiangyinzuo/hello.cpp"
+ } }
+
+ local server = _create_server({
+ capabilities = {
+ positionEncoding = "utf-8"
+ },
+ })
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ local handler = require'vim.lsp.handlers'['typeHierarchy/supertypes']
+ handler(nil, clangd_response, { client_id = client_id, bufnr = 1 })
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = {
+ {
+ bufnr = 2,
+ col = 7,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 10,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'D2',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ {
+ bufnr = 2,
+ col = 7,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 9,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'D1',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ }
+
+ eq(expected, qflist)
+ end)
+
+ it('opens the quickfix list with the right supertypes and details', function()
+ clear()
+ exec_lua(create_server_definition)
+ local qflist = exec_lua([=[
+ local jdtls_response = {
+ {
+ data = { element = '=hello-java_ed323c3c/_<{Main.java[Main[A' },
+ detail = '',
+ kind = 5,
+ name = 'A',
+ range = {
+ ['end'] = { character = 26, line = 3 },
+ start = { character = 1, line = 3 },
+ },
+ selectionRange = {
+ ['end'] = { character = 8, line = 3 },
+ start = { character = 7, line = 3 },
+ },
+ tags = {},
+ uri = 'file:///home/jiangyinzuo/hello-java/Main.java',
+ },
+ {
+ data = { element = '=hello-java_ed323c3c/_<mylist{MyList.java[MyList[Inner' },
+ detail = 'mylist',
+ kind = 5,
+ name = 'MyList$Inner',
+ range = {
+ ['end'] = { character = 37, line = 3 },
+ start = { character = 1, line = 3 },
+ },
+ selectionRange = {
+ ['end'] = { character = 19, line = 3 },
+ start = { character = 14, line = 3 },
+ },
+ tags = {},
+ uri = 'file:///home/jiangyinzuo/hello-java/mylist/MyList.java',
+ },
+ }
+
+ local server = _create_server({
+ capabilities = {
+ positionEncoding = "utf-8"
+ },
+ })
+ local client_id = vim.lsp.start({ name = 'dummy', cmd = server.cmd })
+ local handler = require'vim.lsp.handlers'['typeHierarchy/supertypes']
+ handler(nil, jdtls_response, { client_id = client_id, bufnr = 1 })
+ return vim.fn.getqflist()
+ ]=])
+
+ local expected = {
+ {
+ bufnr = 2,
+ col = 2,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 4,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'A',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ {
+ bufnr = 3,
+ col = 2,
+ end_col = 0,
+ end_lnum = 0,
+ lnum = 4,
+ module = '',
+ nr = 0,
+ pattern = '',
+ text = 'MyList$Inner mylist',
+ type = '',
+ valid = 1,
+ vcol = 0,
+ },
+ }
+ eq(expected, qflist)
+ end)
+ end)
+
describe('vim.lsp.buf.rename', function()
for _, test in ipairs({
{