aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua')
-rw-r--r--runtime/lua/vim/lsp.lua34
-rw-r--r--runtime/lua/vim/lsp/buf.lua27
-rw-r--r--runtime/lua/vim/lsp/callbacks.lua44
-rw-r--r--runtime/lua/vim/lsp/log.lua1
-rw-r--r--runtime/lua/vim/lsp/protocol.lua26
-rw-r--r--runtime/lua/vim/lsp/util.lua119
-rw-r--r--runtime/lua/vim/shared.lua18
-rw-r--r--runtime/lua/vim/treesitter.lua46
-rw-r--r--runtime/lua/vim/tshighlighter.lua2
9 files changed, 261 insertions, 56 deletions
diff --git a/runtime/lua/vim/lsp.lua b/runtime/lua/vim/lsp.lua
index 8190542955..afff4d9900 100644
--- a/runtime/lua/vim/lsp.lua
+++ b/runtime/lua/vim/lsp.lua
@@ -101,7 +101,7 @@ local function for_each_buffer_client(bufnr, callback)
for client_id in pairs(client_ids) do
local client = active_clients[client_id]
if client then
- callback(client, client_id)
+ callback(client, client_id, bufnr)
end
end
end
@@ -520,23 +520,24 @@ function lsp.start_client(config)
end
--- Checks capabilities before rpc.request-ing.
- function client.request(method, params, callback)
+ function client.request(method, params, callback, bufnr)
if not callback then
callback = resolve_callback(method)
or error("not found: request callback for client "..client.name)
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?
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.goto_definition and method == 'textDocument/definition')
or (not client.resolved_capabilities.implementation and method == 'textDocument/implementation')
+ or (not client.resolved_capabilities.document_symbol and method == 'textDocument/documentSymbol')
then
- callback(unsupported_method(method), method, nil, client_id)
+ callback(unsupported_method(method), method, nil, client_id, bufnr)
return
end
return rpc.request(method, params, function(err, result)
- callback(err, method, result, client_id)
+ callback(err, method, result, client_id, bufnr)
end)
end
@@ -838,8 +839,8 @@ function lsp.buf_request(bufnr, method, params, callback)
callback = { callback, 'f', true };
}
local client_request_ids = {}
- for_each_buffer_client(bufnr, function(client, client_id)
- local request_success, request_id = client.request(method, params, callback)
+ for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
+ local request_success, request_id = client.request(method, params, callback, resolved_bufnr)
-- This could only fail if the client shut down in the time since we looked
-- it up and we did the request, which should be rare.
@@ -895,21 +896,22 @@ function lsp.buf_request_sync(bufnr, method, params, timeout_ms)
return request_results
end
---- Sends a notification to all servers attached to the buffer.
----
---@param bufnr (optional, number) Buffer handle, or 0 for current
---@param method (string) LSP method name
---@param params (string) Parameters to send to the server
----
---@returns nil
+--- Send a notification to a server
+-- @param bufnr [number] (optional): The number of the buffer
+-- @param method [string]: Name of the request method
+-- @param params [string]: Arguments to send to the server
+--
+-- @returns true if any client returns true; false otherwise
function lsp.buf_notify(bufnr, method, params)
validate {
bufnr = { bufnr, 'n', true };
method = { method, 's' };
}
- for_each_buffer_client(bufnr, function(client, _client_id)
- client.rpc.notify(method, params)
+ local resp = false
+ for_each_buffer_client(bufnr, function(client, _client_id, _resolved_bufnr)
+ if client.rpc.notify(method, params) then resp = true end
end)
+ return resp
end
--- Implements 'omnifunc' compatible LSP completion.
diff --git a/runtime/lua/vim/lsp/buf.lua b/runtime/lua/vim/lsp/buf.lua
index 19deb5df45..fc9e10cb73 100644
--- a/runtime/lua/vim/lsp/buf.lua
+++ b/runtime/lua/vim/lsp/buf.lua
@@ -23,6 +23,10 @@ local function request(method, params, callback)
return vim.lsp.buf_request(0, method, params, callback)
end
+function M.server_ready()
+ return not not vim.lsp.buf_notify(0, "window/progress", {})
+end
+
function M.hover()
local params = util.make_position_params()
request('textDocument/hover', params)
@@ -134,5 +138,28 @@ function M.references(context)
request('textDocument/references', params)
end
+function M.document_symbol()
+ local params = { textDocument = util.make_text_document_params() }
+ request('textDocument/documentSymbol', params)
+end
+
+--- Send request to server to resolve document highlights for the
+--- current text document position. This request can be associated
+--- to key mapping or to events such as `CursorHold`, eg:
+---
+--- <pre>
+--- vim.api.nvim_command [[autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorHoldI <buffer> lua vim.lsp.buf.document_highlight()]]
+--- vim.api.nvim_command [[autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()]]
+--- </pre>
+function M.document_highlight()
+ local params = util.make_position_params()
+ request('textDocument/documentHighlight', params)
+end
+
+function M.clear_references()
+ util.buf_clear_references()
+end
+
return M
-- vim:sw=2 ts=2 et
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua
index e76e07ca96..644c12f98c 100644
--- a/runtime/lua/vim/lsp/callbacks.lua
+++ b/runtime/lua/vim/lsp/callbacks.lua
@@ -32,12 +32,19 @@ M['textDocument/publishDiagnostics'] = function(_, _, result)
util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
util.buf_diagnostics_underline(bufnr, result.diagnostics)
util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
- -- util.set_loclist(result.diagnostics)
+ util.buf_diagnostics_signs(bufnr, result.diagnostics)
+ vim.api.nvim_command("doautocmd User LspDiagnosticsChanged")
end
M['textDocument/references'] = function(_, _, result)
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("wincmd p")
end
@@ -96,7 +103,7 @@ local function location_callback(_, method, result)
end
util.jump_to_location(result[1])
if #result > 1 then
- util.set_qflist(result)
+ util.set_qflist(util.locations_to_items(result))
api.nvim_command("copen")
api.nvim_command("wincmd p")
end
@@ -196,7 +203,33 @@ M['textDocument/peekDefinition'] = function(_, _, result, _)
api.nvim_buf_add_highlight(headbuf, -1, 'Keyword', 0, -1)
end
-local function log_message(_, _, result, client_id)
+M['textDocument/documentHighlight'] = function(_, _, result, _)
+ if not result then return end
+ local bufnr = api.nvim_get_current_buf()
+ util.buf_highlight_references(bufnr, result)
+end
+
+M['window/logMessage'] = function(_, _, result, client_id)
+ local message_type = result.type
+ local message = result.message
+ local client = vim.lsp.get_client_by_id(client_id)
+ local client_name = client and client.name or string.format("id=%d", client_id)
+ if not client then
+ err_message("LSP[", client_name, "] client has shut down after sending the message")
+ end
+ if message_type == protocol.MessageType.Error then
+ log.error(message)
+ elseif message_type == protocol.MessageType.Warning then
+ log.warn(message)
+ elseif message_type == protocol.MessageType.Info then
+ log.info(message)
+ else
+ log.debug(message)
+ end
+ return result
+end
+
+M['window/showMessage'] = function(_, _, result, client_id)
local message_type = result.type
local message = result.message
local client = vim.lsp.get_client_by_id(client_id)
@@ -213,9 +246,6 @@ local function log_message(_, _, result, client_id)
return result
end
-M['window/showMessage'] = log_message
-M['window/logMessage'] = log_message
-
-- Add boilerplate error validation and logging for all of these.
for k, fn in pairs(M) do
M[k] = function(err, method, params, client_id)
diff --git a/runtime/lua/vim/lsp/log.lua b/runtime/lua/vim/lsp/log.lua
index 974eaae38c..c0db5e5485 100644
--- a/runtime/lua/vim/lsp/log.lua
+++ b/runtime/lua/vim/lsp/log.lua
@@ -13,7 +13,6 @@ log.levels = {
INFO = 2;
WARN = 3;
ERROR = 4;
- -- FATAL = 4;
}
-- Default log level is warn.
diff --git a/runtime/lua/vim/lsp/protocol.lua b/runtime/lua/vim/lsp/protocol.lua
index f64b0b50e7..41e8119c8c 100644
--- a/runtime/lua/vim/lsp/protocol.lua
+++ b/runtime/lua/vim/lsp/protocol.lua
@@ -663,19 +663,19 @@ function protocol.make_client_capabilities()
documentHighlight = {
dynamicRegistration = false
};
- -- documentSymbol = {
- -- dynamicRegistration = false;
- -- symbolKind = {
- -- valueSet = (function()
- -- local res = {}
- -- for k in pairs(protocol.SymbolKind) do
- -- if type(k) == 'string' then table.insert(res, k) end
- -- end
- -- return res
- -- end)();
- -- };
- -- hierarchicalDocumentSymbolSupport = false;
- -- };
+ documentSymbol = {
+ dynamicRegistration = false;
+ symbolKind = {
+ valueSet = (function()
+ local res = {}
+ for k in pairs(protocol.SymbolKind) do
+ if type(k) == 'number' then table.insert(res, k) end
+ end
+ return res
+ end)();
+ };
+ hierarchicalDocumentSymbolSupport = true;
+ };
};
workspace = nil;
experimental = nil;
diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua
index 3dfe4d7d02..5dd010f2a4 100644
--- a/runtime/lua/vim/lsp/util.lua
+++ b/runtime/lua/vim/lsp/util.lua
@@ -269,7 +269,7 @@ function M.convert_input_to_markdown_lines(input, contents)
end
end
end
- if contents[1] == '' or contents[1] == nil then
+ if (contents[1] == '' or contents[1] == nil) and #contents == 1 then
return {}
end
return contents
@@ -569,7 +569,8 @@ do
local all_buffer_diagnostics = {}
local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics")
-
+ local reference_ns = api.nvim_create_namespace("vim_lsp_references")
+ local sign_ns = 'vim_lsp_signs'
local underline_highlight_name = "LspDiagnosticsUnderline"
vim.cmd(string.format("highlight default %s gui=underline cterm=underline", underline_highlight_name))
for kind, _ in pairs(protocol.DiagnosticSeverity) do
@@ -603,6 +604,11 @@ do
function M.buf_clear_diagnostics(bufnr)
validate { bufnr = {bufnr, 'n', true} }
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
+
+ -- clear sign group
+ vim.fn.sign_unplace(sign_ns, {buffer=bufnr})
+
+ -- clear virtual text namespace
api.nvim_buf_clear_namespace(bufnr, diagnostic_ns, 0, -1)
end
@@ -683,7 +689,6 @@ do
end
end
-
function M.buf_diagnostics_underline(bufnr, diagnostics)
for _, diagnostic in ipairs(diagnostics) do
local start = diagnostic.range["start"]
@@ -705,6 +710,25 @@ do
end
end
+ function M.buf_clear_references(bufnr)
+ validate { bufnr = {bufnr, 'n', true} }
+ api.nvim_buf_clear_namespace(bufnr, reference_ns, 0, -1)
+ end
+
+ function M.buf_highlight_references(bufnr, references)
+ validate { bufnr = {bufnr, 'n', true} }
+ for _, reference in ipairs(references) do
+ local start_pos = {reference["range"]["start"]["line"], reference["range"]["start"]["character"]}
+ local end_pos = {reference["range"]["end"]["line"], reference["range"]["end"]["character"]}
+ local document_highlight_kind = {
+ [protocol.DocumentHighlightKind.Text] = "LspReferenceText";
+ [protocol.DocumentHighlightKind.Read] = "LspReferenceRead";
+ [protocol.DocumentHighlightKind.Write] = "LspReferenceWrite";
+ }
+ highlight_range(bufnr, reference_ns, document_highlight_kind[reference["kind"]], start_pos, end_pos)
+ end
+ end
+
function M.buf_diagnostics_virtual_text(bufnr, diagnostics)
local buffer_line_diagnostics = all_buffer_diagnostics[bufnr]
if not buffer_line_diagnostics then
@@ -725,6 +749,34 @@ do
api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {})
end
end
+ function M.buf_diagnostics_count(kind)
+ local bufnr = vim.api.nvim_get_current_buf()
+ local buffer_line_diagnostics = all_buffer_diagnostics[bufnr]
+ if not buffer_line_diagnostics then return end
+ local count = 0
+ for _, line_diags in pairs(buffer_line_diagnostics) do
+ for _, diag in ipairs(line_diags) do
+ if protocol.DiagnosticSeverity[kind] == diag.severity then count = count + 1 end
+ end
+ end
+ return count
+ end
+ function M.buf_diagnostics_signs(bufnr, diagnostics)
+ vim.fn.sign_define('LspDiagnosticsErrorSign', {text=vim.g['LspDiagnosticsErrorSign'] or 'E', texthl='LspDiagnosticsError', linehl='', numhl=''})
+ vim.fn.sign_define('LspDiagnosticsWarningSign', {text=vim.g['LspDiagnosticsWarningSign'] or 'W', texthl='LspDiagnosticsWarning', linehl='', numhl=''})
+ vim.fn.sign_define('LspDiagnosticsInformationSign', {text=vim.g['LspDiagnosticsInformationSign'] or 'I', texthl='LspDiagnosticsInformation', linehl='', numhl=''})
+ vim.fn.sign_define('LspDiagnosticsHintSign', {text=vim.g['LspDiagnosticsHintSign'] or 'H', texthl='LspDiagnosticsHint', linehl='', numhl=''})
+
+ for _, diagnostic in ipairs(diagnostics) do
+ local diagnostic_severity_map = {
+ [protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign";
+ [protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign";
+ [protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign";
+ [protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign";
+ }
+ vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)})
+ end
+ end
end
local position_sort = sort_by_key(function(v)
@@ -745,7 +797,7 @@ function M.locations_to_items(locations)
for _, d in ipairs(locations) do
local start = d.range.start
local fname = assert(vim.uri_to_fname(d.uri))
- table.insert(grouped[fname], {start = start, msg= d.message })
+ table.insert(grouped[fname], {start = start})
end
@@ -772,7 +824,7 @@ function M.locations_to_items(locations)
filename = fname,
lnum = row + 1,
col = col + 1;
- text = temp.msg;
+ text = line;
})
end
end
@@ -782,23 +834,60 @@ function M.locations_to_items(locations)
return items
end
--- locations is Location[]
--- Only sets for the current window.
-function M.set_loclist(locations)
+function M.set_loclist(items)
vim.fn.setloclist(0, {}, ' ', {
title = 'Language Server';
- items = M.locations_to_items(locations);
+ items = items;
})
end
--- locations is Location[]
-function M.set_qflist(locations)
+function M.set_qflist(items)
vim.fn.setqflist({}, ' ', {
title = 'Language Server';
- items = M.locations_to_items(locations);
+ items = items;
})
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.
function M.trim_empty_lines(lines)
local start = 1
@@ -851,11 +940,15 @@ function M.make_position_params()
local line = api.nvim_buf_get_lines(0, row, row+1, true)[1]
col = str_utfindex(line, col)
return {
- textDocument = { uri = vim.uri_from_bufnr(0) };
+ textDocument = M.make_text_document_params();
position = { line = row; character = col; }
}
end
+function M.make_text_document_params()
+ return { uri = vim.uri_from_bufnr(0) }
+end
+
-- @param buf buffer handle or 0 for current.
-- @param row 0-indexed line
-- @param col 0-indexed byte offset in line
diff --git a/runtime/lua/vim/shared.lua b/runtime/lua/vim/shared.lua
index 498992aa2e..1bf1c63fd7 100644
--- a/runtime/lua/vim/shared.lua
+++ b/runtime/lua/vim/shared.lua
@@ -356,6 +356,24 @@ function vim.tbl_islist(t)
end
end
+--- Counts the number of non-nil values in table `t`.
+---
+--- <pre>
+--- vim.tbl_count({ a=1, b=2 }) => 2
+--- vim.tbl_count({ 1, 2 }) => 2
+--- </pre>
+---
+--@see https://github.com/Tieske/Penlight/blob/master/lua/pl/tablex.lua
+--@param Table
+--@returns Number that is the number of the value in table
+function vim.tbl_count(t)
+ vim.validate{t={t,'t'}}
+
+ local count = 0
+ for _ in pairs(t) do count = count + 1 end
+ return count
+end
+
--- Trim whitespace (Lua pattern "%s") from both sides of a string.
---
--@see https://www.lua.org/pil/20.2.html
diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua
index 0d0e22adb3..8dacfa11cf 100644
--- a/runtime/lua/vim/treesitter.lua
+++ b/runtime/lua/vim/treesitter.lua
@@ -113,12 +113,33 @@ end
local Query = {}
Query.__index = Query
+local magic_prefixes = {['\\v']=true, ['\\m']=true, ['\\M']=true, ['\\V']=true}
+local function check_magic(str)
+ if string.len(str) < 2 or magic_prefixes[string.sub(str,1,2)] then
+ return str
+ end
+ return '\\v'..str
+end
+
function M.parse_query(lang, query)
M.require_language(lang)
local self = setmetatable({}, Query)
self.query = vim._ts_parse_query(lang, query)
self.info = self.query:inspect()
self.captures = self.info.captures
+ self.regexes = {}
+ for id,preds in pairs(self.info.patterns) do
+ local regexes = {}
+ for i, pred in ipairs(preds) do
+ if (pred[1] == "match?" and type(pred[2]) == "number"
+ and type(pred[3]) == "string") then
+ regexes[i] = vim.regex(check_magic(pred[3]))
+ end
+ end
+ if next(regexes) then
+ self.regexes[id] = regexes
+ end
+ end
return self
end
@@ -131,8 +152,13 @@ local function get_node_text(node, bufnr)
return string.sub(line, start_col+1, end_col)
end
-local function match_preds(match, preds, bufnr)
- for _, pred in pairs(preds) do
+function Query:match_preds(match, pattern, bufnr)
+ local preds = self.info.patterns[pattern]
+ if not preds then
+ return true
+ end
+ local regexes = self.regexes[pattern]
+ for i, pred in pairs(preds) do
if pred[1] == "eq?" then
local node = match[pred[2]]
local node_text = get_node_text(node, bufnr)
@@ -149,6 +175,16 @@ local function match_preds(match, preds, bufnr)
if node_text ~= str or str == nil then
return false
end
+ elseif pred[1] == "match?" then
+ if not regexes or not regexes[i] then
+ return false
+ end
+ local node = match[pred[2]]
+ local start_row, start_col, end_row, end_col = node:range()
+ if start_row ~= end_row then
+ return false
+ end
+ return regexes[i]:match_line(bufnr, start_row, start_col, end_col)
else
return false
end
@@ -164,8 +200,7 @@ function Query:iter_captures(node, bufnr, start, stop)
local function iter()
local capture, captured_node, match = raw_iter()
if match ~= nil then
- local preds = self.info.patterns[match.pattern]
- local active = match_preds(match, preds, bufnr)
+ local active = self:match_preds(match, match.pattern, bufnr)
match.active = active
if not active then
return iter() -- tail call: try next match
@@ -184,8 +219,7 @@ function Query:iter_matches(node, bufnr, start, stop)
local function iter()
local pattern, match = raw_iter()
if match ~= nil then
- local preds = self.info.patterns[pattern]
- local active = (not preds) or match_preds(match, preds, bufnr)
+ local active = self:match_preds(match, pattern, bufnr)
if not active then
return iter() -- tail call: try next match
end
diff --git a/runtime/lua/vim/tshighlighter.lua b/runtime/lua/vim/tshighlighter.lua
index 9d094f0f9a..1440acf0d0 100644
--- a/runtime/lua/vim/tshighlighter.lua
+++ b/runtime/lua/vim/tshighlighter.lua
@@ -69,6 +69,8 @@ function TSHighlighter:set_query(query)
end
self.id_map[i] = hl
end
+
+ a.nvim__buf_redraw_range(self.buf, 0, a.nvim_buf_line_count(self.buf))
end
function TSHighlighter:on_change(changes)