lsp: add 'textDocument/documentSymbol’ callback

Spec: https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
This commit is contained in:
Hirokazu Hata
2020-02-22 21:20:38 +09:00
parent f157fdef7e
commit 16262472cd
6 changed files with 80 additions and 28 deletions

View File

@@ -48,6 +48,7 @@ go-to-definition, hover, etc. Example config: >
nnoremap <silent> <c-k> <cmd>lua vim.lsp.buf.signature_help()<CR> nnoremap <silent> <c-k> <cmd>lua vim.lsp.buf.signature_help()<CR>
nnoremap <silent> 1gD <cmd>lua vim.lsp.buf.type_definition()<CR> nnoremap <silent> 1gD <cmd>lua vim.lsp.buf.type_definition()<CR>
nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR> nnoremap <silent> gr <cmd>lua vim.lsp.buf.references()<CR>
nnoremap <silent> g0 <cmd>lua vim.lsp.buf.document_symbol()<CR>
Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows Nvim provides the |vim.lsp.omnifunc| 'omnifunc' handler which allows
|i_CTRL-X_CTRL-O| to consume LSP completion. Example config (note the use of |i_CTRL-X_CTRL-O| to consume LSP completion. Example config (note the use of
@@ -733,6 +734,9 @@ definition() *vim.lsp.buf.definition()*
document_highlight() *vim.lsp.buf.document_highlight()* document_highlight() *vim.lsp.buf.document_highlight()*
TODO: Documentation TODO: Documentation
document_symbol() *vim.lsp.buf.document_symbol()*
TODO: Documentation
formatting({options}) *vim.lsp.buf.formatting()* formatting({options}) *vim.lsp.buf.formatting()*
TODO: Documentation TODO: Documentation

View File

@@ -525,14 +525,15 @@ function lsp.start_client(config)
callback = resolve_callback(method) callback = resolve_callback(method)
or error("not found: request callback for client "..client.name) or error("not found: request callback for client "..client.name)
end end
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback) local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
-- TODO keep these checks or just let it go anyway? -- TODO keep these checks or just let it go anyway?
if (not client.resolved_capabilities.hover and method == 'textDocument/hover') if (not client.resolved_capabilities.hover and method == 'textDocument/hover')
or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp') or (not client.resolved_capabilities.signature_help and method == 'textDocument/signatureHelp')
or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition') or (not client.resolved_capabilities.goto_definition and method == 'textDocument/definition')
or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation') or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation')
or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
then then
callback(unsupported_method(method), method, nil, client_id) callback(unsupported_method(method), method, nil, client_id, bufnr)
return return
end end
return rpc.request(method, params, function(err, result) return rpc.request(method, params, function(err, result)
@@ -874,7 +875,7 @@ end
function lsp.buf_request_sync(bufnr, method, params, timeout_ms) function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
local request_results = {} local request_results = {}
local result_count = 0 local result_count = 0
local function _callback(err, _method, result, client_id, bufnr) local function _callback(err, _method, result, client_id)
request_results[client_id] = { error = err, result = result } request_results[client_id] = { error = err, result = result }
result_count = result_count + 1 result_count = result_count + 1
end end
@@ -905,7 +906,7 @@ function lsp.buf_notify(bufnr, method, params)
method = { method, 's' }; method = { method, 's' };
} }
local resp = false local resp = false
for_each_buffer_client(bufnr, function(client, _client_id) for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr)
if client.rpc.notify(method, params) then resp = true end if client.rpc.notify(method, params) then resp = true end
end) end)
return resp return resp

View File

@@ -38,7 +38,13 @@ end
M['textDocument/references'] = function(_, _, result) M['textDocument/references'] = function(_, _, result)
if not result then return end if not result then return end
util.set_qflist(result) util.set_qflist(util.locations_to_items(result))
end
M['textDocument/documentSymbol'] = function(_, _, result, _, bufnr)
if not result or vim.tbl_isempty(result) then return end
util.set_qflist(util.symbols_to_items(result, bufnr))
api.nvim_command("copen") api.nvim_command("copen")
api.nvim_command("wincmd p") api.nvim_command("wincmd p")
end end
@@ -97,7 +103,7 @@ local function location_callback(_, method, result)
end end
util.jump_to_location(result[1]) util.jump_to_location(result[1])
if #result > 1 then if #result > 1 then
util.set_qflist(result) util.set_qflist(util.locations_to_items(result))
api.nvim_command("copen") api.nvim_command("copen")
api.nvim_command("wincmd p") api.nvim_command("wincmd p")
end end

