mirror of
https://github.com/neovim/neovim.git
synced 2024-12-23 12:45:17 -07:00
feat(lsp): add vim.lsp.buf.subtypes(), vim.lsp.buf.supertypes() (#28388)
Co-authored-by: Mathias Fußenegger <mfussenegger@users.noreply.github.com> Co-authored-by: Maria José Solano <majosolano99@gmail.com>
This commit is contained in:
parent
fd085d9082
commit
f190f758ac
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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' },
|
||||
|
@ -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()
|
||||
|
@ -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, _)
|
||||
|
@ -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({
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user