View File

@@ -663,19 +663,19 @@ function protocol.make_client_capabilities()
documentHighlight = { documentHighlight = {
dynamicRegistration = false dynamicRegistration = false
}; };
-- documentSymbol = { documentSymbol = {
-- dynamicRegistration = false; dynamicRegistration = false;
-- symbolKind = { symbolKind = {
-- valueSet = (function() valueSet = (function()
-- local res = {} local res = {}
-- for k in pairs(protocol.SymbolKind) do for k in pairs(protocol.SymbolKind) do
-- if type(k) == 'string' then table.insert(res, k) end if type(k) == 'number' then table.insert(res, k) end
-- end end
-- return res return res
-- end)(); end)();
-- }; };
-- hierarchicalDocumentSymbolSupport = false; hierarchicalDocumentSymbolSupport = true;
-- }; };
}; };
workspace = nil; workspace = nil;
experimental = nil; experimental = nil;

View File

@@ -834,23 +834,60 @@ function M.locations_to_items(locations)
return items return items
end end
-- locations is Location[] function M.set_loclist(items)
-- Only sets for the current window.
function M.set_loclist(locations)
vim.fn.setloclist(0, {}, ' ', { vim.fn.setloclist(0, {}, ' ', {
title = 'Language Server'; title = 'Language Server';
items = M.locations_to_items(locations); items = items;
}) })
end end
-- locations is Location[] function M.set_qflist(items)
function M.set_qflist(locations)
vim.fn.setqflist({}, ' ', { vim.fn.setqflist({}, ' ', {
title = 'Language Server'; title = 'Language Server';
items = M.locations_to_items(locations); items = items;
}) })
end end
--- Convert symbols to quickfix list items
---
--@symbols DocumentSymbol[] or SymbolInformation[]
function M.symbols_to_items(symbols, bufnr)
local function _symbols_to_items(_symbols, _items, _bufnr)
for _, symbol in ipairs(_symbols) do
if symbol.location then -- SymbolInformation type
local range = symbol.location.range
local kind = protocol.SymbolKind[symbol.kind]
table.insert(_items, {
filename = vim.uri_to_fname(symbol.location.uri),
lnum = range.start.line + 1,
col = range.start.character + 1,
kind = kind,
text = '['..kind..'] '..symbol.name,
})
elseif symbol.range then -- DocumentSymbole type
local kind = protocol.SymbolKind[symbol.kind]
table.insert(_items, {
-- bufnr = _bufnr,
filename = vim.api.nvim_buf_get_name(_bufnr),
lnum = symbol.range.start.line + 1,
col = symbol.range.start.character + 1,
kind = kind,
text = '['..kind..'] '..symbol.name
})
if symbol.children then
for _, child in ipairs(symbol) do
for _, v in ipairs(_symbols_to_items(child, _items, _bufnr)) do
vim.list_extend(_items, v)
end
end
end
end
end
return _items
end
return _symbols_to_items(symbols, {}, bufnr)
end
-- Remove empty lines from the beginning and end. -- Remove empty lines from the beginning and end.
function M.trim_empty_lines(lines) function M.trim_empty_lines(lines)
local start = 1 local start = 1
@@ -903,11 +940,15 @@ function M.make_position_params()
local line = api.nvim_buf_get_lines(0, row, row+1, true)[1] local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
col = str_utfindex(line, col) col = str_utfindex(line, col)
return { return {
textDocument = { uri = vim.uri_from_bufnr(0) }; textDocument = M.make_text_document_params();
position = { line = row; character = col; } position = { line = row; character = col; }
} }
end end
function M.make_text_document_params()
return { uri = vim.uri_from_bufnr(0) }
end
-- @param buf buffer handle or 0 for current. -- @param buf buffer handle or 0 for current.
-- @param row 0-indexed line -- @param row 0-indexed line
-- @param col 0-indexed byte offset in line -- @param col 0-indexed byte offset in line

View File

@@ -241,7 +241,7 @@ describe('LSP', function()
it('should succeed with manual shutdown', function() it('should succeed with manual shutdown', function()
local expected_callbacks = { local expected_callbacks = {
{NIL, "shutdown", {}, 1}; {NIL, "shutdown", {}, 1, NIL};
{NIL, "test", {}, 1}; {NIL, "test", {}, 1};
} }
test_rpc_server { test_rpc_server {