mirror of
https://github.com/neovim/neovim.git
synced 2025-10-21 17:21:49 +00:00
lsp: vim.lsp.diagnostic (#12655)
Breaking Changes: - Deprecated all `vim.lsp.util.{*diagnostics*}()` functions. - Instead, all functions must be found in vim.lsp.diagnostic - For now, they issue a warning ONCE per neovim session. In a "little while" we will remove them completely. - `vim.lsp.callbacks` has moved to `vim.lsp.handlers`. - For a "little while" we will just redirect `vim.lsp.callbacks` to `vim.lsp.handlers`. However, we will remove this at some point, so it is recommended that you change all of your references to `callbacks` into `handlers`. - This also means that for functions like |vim.lsp.start_client()| and similar, keyword style arguments have moved from "callbacks" to "handlers". Once again, these are currently being forward, but will cease to be forwarded in a "little while". - Changed the highlight groups for LspDiagnostic highlight as they were inconsistently named. - For more information, see |lsp-highlight-diagnostics| - Changed the sign group names as well, to be consistent with |lsp-highlight-diagnostics| General Enhancements: - Rewrote much of the getting started help document for lsp. It also provides a much nicer configuration strategy, so as to not recommend globally overwriting builtin neovim mappings. LSP Enhancements: - Introduced the concept of |lsp-handlers| which will allow much better customization for users without having to copy & paste entire files / functions / etc. Diagnostic Enhancements: - "goto next diagnostic" |vim.lsp.diagnostic.goto_next()| - "goto prev diagnostic" |vim.lsp.diagnostic.goto_prev()| - For each of the gotos, auto open diagnostics is available as a configuration option - Configurable diagnostic handling: - See |vim.lsp.diagnostic.on_publish_diagnostics()| - Delay display until after insert mode - Configure signs - Configure virtual text - Configure underline - Set the location list with the buffers diagnostics. - See |vim.lsp.diagnostic.set_loclist()| - Better performance for getting counts and line diagnostics - They are now cached on save, to enhance lookups. - Particularly useful for checking in statusline, etc. - Actual testing :) - See ./test/functional/plugin/lsp/diagnostic_spec.lua - Added `guisp` for underline highlighting NOTE: "a little while" means enough time to feel like most plugins and plugin authors have had a chance to refactor their code to use the updated calls. Then we will remove them completely. There is no need to keep them, because we don't have any released version of neovim that exposes these APIs. I'm trying to be nice to people following HEAD :) Co-authored: [Twitch Chat 2020](https://twitch.tv/teej_dv)
This commit is contained in:
@@ -475,6 +475,9 @@ created for extmark changes.
|
|||||||
==============================================================================
|
==============================================================================
|
||||||
Global Functions *api-global*
|
Global Functions *api-global*
|
||||||
|
|
||||||
|
nvim__get_hl_defs({ns_id}) *nvim__get_hl_defs()*
|
||||||
|
TODO: Documentation
|
||||||
|
|
||||||
nvim__get_lib_dir() *nvim__get_lib_dir()*
|
nvim__get_lib_dir() *nvim__get_lib_dir()*
|
||||||
TODO: Documentation
|
TODO: Documentation
|
||||||
|
|
||||||
@@ -952,6 +955,9 @@ nvim_get_runtime_file({name}, {all}) *nvim_get_runtime_file()*
|
|||||||
It is not an error to not find any files. An empty array is
|
It is not an error to not find any files. An empty array is
|
||||||
returned then.
|
returned then.
|
||||||
|
|
||||||
|
Attributes: ~
|
||||||
|
{fast}
|
||||||
|
|
||||||
Parameters: ~
|
Parameters: ~
|
||||||
{name} pattern of files to search for
|
{name} pattern of files to search for
|
||||||
{all} whether to return all matches or only the first
|
{all} whether to return all matches or only the first
|
||||||
@@ -987,6 +993,7 @@ nvim_input({keys}) *nvim_input()*
|
|||||||
Note:
|
Note:
|
||||||
|keycodes| like <CR> are translated, so "<" is special. To
|
|keycodes| like <CR> are translated, so "<" is special. To
|
||||||
input a literal "<", send <LT>.
|
input a literal "<", send <LT>.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
For mouse events use |nvim_input_mouse()|. The pseudokey
|
For mouse events use |nvim_input_mouse()|. The pseudokey
|
||||||
form "<LeftMouse><col,row>" is deprecated since
|
form "<LeftMouse><col,row>" is deprecated since
|
||||||
@@ -1378,8 +1385,7 @@ nvim_select_popupmenu_item({item}, {insert}, {finish}, {opts})
|
|||||||
{opts} Optional parameters. Reserved for future use.
|
{opts} Optional parameters. Reserved for future use.
|
||||||
|
|
||||||
*nvim_set_client_info()*
|
*nvim_set_client_info()*
|
||||||
nvim_set_client_info({name}, {version}, {type}, {methods},
|
nvim_set_client_info({name}, {version}, {type}, {methods}, {attributes})
|
||||||
{attributes})
|
|
||||||
Self-identifies the client.
|
Self-identifies the client.
|
||||||
|
|
||||||
The client/plugin/application should call this after
|
The client/plugin/application should call this after
|
||||||
@@ -1491,7 +1497,7 @@ nvim_set_decoration_provider({ns_id}, {opts})
|
|||||||
disable the provider until the next redraw. Similarily, return
|
disable the provider until the next redraw. Similarily, return
|
||||||
`false` in `on_win` will skip the `on_lines` calls for that
|
`false` in `on_win` will skip the `on_lines` calls for that
|
||||||
window (but any extmarks set in `on_win` will still be used).
|
window (but any extmarks set in `on_win` will still be used).
|
||||||
A plugin managing multiple sources of decorations should
|
A plugin managing multiple sources of decoration should
|
||||||
ideally only set one provider, and merge the sources
|
ideally only set one provider, and merge the sources
|
||||||
internally. You can use multiple `ns_id` for the extmarks
|
internally. You can use multiple `ns_id` for the extmarks
|
||||||
set/modified inside the callback anyway.
|
set/modified inside the callback anyway.
|
||||||
@@ -1519,6 +1525,33 @@ nvim_set_decoration_provider({ns_id}, {opts})
|
|||||||
• on_end: called at the end of a redraw cycle
|
• on_end: called at the end of a redraw cycle
|
||||||
["end", tick]
|
["end", tick]
|
||||||
|
|
||||||
|
nvim_set_hl({ns_id}, {name}, {val}) *nvim_set_hl()*
|
||||||
|
Set a highlight group.
|
||||||
|
|
||||||
|
TODO: ns_id = 0, should modify :highlight namespace TODO val
|
||||||
|
should take update vs reset flag
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{ns_id} number of namespace for this highlight
|
||||||
|
{name} highlight group name, like ErrorMsg
|
||||||
|
{val} highlight definiton map, like
|
||||||
|
|nvim_get_hl_by_name|.
|
||||||
|
|
||||||
|
nvim_set_hl_ns({ns_id}) *nvim_set_hl_ns()*
|
||||||
|
Set active namespace for highlights.
|
||||||
|
|
||||||
|
NB: this function can be called from async contexts, but the
|
||||||
|
semantics are not yet well-defined. To start with
|
||||||
|
|nvim_set_decoration_provider| on_win and on_line callbacks
|
||||||
|
are explicitly allowed to change the namespace during a redraw
|
||||||
|
cycle.
|
||||||
|
|
||||||
|
Attributes: ~
|
||||||
|
{fast}
|
||||||
|
|
||||||
|
Parameters: ~
|
||||||
|
{ns_id} the namespace to activate
|
||||||
|
|
||||||
nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()*
|
nvim_set_keymap({mode}, {lhs}, {rhs}, {opts}) *nvim_set_keymap()*
|
||||||
Sets a global |mapping| for the given mode.
|
Sets a global |mapping| for the given mode.
|
||||||
|
|
||||||
@@ -1618,8 +1651,8 @@ nvim__buf_stats({buffer}) *nvim__buf_stats()*
|
|||||||
TODO: Documentation
|
TODO: Documentation
|
||||||
|
|
||||||
*nvim_buf_add_highlight()*
|
*nvim_buf_add_highlight()*
|
||||||
nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line},
|
nvim_buf_add_highlight({buffer}, {src_id}, {hl_group}, {line}, {col_start},
|
||||||
{col_start}, {col_end})
|
{col_end})
|
||||||
Adds a highlight to buffer.
|
Adds a highlight to buffer.
|
||||||
|
|
||||||
Useful for plugins that dynamically generate highlights to a
|
Useful for plugins that dynamically generate highlights to a
|
||||||
@@ -2067,8 +2100,7 @@ nvim_buf_set_keymap({buffer}, {mode}, {lhs}, {rhs}, {opts})
|
|||||||
|nvim_set_keymap()|
|
|nvim_set_keymap()|
|
||||||
|
|
||||||
*nvim_buf_set_lines()*
|
*nvim_buf_set_lines()*
|
||||||
nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing},
|
nvim_buf_set_lines({buffer}, {start}, {end}, {strict_indexing}, {replacement})
|
||||||
{replacement})
|
|
||||||
Sets (replaces) a line-range in the buffer.
|
Sets (replaces) a line-range in the buffer.
|
||||||
|
|
||||||
Indexing is zero-based, end-exclusive. Negative indices are
|
Indexing is zero-based, end-exclusive. Negative indices are
|
||||||
@@ -2116,8 +2148,7 @@ nvim_buf_set_var({buffer}, {name}, {value}) *nvim_buf_set_var()*
|
|||||||
{value} Variable value
|
{value} Variable value
|
||||||
|
|
||||||
*nvim_buf_set_virtual_text()*
|
*nvim_buf_set_virtual_text()*
|
||||||
nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks},
|
nvim_buf_set_virtual_text({buffer}, {src_id}, {line}, {chunks}, {opts})
|
||||||
{opts})
|
|
||||||
Set the virtual text (annotation) for a buffer line.
|
Set the virtual text (annotation) for a buffer line.
|
||||||
|
|
||||||
By default (and currently the only option) the text will be
|
By default (and currently the only option) the text will be
|
||||||
@@ -2449,8 +2480,8 @@ nvim_ui_pum_set_bounds({width}, {height}, {row}, {col})
|
|||||||
Note that this method is not to be confused with
|
Note that this method is not to be confused with
|
||||||
|nvim_ui_pum_set_height()|, which sets the number of visible
|
|nvim_ui_pum_set_height()|, which sets the number of visible
|
||||||
items in the popup menu, while this function sets the bounding
|
items in the popup menu, while this function sets the bounding
|
||||||
box of the popup menu, including visual decorations such as
|
box of the popup menu, including visual elements such as
|
||||||
boarders and sliders. Floats need not use the same font size,
|
borders and sliders. Floats need not use the same font size,
|
||||||
nor be anchored to exact grid corners, so one can set
|
nor be anchored to exact grid corners, so one can set
|
||||||
floating-point numbers to the popup menu geometry.
|
floating-point numbers to the popup menu geometry.
|
||||||
|
|
||||||
|
129
runtime/doc/lsp-extension.txt
Normal file
129
runtime/doc/lsp-extension.txt
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
*lsp-extension.txt* LSP Extension
|
||||||
|
|
||||||
|
NVIM REFERENCE MANUAL
|
||||||
|
|
||||||
|
|
||||||
|
The `vim.lsp` Lua module is a framework for building LSP plugins.
|
||||||
|
|
||||||
|
1. Start with |vim.lsp.start_client()| and |vim.lsp.buf_attach_client()|.
|
||||||
|
2. Peek at the API: >
|
||||||
|
:lua print(vim.inspect(vim.lsp))
|
||||||
|
< 3. See |lsp-extension-example| for a full example.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
LSP EXAMPLE *lsp-extension-example*
|
||||||
|
|
||||||
|
This example is for plugin authors or users who want a lot of control. If you
|
||||||
|
are just getting started see |lsp-quickstart|.
|
||||||
|
|
||||||
|
For more advanced configurations where just filtering by filetype isn't
|
||||||
|
sufficient, you can use the `vim.lsp.start_client()` and
|
||||||
|
`vim.lsp.buf_attach_client()` commands to easily customize the configuration
|
||||||
|
however you please. For example, if you want to do your own filtering, or
|
||||||
|
start a new LSP client based on the root directory for working with multiple
|
||||||
|
projects in a single session. To illustrate, the following is a fully working
|
||||||
|
Lua example.
|
||||||
|
|
||||||
|
The example will:
|
||||||
|
1. Check for each new buffer whether or not we want to start an LSP client.
|
||||||
|
2. Try to find a root directory by ascending from the buffer's path.
|
||||||
|
3. Create a new LSP for that root directory if one doesn't exist.
|
||||||
|
4. Attach the buffer to the client for that root directory.
|
||||||
|
|
||||||
|
>
|
||||||
|
-- Some path manipulation utilities
|
||||||
|
local function is_dir(filename)
|
||||||
|
local stat = vim.loop.fs_stat(filename)
|
||||||
|
return stat and stat.type == 'directory' or false
|
||||||
|
end
|
||||||
|
|
||||||
|
local path_sep = vim.loop.os_uname().sysname == "Windows" and "\\" or "/"
|
||||||
|
-- Assumes filepath is a file.
|
||||||
|
local function dirname(filepath)
|
||||||
|
local is_changed = false
|
||||||
|
local result = filepath:gsub(path_sep.."([^"..path_sep.."]+)$", function()
|
||||||
|
is_changed = true
|
||||||
|
return ""
|
||||||
|
end)
|
||||||
|
return result, is_changed
|
||||||
|
end
|
||||||
|
|
||||||
|
local function path_join(...)
|
||||||
|
return table.concat(vim.tbl_flatten {...}, path_sep)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ascend the buffer's path until we find the rootdir.
|
||||||
|
-- is_root_path is a function which returns bool
|
||||||
|
local function buffer_find_root_dir(bufnr, is_root_path)
|
||||||
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
|
if vim.fn.filereadable(bufname) == 0 then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local dir = bufname
|
||||||
|
-- Just in case our algo is buggy, don't infinite loop.
|
||||||
|
for _ = 1, 100 do
|
||||||
|
local did_change
|
||||||
|
dir, did_change = dirname(dir)
|
||||||
|
if is_root_path(dir, bufname) then
|
||||||
|
return dir, bufname
|
||||||
|
end
|
||||||
|
-- If we can't ascend further, then stop looking.
|
||||||
|
if not did_change then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- A table to store our root_dir to client_id lookup. We want one LSP per
|
||||||
|
-- root directory, and this is how we assert that.
|
||||||
|
local javascript_lsps = {}
|
||||||
|
-- Which filetypes we want to consider.
|
||||||
|
local javascript_filetypes = {
|
||||||
|
["javascript.jsx"] = true;
|
||||||
|
["javascript"] = true;
|
||||||
|
["typescript"] = true;
|
||||||
|
["typescript.jsx"] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
-- Create a template configuration for a server to start, minus the root_dir
|
||||||
|
-- which we will specify later.
|
||||||
|
local javascript_lsp_config = {
|
||||||
|
name = "javascript";
|
||||||
|
cmd = { path_join(os.getenv("JAVASCRIPT_LANGUAGE_SERVER_DIRECTORY"), "lib", "language-server-stdio.js") };
|
||||||
|
}
|
||||||
|
|
||||||
|
-- This needs to be global so that we can call it from the autocmd.
|
||||||
|
function check_start_javascript_lsp()
|
||||||
|
local bufnr = vim.api.nvim_get_current_buf()
|
||||||
|
-- Filter which files we are considering.
|
||||||
|
if not javascript_filetypes[vim.api.nvim_buf_get_option(bufnr, 'filetype')] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
-- Try to find our root directory. We will define this as a directory which contains
|
||||||
|
-- node_modules. Another choice would be to check for `package.json`, or for `.git`.
|
||||||
|
local root_dir = buffer_find_root_dir(bufnr, function(dir)
|
||||||
|
return is_dir(path_join(dir, 'node_modules'))
|
||||||
|
-- return vim.fn.filereadable(path_join(dir, 'package.json')) == 1
|
||||||
|
-- return is_dir(path_join(dir, '.git'))
|
||||||
|
end)
|
||||||
|
-- We couldn't find a root directory, so ignore this file.
|
||||||
|
if not root_dir then return end
|
||||||
|
|
||||||
|
-- Check if we have a client already or start and store it.
|
||||||
|
local client_id = javascript_lsps[root_dir]
|
||||||
|
if not client_id then
|
||||||
|
local new_config = vim.tbl_extend("error", javascript_lsp_config, {
|
||||||
|
root_dir = root_dir;
|
||||||
|
})
|
||||||
|
client_id = vim.lsp.start_client(new_config)
|
||||||
|
javascript_lsps[root_dir] = client_id
|
||||||
|
end
|
||||||
|
-- Finally, attach to the buffer to track changes. This will do nothing if we
|
||||||
|
-- are already attached.
|
||||||
|
vim.lsp.buf_attach_client(bufnr, client_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_command [[autocmd BufReadPost * lua check_start_javascript_lsp()]]
|
||||||
|
<
|
||||||
|
|
||||||
|
vim:tw=78:ts=8:ft=help:norl:
|
1354
runtime/doc/lsp.txt
1354
runtime/doc/lsp.txt
File diff suppressed because it is too large
Load Diff
@@ -1325,6 +1325,7 @@ uri_to_bufnr({uri}) *vim.uri_to_bufnr()*
|
|||||||
|
|
||||||
Return: ~
|
Return: ~
|
||||||
bufnr.
|
bufnr.
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
Creates buffer but does not load it
|
Creates buffer but does not load it
|
||||||
|
|
||||||
|
24
runtime/lua/vim/F.lua
Normal file
24
runtime/lua/vim/F.lua
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
local F = {}
|
||||||
|
|
||||||
|
--- Returns {a} if it is not nil, otherwise returns {b}.
|
||||||
|
---
|
||||||
|
--@param a
|
||||||
|
--@param b
|
||||||
|
function F.if_nil(a, b)
|
||||||
|
if a == nil then return b end
|
||||||
|
return a
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Use in combination with pcall
|
||||||
|
function F.ok_or_nil(status, ...)
|
||||||
|
if not status then return end
|
||||||
|
return ...
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Nil pcall.
|
||||||
|
function F.npcall(fn, ...)
|
||||||
|
return F.ok_or_nil(pcall(fn, ...))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
return F
|
@@ -2,6 +2,22 @@ local api = vim.api
|
|||||||
|
|
||||||
local highlight = {}
|
local highlight = {}
|
||||||
|
|
||||||
|
--@private
|
||||||
|
function highlight.create(higroup, hi_info, default)
|
||||||
|
local options = {}
|
||||||
|
-- TODO: Add validation
|
||||||
|
for k, v in pairs(hi_info) do
|
||||||
|
table.insert(options, string.format("%s=%s", k, v))
|
||||||
|
end
|
||||||
|
vim.cmd(string.format([[highlight %s %s %s]], default and "default" or "", higroup, table.concat(options, " ")))
|
||||||
|
end
|
||||||
|
|
||||||
|
--@private
|
||||||
|
function highlight.link(higroup, link_to, force)
|
||||||
|
vim.cmd(string.format([[highlight%s link %s %s]], force and "!" or " default", higroup, link_to))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
--- Highlight range between two positions
|
--- Highlight range between two positions
|
||||||
---
|
---
|
||||||
--@param bufnr number of buffer to apply highlighting to
|
--@param bufnr number of buffer to apply highlighting to
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
local default_callbacks = require 'vim.lsp.callbacks'
|
local default_handlers = require 'vim.lsp.handlers'
|
||||||
local log = require 'vim.lsp.log'
|
local log = require 'vim.lsp.log'
|
||||||
local lsp_rpc = require 'vim.lsp.rpc'
|
local lsp_rpc = require 'vim.lsp.rpc'
|
||||||
local protocol = require 'vim.lsp.protocol'
|
local protocol = require 'vim.lsp.protocol'
|
||||||
@@ -13,16 +13,21 @@ local validate = vim.validate
|
|||||||
|
|
||||||
local lsp = {
|
local lsp = {
|
||||||
protocol = protocol;
|
protocol = protocol;
|
||||||
callbacks = default_callbacks;
|
|
||||||
|
-- TODO(tjdevries): Add in the warning that `callbacks` is no longer supported.
|
||||||
|
-- util.warn_once("vim.lsp.callbacks is deprecated. Use vim.lsp.handlers instead.")
|
||||||
|
handlers = default_handlers;
|
||||||
|
callbacks = default_handlers;
|
||||||
|
|
||||||
buf = require'vim.lsp.buf';
|
buf = require'vim.lsp.buf';
|
||||||
|
diagnostic = require'vim.lsp.diagnostic';
|
||||||
util = util;
|
util = util;
|
||||||
|
|
||||||
-- Allow raw RPC access.
|
-- Allow raw RPC access.
|
||||||
rpc = lsp_rpc;
|
rpc = lsp_rpc;
|
||||||
|
|
||||||
-- Export these directly from rpc.
|
-- Export these directly from rpc.
|
||||||
rpc_response_error = lsp_rpc.rpc_response_error;
|
rpc_response_error = lsp_rpc.rpc_response_error;
|
||||||
-- You probably won't need this directly, since __tostring is set for errors
|
|
||||||
-- by the RPC.
|
|
||||||
-- format_rpc_error = lsp_rpc.format_rpc_error;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- maps request name to the required resolved_capability in the client.
|
-- maps request name to the required resolved_capability in the client.
|
||||||
@@ -72,7 +77,7 @@ local function resolve_bufnr(bufnr)
|
|||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- callback called by the client when trying to call a method that's not
|
--- Called by the client when trying to call a method that's not
|
||||||
--- supported in any of the servers registered for the current buffer.
|
--- supported in any of the servers registered for the current buffer.
|
||||||
--@param method (string) name of the method
|
--@param method (string) name of the method
|
||||||
function lsp._unsupported_method(method)
|
function lsp._unsupported_method(method)
|
||||||
@@ -115,14 +120,14 @@ local all_buffer_active_clients = {}
|
|||||||
local uninitialized_clients = {}
|
local uninitialized_clients = {}
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- Invokes a callback for each LSP client attached to the buffer {bufnr}.
|
--- Invokes a function for each LSP client attached to the buffer {bufnr}.
|
||||||
---
|
---
|
||||||
--@param bufnr (Number) of buffer
|
--@param bufnr (Number) of buffer
|
||||||
--@param callback (function({client}, {client_id}, {bufnr}) Function to run on
|
--@param fn (function({client}, {client_id}, {bufnr}) Function to run on
|
||||||
---each client attached to that buffer.
|
---each client attached to that buffer.
|
||||||
local function for_each_buffer_client(bufnr, callback)
|
local function for_each_buffer_client(bufnr, fn)
|
||||||
validate {
|
validate {
|
||||||
callback = { callback, 'f' };
|
fn = { fn, 'f' };
|
||||||
}
|
}
|
||||||
bufnr = resolve_bufnr(bufnr)
|
bufnr = resolve_bufnr(bufnr)
|
||||||
local client_ids = all_buffer_active_clients[bufnr]
|
local client_ids = all_buffer_active_clients[bufnr]
|
||||||
@@ -132,7 +137,7 @@ local function for_each_buffer_client(bufnr, callback)
|
|||||||
for client_id in pairs(client_ids) do
|
for client_id in pairs(client_ids) do
|
||||||
local client = active_clients[client_id]
|
local client = active_clients[client_id]
|
||||||
if client then
|
if client then
|
||||||
callback(client, client_id, bufnr)
|
fn(client, client_id, bufnr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -209,7 +214,9 @@ local function validate_client_config(config)
|
|||||||
}
|
}
|
||||||
validate {
|
validate {
|
||||||
root_dir = { config.root_dir, is_dir, "directory" };
|
root_dir = { config.root_dir, is_dir, "directory" };
|
||||||
|
-- TODO(remove-callbacks)
|
||||||
callbacks = { config.callbacks, "t", true };
|
callbacks = { config.callbacks, "t", true };
|
||||||
|
handlers = { config.handlers, "t", true };
|
||||||
capabilities = { config.capabilities, "t", true };
|
capabilities = { config.capabilities, "t", true };
|
||||||
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
|
cmd_cwd = { config.cmd_cwd, optional_validator(is_dir), "directory" };
|
||||||
cmd_env = { config.cmd_env, "t", true };
|
cmd_env = { config.cmd_env, "t", true };
|
||||||
@@ -220,13 +227,23 @@ local function validate_client_config(config)
|
|||||||
before_init = { config.before_init, "f", true };
|
before_init = { config.before_init, "f", true };
|
||||||
offset_encoding = { config.offset_encoding, "s", true };
|
offset_encoding = { config.offset_encoding, "s", true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- TODO(remove-callbacks)
|
||||||
|
if config.handlers and config.callbacks then
|
||||||
|
error(debug.traceback(
|
||||||
|
"Unable to configure LSP with both 'config.handlers' and 'config.callbacks'. Use 'config.handlers' exclusively."
|
||||||
|
))
|
||||||
|
end
|
||||||
|
|
||||||
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
|
local cmd, cmd_args = lsp._cmd_parts(config.cmd)
|
||||||
local offset_encoding = valid_encodings.UTF16
|
local offset_encoding = valid_encodings.UTF16
|
||||||
if config.offset_encoding then
|
if config.offset_encoding then
|
||||||
offset_encoding = validate_encoding(config.offset_encoding)
|
offset_encoding = validate_encoding(config.offset_encoding)
|
||||||
end
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cmd = cmd; cmd_args = cmd_args;
|
cmd = cmd;
|
||||||
|
cmd_args = cmd_args;
|
||||||
offset_encoding = offset_encoding;
|
offset_encoding = offset_encoding;
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -276,12 +293,11 @@ end
|
|||||||
---
|
---
|
||||||
--- - Methods:
|
--- - Methods:
|
||||||
---
|
---
|
||||||
--- - request(method, params, [callback], bufnr)
|
--- - request(method, params, [handler], bufnr)
|
||||||
--- Sends a request to the server.
|
--- Sends a request to the server.
|
||||||
--- This is a thin wrapper around {client.rpc.request} with some additional
|
--- This is a thin wrapper around {client.rpc.request} with some additional
|
||||||
--- checking.
|
--- checking.
|
||||||
--- If {callback} is not specified, it will use {client.callbacks} to try to
|
--- If {handler} is not specified, If one is not found there, then an error will occur.
|
||||||
--- find a callback. If one is not found there, then an error will occur.
|
|
||||||
--- Returns: {status}, {[client_id]}. {status} is a boolean indicating if
|
--- Returns: {status}, {[client_id]}. {status} is a boolean indicating if
|
||||||
--- the notification was successful. If it is `false`, then it will always
|
--- the notification was successful. If it is `false`, then it will always
|
||||||
--- be `false` (the client has shutdown).
|
--- be `false` (the client has shutdown).
|
||||||
@@ -325,8 +341,7 @@ end
|
|||||||
--- with the server. You can modify this in the `config`'s `on_init` method
|
--- with the server. You can modify this in the `config`'s `on_init` method
|
||||||
--- before text is sent to the server.
|
--- before text is sent to the server.
|
||||||
---
|
---
|
||||||
--- - {callbacks} (table): The callbacks used by the client as
|
--- - {handlers} (table): The handlers used by the client as described in |lsp-handler|.
|
||||||
--- described in |lsp-callbacks|.
|
|
||||||
---
|
---
|
||||||
--- - {config} (table): copy of the table that was passed by the user
|
--- - {config} (table): copy of the table that was passed by the user
|
||||||
--- to |vim.lsp.start_client()|.
|
--- to |vim.lsp.start_client()|.
|
||||||
@@ -378,15 +393,7 @@ end
|
|||||||
--- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an
|
--- `{[vim.type_idx]=vim.types.dictionary}`, else it will be encoded as an
|
||||||
--- array.
|
--- array.
|
||||||
---
|
---
|
||||||
--@param callbacks Map of language server method names to
|
--@param handlers Map of language server method names to |lsp-handler|
|
||||||
--- `function(err, method, params, client_id)` handler. Invoked for:
|
|
||||||
--- - Notifications to the server, where `err` will always be `nil`.
|
|
||||||
--- - Requests by the server. For these you can respond by returning
|
|
||||||
--- two values: `result, err` where err must be shaped like a RPC error,
|
|
||||||
--- i.e. `{ code, message, data? }`. Use |vim.lsp.rpc_response_error()| to
|
|
||||||
--- help with this.
|
|
||||||
--- - Default callback for client requests not explicitly specifying
|
|
||||||
--- a callback.
|
|
||||||
---
|
---
|
||||||
--@param init_options Values to pass in the initialization request
|
--@param init_options Values to pass in the initialization request
|
||||||
--- as `initializationOptions`. See `initialize` in the LSP spec.
|
--- as `initializationOptions`. See `initialize` in the LSP spec.
|
||||||
@@ -437,52 +444,51 @@ function lsp.start_client(config)
|
|||||||
|
|
||||||
local client_id = next_client_id()
|
local client_id = next_client_id()
|
||||||
|
|
||||||
local callbacks = config.callbacks or {}
|
-- TODO(remove-callbacks)
|
||||||
|
local handlers = config.handlers or config.callbacks or {}
|
||||||
local name = config.name or tostring(client_id)
|
local name = config.name or tostring(client_id)
|
||||||
local log_prefix = string.format("LSP[%s]", name)
|
local log_prefix = string.format("LSP[%s]", name)
|
||||||
|
|
||||||
local handlers = {}
|
local dispatch = {}
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- Returns the callback associated with an LSP method. Returns the default
|
--- Returns the handler associated with an LSP method.
|
||||||
--- callback if the user hasn't set a custom one.
|
--- Returns the default handler if the user hasn't set a custom one.
|
||||||
---
|
---
|
||||||
--@param method (string) LSP method name
|
--@param method (string) LSP method name
|
||||||
--@returns (fn) The callback for the given method, if defined, or the default
|
--@returns (fn) The handler for the given method, if defined, or the default from |vim.lsp.handlers|
|
||||||
---from |lsp-callbacks|
|
local function resolve_handler(method)
|
||||||
local function resolve_callback(method)
|
return handlers[method] or default_handlers[method]
|
||||||
return callbacks[method] or default_callbacks[method]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- Handles a notification sent by an LSP server by invoking the
|
--- Handles a notification sent by an LSP server by invoking the
|
||||||
--- corresponding callback.
|
--- corresponding handler.
|
||||||
---
|
---
|
||||||
--@param method (string) LSP method name
|
--@param method (string) LSP method name
|
||||||
--@param params (table) The parameters for that method.
|
--@param params (table) The parameters for that method.
|
||||||
function handlers.notification(method, params)
|
function dispatch.notification(method, params)
|
||||||
local _ = log.debug() and log.debug('notification', method, params)
|
local _ = log.debug() and log.debug('notification', method, params)
|
||||||
local callback = resolve_callback(method)
|
local handler = resolve_handler(method)
|
||||||
if callback then
|
if handler then
|
||||||
-- Method name is provided here for convenience.
|
-- Method name is provided here for convenience.
|
||||||
callback(nil, method, params, client_id)
|
handler(nil, method, params, client_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- Handles a request from an LSP server by invoking the corresponding
|
--- Handles a request from an LSP server by invoking the corresponding handler.
|
||||||
--- callback.
|
|
||||||
---
|
---
|
||||||
--@param method (string) LSP method name
|
--@param method (string) LSP method name
|
||||||
--@param params (table) The parameters for that method
|
--@param params (table) The parameters for that method
|
||||||
function handlers.server_request(method, params)
|
function dispatch.server_request(method, params)
|
||||||
local _ = log.debug() and log.debug('server_request', method, params)
|
local _ = log.debug() and log.debug('server_request', method, params)
|
||||||
local callback = resolve_callback(method)
|
local handler = resolve_handler(method)
|
||||||
if callback then
|
if handler then
|
||||||
local _ = log.debug() and log.debug("server_request: found callback for", method)
|
local _ = log.debug() and log.debug("server_request: found handler for", method)
|
||||||
return callback(nil, method, params, client_id)
|
return handler(nil, method, params, client_id)
|
||||||
end
|
end
|
||||||
local _ = log.debug() and log.debug("server_request: no callback found for", method)
|
local _ = log.debug() and log.debug("server_request: no handler found for", method)
|
||||||
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
|
return nil, lsp.rpc_response_error(protocol.ErrorCodes.MethodNotFound)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -493,7 +499,7 @@ function lsp.start_client(config)
|
|||||||
--@param err (...) Other arguments may be passed depending on the error kind
|
--@param err (...) Other arguments may be passed depending on the error kind
|
||||||
--@see |vim.lsp.client_errors| for possible errors. Use
|
--@see |vim.lsp.client_errors| for possible errors. Use
|
||||||
---`vim.lsp.client_errors[code]` to get a human-friendly name.
|
---`vim.lsp.client_errors[code]` to get a human-friendly name.
|
||||||
function handlers.on_error(code, err)
|
function dispatch.on_error(code, err)
|
||||||
local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err })
|
local _ = log.error() and log.error(log_prefix, "on_error", { code = lsp.client_errors[code], err = err })
|
||||||
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
|
err_message(log_prefix, ': Error ', lsp.client_errors[code], ': ', vim.inspect(err))
|
||||||
if config.on_error then
|
if config.on_error then
|
||||||
@@ -510,7 +516,7 @@ function lsp.start_client(config)
|
|||||||
---
|
---
|
||||||
--@param code (number) exit code of the process
|
--@param code (number) exit code of the process
|
||||||
--@param signal (number) the signal used to terminate (if any)
|
--@param signal (number) the signal used to terminate (if any)
|
||||||
function handlers.on_exit(code, signal)
|
function dispatch.on_exit(code, signal)
|
||||||
active_clients[client_id] = nil
|
active_clients[client_id] = nil
|
||||||
uninitialized_clients[client_id] = nil
|
uninitialized_clients[client_id] = nil
|
||||||
local active_buffers = {}
|
local active_buffers = {}
|
||||||
@@ -523,7 +529,7 @@ function lsp.start_client(config)
|
|||||||
-- Buffer level cleanup
|
-- Buffer level cleanup
|
||||||
vim.schedule(function()
|
vim.schedule(function()
|
||||||
for _, bufnr in ipairs(active_buffers) do
|
for _, bufnr in ipairs(active_buffers) do
|
||||||
util.buf_clear_diagnostics(bufnr)
|
lsp.diagnostic.clear(bufnr)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
if config.on_exit then
|
if config.on_exit then
|
||||||
@@ -532,7 +538,7 @@ function lsp.start_client(config)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Start the RPC client.
|
-- Start the RPC client.
|
||||||
local rpc = lsp_rpc.start(cmd, cmd_args, handlers, {
|
local rpc = lsp_rpc.start(cmd, cmd_args, dispatch, {
|
||||||
cwd = config.cmd_cwd;
|
cwd = config.cmd_cwd;
|
||||||
env = config.cmd_env;
|
env = config.cmd_env;
|
||||||
})
|
})
|
||||||
@@ -542,12 +548,14 @@ function lsp.start_client(config)
|
|||||||
name = name;
|
name = name;
|
||||||
rpc = rpc;
|
rpc = rpc;
|
||||||
offset_encoding = offset_encoding;
|
offset_encoding = offset_encoding;
|
||||||
callbacks = callbacks;
|
|
||||||
config = config;
|
config = config;
|
||||||
|
|
||||||
|
-- TODO(remove-callbacks)
|
||||||
|
callbacks = handlers;
|
||||||
|
handlers = handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Store the uninitialized_clients for cleanup in case we exit before
|
-- Store the uninitialized_clients for cleanup in case we exit before initialize finishes.
|
||||||
-- initialize finishes.
|
|
||||||
uninitialized_clients[client_id] = client;
|
uninitialized_clients[client_id] = client;
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
@@ -641,13 +649,11 @@ function lsp.start_client(config)
|
|||||||
--- Sends a request to the server.
|
--- Sends a request to the server.
|
||||||
---
|
---
|
||||||
--- This is a thin wrapper around {client.rpc.request} with some additional
|
--- This is a thin wrapper around {client.rpc.request} with some additional
|
||||||
--- checks for capabilities and callback availability.
|
--- checks for capabilities and handler availability.
|
||||||
---
|
---
|
||||||
--@param method (string) LSP method name.
|
--@param method (string) LSP method name.
|
||||||
--@param params (table) LSP request params.
|
--@param params (table) LSP request params.
|
||||||
--@param callback (function, optional) Response handler for this method.
|
--@param handler (function, optional) Response |lsp-handler| for this method.
|
||||||
---If {callback} is not specified, it will use {client.callbacks} to try to
|
|
||||||
---find a callback. If one is not found there, then an error will occur.
|
|
||||||
--@param bufnr (number) Buffer handle (0 for current).
|
--@param bufnr (number) Buffer handle (0 for current).
|
||||||
--@returns ({status}, [request_id]): {status} is a bool indicating
|
--@returns ({status}, [request_id]): {status} is a bool indicating
|
||||||
---whether the request was successful. If it is `false`, then it will
|
---whether the request was successful. If it is `false`, then it will
|
||||||
@@ -656,16 +662,14 @@ function lsp.start_client(config)
|
|||||||
---second result. You can use this with `client.cancel_request(request_id)`
|
---second result. You can use this with `client.cancel_request(request_id)`
|
||||||
---to cancel the-request.
|
---to cancel the-request.
|
||||||
--@see |vim.lsp.buf_request()|
|
--@see |vim.lsp.buf_request()|
|
||||||
function client.request(method, params, callback, bufnr)
|
function client.request(method, params, handler, bufnr)
|
||||||
-- FIXME: callback is optional, but bufnr is apparently not? Shouldn't that
|
if not handler then
|
||||||
-- require a `select('#', ...)` call?
|
handler = resolve_handler(method)
|
||||||
if not callback then
|
or error(string.format("not found: %q request handler for client %q.", method, client.name))
|
||||||
callback = resolve_callback(method)
|
|
||||||
or error(string.format("not found: %q request callback for client %q.", method, client.name))
|
|
||||||
end
|
end
|
||||||
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, callback, bufnr)
|
local _ = log.debug() and log.debug(log_prefix, "client.request", client_id, method, params, handler, bufnr)
|
||||||
return rpc.request(method, params, function(err, result)
|
return rpc.request(method, params, function(err, result)
|
||||||
callback(err, method, result, client_id, bufnr)
|
handler(err, method, result, client_id, bufnr)
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -995,19 +999,18 @@ nvim_command("autocmd VimLeavePre * lua vim.lsp._vim_exit_handler()")
|
|||||||
--@param bufnr (number) Buffer handle, or 0 for current.
|
--@param bufnr (number) Buffer handle, or 0 for current.
|
||||||
--@param method (string) LSP method name
|
--@param method (string) LSP method name
|
||||||
--@param params (optional, table) Parameters to send to the server
|
--@param params (optional, table) Parameters to send to the server
|
||||||
--@param callback (optional, functionnil) Handler
|
--@param handler (optional, function) See |lsp-handler|
|
||||||
-- `function(err, method, params, client_id)` for this request. Defaults
|
-- If nil, follows resolution strategy defined in |lsp-handler-configuration|
|
||||||
-- to the client callback in `client.callbacks`. See |lsp-callbacks|.
|
|
||||||
--
|
--
|
||||||
--@returns 2-tuple:
|
--@returns 2-tuple:
|
||||||
--- - Map of client-id:request-id pairs for all successful requests.
|
--- - Map of client-id:request-id pairs for all successful requests.
|
||||||
--- - Function which can be used to cancel all the requests. You could instead
|
--- - Function which can be used to cancel all the requests. You could instead
|
||||||
--- iterate all clients and call their `cancel_request()` methods.
|
--- iterate all clients and call their `cancel_request()` methods.
|
||||||
function lsp.buf_request(bufnr, method, params, callback)
|
function lsp.buf_request(bufnr, method, params, handler)
|
||||||
validate {
|
validate {
|
||||||
bufnr = { bufnr, 'n', true };
|
bufnr = { bufnr, 'n', true };
|
||||||
method = { method, 's' };
|
method = { method, 's' };
|
||||||
callback = { callback, 'f', true };
|
handler = { handler, 'f', true };
|
||||||
}
|
}
|
||||||
local client_request_ids = {}
|
local client_request_ids = {}
|
||||||
|
|
||||||
@@ -1015,7 +1018,7 @@ function lsp.buf_request(bufnr, method, params, callback)
|
|||||||
for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
|
for_each_buffer_client(bufnr, function(client, client_id, resolved_bufnr)
|
||||||
if client.supports_method(method) then
|
if client.supports_method(method) then
|
||||||
method_supported = true
|
method_supported = true
|
||||||
local request_success, request_id = client.request(method, params, callback, resolved_bufnr)
|
local request_success, request_id = client.request(method, params, handler, resolved_bufnr)
|
||||||
|
|
||||||
-- This could only fail if the client shut down in the time since we looked
|
-- 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.
|
-- it up and we did the request, which should be rare.
|
||||||
@@ -1025,13 +1028,13 @@ function lsp.buf_request(bufnr, method, params, callback)
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- if no clients support the given method, call the callback with the proper
|
-- if no clients support the given method, call the handler with the proper
|
||||||
-- error message.
|
-- error message.
|
||||||
if not method_supported then
|
if not method_supported then
|
||||||
local unsupported_err = lsp._unsupported_method(method)
|
local unsupported_err = lsp._unsupported_method(method)
|
||||||
local cb = callback or lsp.callbacks[method]
|
handler = handler or lsp.handlers[method]
|
||||||
if cb then
|
if handler then
|
||||||
cb(unsupported_err, method, bufnr)
|
handler(unsupported_err, method, bufnr)
|
||||||
end
|
end
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@@ -1064,11 +1067,11 @@ 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)
|
local function _sync_handler(err, _, 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
|
||||||
local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _callback)
|
local client_request_ids, cancel = lsp.buf_request(bufnr, method, params, _sync_handler)
|
||||||
local expected_result_count = 0
|
local expected_result_count = 0
|
||||||
for _ in pairs(client_request_ids) do
|
for _ in pairs(client_request_ids) do
|
||||||
expected_result_count = expected_result_count + 1
|
expected_result_count = expected_result_count + 1
|
||||||
@@ -1209,22 +1212,53 @@ function lsp.get_log_path()
|
|||||||
return log.get_filename()
|
return log.get_filename()
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Defines the LspDiagnostics signs if they're not defined already.
|
--- Call {fn} for every client attached to {bufnr}
|
||||||
do
|
function lsp.for_each_buffer_client(bufnr, fn)
|
||||||
--@private
|
return for_each_buffer_client(bufnr, fn)
|
||||||
--- Defines a sign if it isn't already defined.
|
end
|
||||||
--@param name (String) Name of the sign
|
|
||||||
--@param properties (table) Properties to attach to the sign
|
--- Function to manage overriding defaults for LSP handlers.
|
||||||
local function define_default_sign(name, properties)
|
--@param handler (function) See |lsp-handler|
|
||||||
if vim.tbl_isempty(vim.fn.sign_getdefined(name)) then
|
--@param override_config (table) Table containing the keys to override behavior of the {handler}
|
||||||
vim.fn.sign_define(name, properties)
|
function lsp.with(handler, override_config)
|
||||||
|
return function(err, method, params, client_id, bufnr, config)
|
||||||
|
return handler(err, method, params, client_id, bufnr, vim.tbl_deep_extend("force", config or {}, override_config))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Helper function to use when implementing a handler.
|
||||||
|
--- This will check that all of the keys in the user configuration
|
||||||
|
--- are valid keys and make sense to include for this handler.
|
||||||
|
---
|
||||||
|
--- Will error on invalid keys (i.e. keys that do not exist in the options)
|
||||||
|
function lsp._with_extend(name, options, user_config)
|
||||||
|
user_config = user_config or {}
|
||||||
|
|
||||||
|
local resulting_config = {}
|
||||||
|
for k, v in pairs(user_config) do
|
||||||
|
if options[k] == nil then
|
||||||
|
error(debug.traceback(string.format(
|
||||||
|
"Invalid option for `%s`: %s. Valid options are:\n%s",
|
||||||
|
name,
|
||||||
|
k,
|
||||||
|
vim.inspect(vim.tbl_keys(options))
|
||||||
|
)))
|
||||||
|
end
|
||||||
|
|
||||||
|
resulting_config[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
for k, v in pairs(options) do
|
||||||
|
if resulting_config[k] == nil then
|
||||||
|
resulting_config[k] = v
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
define_default_sign('LspDiagnosticsErrorSign', {text='E', texthl='LspDiagnosticsErrorSign', linehl='', numhl=''})
|
|
||||||
define_default_sign('LspDiagnosticsWarningSign', {text='W', texthl='LspDiagnosticsWarningSign', linehl='', numhl=''})
|
return resulting_config
|
||||||
define_default_sign('LspDiagnosticsInformationSign', {text='I', texthl='LspDiagnosticsInformationSign', linehl='', numhl=''})
|
|
||||||
define_default_sign('LspDiagnosticsHintSign', {text='H', texthl='LspDiagnosticsHintSign', linehl='', numhl=''})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Define the LspDiagnostics signs if they're not defined already.
|
||||||
|
require('vim.lsp.diagnostic')._define_default_signs_and_highlights()
|
||||||
|
|
||||||
return lsp
|
return lsp
|
||||||
-- vim:sw=2 ts=2 et
|
-- vim:sw=2 ts=2 et
|
||||||
|
@@ -30,9 +30,7 @@ end
|
|||||||
---
|
---
|
||||||
--@param method (string) LSP method name
|
--@param method (string) LSP method name
|
||||||
--@param params (optional, table) Parameters to send to the server
|
--@param params (optional, table) Parameters to send to the server
|
||||||
--@param callback (optional, functionnil) Handler
|
--@param handler (optional, functionnil) See |lsp-handler|. Follows |lsp-handler-resolution|
|
||||||
-- `function(err, method, params, client_id)` for this request. Defaults
|
|
||||||
-- to the client callback in `client.callbacks`. See |lsp-callbacks|.
|
|
||||||
--
|
--
|
||||||
--@returns 2-tuple:
|
--@returns 2-tuple:
|
||||||
--- - Map of client-id:request-id pairs for all successful requests.
|
--- - Map of client-id:request-id pairs for all successful requests.
|
||||||
@@ -40,12 +38,12 @@ end
|
|||||||
--- iterate all clients and call their `cancel_request()` methods.
|
--- iterate all clients and call their `cancel_request()` methods.
|
||||||
---
|
---
|
||||||
--@see |vim.lsp.buf_request()|
|
--@see |vim.lsp.buf_request()|
|
||||||
local function request(method, params, callback)
|
local function request(method, params, handler)
|
||||||
validate {
|
validate {
|
||||||
method = {method, 's'};
|
method = {method, 's'};
|
||||||
callback = {callback, 'f', true};
|
handler = {handler, 'f', true};
|
||||||
}
|
}
|
||||||
return vim.lsp.buf_request(0, method, params, callback)
|
return vim.lsp.buf_request(0, method, params, handler)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Checks whether the language servers attached to the current buffer are
|
--- Checks whether the language servers attached to the current buffer are
|
||||||
@@ -64,6 +62,7 @@ function M.hover()
|
|||||||
end
|
end
|
||||||
|
|
||||||
--- Jumps to the declaration of the symbol under the cursor.
|
--- Jumps to the declaration of the symbol under the cursor.
|
||||||
|
--@note Many servers do not implement this method. Generally, see |vim.lsp.buf.definition()| instead.
|
||||||
---
|
---
|
||||||
function M.declaration()
|
function M.declaration()
|
||||||
local params = util.make_position_params()
|
local params = util.make_position_params()
|
||||||
@@ -279,7 +278,7 @@ end
|
|||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
||||||
function M.code_action(context)
|
function M.code_action(context)
|
||||||
validate { context = { context, 't', true } }
|
validate { context = { context, 't', true } }
|
||||||
context = context or { diagnostics = util.get_line_diagnostics() }
|
context = context or { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() }
|
||||||
local params = util.make_range_params()
|
local params = util.make_range_params()
|
||||||
params.context = context
|
params.context = context
|
||||||
request('textDocument/codeAction', params)
|
request('textDocument/codeAction', params)
|
||||||
@@ -294,7 +293,7 @@ end
|
|||||||
---Defaults to the end of the last visual selection.
|
---Defaults to the end of the last visual selection.
|
||||||
function M.range_code_action(context, start_pos, end_pos)
|
function M.range_code_action(context, start_pos, end_pos)
|
||||||
validate { context = { context, 't', true } }
|
validate { context = { context, 't', true } }
|
||||||
context = context or { diagnostics = util.get_line_diagnostics() }
|
context = context or { diagnostics = vim.lsp.diagnostic.get_line_diagnostics() }
|
||||||
local params = util.make_given_range_params(start_pos, end_pos)
|
local params = util.make_given_range_params(start_pos, end_pos)
|
||||||
params.context = context
|
params.context = context
|
||||||
request('textDocument/codeAction', params)
|
request('textDocument/codeAction', params)
|
||||||
|
@@ -1,345 +1,4 @@
|
|||||||
local log = require 'vim.lsp.log'
|
|
||||||
local protocol = require 'vim.lsp.protocol'
|
|
||||||
local util = require 'vim.lsp.util'
|
local util = require 'vim.lsp.util'
|
||||||
local vim = vim
|
|
||||||
local api = vim.api
|
|
||||||
local buf = require 'vim.lsp.buf'
|
|
||||||
|
|
||||||
local M = {}
|
util._warn_once("require('vim.lsp.callbacks') is deprecated. Use vim.lsp.handlers instead.")
|
||||||
|
return require('vim.lsp.handlers')
|
||||||
-- FIXME: DOC: Expose in vimdocs
|
|
||||||
|
|
||||||
--@private
|
|
||||||
--- Writes to error buffer.
|
|
||||||
--@param ... (table of strings) Will be concatenated before being written
|
|
||||||
local function err_message(...)
|
|
||||||
api.nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
|
|
||||||
api.nvim_command("redraw")
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
|
|
||||||
M['workspace/executeCommand'] = function(err, _)
|
|
||||||
if err then
|
|
||||||
error("Could not execute code action: "..err.message)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
|
||||||
M['textDocument/codeAction'] = function(_, _, actions)
|
|
||||||
if actions == nil or vim.tbl_isempty(actions) then
|
|
||||||
print("No code actions available")
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local option_strings = {"Code Actions:"}
|
|
||||||
for i, action in ipairs(actions) do
|
|
||||||
local title = action.title:gsub('\r\n', '\\r\\n')
|
|
||||||
title = title:gsub('\n', '\\n')
|
|
||||||
table.insert(option_strings, string.format("%d. %s", i, title))
|
|
||||||
end
|
|
||||||
|
|
||||||
local choice = vim.fn.inputlist(option_strings)
|
|
||||||
if choice < 1 or choice > #actions then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local action_chosen = actions[choice]
|
|
||||||
-- textDocument/codeAction can return either Command[] or CodeAction[].
|
|
||||||
-- If it is a CodeAction, it can have either an edit, a command or both.
|
|
||||||
-- Edits should be executed first
|
|
||||||
if action_chosen.edit or type(action_chosen.command) == "table" then
|
|
||||||
if action_chosen.edit then
|
|
||||||
util.apply_workspace_edit(action_chosen.edit)
|
|
||||||
end
|
|
||||||
if type(action_chosen.command) == "table" then
|
|
||||||
buf.execute_command(action_chosen.command)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
buf.execute_command(action_chosen)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
|
|
||||||
M['workspace/applyEdit'] = function(_, _, workspace_edit)
|
|
||||||
if not workspace_edit then return end
|
|
||||||
-- TODO(ashkan) Do something more with label?
|
|
||||||
if workspace_edit.label then
|
|
||||||
print("Workspace edit", workspace_edit.label)
|
|
||||||
end
|
|
||||||
local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit)
|
|
||||||
return {
|
|
||||||
applied = status;
|
|
||||||
failureReason = result;
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_publishDiagnostics
|
|
||||||
M['textDocument/publishDiagnostics'] = function(_, _, result)
|
|
||||||
if not result then return end
|
|
||||||
local uri = result.uri
|
|
||||||
local bufnr = vim.uri_to_bufnr(uri)
|
|
||||||
if not bufnr then
|
|
||||||
err_message("LSP.publishDiagnostics: Couldn't find buffer for ", uri)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
|
|
||||||
-- The diagnostic's severity. Can be omitted. If omitted it is up to the
|
|
||||||
-- client to interpret diagnostics as error, warning, info or hint.
|
|
||||||
-- TODO: Replace this with server-specific heuristics to infer severity.
|
|
||||||
for _, diagnostic in ipairs(result.diagnostics) do
|
|
||||||
if diagnostic.severity == nil then
|
|
||||||
diagnostic.severity = protocol.DiagnosticSeverity.Error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
util.buf_clear_diagnostics(bufnr)
|
|
||||||
|
|
||||||
-- Always save the diagnostics, even if the buf is not loaded.
|
|
||||||
-- Language servers may report compile or build errors via diagnostics
|
|
||||||
-- Users should be able to find these, even if they're in files which
|
|
||||||
-- are not loaded.
|
|
||||||
util.buf_diagnostics_save_positions(bufnr, result.diagnostics)
|
|
||||||
|
|
||||||
-- Unloaded buffers should not handle diagnostics.
|
|
||||||
-- When the buffer is loaded, we'll call on_attach, which sends textDocument/didOpen.
|
|
||||||
-- This should trigger another publish of the diagnostics.
|
|
||||||
--
|
|
||||||
-- In particular, this stops a ton of spam when first starting a server for current
|
|
||||||
-- unloaded buffers.
|
|
||||||
if not api.nvim_buf_is_loaded(bufnr) then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
util.buf_diagnostics_underline(bufnr, result.diagnostics)
|
|
||||||
util.buf_diagnostics_virtual_text(bufnr, result.diagnostics)
|
|
||||||
util.buf_diagnostics_signs(bufnr, result.diagnostics)
|
|
||||||
vim.api.nvim_command("doautocmd User LspDiagnosticsChanged")
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
|
|
||||||
M['textDocument/references'] = function(_, _, result)
|
|
||||||
if not result then return end
|
|
||||||
util.set_qflist(util.locations_to_items(result))
|
|
||||||
api.nvim_command("copen")
|
|
||||||
api.nvim_command("wincmd p")
|
|
||||||
end
|
|
||||||
|
|
||||||
--@private
|
|
||||||
--- Prints given list of symbols to the quickfix list.
|
|
||||||
--@param _ (not used)
|
|
||||||
--@param _ (not used)
|
|
||||||
--@param result (list of Symbols) LSP method name
|
|
||||||
--@param result (table) result of LSP method; a location or a list of locations.
|
|
||||||
---(`textDocument/definition` can return `Location` or `Location[]`
|
|
||||||
local symbol_callback = 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
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
|
|
||||||
M['textDocument/documentSymbol'] = symbol_callback
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
|
|
||||||
M['workspace/symbol'] = symbol_callback
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
|
|
||||||
M['textDocument/rename'] = function(_, _, result)
|
|
||||||
if not result then return end
|
|
||||||
util.apply_workspace_edit(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
|
|
||||||
M['textDocument/rangeFormatting'] = function(_, _, result)
|
|
||||||
if not result then return end
|
|
||||||
util.apply_text_edits(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
|
|
||||||
M['textDocument/formatting'] = function(_, _, result)
|
|
||||||
if not result then return end
|
|
||||||
util.apply_text_edits(result)
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
|
||||||
M['textDocument/completion'] = function(_, _, result)
|
|
||||||
if vim.tbl_isempty(result or {}) then return end
|
|
||||||
local row, col = unpack(api.nvim_win_get_cursor(0))
|
|
||||||
local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
|
|
||||||
local line_to_cursor = line:sub(col+1)
|
|
||||||
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
|
|
||||||
local prefix = line_to_cursor:sub(textMatch+1)
|
|
||||||
|
|
||||||
local matches = util.text_document_completion_list_to_complete_items(result, prefix)
|
|
||||||
vim.fn.complete(textMatch+1, matches)
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
|
|
||||||
M['textDocument/hover'] = function(_, method, result)
|
|
||||||
util.focusable_float(method, function()
|
|
||||||
if not (result and result.contents) then
|
|
||||||
-- return { 'No information available' }
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
|
|
||||||
markdown_lines = util.trim_empty_lines(markdown_lines)
|
|
||||||
if vim.tbl_isempty(markdown_lines) then
|
|
||||||
-- return { 'No information available' }
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, {
|
|
||||||
pad_left = 1; pad_right = 1;
|
|
||||||
})
|
|
||||||
util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
|
|
||||||
return bufnr, winnr
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--@private
|
|
||||||
--- Jumps to a location. Used as a callback for multiple LSP methods.
|
|
||||||
--@param _ (not used)
|
|
||||||
--@param method (string) LSP method name
|
|
||||||
--@param result (table) result of LSP method; a location or a list of locations.
|
|
||||||
---(`textDocument/definition` can return `Location` or `Location[]`
|
|
||||||
local function location_callback(_, method, result)
|
|
||||||
if result == nil or vim.tbl_isempty(result) then
|
|
||||||
local _ = log.info() and log.info(method, 'No location found')
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
|
|
||||||
-- textDocument/definition can return Location or Location[]
|
|
||||||
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
|
|
||||||
|
|
||||||
if vim.tbl_islist(result) then
|
|
||||||
util.jump_to_location(result[1])
|
|
||||||
|
|
||||||
if #result > 1 then
|
|
||||||
util.set_qflist(util.locations_to_items(result))
|
|
||||||
api.nvim_command("copen")
|
|
||||||
api.nvim_command("wincmd p")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
util.jump_to_location(result)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
|
|
||||||
M['textDocument/declaration'] = location_callback
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
|
|
||||||
M['textDocument/definition'] = location_callback
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
|
|
||||||
M['textDocument/typeDefinition'] = location_callback
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
|
|
||||||
M['textDocument/implementation'] = location_callback
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
|
|
||||||
M['textDocument/signatureHelp'] = function(_, method, result)
|
|
||||||
-- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp callback
|
|
||||||
-- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore
|
|
||||||
if not (result and result.signatures and result.signatures[1]) then
|
|
||||||
print('No signature help available')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local lines = util.convert_signature_help_to_markdown_lines(result)
|
|
||||||
lines = util.trim_empty_lines(lines)
|
|
||||||
if vim.tbl_isempty(lines) then
|
|
||||||
print('No signature help available')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
util.focusable_preview(method, function()
|
|
||||||
return lines, util.try_trim_markdown_code_blocks(lines)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
|
|
||||||
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
|
|
||||||
|
|
||||||
--@private
|
|
||||||
---
|
|
||||||
--- Displays call hierarchy in the quickfix window.
|
|
||||||
---
|
|
||||||
--@param direction `"from"` for incoming calls and `"to"` for outgoing calls
|
|
||||||
--@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
|
|
||||||
--@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
|
|
||||||
local make_call_hierarchy_callback = function(direction)
|
|
||||||
return function(_, _, result)
|
|
||||||
if not result then return end
|
|
||||||
local items = {}
|
|
||||||
for _, call_hierarchy_call in pairs(result) do
|
|
||||||
local call_hierarchy_item = call_hierarchy_call[direction]
|
|
||||||
for _, range in pairs(call_hierarchy_call.fromRanges) do
|
|
||||||
table.insert(items, {
|
|
||||||
filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
|
|
||||||
text = call_hierarchy_item.name,
|
|
||||||
lnum = range.start.line + 1,
|
|
||||||
col = range.start.character + 1,
|
|
||||||
})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
util.set_qflist(items)
|
|
||||||
api.nvim_command("copen")
|
|
||||||
api.nvim_command("wincmd p")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/incomingCalls
|
|
||||||
M['callHierarchy/incomingCalls'] = make_call_hierarchy_callback('from')
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/outgoingCalls
|
|
||||||
M['callHierarchy/outgoingCalls'] = make_call_hierarchy_callback('to')
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/logMessage
|
|
||||||
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
|
|
||||||
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/showMessage
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
err_message("LSP[", client_name, "] ", message)
|
|
||||||
else
|
|
||||||
local message_type_name = protocol.MessageType[message_type]
|
|
||||||
api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
|
|
||||||
end
|
|
||||||
return result
|
|
||||||
end
|
|
||||||
|
|
||||||
-- 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, bufnr)
|
|
||||||
log.debug('default_callback', method, { params = params, client_id = client_id, err = err, bufnr = bufnr })
|
|
||||||
if err then
|
|
||||||
error(tostring(err))
|
|
||||||
end
|
|
||||||
return fn(err, method, params, client_id, bufnr)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
-- vim:sw=2 ts=2 et
|
|
||||||
|
1195
runtime/lua/vim/lsp/diagnostic.lua
Normal file
1195
runtime/lua/vim/lsp/diagnostic.lua
Normal file
File diff suppressed because it is too large
Load Diff
310
runtime/lua/vim/lsp/handlers.lua
Normal file
310
runtime/lua/vim/lsp/handlers.lua
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
local log = require 'vim.lsp.log'
|
||||||
|
local protocol = require 'vim.lsp.protocol'
|
||||||
|
local util = require 'vim.lsp.util'
|
||||||
|
local vim = vim
|
||||||
|
local api = vim.api
|
||||||
|
local buf = require 'vim.lsp.buf'
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
-- FIXME: DOC: Expose in vimdocs
|
||||||
|
|
||||||
|
--@private
|
||||||
|
--- Writes to error buffer.
|
||||||
|
--@param ... (table of strings) Will be concatenated before being written
|
||||||
|
local function err_message(...)
|
||||||
|
api.nvim_err_writeln(table.concat(vim.tbl_flatten{...}))
|
||||||
|
api.nvim_command("redraw")
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_executeCommand
|
||||||
|
M['workspace/executeCommand'] = function(err, _)
|
||||||
|
if err then
|
||||||
|
error("Could not execute code action: "..err.message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_codeAction
|
||||||
|
M['textDocument/codeAction'] = function(_, _, actions)
|
||||||
|
if actions == nil or vim.tbl_isempty(actions) then
|
||||||
|
print("No code actions available")
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local option_strings = {"Code Actions:"}
|
||||||
|
for i, action in ipairs(actions) do
|
||||||
|
local title = action.title:gsub('\r\n', '\\r\\n')
|
||||||
|
title = title:gsub('\n', '\\n')
|
||||||
|
table.insert(option_strings, string.format("%d. %s", i, title))
|
||||||
|
end
|
||||||
|
|
||||||
|
local choice = vim.fn.inputlist(option_strings)
|
||||||
|
if choice < 1 or choice > #actions then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local action_chosen = actions[choice]
|
||||||
|
-- textDocument/codeAction can return either Command[] or CodeAction[].
|
||||||
|
-- If it is a CodeAction, it can have either an edit, a command or both.
|
||||||
|
-- Edits should be executed first
|
||||||
|
if action_chosen.edit or type(action_chosen.command) == "table" then
|
||||||
|
if action_chosen.edit then
|
||||||
|
util.apply_workspace_edit(action_chosen.edit)
|
||||||
|
end
|
||||||
|
if type(action_chosen.command) == "table" then
|
||||||
|
buf.execute_command(action_chosen.command)
|
||||||
|
end
|
||||||
|
else
|
||||||
|
buf.execute_command(action_chosen)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_applyEdit
|
||||||
|
M['workspace/applyEdit'] = function(_, _, workspace_edit)
|
||||||
|
if not workspace_edit then return end
|
||||||
|
-- TODO(ashkan) Do something more with label?
|
||||||
|
if workspace_edit.label then
|
||||||
|
print("Workspace edit", workspace_edit.label)
|
||||||
|
end
|
||||||
|
local status, result = pcall(util.apply_workspace_edit, workspace_edit.edit)
|
||||||
|
return {
|
||||||
|
applied = status;
|
||||||
|
failureReason = result;
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
M['textDocument/publishDiagnostics'] = function(...)
|
||||||
|
return require('vim.lsp.diagnostic').on_publish_diagnostics(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_references
|
||||||
|
M['textDocument/references'] = function(_, _, result)
|
||||||
|
if not result then return end
|
||||||
|
util.set_qflist(util.locations_to_items(result))
|
||||||
|
api.nvim_command("copen")
|
||||||
|
api.nvim_command("wincmd p")
|
||||||
|
end
|
||||||
|
|
||||||
|
--@private
|
||||||
|
--- Prints given list of symbols to the quickfix list.
|
||||||
|
--@param _ (not used)
|
||||||
|
--@param _ (not used)
|
||||||
|
--@param result (list of Symbols) LSP method name
|
||||||
|
--@param result (table) result of LSP method; a location or a list of locations.
|
||||||
|
---(`textDocument/definition` can return `Location` or `Location[]`
|
||||||
|
local symbol_handler = 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
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentSymbol
|
||||||
|
M['textDocument/documentSymbol'] = symbol_handler
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workspace_symbol
|
||||||
|
M['workspace/symbol'] = symbol_handler
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rename
|
||||||
|
M['textDocument/rename'] = function(_, _, result)
|
||||||
|
if not result then return end
|
||||||
|
util.apply_workspace_edit(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_rangeFormatting
|
||||||
|
M['textDocument/rangeFormatting'] = function(_, _, result)
|
||||||
|
if not result then return end
|
||||||
|
util.apply_text_edits(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_formatting
|
||||||
|
M['textDocument/formatting'] = function(_, _, result)
|
||||||
|
if not result then return end
|
||||||
|
util.apply_text_edits(result)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion
|
||||||
|
M['textDocument/completion'] = function(_, _, result)
|
||||||
|
if vim.tbl_isempty(result or {}) then return end
|
||||||
|
local row, col = unpack(api.nvim_win_get_cursor(0))
|
||||||
|
local line = assert(api.nvim_buf_get_lines(0, row-1, row, false)[1])
|
||||||
|
local line_to_cursor = line:sub(col+1)
|
||||||
|
local textMatch = vim.fn.match(line_to_cursor, '\\k*$')
|
||||||
|
local prefix = line_to_cursor:sub(textMatch+1)
|
||||||
|
|
||||||
|
local matches = util.text_document_completion_list_to_complete_items(result, prefix)
|
||||||
|
vim.fn.complete(textMatch+1, matches)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
|
||||||
|
M['textDocument/hover'] = function(_, method, result)
|
||||||
|
util.focusable_float(method, function()
|
||||||
|
if not (result and result.contents) then
|
||||||
|
-- return { 'No information available' }
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
|
||||||
|
markdown_lines = util.trim_empty_lines(markdown_lines)
|
||||||
|
if vim.tbl_isempty(markdown_lines) then
|
||||||
|
-- return { 'No information available' }
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, {
|
||||||
|
pad_left = 1; pad_right = 1;
|
||||||
|
})
|
||||||
|
util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
|
||||||
|
return bufnr, winnr
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@private
|
||||||
|
--- Jumps to a location. Used as a handler for multiple LSP methods.
|
||||||
|
--@param _ (not used)
|
||||||
|
--@param method (string) LSP method name
|
||||||
|
--@param result (table) result of LSP method; a location or a list of locations.
|
||||||
|
---(`textDocument/definition` can return `Location` or `Location[]`
|
||||||
|
local function location_handler(_, method, result)
|
||||||
|
if result == nil or vim.tbl_isempty(result) then
|
||||||
|
local _ = log.info() and log.info(method, 'No location found')
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- textDocument/definition can return Location or Location[]
|
||||||
|
-- https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
|
||||||
|
|
||||||
|
if vim.tbl_islist(result) then
|
||||||
|
util.jump_to_location(result[1])
|
||||||
|
|
||||||
|
if #result > 1 then
|
||||||
|
util.set_qflist(util.locations_to_items(result))
|
||||||
|
api.nvim_command("copen")
|
||||||
|
api.nvim_command("wincmd p")
|
||||||
|
end
|
||||||
|
else
|
||||||
|
util.jump_to_location(result)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_declaration
|
||||||
|
M['textDocument/declaration'] = location_handler
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_definition
|
||||||
|
M['textDocument/definition'] = location_handler
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_typeDefinition
|
||||||
|
M['textDocument/typeDefinition'] = location_handler
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_implementation
|
||||||
|
M['textDocument/implementation'] = location_handler
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp
|
||||||
|
M['textDocument/signatureHelp'] = function(_, method, result)
|
||||||
|
-- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler
|
||||||
|
-- If the completion item doesn't have signatures It will make noise. Change to use `print` that can use `<silent>` to ignore
|
||||||
|
if not (result and result.signatures and result.signatures[1]) then
|
||||||
|
print('No signature help available')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local lines = util.convert_signature_help_to_markdown_lines(result)
|
||||||
|
lines = util.trim_empty_lines(lines)
|
||||||
|
if vim.tbl_isempty(lines) then
|
||||||
|
print('No signature help available')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
util.focusable_preview(method, function()
|
||||||
|
return lines, util.try_trim_markdown_code_blocks(lines)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_documentHighlight
|
||||||
|
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
|
||||||
|
|
||||||
|
--@private
|
||||||
|
---
|
||||||
|
--- Displays call hierarchy in the quickfix window.
|
||||||
|
---
|
||||||
|
--@param direction `"from"` for incoming calls and `"to"` for outgoing calls
|
||||||
|
--@returns `CallHierarchyIncomingCall[]` if {direction} is `"from"`,
|
||||||
|
--@returns `CallHierarchyOutgoingCall[]` if {direction} is `"to"`,
|
||||||
|
local make_call_hierarchy_handler = function(direction)
|
||||||
|
return function(_, _, result)
|
||||||
|
if not result then return end
|
||||||
|
local items = {}
|
||||||
|
for _, call_hierarchy_call in pairs(result) do
|
||||||
|
local call_hierarchy_item = call_hierarchy_call[direction]
|
||||||
|
for _, range in pairs(call_hierarchy_call.fromRanges) do
|
||||||
|
table.insert(items, {
|
||||||
|
filename = assert(vim.uri_to_fname(call_hierarchy_item.uri)),
|
||||||
|
text = call_hierarchy_item.name,
|
||||||
|
lnum = range.start.line + 1,
|
||||||
|
col = range.start.character + 1,
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
util.set_qflist(items)
|
||||||
|
api.nvim_command("copen")
|
||||||
|
api.nvim_command("wincmd p")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/incomingCalls
|
||||||
|
M['callHierarchy/incomingCalls'] = make_call_hierarchy_handler('from')
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#callHierarchy/outgoingCalls
|
||||||
|
M['callHierarchy/outgoingCalls'] = make_call_hierarchy_handler('to')
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/logMessage
|
||||||
|
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
|
||||||
|
|
||||||
|
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#window/showMessage
|
||||||
|
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)
|
||||||
|
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
|
||||||
|
err_message("LSP[", client_name, "] ", message)
|
||||||
|
else
|
||||||
|
local message_type_name = protocol.MessageType[message_type]
|
||||||
|
api.nvim_out_write(string.format("LSP[%s][%s] %s\n", client_name, message_type_name, message))
|
||||||
|
end
|
||||||
|
return result
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 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, bufnr, config)
|
||||||
|
local _ = log.debug() and log.debug('default_handler', method, {
|
||||||
|
params = params, client_id = client_id, err = err, bufnr = bufnr, config = config
|
||||||
|
})
|
||||||
|
|
||||||
|
if err then
|
||||||
|
error(tostring(err))
|
||||||
|
end
|
||||||
|
|
||||||
|
return fn(err, method, params, client_id, bufnr, config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
-- vim:sw=2 ts=2 et
|
@@ -1,18 +1,9 @@
|
|||||||
-- Protocol for the Microsoft Language Server Protocol (mslsp)
|
-- Protocol for the Microsoft Language Server Protocol (mslsp)
|
||||||
|
|
||||||
|
local if_nil = vim.F.if_nil
|
||||||
|
|
||||||
local protocol = {}
|
local protocol = {}
|
||||||
|
|
||||||
--@private
|
|
||||||
--- Returns {a} if it is not nil, otherwise returns {b}.
|
|
||||||
---
|
|
||||||
--@param a
|
|
||||||
--@param b
|
|
||||||
local function ifnil(a, b)
|
|
||||||
if a == nil then return b end
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
--@private
|
--@private
|
||||||
--- Useful for interfacing with:
|
--- Useful for interfacing with:
|
||||||
@@ -909,12 +900,12 @@ function protocol.resolve_capabilities(server_capabilities)
|
|||||||
}
|
}
|
||||||
elseif type(textDocumentSync) == 'table' then
|
elseif type(textDocumentSync) == 'table' then
|
||||||
text_document_sync_properties = {
|
text_document_sync_properties = {
|
||||||
text_document_open_close = ifnil(textDocumentSync.openClose, false);
|
text_document_open_close = if_nil(textDocumentSync.openClose, false);
|
||||||
text_document_did_change = ifnil(textDocumentSync.change, TextDocumentSyncKind.None);
|
text_document_did_change = if_nil(textDocumentSync.change, TextDocumentSyncKind.None);
|
||||||
text_document_will_save = ifnil(textDocumentSync.willSave, false);
|
text_document_will_save = if_nil(textDocumentSync.willSave, false);
|
||||||
text_document_will_save_wait_until = ifnil(textDocumentSync.willSaveWaitUntil, false);
|
text_document_will_save_wait_until = if_nil(textDocumentSync.willSaveWaitUntil, false);
|
||||||
text_document_save = ifnil(textDocumentSync.save, false);
|
text_document_save = if_nil(textDocumentSync.save, false);
|
||||||
text_document_save_include_text = ifnil(type(textDocumentSync.save) == 'table'
|
text_document_save_include_text = if_nil(type(textDocumentSync.save) == 'table'
|
||||||
and textDocumentSync.save.includeText, false);
|
and textDocumentSync.save.includeText, false);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@@ -231,41 +231,42 @@ local function rpc_response_error(code, message, data)
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
local default_handlers = {}
|
local default_dispatchers = {}
|
||||||
|
|
||||||
--@private
|
--@private
|
||||||
--- Default handler for notifications sent to an LSP server.
|
--- Default dispatcher for notifications sent to an LSP server.
|
||||||
---
|
---
|
||||||
--@param method (string) The invoked LSP method
|
--@param method (string) The invoked LSP method
|
||||||
--@param params (table): Parameters for the invoked LSP method
|
--@param params (table): Parameters for the invoked LSP method
|
||||||
function default_handlers.notification(method, params)
|
function default_dispatchers.notification(method, params)
|
||||||
local _ = log.debug() and log.debug('notification', method, params)
|
local _ = log.debug() and log.debug('notification', method, params)
|
||||||
end
|
end
|
||||||
--@private
|
--@private
|
||||||
--- Default handler for requests sent to an LSP server.
|
--- Default dispatcher for requests sent to an LSP server.
|
||||||
---
|
---
|
||||||
--@param method (string) The invoked LSP method
|
--@param method (string) The invoked LSP method
|
||||||
--@param params (table): Parameters for the invoked LSP method
|
--@param params (table): Parameters for the invoked LSP method
|
||||||
--@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`.
|
--@returns `nil` and `vim.lsp.protocol.ErrorCodes.MethodNotFound`.
|
||||||
function default_handlers.server_request(method, params)
|
function default_dispatchers.server_request(method, params)
|
||||||
local _ = log.debug() and log.debug('server_request', method, params)
|
local _ = log.debug() and log.debug('server_request', method, params)
|
||||||
return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound)
|
return nil, rpc_response_error(protocol.ErrorCodes.MethodNotFound)
|
||||||
end
|
end
|
||||||
--@private
|
--@private
|
||||||
--- Default handler for when a client exits.
|
--- Default dispatcher for when a client exits.
|
||||||
---
|
---
|
||||||
--@param code (number): Exit code
|
--@param code (number): Exit code
|
||||||
--@param signal (number): Number describing the signal used to terminate (if
|
--@param signal (number): Number describing the signal used to terminate (if
|
||||||
---any)
|
---any)
|
||||||
function default_handlers.on_exit(code, signal)
|
function default_dispatchers.on_exit(code, signal)
|
||||||
local _ = log.info() and log.info("client_exit", { code = code, signal = signal })
|
local _ = log.info() and log.info("client_exit", { code = code, signal = signal })
|
||||||
end
|
end
|
||||||
--@private
|
--@private
|
||||||
--- Default handler for client errors.
|
--- Default dispatcher for client errors.
|
||||||
---
|
---
|
||||||
--@param code (number): Error code
|
--@param code (number): Error code
|
||||||
--@param err (any): Details about the error
|
--@param err (any): Details about the error
|
||||||
---any)
|
---any)
|
||||||
function default_handlers.on_error(code, err)
|
function default_dispatchers.on_error(code, err)
|
||||||
local _ = log.error() and log.error('client_error:', client_errors[code], err)
|
local _ = log.error() and log.error('client_error:', client_errors[code], err)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -274,8 +275,8 @@ end
|
|||||||
---
|
---
|
||||||
--@param cmd (string) Command to start the LSP server.
|
--@param cmd (string) Command to start the LSP server.
|
||||||
--@param cmd_args (table) List of additional string arguments to pass to {cmd}.
|
--@param cmd_args (table) List of additional string arguments to pass to {cmd}.
|
||||||
--@param handlers (table, optional) Handlers for LSP message types. Valid
|
--@param dispatchers (table, optional) Dispatchers for LSP message types. Valid
|
||||||
---handler names are:
|
---dispatcher names are:
|
||||||
--- - `"notification"`
|
--- - `"notification"`
|
||||||
--- - `"server_request"`
|
--- - `"server_request"`
|
||||||
--- - `"on_error"`
|
--- - `"on_error"`
|
||||||
@@ -294,39 +295,39 @@ end
|
|||||||
--- - {pid} (number) The LSP server's PID.
|
--- - {pid} (number) The LSP server's PID.
|
||||||
--- - {handle} A handle for low-level interaction with the LSP server process
|
--- - {handle} A handle for low-level interaction with the LSP server process
|
||||||
--- |vim.loop|.
|
--- |vim.loop|.
|
||||||
local function start(cmd, cmd_args, handlers, extra_spawn_params)
|
local function start(cmd, cmd_args, dispatchers, extra_spawn_params)
|
||||||
local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params})
|
local _ = log.info() and log.info("Starting RPC client", {cmd = cmd, args = cmd_args, extra = extra_spawn_params})
|
||||||
validate {
|
validate {
|
||||||
cmd = { cmd, 's' };
|
cmd = { cmd, 's' };
|
||||||
cmd_args = { cmd_args, 't' };
|
cmd_args = { cmd_args, 't' };
|
||||||
handlers = { handlers, 't', true };
|
dispatchers = { dispatchers, 't', true };
|
||||||
}
|
}
|
||||||
|
|
||||||
if not (vim.fn.executable(cmd) == 1) then
|
if not (vim.fn.executable(cmd) == 1) then
|
||||||
error(string.format("The given command %q is not executable.", cmd))
|
error(string.format("The given command %q is not executable.", cmd))
|
||||||
end
|
end
|
||||||
if handlers then
|
if dispatchers then
|
||||||
local user_handlers = handlers
|
local user_dispatchers = dispatchers
|
||||||
handlers = {}
|
dispatchers = {}
|
||||||
for handle_name, default_handler in pairs(default_handlers) do
|
for dispatch_name, default_dispatch in pairs(default_dispatchers) do
|
||||||
local user_handler = user_handlers[handle_name]
|
local user_dispatcher = user_dispatchers[dispatch_name]
|
||||||
if user_handler then
|
if user_dispatcher then
|
||||||
if type(user_handler) ~= 'function' then
|
if type(user_dispatcher) ~= 'function' then
|
||||||
error(string.format("handler.%s must be a function", handle_name))
|
error(string.format("dispatcher.%s must be a function", dispatch_name))
|
||||||
end
|
end
|
||||||
-- server_request is wrapped elsewhere.
|
-- server_request is wrapped elsewhere.
|
||||||
if not (handle_name == 'server_request'
|
if not (dispatch_name == 'server_request'
|
||||||
or handle_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
|
or dispatch_name == 'on_exit') -- TODO this blocks the loop exiting for some reason.
|
||||||
then
|
then
|
||||||
user_handler = schedule_wrap(user_handler)
|
user_dispatcher = schedule_wrap(user_dispatcher)
|
||||||
end
|
end
|
||||||
handlers[handle_name] = user_handler
|
dispatchers[dispatch_name] = user_dispatcher
|
||||||
else
|
else
|
||||||
handlers[handle_name] = default_handler
|
dispatchers[dispatch_name] = default_dispatch
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
handlers = default_handlers
|
dispatchers = default_dispatchers
|
||||||
end
|
end
|
||||||
|
|
||||||
local stdin = uv.new_pipe(false)
|
local stdin = uv.new_pipe(false)
|
||||||
@@ -339,8 +340,7 @@ local function start(cmd, cmd_args, handlers, extra_spawn_params)
|
|||||||
local handle, pid
|
local handle, pid
|
||||||
do
|
do
|
||||||
--@private
|
--@private
|
||||||
--- Callback for |vim.loop.spawn()| Closes all streams and runs the
|
--- Callback for |vim.loop.spawn()| Closes all streams and runs the `on_exit` dispatcher.
|
||||||
--- `on_exit` handler.
|
|
||||||
--@param code (number) Exit code
|
--@param code (number) Exit code
|
||||||
--@param signal (number) Signal that was used to terminate (if any)
|
--@param signal (number) Signal that was used to terminate (if any)
|
||||||
local function onexit(code, signal)
|
local function onexit(code, signal)
|
||||||
@@ -350,7 +350,7 @@ local function start(cmd, cmd_args, handlers, extra_spawn_params)
|
|||||||
handle:close()
|
handle:close()
|
||||||
-- Make sure that message_callbacks can be gc'd.
|
-- Make sure that message_callbacks can be gc'd.
|
||||||
message_callbacks = nil
|
message_callbacks = nil
|
||||||
handlers.on_exit(code, signal)
|
dispatchers.on_exit(code, signal)
|
||||||
end
|
end
|
||||||
local spawn_params = {
|
local spawn_params = {
|
||||||
args = cmd_args;
|
args = cmd_args;
|
||||||
@@ -448,7 +448,7 @@ local function start(cmd, cmd_args, handlers, extra_spawn_params)
|
|||||||
local function on_error(errkind, ...)
|
local function on_error(errkind, ...)
|
||||||
assert(client_errors[errkind])
|
assert(client_errors[errkind])
|
||||||
-- TODO what to do if this fails?
|
-- TODO what to do if this fails?
|
||||||
pcall(handlers.on_error, errkind, ...)
|
pcall(dispatchers.on_error, errkind, ...)
|
||||||
end
|
end
|
||||||
--@private
|
--@private
|
||||||
local function pcall_handler(errkind, status, head, ...)
|
local function pcall_handler(errkind, status, head, ...)
|
||||||
@@ -471,7 +471,7 @@ local function start(cmd, cmd_args, handlers, extra_spawn_params)
|
|||||||
local function handle_body(body)
|
local function handle_body(body)
|
||||||
local decoded, err = json_decode(body)
|
local decoded, err = json_decode(body)
|
||||||
if not decoded then
|
if not decoded then
|
||||||
on_error(client_errors.INVALID_SERVER_JSON, err)
|
-- on_error(client_errors.INVALID_SERVER_JSON, err)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local _ = log.debug() and log.debug("decoded", decoded)
|
local _ = log.debug() and log.debug("decoded", decoded)
|
||||||
@@ -484,7 +484,7 @@ local function start(cmd, cmd_args, handlers, extra_spawn_params)
|
|||||||
schedule(function()
|
schedule(function()
|
||||||
local status, result
|
local status, result
|
||||||
status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR,
|
status, result, err = try_call(client_errors.SERVER_REQUEST_HANDLER_ERROR,
|
||||||
handlers.server_request, decoded.method, decoded.params)
|
dispatchers.server_request, decoded.method, decoded.params)
|
||||||
local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err })
|
local _ = log.debug() and log.debug("server_request: callback result", { status = status, result = result, err = err })
|
||||||
if status then
|
if status then
|
||||||
if not (result or err) then
|
if not (result or err) then
|
||||||
@@ -551,7 +551,7 @@ local function start(cmd, cmd_args, handlers, extra_spawn_params)
|
|||||||
-- Notification
|
-- Notification
|
||||||
decoded.params = convert_NIL(decoded.params)
|
decoded.params = convert_NIL(decoded.params)
|
||||||
try_call(client_errors.NOTIFICATION_HANDLER_ERROR,
|
try_call(client_errors.NOTIFICATION_HANDLER_ERROR,
|
||||||
handlers.notification, decoded.method, decoded.params)
|
dispatchers.notification, decoded.method, decoded.params)
|
||||||
else
|
else
|
||||||
-- Invalid server message
|
-- Invalid server message
|
||||||
on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
|
on_error(client_errors.INVALID_SERVER_MESSAGE, decoded)
|
||||||
|
@@ -5,50 +5,32 @@ local api = vim.api
|
|||||||
local list_extend = vim.list_extend
|
local list_extend = vim.list_extend
|
||||||
local highlight = require 'vim.highlight'
|
local highlight = require 'vim.highlight'
|
||||||
|
|
||||||
|
local npcall = vim.F.npcall
|
||||||
|
local split = vim.split
|
||||||
|
|
||||||
|
local _warned = {}
|
||||||
|
local warn_once = function(message)
|
||||||
|
if not _warned[message] then
|
||||||
|
vim.api.nvim_err_writeln(message)
|
||||||
|
_warned[message] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
-- FIXME: DOC: Expose in vimdocs
|
-- TODO(remove-callbacks)
|
||||||
--- Diagnostics received from the server via `textDocument/publishDiagnostics`
|
M.diagnostics_by_buf = setmetatable({}, {
|
||||||
-- by buffer.
|
__index = function(_, bufnr)
|
||||||
--
|
warn_once("diagnostics_by_buf is deprecated. Use 'vim.lsp.diagnostic.get'")
|
||||||
-- {<bufnr>: {diagnostics}}
|
return vim.lsp.diagnostic.get(bufnr)
|
||||||
--
|
end
|
||||||
-- This contains only entries for active buffers. Entries for detached buffers
|
})
|
||||||
-- are discarded.
|
|
||||||
--
|
|
||||||
-- If you override the `textDocument/publishDiagnostic` callback,
|
|
||||||
-- this will be empty unless you call `buf_diagnostics_save_positions`.
|
|
||||||
--
|
|
||||||
--
|
|
||||||
-- Diagnostic is:
|
|
||||||
--
|
|
||||||
-- {
|
|
||||||
-- range: Range
|
|
||||||
-- message: string
|
|
||||||
-- severity?: DiagnosticSeverity
|
|
||||||
-- code?: number | string
|
|
||||||
-- source?: string
|
|
||||||
-- tags?: DiagnosticTag[]
|
|
||||||
-- relatedInformation?: DiagnosticRelatedInformation[]
|
|
||||||
-- }
|
|
||||||
M.diagnostics_by_buf = {}
|
|
||||||
|
|
||||||
local split = vim.split
|
|
||||||
--@private
|
--@private
|
||||||
local function split_lines(value)
|
local function split_lines(value)
|
||||||
return split(value, '\n', true)
|
return split(value, '\n', true)
|
||||||
end
|
end
|
||||||
|
|
||||||
--@private
|
|
||||||
local function ok_or_nil(status, ...)
|
|
||||||
if not status then return end
|
|
||||||
return ...
|
|
||||||
end
|
|
||||||
--@private
|
|
||||||
local function npcall(fn, ...)
|
|
||||||
return ok_or_nil(pcall(fn, ...))
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Replaces text in a range with new text.
|
--- Replaces text in a range with new text.
|
||||||
---
|
---
|
||||||
--- CAUTION: Changes in-place!
|
--- CAUTION: Changes in-place!
|
||||||
@@ -121,10 +103,18 @@ local function get_line_byte_from_position(bufnr, position)
|
|||||||
-- When on the first character, we can ignore the difference between byte and
|
-- When on the first character, we can ignore the difference between byte and
|
||||||
-- character
|
-- character
|
||||||
if col > 0 then
|
if col > 0 then
|
||||||
|
if not api.nvim_buf_is_loaded(bufnr) then
|
||||||
|
vim.fn.bufload(bufnr)
|
||||||
|
end
|
||||||
|
|
||||||
local line = position.line
|
local line = position.line
|
||||||
local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false)
|
local lines = api.nvim_buf_get_lines(bufnr, line, line + 1, false)
|
||||||
if #lines > 0 then
|
if #lines > 0 then
|
||||||
return vim.str_byteindex(lines[1], col)
|
local ok, result = pcall(vim.str_byteindex, lines[1], col)
|
||||||
|
|
||||||
|
if ok then
|
||||||
|
return result
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return col
|
return col
|
||||||
@@ -700,13 +690,13 @@ end
|
|||||||
|
|
||||||
--- Trims empty lines from input and pad left and right with spaces
|
--- Trims empty lines from input and pad left and right with spaces
|
||||||
---
|
---
|
||||||
--@param contents table of lines to trim and pad
|
---@param contents table of lines to trim and pad
|
||||||
--@param opts dictionary with optional fields
|
---@param opts dictionary with optional fields
|
||||||
-- - pad_left number of columns to pad contents at left (default 1)
|
--- - pad_left number of columns to pad contents at left (default 1)
|
||||||
-- - pad_right number of columns to pad contents at right (default 1)
|
--- - pad_right number of columns to pad contents at right (default 1)
|
||||||
-- - pad_top number of lines to pad contents at top (default 0)
|
--- - pad_top number of lines to pad contents at top (default 0)
|
||||||
-- - pad_bottom number of lines to pad contents at bottom (default 0)
|
--- - pad_bottom number of lines to pad contents at bottom (default 0)
|
||||||
--@returns contents table of trimmed and padded lines
|
---@return contents table of trimmed and padded lines
|
||||||
function M._trim_and_pad(contents, opts)
|
function M._trim_and_pad(contents, opts)
|
||||||
validate {
|
validate {
|
||||||
contents = { contents, 't' };
|
contents = { contents, 't' };
|
||||||
@@ -742,19 +732,19 @@ end
|
|||||||
--- regions to improve readability.
|
--- regions to improve readability.
|
||||||
--- The result is shown in a floating preview.
|
--- The result is shown in a floating preview.
|
||||||
---
|
---
|
||||||
--@param contents table of lines to show in window
|
---@param contents table of lines to show in window
|
||||||
--@param opts dictionary with optional fields
|
---@param opts dictionary with optional fields
|
||||||
-- - height of floating window
|
--- - height of floating window
|
||||||
-- - width of floating window
|
--- - width of floating window
|
||||||
-- - wrap_at character to wrap at for computing height
|
--- - wrap_at character to wrap at for computing height
|
||||||
-- - max_width maximal width of floating window
|
--- - max_width maximal width of floating window
|
||||||
-- - max_height maximal height of floating window
|
--- - max_height maximal height of floating window
|
||||||
-- - pad_left number of columns to pad contents at left
|
--- - pad_left number of columns to pad contents at left
|
||||||
-- - pad_right number of columns to pad contents at right
|
--- - pad_right number of columns to pad contents at right
|
||||||
-- - pad_top number of lines to pad contents at top
|
--- - pad_top number of lines to pad contents at top
|
||||||
-- - pad_bottom number of lines to pad contents at bottom
|
--- - pad_bottom number of lines to pad contents at bottom
|
||||||
-- - separator insert separator after code block
|
--- - separator insert separator after code block
|
||||||
--@returns width,height size of float
|
---@returns width,height size of float
|
||||||
function M.fancy_floating_markdown(contents, opts)
|
function M.fancy_floating_markdown(contents, opts)
|
||||||
validate {
|
validate {
|
||||||
contents = { contents, 't' };
|
contents = { contents, 't' };
|
||||||
@@ -971,171 +961,81 @@ function M.open_floating_preview(contents, filetype, opts)
|
|||||||
return floating_bufnr, floating_winnr
|
return floating_bufnr, floating_winnr
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- TODO(remove-callbacks)
|
||||||
do
|
do
|
||||||
local diagnostic_ns = api.nvim_create_namespace("vim_lsp_diagnostics")
|
--@deprecated
|
||||||
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
|
|
||||||
if type(kind) == 'string' then
|
|
||||||
vim.cmd(string.format("highlight default link %s%s %s", underline_highlight_name, kind, underline_highlight_name))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local severity_highlights = {}
|
|
||||||
|
|
||||||
local severity_floating_highlights = {}
|
|
||||||
|
|
||||||
local default_severity_highlight = {
|
|
||||||
[protocol.DiagnosticSeverity.Error] = { guifg = "Red" };
|
|
||||||
[protocol.DiagnosticSeverity.Warning] = { guifg = "Orange" };
|
|
||||||
[protocol.DiagnosticSeverity.Information] = { guifg = "LightBlue" };
|
|
||||||
[protocol.DiagnosticSeverity.Hint] = { guifg = "LightGrey" };
|
|
||||||
}
|
|
||||||
|
|
||||||
-- Initialize default severity highlights
|
|
||||||
for severity, hi_info in pairs(default_severity_highlight) do
|
|
||||||
local severity_name = protocol.DiagnosticSeverity[severity]
|
|
||||||
local highlight_name = "LspDiagnostics"..severity_name
|
|
||||||
local floating_highlight_name = highlight_name.."Floating"
|
|
||||||
-- Try to fill in the foreground color with a sane default.
|
|
||||||
local cmd_parts = {"highlight", "default", highlight_name}
|
|
||||||
for k, v in pairs(hi_info) do
|
|
||||||
table.insert(cmd_parts, k.."="..v)
|
|
||||||
end
|
|
||||||
api.nvim_command(table.concat(cmd_parts, ' '))
|
|
||||||
api.nvim_command('highlight link ' .. highlight_name .. 'Sign ' .. highlight_name)
|
|
||||||
api.nvim_command('highlight link ' .. highlight_name .. 'Floating ' .. highlight_name)
|
|
||||||
severity_highlights[severity] = highlight_name
|
|
||||||
severity_floating_highlights[severity] = floating_highlight_name
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Clears diagnostics for a buffer.
|
|
||||||
---
|
|
||||||
--@param bufnr (number) buffer id
|
|
||||||
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
|
|
||||||
|
|
||||||
--- Gets the name of a severity's highlight group.
|
|
||||||
---
|
|
||||||
--@param severity A member of `vim.lsp.protocol.DiagnosticSeverity`
|
|
||||||
--@returns (string) Highlight group name
|
|
||||||
function M.get_severity_highlight_name(severity)
|
function M.get_severity_highlight_name(severity)
|
||||||
return severity_highlights[severity]
|
warn_once("vim.lsp.util.get_severity_highlight_name is deprecated.")
|
||||||
|
return vim.lsp.diagnostic._get_severity_highlight_name(severity)
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Gets list of diagnostics for the current line.
|
--@deprecated
|
||||||
---
|
function M.buf_clear_diagnostics(bufnr, client_id)
|
||||||
--@returns (table) list of `Diagnostic` tables
|
warn_once("buf_clear_diagnostics is deprecated. Use vim.lsp.diagnostic.clear")
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
|
return vim.lsp.diagnostic.clear(bufnr, client_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@deprecated
|
||||||
function M.get_line_diagnostics()
|
function M.get_line_diagnostics()
|
||||||
|
warn_once("get_line_diagnostics is deprecated. Use vim.lsp.diagnostic.get_line_diagnostics")
|
||||||
|
|
||||||
local bufnr = api.nvim_get_current_buf()
|
local bufnr = api.nvim_get_current_buf()
|
||||||
local linenr = api.nvim_win_get_cursor(0)[1] - 1
|
local line_nr = api.nvim_win_get_cursor(0)[1] - 1
|
||||||
|
|
||||||
local buffer_diagnostics = M.diagnostics_by_buf[bufnr]
|
return vim.lsp.diagnostic.get_line_diagnostics(bufnr, line_nr)
|
||||||
|
|
||||||
if not buffer_diagnostics then
|
|
||||||
return {}
|
|
||||||
end
|
|
||||||
|
|
||||||
local diagnostics_by_line = M.diagnostics_group_by_line(buffer_diagnostics)
|
|
||||||
return diagnostics_by_line[linenr] or {}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Displays the diagnostics for the current line in a floating hover
|
--@deprecated
|
||||||
--- window.
|
|
||||||
function M.show_line_diagnostics()
|
function M.show_line_diagnostics()
|
||||||
-- local marks = api.nvim_buf_get_extmarks(bufnr, diagnostic_ns, {line, 0}, {line, -1}, {})
|
warn_once("show_line_diagnostics is deprecated. Use vim.lsp.diagnostic.show_line_diagnostics")
|
||||||
-- if #marks == 0 then
|
|
||||||
-- return
|
|
||||||
-- end
|
|
||||||
local lines = {"Diagnostics:"}
|
|
||||||
local highlights = {{0, "Bold"}}
|
|
||||||
local line_diagnostics = M.get_line_diagnostics()
|
|
||||||
if vim.tbl_isempty(line_diagnostics) then return end
|
|
||||||
|
|
||||||
for i, diagnostic in ipairs(line_diagnostics) do
|
local bufnr = api.nvim_get_current_buf()
|
||||||
-- for i, mark in ipairs(marks) do
|
local line_nr = api.nvim_win_get_cursor(0)[1] - 1
|
||||||
-- local mark_id = mark[1]
|
|
||||||
-- local diagnostic = buffer_diagnostics[mark_id]
|
|
||||||
|
|
||||||
-- TODO(ashkan) make format configurable?
|
return vim.lsp.diagnostic.show_line_diagnostics(bufnr, line_nr)
|
||||||
local prefix = string.format("%d. ", i)
|
|
||||||
local hiname = severity_floating_highlights[diagnostic.severity]
|
|
||||||
assert(hiname, 'unknown severity: ' .. tostring(diagnostic.severity))
|
|
||||||
local message_lines = split_lines(diagnostic.message)
|
|
||||||
table.insert(lines, prefix..message_lines[1])
|
|
||||||
table.insert(highlights, {#prefix + 1, hiname})
|
|
||||||
for j = 2, #message_lines do
|
|
||||||
table.insert(lines, message_lines[j])
|
|
||||||
table.insert(highlights, {0, hiname})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
local popup_bufnr, winnr = M.open_floating_preview(lines, 'plaintext')
|
|
||||||
for i, hi in ipairs(highlights) do
|
|
||||||
local prefixlen, hiname = unpack(hi)
|
|
||||||
-- Start highlight after the prefix
|
|
||||||
api.nvim_buf_add_highlight(popup_bufnr, -1, hiname, i-1, prefixlen, -1)
|
|
||||||
end
|
|
||||||
return popup_bufnr, winnr
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Saves diagnostics into vim.lsp.util.diagnostics_by_buf[{bufnr}].
|
--@deprecated
|
||||||
---
|
function M.buf_diagnostics_save_positions(bufnr, diagnostics, client_id)
|
||||||
--@param bufnr (number) buffer id for which the diagnostics are for
|
warn_once("buf_diagnostics_save_positions is deprecated. Use vim.lsp.diagnostic.save")
|
||||||
--@param diagnostics list of `Diagnostic`s received from the LSP server
|
return vim.lsp.diagnostic.save(diagnostics, bufnr, client_id)
|
||||||
function M.buf_diagnostics_save_positions(bufnr, diagnostics)
|
|
||||||
validate {
|
|
||||||
bufnr = {bufnr, 'n', true};
|
|
||||||
diagnostics = {diagnostics, 't', true};
|
|
||||||
}
|
|
||||||
if not diagnostics then return end
|
|
||||||
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
|
|
||||||
|
|
||||||
if not M.diagnostics_by_buf[bufnr] then
|
|
||||||
-- Clean up our data when the buffer unloads.
|
|
||||||
api.nvim_buf_attach(bufnr, false, {
|
|
||||||
on_detach = function(b)
|
|
||||||
M.diagnostics_by_buf[b] = nil
|
|
||||||
end
|
|
||||||
})
|
|
||||||
end
|
|
||||||
M.diagnostics_by_buf[bufnr] = diagnostics
|
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Highlights a list of diagnostics in a buffer by underlining them.
|
--@deprecated
|
||||||
---
|
function M.buf_diagnostics_get_positions(bufnr, client_id)
|
||||||
--@param bufnr (number) buffer id
|
warn_once("buf_diagnostics_get_positions is deprecated. Use vim.lsp.diagnostic.get")
|
||||||
--@param diagnostics (list of `Diagnostic`s)
|
return vim.lsp.diagnostic.get(bufnr, client_id)
|
||||||
function M.buf_diagnostics_underline(bufnr, diagnostics)
|
|
||||||
for _, diagnostic in ipairs(diagnostics) do
|
|
||||||
local start = diagnostic.range["start"]
|
|
||||||
local finish = diagnostic.range["end"]
|
|
||||||
|
|
||||||
local hlmap = {
|
|
||||||
[protocol.DiagnosticSeverity.Error]='Error',
|
|
||||||
[protocol.DiagnosticSeverity.Warning]='Warning',
|
|
||||||
[protocol.DiagnosticSeverity.Information]='Information',
|
|
||||||
[protocol.DiagnosticSeverity.Hint]='Hint',
|
|
||||||
}
|
|
||||||
|
|
||||||
highlight.range(bufnr, diagnostic_ns,
|
|
||||||
underline_highlight_name..hlmap[diagnostic.severity],
|
|
||||||
{start.line, start.character},
|
|
||||||
{finish.line, finish.character}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--@deprecated
|
||||||
|
function M.buf_diagnostics_underline(bufnr, diagnostics, client_id)
|
||||||
|
warn_once("buf_diagnostics_underline is deprecated. Use 'vim.lsp.diagnostic.set_underline'")
|
||||||
|
return vim.lsp.diagnostic.set_underline(diagnostics, bufnr, client_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@deprecated
|
||||||
|
function M.buf_diagnostics_virtual_text(bufnr, diagnostics, client_id)
|
||||||
|
warn_once("buf_diagnostics_virtual_text is deprecated. Use 'vim.lsp.diagnostic.set_virtual_text'")
|
||||||
|
return vim.lsp.diagnostic.set_virtual_text(diagnostics, bufnr, client_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@deprecated
|
||||||
|
function M.buf_diagnostics_signs(bufnr, diagnostics, client_id)
|
||||||
|
warn_once("buf_diagnostics_signs is deprecated. Use 'vim.lsp.diagnostics.set_signs'")
|
||||||
|
return vim.lsp.diagnostic.set_signs(diagnostics, bufnr, client_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
--@deprecated
|
||||||
|
function M.buf_diagnostics_count(kind, client_id)
|
||||||
|
warn_once("buf_diagnostics_count is deprecated. Use 'vim.lsp.diagnostic.get_count'")
|
||||||
|
return vim.lsp.diagnostic.get_count(vim.api.nvim_get_current_buf(), client_id, kind)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
do --[[ References ]]
|
||||||
|
local reference_ns = api.nvim_create_namespace("vim_lsp_references")
|
||||||
|
|
||||||
--- Removes document highlights from a buffer.
|
--- Removes document highlights from a buffer.
|
||||||
---
|
---
|
||||||
--@param bufnr buffer id
|
--@param bufnr buffer id
|
||||||
@@ -1162,109 +1062,6 @@ do
|
|||||||
highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
|
highlight.range(bufnr, reference_ns, document_highlight_kind[kind], start_pos, end_pos)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
--- Groups a list of diagnostics by line.
|
|
||||||
---
|
|
||||||
--@param diagnostics (table) list of `Diagnostic`s
|
|
||||||
--@returns (table) dictionary mapping lines to lists of diagnostics valid on
|
|
||||||
---those lines
|
|
||||||
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#diagnostic
|
|
||||||
function M.diagnostics_group_by_line(diagnostics)
|
|
||||||
if not diagnostics then return end
|
|
||||||
local diagnostics_by_line = {}
|
|
||||||
for _, diagnostic in ipairs(diagnostics) do
|
|
||||||
local start = diagnostic.range.start
|
|
||||||
-- TODO: Are diagnostics only valid for a single line? I don't understand
|
|
||||||
-- why this would be okay otherwise
|
|
||||||
local line_diagnostics = diagnostics_by_line[start.line]
|
|
||||||
if not line_diagnostics then
|
|
||||||
line_diagnostics = {}
|
|
||||||
diagnostics_by_line[start.line] = line_diagnostics
|
|
||||||
end
|
|
||||||
table.insert(line_diagnostics, diagnostic)
|
|
||||||
end
|
|
||||||
return diagnostics_by_line
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Given a list of diagnostics, sets the corresponding virtual text for a
|
|
||||||
--- buffer.
|
|
||||||
---
|
|
||||||
--@param bufnr buffer id
|
|
||||||
--@param diagnostics (table) list of `Diagnostic`s
|
|
||||||
function M.buf_diagnostics_virtual_text(bufnr, diagnostics)
|
|
||||||
if not diagnostics then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local buffer_line_diagnostics = M.diagnostics_group_by_line(diagnostics)
|
|
||||||
for line, line_diags in pairs(buffer_line_diagnostics) do
|
|
||||||
local virt_texts = {}
|
|
||||||
for i = 1, #line_diags - 1 do
|
|
||||||
table.insert(virt_texts, {"■", severity_highlights[line_diags[i].severity]})
|
|
||||||
end
|
|
||||||
local last = line_diags[#line_diags]
|
|
||||||
-- TODO(ashkan) use first line instead of subbing 2 spaces?
|
|
||||||
table.insert(virt_texts, {"■ "..last.message:gsub("\r", ""):gsub("\n", " "), severity_highlights[last.severity]})
|
|
||||||
api.nvim_buf_set_virtual_text(bufnr, diagnostic_ns, line, virt_texts, {})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--- Returns the number of diagnostics of given kind for current buffer.
|
|
||||||
---
|
|
||||||
--- Useful for showing diagnostic counts in statusline. eg:
|
|
||||||
---
|
|
||||||
--- <pre>
|
|
||||||
--- function! LspStatus() abort
|
|
||||||
--- let sl = ''
|
|
||||||
--- if luaeval('not vim.tbl_isempty(vim.lsp.buf_get_clients(0))')
|
|
||||||
--- let sl.='%#MyStatuslineLSP#E:'
|
|
||||||
--- let sl.='%#MyStatuslineLSPErrors#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Error]])")}'
|
|
||||||
--- let sl.='%#MyStatuslineLSP# W:'
|
|
||||||
--- let sl.='%#MyStatuslineLSPWarnings#%{luaeval("vim.lsp.util.buf_diagnostics_count([[Warning]])")}'
|
|
||||||
--- else
|
|
||||||
--- let sl.='%#MyStatuslineLSPErrors#off'
|
|
||||||
--- endif
|
|
||||||
--- return sl
|
|
||||||
--- endfunction
|
|
||||||
--- let &l:statusline = '%#MyStatuslineLSP#LSP '.LspStatus()
|
|
||||||
--- </pre>
|
|
||||||
---
|
|
||||||
--@param kind Diagnostic severity kind: See |vim.lsp.protocol.DiagnosticSeverity|
|
|
||||||
--@returns Count of diagnostics
|
|
||||||
function M.buf_diagnostics_count(kind)
|
|
||||||
local bufnr = vim.api.nvim_get_current_buf()
|
|
||||||
local diagnostics = M.diagnostics_by_buf[bufnr]
|
|
||||||
if not diagnostics then return end
|
|
||||||
local count = 0
|
|
||||||
for _, diagnostic in pairs(diagnostics) do
|
|
||||||
if protocol.DiagnosticSeverity[kind] == diagnostic.severity then
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return count
|
|
||||||
end
|
|
||||||
|
|
||||||
local diagnostic_severity_map = {
|
|
||||||
[protocol.DiagnosticSeverity.Error] = "LspDiagnosticsErrorSign";
|
|
||||||
[protocol.DiagnosticSeverity.Warning] = "LspDiagnosticsWarningSign";
|
|
||||||
[protocol.DiagnosticSeverity.Information] = "LspDiagnosticsInformationSign";
|
|
||||||
[protocol.DiagnosticSeverity.Hint] = "LspDiagnosticsHintSign";
|
|
||||||
}
|
|
||||||
|
|
||||||
--- Places signs for each diagnostic in the sign column.
|
|
||||||
---
|
|
||||||
--- Sign characters can be customized with the following commands:
|
|
||||||
---
|
|
||||||
--- <pre>
|
|
||||||
--- sign define LspDiagnosticsErrorSign text=E texthl=LspDiagnosticsError linehl= numhl=
|
|
||||||
--- sign define LspDiagnosticsWarningSign text=W texthl=LspDiagnosticsWarning linehl= numhl=
|
|
||||||
--- sign define LspDiagnosticsInformationSign text=I texthl=LspDiagnosticsInformation linehl= numhl=
|
|
||||||
--- sign define LspDiagnosticsHintSign text=H texthl=LspDiagnosticsHint linehl= numhl=
|
|
||||||
--- </pre>
|
|
||||||
function M.buf_diagnostics_signs(bufnr, diagnostics)
|
|
||||||
for _, diagnostic in ipairs(diagnostics) do
|
|
||||||
vim.fn.sign_place(0, sign_ns, diagnostic_severity_map[diagnostic.severity], bufnr, {lnum=(diagnostic.range.start.line+1)})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
local position_sort = sort_by_key(function(v)
|
local position_sort = sort_by_key(function(v)
|
||||||
@@ -1561,6 +1358,9 @@ function M.character_offset(buf, row, col)
|
|||||||
return str_utfindex(line, col)
|
return str_utfindex(line, col)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
M._get_line_byte_from_position = get_line_byte_from_position
|
||||||
|
M._warn_once = warn_once
|
||||||
|
|
||||||
M.buf_versions = {}
|
M.buf_versions = {}
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
@@ -96,7 +96,7 @@ end
|
|||||||
---
|
---
|
||||||
--- Examples:
|
--- Examples:
|
||||||
--- <pre>
|
--- <pre>
|
||||||
--- split(":aa::b:", ":") --> {'','aa','','bb',''}
|
--- split(":aa::b:", ":") --> {'','aa','','b',''}
|
||||||
--- split("axaby", "ab?") --> {'','x','y'}
|
--- split("axaby", "ab?") --> {'','x','y'}
|
||||||
--- split(x*yz*o, "*", true) --> {'x','yz','o'}
|
--- split(x*yz*o, "*", true) --> {'x','yz','o'}
|
||||||
--- </pre>
|
--- </pre>
|
||||||
@@ -496,7 +496,7 @@ do
|
|||||||
}
|
}
|
||||||
|
|
||||||
local function _is_type(val, t)
|
local function _is_type(val, t)
|
||||||
return t == 'callable' and vim.is_callable(val) or type(val) == t
|
return type(val) == t or (t == 'callable' and vim.is_callable(val))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function is_valid(opt)
|
local function is_valid(opt)
|
||||||
|
@@ -143,12 +143,13 @@ CONFIG = {
|
|||||||
'section_start_token': '*lsp-core*',
|
'section_start_token': '*lsp-core*',
|
||||||
'section_order': [
|
'section_order': [
|
||||||
'lsp.lua',
|
'lsp.lua',
|
||||||
'protocol.lua',
|
|
||||||
'buf.lua',
|
'buf.lua',
|
||||||
'callbacks.lua',
|
'diagnostic.lua',
|
||||||
|
'handlers.lua',
|
||||||
|
'util.lua',
|
||||||
'log.lua',
|
'log.lua',
|
||||||
'rpc.lua',
|
'rpc.lua',
|
||||||
'util.lua'
|
'protocol.lua',
|
||||||
],
|
],
|
||||||
'files': ' '.join([
|
'files': ' '.join([
|
||||||
os.path.join(base_dir, 'runtime/lua/vim/lsp'),
|
os.path.join(base_dir, 'runtime/lua/vim/lsp'),
|
||||||
@@ -447,7 +448,7 @@ def render_node(n, text, prefix='', indent='', width=62):
|
|||||||
indent=indent, width=width))
|
indent=indent, width=width))
|
||||||
i = i + 1
|
i = i + 1
|
||||||
elif n.nodeName == 'simplesect' and 'note' == n.getAttribute('kind'):
|
elif n.nodeName == 'simplesect' and 'note' == n.getAttribute('kind'):
|
||||||
text += 'Note:\n '
|
text += '\nNote:\n '
|
||||||
for c in n.childNodes:
|
for c in n.childNodes:
|
||||||
text += render_node(c, text, indent=' ', width=width)
|
text += render_node(c, text, indent=' ', width=width)
|
||||||
text += '\n'
|
text += '\n'
|
||||||
@@ -461,6 +462,8 @@ def render_node(n, text, prefix='', indent='', width=62):
|
|||||||
text += ind(' ')
|
text += ind(' ')
|
||||||
for c in n.childNodes:
|
for c in n.childNodes:
|
||||||
text += render_node(c, text, indent=' ', width=width)
|
text += render_node(c, text, indent=' ', width=width)
|
||||||
|
elif n.nodeName == 'computeroutput':
|
||||||
|
return get_text(n)
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('unhandled node type: {}\n{}'.format(
|
raise RuntimeError('unhandled node type: {}\n{}'.format(
|
||||||
n.nodeName, n.toprettyxml(indent=' ', newl='\n')))
|
n.nodeName, n.toprettyxml(indent=' ', newl='\n')))
|
||||||
@@ -526,6 +529,7 @@ def para_as_map(parent, indent='', width=62):
|
|||||||
and is_inline(self_or_child(prev))
|
and is_inline(self_or_child(prev))
|
||||||
and is_inline(self_or_child(child))
|
and is_inline(self_or_child(child))
|
||||||
and '' != get_text(self_or_child(child)).strip()
|
and '' != get_text(self_or_child(child)).strip()
|
||||||
|
and text
|
||||||
and ' ' != text[-1]):
|
and ' ' != text[-1]):
|
||||||
text += ' '
|
text += ' '
|
||||||
|
|
||||||
@@ -705,7 +709,7 @@ def extract_from_xml(filename, target, width):
|
|||||||
|
|
||||||
if len(prefix) + len(suffix) > lhs:
|
if len(prefix) + len(suffix) > lhs:
|
||||||
signature = vimtag.rjust(width) + '\n'
|
signature = vimtag.rjust(width) + '\n'
|
||||||
signature += doc_wrap(suffix, width=width-8, prefix=prefix,
|
signature += doc_wrap(suffix, width=width, prefix=prefix,
|
||||||
func=True)
|
func=True)
|
||||||
else:
|
else:
|
||||||
signature = prefix + suffix
|
signature = prefix + suffix
|
||||||
|
@@ -73,7 +73,7 @@ function class(BaseClass, ClassInitialiser)
|
|||||||
local newInstance = {}
|
local newInstance = {}
|
||||||
setmetatable(newInstance,newClass)
|
setmetatable(newInstance,newClass)
|
||||||
--if init then
|
--if init then
|
||||||
-- init(newInstance,...)
|
-- init(newInstance,...)
|
||||||
if class_tbl.init then
|
if class_tbl.init then
|
||||||
class_tbl.init(newInstance,...)
|
class_tbl.init(newInstance,...)
|
||||||
else
|
else
|
||||||
@@ -214,7 +214,7 @@ TStream_Read = class()
|
|||||||
--! \brief get contents of file
|
--! \brief get contents of file
|
||||||
--!
|
--!
|
||||||
--! \param Filename name of file to read (or nil == stdin)
|
--! \param Filename name of file to read (or nil == stdin)
|
||||||
function TStream_Read.getContents(this,Filename)
|
function TStream_Read.getContents(this,Filename)
|
||||||
-- get lines from file
|
-- get lines from file
|
||||||
local filecontents
|
local filecontents
|
||||||
if Filename then
|
if Filename then
|
||||||
@@ -365,7 +365,7 @@ end
|
|||||||
--! \brief check comment for fn
|
--! \brief check comment for fn
|
||||||
local function checkComment4fn(Fn_magic,MagicLines)
|
local function checkComment4fn(Fn_magic,MagicLines)
|
||||||
local fn_magic = Fn_magic
|
local fn_magic = Fn_magic
|
||||||
-- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"')
|
-- TCore_IO_writeln('// checkComment4fn "' .. MagicLines .. '"')
|
||||||
|
|
||||||
local magicLines = string_split(MagicLines,'\n')
|
local magicLines = string_split(MagicLines,'\n')
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ local function checkComment4fn(Fn_magic,MagicLines)
|
|||||||
macro,tail = getMagicDirective(line)
|
macro,tail = getMagicDirective(line)
|
||||||
if macro == 'fn' then
|
if macro == 'fn' then
|
||||||
fn_magic = tail
|
fn_magic = tail
|
||||||
-- TCore_IO_writeln('// found fn "' .. fn_magic .. '"')
|
-- TCore_IO_writeln('// found fn "' .. fn_magic .. '"')
|
||||||
else
|
else
|
||||||
--TCore_IO_writeln('// not found fn "' .. line .. '"')
|
--TCore_IO_writeln('// not found fn "' .. line .. '"')
|
||||||
end
|
end
|
||||||
@@ -401,15 +401,23 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename)
|
|||||||
outStream:writelnTail('// #######################')
|
outStream:writelnTail('// #######################')
|
||||||
outStream:writelnTail()
|
outStream:writelnTail()
|
||||||
|
|
||||||
local state = ''
|
local state, offset = '', 0
|
||||||
while not (err or inStream:eof()) do
|
while not (err or inStream:eof()) do
|
||||||
line = string_trim(inStream:getLine())
|
line = string_trim(inStream:getLine())
|
||||||
-- TCore_Debug_show_var('inStream',inStream)
|
-- TCore_Debug_show_var('inStream',inStream)
|
||||||
-- TCore_Debug_show_var('line',line )
|
-- TCore_Debug_show_var('line',line )
|
||||||
if string.sub(line,1,2)=='--' then -- it's a comment
|
if string.sub(line,1,2) == '--' then -- it's a comment
|
||||||
if string.sub(line,3,3)=='@' then -- it's a magic comment
|
-- Allow people to write style similar to EmmyLua (since they are basically the same)
|
||||||
|
-- instead of silently skipping things that start with ---
|
||||||
|
if string.sub(line, 3, 3) == '@' then -- it's a magic comment
|
||||||
|
offset = 0
|
||||||
|
elseif string.sub(line, 1, 4) == '---@' then -- it's a magic comment
|
||||||
|
offset = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
if string.sub(line, 3, 3) == '@' or string.sub(line, 1, 4) == '---@' then -- it's a magic comment
|
||||||
state = 'in_magic_comment'
|
state = 'in_magic_comment'
|
||||||
local magic = string.sub(line,4)
|
local magic = string.sub(line, 4 + offset)
|
||||||
outStream:writeln('/// @' .. magic)
|
outStream:writeln('/// @' .. magic)
|
||||||
fn_magic = checkComment4fn(fn_magic,magic)
|
fn_magic = checkComment4fn(fn_magic,magic)
|
||||||
elseif string.sub(line,3,3)=='-' then -- it's a nonmagic doc comment
|
elseif string.sub(line,3,3)=='-' then -- it's a nonmagic doc comment
|
||||||
@@ -450,7 +458,7 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename)
|
|||||||
outStream:writeln('// zz:"' .. line .. '"')
|
outStream:writeln('// zz:"' .. line .. '"')
|
||||||
fn_magic = nil
|
fn_magic = nil
|
||||||
end
|
end
|
||||||
elseif string.find(line,'^function') or string.find(line,'^local%s+function') then
|
elseif string.find(line, '^function') or string.find(line, '^local%s+function') then
|
||||||
state = 'in_function' -- it's a function
|
state = 'in_function' -- it's a function
|
||||||
local pos_fn = string.find(line,'function')
|
local pos_fn = string.find(line,'function')
|
||||||
-- function
|
-- function
|
||||||
@@ -490,6 +498,13 @@ function TLua2DoX_filter.readfile(this,AppStamp,Filename)
|
|||||||
this:warning(inStream:getLineNo(),'something weird here')
|
this:warning(inStream:getLineNo(),'something weird here')
|
||||||
end
|
end
|
||||||
fn_magic = nil -- mustn't indavertently use it again
|
fn_magic = nil -- mustn't indavertently use it again
|
||||||
|
|
||||||
|
-- TODO: If we can make this learn how to generate these, that would be helpful.
|
||||||
|
-- elseif string.find(line, "^M%['.*'%] = function") then
|
||||||
|
-- state = 'in_function' -- it's a function
|
||||||
|
-- outStream:writeln("function textDocument/publishDiagnostics(...){}")
|
||||||
|
|
||||||
|
-- fn_magic = nil -- mustn't indavertently use it again
|
||||||
else
|
else
|
||||||
state = '' -- unknown
|
state = '' -- unknown
|
||||||
if #line>0 then -- we don't know what this line means, so just comment it out
|
if #line>0 then -- we don't know what this line means, so just comment it out
|
||||||
|
@@ -272,6 +272,9 @@ local function __index(t, key)
|
|||||||
elseif key == 'highlight' then
|
elseif key == 'highlight' then
|
||||||
t.highlight = require('vim.highlight')
|
t.highlight = require('vim.highlight')
|
||||||
return t.highlight
|
return t.highlight
|
||||||
|
elseif key == 'F' then
|
||||||
|
t.F = require('vim.F')
|
||||||
|
return t.F
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -4296,8 +4296,9 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
|
|||||||
current_syn_inc_tag = ++running_syn_inc_tag;
|
current_syn_inc_tag = ++running_syn_inc_tag;
|
||||||
prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
|
prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
|
||||||
curwin->w_s->b_syn_topgrp = sgl_id;
|
curwin->w_s->b_syn_topgrp = sgl_id;
|
||||||
if (source ? do_source(eap->arg, false, DOSO_NONE) == FAIL
|
if (source
|
||||||
: source_in_path(p_rtp, eap->arg, DIP_ALL) == FAIL) {
|
? do_source(eap->arg, false, DOSO_NONE) == FAIL
|
||||||
|
: source_in_path(p_rtp, eap->arg, DIP_ALL) == FAIL) {
|
||||||
EMSG2(_(e_notopen), eap->arg);
|
EMSG2(_(e_notopen), eap->arg);
|
||||||
}
|
}
|
||||||
curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
|
curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
|
||||||
|
767
test/functional/plugin/lsp/diagnostic_spec.lua
Normal file
767
test/functional/plugin/lsp/diagnostic_spec.lua
Normal file
@@ -0,0 +1,767 @@
|
|||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local clear = helpers.clear
|
||||||
|
local exec_lua = helpers.exec_lua
|
||||||
|
local eq = helpers.eq
|
||||||
|
local nvim = helpers.nvim
|
||||||
|
|
||||||
|
describe('vim.lsp.diagnostic', function()
|
||||||
|
local fake_uri
|
||||||
|
|
||||||
|
before_each(function()
|
||||||
|
clear()
|
||||||
|
|
||||||
|
exec_lua [[
|
||||||
|
require('vim.lsp')
|
||||||
|
|
||||||
|
make_range = function(x1, y1, x2, y2)
|
||||||
|
return { start = { line = x1, character = y1 }, ['end'] = { line = x2, character = y2 } }
|
||||||
|
end
|
||||||
|
|
||||||
|
make_error = function(msg, x1, y1, x2, y2)
|
||||||
|
return {
|
||||||
|
range = make_range(x1, y1, x2, y2),
|
||||||
|
message = msg,
|
||||||
|
severity = 1,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
make_warning = function(msg, x1, y1, x2, y2)
|
||||||
|
return {
|
||||||
|
range = make_range(x1, y1, x2, y2),
|
||||||
|
message = msg,
|
||||||
|
severity = 2,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
make_information = function(msg, x1, y1, x2, y2)
|
||||||
|
return {
|
||||||
|
range = make_range(x1, y1, x2, y2),
|
||||||
|
message = msg,
|
||||||
|
severity = 3,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
count_of_extmarks_for_client = function(bufnr, client_id)
|
||||||
|
return #vim.api.nvim_buf_get_extmarks(
|
||||||
|
bufnr, vim.lsp.diagnostic._get_diagnostic_namespace(client_id), 0, -1, {}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
]]
|
||||||
|
|
||||||
|
fake_uri = "file://fake/uri"
|
||||||
|
|
||||||
|
exec_lua([[
|
||||||
|
fake_uri = ...
|
||||||
|
diagnostic_bufnr = vim.uri_to_bufnr(fake_uri)
|
||||||
|
local lines = {"1st line of text", "2nd line of text", "wow", "cool", "more", "lines"}
|
||||||
|
vim.fn.bufload(diagnostic_bufnr)
|
||||||
|
vim.api.nvim_buf_set_lines(diagnostic_bufnr, 0, 1, false, lines)
|
||||||
|
return diagnostic_bufnr
|
||||||
|
]], fake_uri)
|
||||||
|
end)
|
||||||
|
|
||||||
|
after_each(function()
|
||||||
|
clear()
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('vim.lsp.diagnostic', function()
|
||||||
|
describe('handle_publish_diagnostics', function()
|
||||||
|
it('should be able to save and count a single client error', function()
|
||||||
|
eq(1, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
}, 0, 1
|
||||||
|
)
|
||||||
|
return vim.lsp.diagnostic.get_count(0, "Error", 1)
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should be able to save and count from two clients', function()
|
||||||
|
eq(2, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
make_error('Diagnostic #2', 2, 1, 2, 1),
|
||||||
|
}, 0, 1
|
||||||
|
)
|
||||||
|
return vim.lsp.diagnostic.get_count(0, "Error", 1)
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should be able to save and count from multiple clients', function()
|
||||||
|
eq({1, 1, 2}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic From Server 1', 1, 1, 1, 1),
|
||||||
|
}, 0, 1
|
||||||
|
)
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic From Server 2', 1, 1, 1, 1),
|
||||||
|
}, 0, 2
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
-- Server 1
|
||||||
|
vim.lsp.diagnostic.get_count(0, "Error", 1),
|
||||||
|
-- Server 2
|
||||||
|
vim.lsp.diagnostic.get_count(0, "Error", 2),
|
||||||
|
-- All servers
|
||||||
|
vim.lsp.diagnostic.get_count(0, "Error", nil),
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should be able to save and count from multiple clients with respect to severity', function()
|
||||||
|
eq({3, 0, 3}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic From Server 1:1', 1, 1, 1, 1),
|
||||||
|
make_error('Diagnostic From Server 1:2', 2, 2, 2, 2),
|
||||||
|
make_error('Diagnostic From Server 1:3', 2, 3, 3, 2),
|
||||||
|
}, 0, 1
|
||||||
|
)
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_warning('Warning From Server 2', 3, 3, 3, 3),
|
||||||
|
}, 0, 2
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
-- Server 1
|
||||||
|
vim.lsp.diagnostic.get_count(0, "Error", 1),
|
||||||
|
-- Server 2
|
||||||
|
vim.lsp.diagnostic.get_count(0, "Error", 2),
|
||||||
|
-- All servers
|
||||||
|
vim.lsp.diagnostic.get_count(0, "Error", nil),
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should handle one server clearing highlights while the other still has highlights', function()
|
||||||
|
-- 1 Error (1)
|
||||||
|
-- 1 Warning (2)
|
||||||
|
-- 1 Warning (2) + 1 Warning (1)
|
||||||
|
-- 2 highlights and 2 underlines (since error)
|
||||||
|
-- 1 highlight + 1 underline
|
||||||
|
local all_highlights = {1, 1, 2, 4, 2}
|
||||||
|
eq(all_highlights, exec_lua [[
|
||||||
|
local server_1_diags = {
|
||||||
|
make_error("Error 1", 1, 1, 1, 5),
|
||||||
|
make_warning("Warning on Server 1", 2, 1, 2, 5),
|
||||||
|
}
|
||||||
|
local server_2_diags = {
|
||||||
|
make_warning("Warning 1", 2, 1, 2, 5),
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_1_diags }, 1)
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, { uri = fake_uri, diagnostics = server_2_diags }, 2)
|
||||||
|
return {
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1),
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2),
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil),
|
||||||
|
count_of_extmarks_for_client(diagnostic_bufnr, 1),
|
||||||
|
count_of_extmarks_for_client(diagnostic_bufnr, 2),
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Clear diagnostics from server 1, and make sure we have the right amount of stuff for client 2
|
||||||
|
eq({1, 1, 2, 0, 2}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.clear(diagnostic_bufnr, 1)
|
||||||
|
return {
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1),
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2),
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil),
|
||||||
|
count_of_extmarks_for_client(diagnostic_bufnr, 1),
|
||||||
|
count_of_extmarks_for_client(diagnostic_bufnr, 2),
|
||||||
|
}
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Show diagnostics from server 1 again
|
||||||
|
eq(all_highlights, exec_lua([[
|
||||||
|
vim.lsp.diagnostic.display(nil, diagnostic_bufnr, 1)
|
||||||
|
return {
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1),
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", 2),
|
||||||
|
vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Warning", nil),
|
||||||
|
count_of_extmarks_for_client(diagnostic_bufnr, 1),
|
||||||
|
count_of_extmarks_for_client(diagnostic_bufnr, 2),
|
||||||
|
}
|
||||||
|
]]))
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('get_next_diagnostic_pos', function()
|
||||||
|
it('can find the next pos with only one client', function()
|
||||||
|
eq({1, 1}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
return vim.lsp.diagnostic.get_next_pos()
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can find next pos with two errors', function()
|
||||||
|
eq({4, 4}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
make_error('Diagnostic #2', 4, 4, 4, 4),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {3, 1})
|
||||||
|
return vim.lsp.diagnostic.get_next_pos { client_id = 1 }
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can cycle when position is past error', function()
|
||||||
|
eq({1, 1}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {3, 1})
|
||||||
|
return vim.lsp.diagnostic.get_next_pos { client_id = 1 }
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('will not cycle when wrap is off', function()
|
||||||
|
eq(false, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {3, 1})
|
||||||
|
return vim.lsp.diagnostic.get_next_pos { client_id = 1, wrap = false }
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can cycle even from the last line', function()
|
||||||
|
eq({4, 4}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #2', 4, 4, 4, 4),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {vim.api.nvim_buf_line_count(0), 1})
|
||||||
|
return vim.lsp.diagnostic.get_prev_pos { client_id = 1 }
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('get_prev_diagnostic_pos', function()
|
||||||
|
it('can find the prev pos with only one client', function()
|
||||||
|
eq({1, 1}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {3, 1})
|
||||||
|
return vim.lsp.diagnostic.get_prev_pos()
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can find prev pos with two errors', function()
|
||||||
|
eq({1, 1}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #1', 1, 1, 1, 1),
|
||||||
|
make_error('Diagnostic #2', 4, 4, 4, 4),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {3, 1})
|
||||||
|
return vim.lsp.diagnostic.get_prev_pos { client_id = 1 }
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can cycle when position is past error', function()
|
||||||
|
eq({4, 4}, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #2', 4, 4, 4, 4),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {3, 1})
|
||||||
|
return vim.lsp.diagnostic.get_prev_pos { client_id = 1 }
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('respects wrap parameter', function()
|
||||||
|
eq(false, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.save(
|
||||||
|
{
|
||||||
|
make_error('Diagnostic #2', 4, 4, 4, 4),
|
||||||
|
}, diagnostic_bufnr, 1
|
||||||
|
)
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.api.nvim_win_set_cursor(0, {3, 1})
|
||||||
|
return vim.lsp.diagnostic.get_prev_pos { client_id = 1, wrap = false}
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("vim.lsp.diagnostic.get_line_diagnostics", function()
|
||||||
|
it('should return an empty table when no diagnostics are present', function()
|
||||||
|
eq({}, exec_lua [[return vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should return all diagnostics when no severity is supplied', function()
|
||||||
|
eq(2, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error("Error 1", 1, 1, 1, 5),
|
||||||
|
make_warning("Warning on Server 1", 1, 1, 2, 5),
|
||||||
|
make_error("Error On Other Line", 2, 1, 1, 5),
|
||||||
|
}
|
||||||
|
}, 1)
|
||||||
|
|
||||||
|
return #vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1)
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should return only requested diagnostics when severity_limit is supplied', function()
|
||||||
|
eq(2, exec_lua [[
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error("Error 1", 1, 1, 1, 5),
|
||||||
|
make_warning("Warning on Server 1", 1, 1, 2, 5),
|
||||||
|
make_information("Ignored information", 1, 1, 2, 5),
|
||||||
|
make_error("Error On Other Line", 2, 1, 1, 5),
|
||||||
|
}
|
||||||
|
}, 1)
|
||||||
|
|
||||||
|
return #vim.lsp.diagnostic.get_line_diagnostics(diagnostic_bufnr, 1, { severity_limit = "Warning" })
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("vim.lsp.diagnostic.on_publish_diagnostics", function()
|
||||||
|
it('can use functions for config values', function()
|
||||||
|
exec_lua [[
|
||||||
|
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
virtual_text = function() return true end,
|
||||||
|
})(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
]]
|
||||||
|
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
|
||||||
|
-- Now, don't enable virtual text.
|
||||||
|
-- We should have one less extmark displayed.
|
||||||
|
exec_lua [[
|
||||||
|
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
virtual_text = function() return false end,
|
||||||
|
})(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
]]
|
||||||
|
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(1, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can perform updates after insert_leave', function()
|
||||||
|
exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
|
||||||
|
nvim("input", "o")
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
-- Save the diagnostics
|
||||||
|
exec_lua [[
|
||||||
|
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
update_in_insert = false,
|
||||||
|
})(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- No diagnostics displayed yet.
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(0, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
|
||||||
|
nvim("input", "<esc>")
|
||||||
|
eq({mode='n', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('does not perform updates when not needed', function()
|
||||||
|
exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
|
||||||
|
nvim("input", "o")
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
-- Save the diagnostics
|
||||||
|
exec_lua [[
|
||||||
|
PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
update_in_insert = false,
|
||||||
|
virtual_text = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Count how many times we call display.
|
||||||
|
SetVirtualTextOriginal = vim.lsp.diagnostic.set_virtual_text
|
||||||
|
|
||||||
|
DisplayCount = 0
|
||||||
|
vim.lsp.diagnostic.set_virtual_text = function(...)
|
||||||
|
DisplayCount = DisplayCount + 1
|
||||||
|
return SetVirtualTextOriginal(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
PublishDiagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- No diagnostics displayed yet.
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(0, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
eq(0, exec_lua [[return DisplayCount]])
|
||||||
|
|
||||||
|
nvim("input", "<esc>")
|
||||||
|
eq({mode='n', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
eq(1, exec_lua [[return DisplayCount]])
|
||||||
|
|
||||||
|
-- Go in and out of insert mode one more time.
|
||||||
|
nvim("input", "o")
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
nvim("input", "<esc>")
|
||||||
|
eq({mode='n', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
-- Should not have set the virtual text again.
|
||||||
|
eq(1, exec_lua [[return DisplayCount]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('never sets virtual text, in combination with insert leave', function()
|
||||||
|
exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
|
||||||
|
nvim("input", "o")
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
-- Save the diagnostics
|
||||||
|
exec_lua [[
|
||||||
|
PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
update_in_insert = false,
|
||||||
|
virtual_text = false,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Count how many times we call display.
|
||||||
|
SetVirtualTextOriginal = vim.lsp.diagnostic.set_virtual_text
|
||||||
|
|
||||||
|
DisplayCount = 0
|
||||||
|
vim.lsp.diagnostic.set_virtual_text = function(...)
|
||||||
|
DisplayCount = DisplayCount + 1
|
||||||
|
return SetVirtualTextOriginal(...)
|
||||||
|
end
|
||||||
|
|
||||||
|
PublishDiagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- No diagnostics displayed yet.
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(0, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
eq(0, exec_lua [[return DisplayCount]])
|
||||||
|
|
||||||
|
nvim("input", "<esc>")
|
||||||
|
eq({mode='n', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(1, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
eq(0, exec_lua [[return DisplayCount]])
|
||||||
|
|
||||||
|
-- Go in and out of insert mode one more time.
|
||||||
|
nvim("input", "o")
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
nvim("input", "<esc>")
|
||||||
|
eq({mode='n', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
-- Should not have set the virtual text still.
|
||||||
|
eq(0, exec_lua [[return DisplayCount]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('can perform updates while in insert mode, if desired', function()
|
||||||
|
exec_lua [[vim.api.nvim_set_current_buf(diagnostic_bufnr)]]
|
||||||
|
nvim("input", "o")
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
-- Save the diagnostics
|
||||||
|
exec_lua [[
|
||||||
|
vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
update_in_insert = true,
|
||||||
|
})(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
]]
|
||||||
|
|
||||||
|
-- Diagnostics are displayed, because the user wanted them that way!
|
||||||
|
eq({mode='i', blocking=false}, nvim("get_mode"))
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
|
||||||
|
nvim("input", "<esc>")
|
||||||
|
eq({mode='n', blocking=false}, nvim("get_mode"))
|
||||||
|
|
||||||
|
eq(1, exec_lua [[return vim.lsp.diagnostic.get_count(diagnostic_bufnr, "Error", 1)]])
|
||||||
|
eq(2, exec_lua [[return count_of_extmarks_for_client(diagnostic_bufnr, 1)]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('allows configuring the virtual text via vim.lsp.with', function()
|
||||||
|
local expected_spacing = 10
|
||||||
|
local extmarks = exec_lua([[
|
||||||
|
PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
virtual_text = {
|
||||||
|
spacing = ...,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
PublishDiagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
return vim.api.nvim_buf_get_extmarks(
|
||||||
|
diagnostic_bufnr,
|
||||||
|
vim.lsp.diagnostic._get_diagnostic_namespace(1),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
{ details = true }
|
||||||
|
)
|
||||||
|
]], expected_spacing)
|
||||||
|
|
||||||
|
local virt_text = extmarks[1][4].virt_text
|
||||||
|
local spacing = virt_text[1][1]
|
||||||
|
|
||||||
|
eq(expected_spacing, #spacing)
|
||||||
|
end)
|
||||||
|
|
||||||
|
|
||||||
|
it('allows configuring the virtual text via vim.lsp.with using a function', function()
|
||||||
|
local expected_spacing = 10
|
||||||
|
local extmarks = exec_lua([[
|
||||||
|
spacing = ...
|
||||||
|
|
||||||
|
PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
virtual_text = function()
|
||||||
|
return {
|
||||||
|
spacing = spacing,
|
||||||
|
}
|
||||||
|
end,
|
||||||
|
})
|
||||||
|
|
||||||
|
PublishDiagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
return vim.api.nvim_buf_get_extmarks(
|
||||||
|
diagnostic_bufnr,
|
||||||
|
vim.lsp.diagnostic._get_diagnostic_namespace(1),
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
{ details = true }
|
||||||
|
)
|
||||||
|
]], expected_spacing)
|
||||||
|
|
||||||
|
local virt_text = extmarks[1][4].virt_text
|
||||||
|
local spacing = virt_text[1][1]
|
||||||
|
|
||||||
|
eq(expected_spacing, #spacing)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('lsp.util.show_line_diagnostics', function()
|
||||||
|
it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function()
|
||||||
|
-- Two lines:
|
||||||
|
-- Diagnostic:
|
||||||
|
-- 1. <msg>
|
||||||
|
eq(2, exec_lua [[
|
||||||
|
local buffer = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, {
|
||||||
|
"testing";
|
||||||
|
"123";
|
||||||
|
})
|
||||||
|
local diagnostics = {
|
||||||
|
{
|
||||||
|
range = {
|
||||||
|
start = { line = 0; character = 1; };
|
||||||
|
["end"] = { line = 0; character = 3; };
|
||||||
|
};
|
||||||
|
severity = vim.lsp.protocol.DiagnosticSeverity.Error;
|
||||||
|
message = "Syntax error";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
vim.api.nvim_win_set_buf(0, buffer)
|
||||||
|
vim.lsp.diagnostic.save(diagnostics, buffer, 1)
|
||||||
|
local popup_bufnr, winnr = vim.lsp.diagnostic.show_line_diagnostics()
|
||||||
|
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('creates floating window and returns popup bufnr and winnr without header, if requested', function()
|
||||||
|
-- One line (since no header):
|
||||||
|
-- 1. <msg>
|
||||||
|
eq(1, exec_lua [[
|
||||||
|
local buffer = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, {
|
||||||
|
"testing";
|
||||||
|
"123";
|
||||||
|
})
|
||||||
|
local diagnostics = {
|
||||||
|
{
|
||||||
|
range = {
|
||||||
|
start = { line = 0; character = 1; };
|
||||||
|
["end"] = { line = 0; character = 3; };
|
||||||
|
};
|
||||||
|
severity = vim.lsp.protocol.DiagnosticSeverity.Error;
|
||||||
|
message = "Syntax error";
|
||||||
|
},
|
||||||
|
}
|
||||||
|
vim.api.nvim_win_set_buf(0, buffer)
|
||||||
|
vim.lsp.diagnostic.save(diagnostics, buffer, 1)
|
||||||
|
local popup_bufnr, winnr = vim.lsp.diagnostic.show_line_diagnostics { show_header = false }
|
||||||
|
return #vim.api.nvim_buf_get_lines(popup_bufnr, 0, -1, false)
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('set_signs', function()
|
||||||
|
-- TODO(tjdevries): Find out why signs are not displayed when set from Lua...??
|
||||||
|
pending('sets signs by default', function()
|
||||||
|
exec_lua [[
|
||||||
|
PublishDiagnostics = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, {
|
||||||
|
update_in_insert = true,
|
||||||
|
signs = true,
|
||||||
|
})
|
||||||
|
|
||||||
|
local diagnostics = {
|
||||||
|
make_error('Delayed Diagnostic', 1, 1, 1, 2),
|
||||||
|
make_error('Delayed Diagnostic', 3, 3, 3, 3),
|
||||||
|
}
|
||||||
|
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = diagnostics
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.set_signs(diagnostics, diagnostic_bufnr, 1)
|
||||||
|
-- return vim.fn.sign_getplaced()
|
||||||
|
]]
|
||||||
|
|
||||||
|
nvim("input", "o")
|
||||||
|
nvim("input", "<esc>")
|
||||||
|
|
||||||
|
-- TODO(tjdevries): Find a way to get the signs to display in the test...
|
||||||
|
eq(nil, exec_lua [[
|
||||||
|
return im.fn.sign_getplaced()[1].signs
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('set_loclist()', function()
|
||||||
|
it('sets diagnostics in lnum order', function()
|
||||||
|
local loc_list = exec_lua [[
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Farther Diagnostic', 4, 4, 4, 4),
|
||||||
|
make_error('Lower Diagnostic', 1, 1, 1, 1),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.set_loclist()
|
||||||
|
|
||||||
|
return vim.fn.getloclist(0)
|
||||||
|
]]
|
||||||
|
|
||||||
|
assert(loc_list[1].lnum < loc_list[2].lnum)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('sets diagnostics in lnum order, regardless of client', function()
|
||||||
|
local loc_list = exec_lua [[
|
||||||
|
vim.api.nvim_win_set_buf(0, diagnostic_bufnr)
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_error('Lower Diagnostic', 1, 1, 1, 1),
|
||||||
|
}
|
||||||
|
}, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.on_publish_diagnostics(nil, nil, {
|
||||||
|
uri = fake_uri,
|
||||||
|
diagnostics = {
|
||||||
|
make_warning('Farther Diagnostic', 4, 4, 4, 4),
|
||||||
|
}
|
||||||
|
}, 2
|
||||||
|
)
|
||||||
|
|
||||||
|
vim.lsp.diagnostic.set_loclist()
|
||||||
|
|
||||||
|
return vim.fn.getloclist(0)
|
||||||
|
]]
|
||||||
|
|
||||||
|
assert(loc_list[1].lnum < loc_list[2].lnum)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
29
test/functional/plugin/lsp/handler_spec.lua
Normal file
29
test/functional/plugin/lsp/handler_spec.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
local helpers = require('test.functional.helpers')(after_each)
|
||||||
|
|
||||||
|
local eq = helpers.eq
|
||||||
|
local exec_lua = helpers.exec_lua
|
||||||
|
local pcall_err = helpers.pcall_err
|
||||||
|
local matches = helpers.matches
|
||||||
|
|
||||||
|
describe('lsp-handlers', function()
|
||||||
|
describe('vim.lsp._with_extend', function()
|
||||||
|
it('should return a table with the default keys', function()
|
||||||
|
eq({hello = 'world' }, exec_lua [[
|
||||||
|
return vim.lsp._with_extend('test', { hello = 'world' })
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should override with config keys', function()
|
||||||
|
eq({hello = 'universe', other = true}, exec_lua [[
|
||||||
|
return vim.lsp._with_extend('test', { other = true, hello = 'world' }, { hello = 'universe' })
|
||||||
|
]])
|
||||||
|
end)
|
||||||
|
|
||||||
|
it('should not allow invalid keys', function()
|
||||||
|
matches(
|
||||||
|
'.*Invalid option for `test`.*',
|
||||||
|
pcall_err(exec_lua, "return vim.lsp._with_extend('test', { hello = 'world' }, { invalid = true })")
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
@@ -323,7 +323,7 @@ describe('LSP', function()
|
|||||||
test_name = "capabilities_for_client_supports_method";
|
test_name = "capabilities_for_client_supports_method";
|
||||||
on_setup = function()
|
on_setup = function()
|
||||||
exec_lua([=[
|
exec_lua([=[
|
||||||
vim.lsp.callbacks['textDocument/hover'] = function(err, method)
|
vim.lsp.handlers['textDocument/hover'] = function(err, method)
|
||||||
vim.lsp._last_lsp_callback = { err = err; method = method }
|
vim.lsp._last_lsp_callback = { err = err; method = method }
|
||||||
end
|
end
|
||||||
vim.lsp._unsupported_method = function(method)
|
vim.lsp._unsupported_method = function(method)
|
||||||
@@ -847,25 +847,28 @@ describe('LSP', function()
|
|||||||
end
|
end
|
||||||
|
|
||||||
it('highlight groups', function()
|
it('highlight groups', function()
|
||||||
eq({'LspDiagnosticsError',
|
eq({
|
||||||
'LspDiagnosticsErrorFloating',
|
'LspDiagnosticsDefaultError',
|
||||||
'LspDiagnosticsErrorSign',
|
'LspDiagnosticsDefaultHint',
|
||||||
'LspDiagnosticsHint',
|
'LspDiagnosticsDefaultInformation',
|
||||||
'LspDiagnosticsHintFloating',
|
'LspDiagnosticsDefaultWarning',
|
||||||
'LspDiagnosticsHintSign',
|
'LspDiagnosticsFloatingError',
|
||||||
'LspDiagnosticsInformation',
|
'LspDiagnosticsFloatingHint',
|
||||||
'LspDiagnosticsInformationFloating',
|
'LspDiagnosticsFloatingInformation',
|
||||||
'LspDiagnosticsInformationSign',
|
'LspDiagnosticsFloatingWarning',
|
||||||
'LspDiagnosticsUnderline',
|
'LspDiagnosticsSignError',
|
||||||
'LspDiagnosticsUnderlineError',
|
'LspDiagnosticsSignHint',
|
||||||
'LspDiagnosticsUnderlineHint',
|
'LspDiagnosticsSignInformation',
|
||||||
'LspDiagnosticsUnderlineInformation',
|
'LspDiagnosticsSignWarning',
|
||||||
'LspDiagnosticsUnderlineWarning',
|
'LspDiagnosticsUnderlineError',
|
||||||
'LspDiagnosticsWarning',
|
'LspDiagnosticsUnderlineHint',
|
||||||
'LspDiagnosticsWarningFloating',
|
'LspDiagnosticsUnderlineInformation',
|
||||||
'LspDiagnosticsWarningSign',
|
'LspDiagnosticsUnderlineWarning',
|
||||||
},
|
'LspDiagnosticsVirtualTextError',
|
||||||
exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]]))
|
'LspDiagnosticsVirtualTextHint',
|
||||||
|
'LspDiagnosticsVirtualTextInformation',
|
||||||
|
'LspDiagnosticsVirtualTextWarning',
|
||||||
|
}, exec_lua([[require'vim.lsp'; return vim.fn.getcompletion('Lsp', 'highlight')]]))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe('apply_text_edits', function()
|
describe('apply_text_edits', function()
|
||||||
@@ -1037,7 +1040,7 @@ describe('LSP', function()
|
|||||||
label = nil;
|
label = nil;
|
||||||
edit = {};
|
edit = {};
|
||||||
}
|
}
|
||||||
return vim.lsp.callbacks['workspace/applyEdit'](nil, nil, apply_edit)
|
return vim.lsp.handlers['workspace/applyEdit'](nil, nil, apply_edit)
|
||||||
]])
|
]])
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
@@ -1084,47 +1087,7 @@ describe('LSP', function()
|
|||||||
eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix))
|
eq({}, exec_lua([[return vim.lsp.util.text_document_completion_list_to_complete_items(...)]], {}, prefix))
|
||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
describe('buf_diagnostics_save_positions', function()
|
|
||||||
it('stores the diagnostics in diagnostics_by_buf', function ()
|
|
||||||
local diagnostics = {
|
|
||||||
{ range = {}; message = "diag1" },
|
|
||||||
{ range = {}; message = "diag2" },
|
|
||||||
}
|
|
||||||
exec_lua([[
|
|
||||||
vim.lsp.util.buf_diagnostics_save_positions(...)]], 0, diagnostics)
|
|
||||||
eq(1, exec_lua [[ return #vim.lsp.util.diagnostics_by_buf ]])
|
|
||||||
eq(diagnostics, exec_lua [[
|
|
||||||
for _, diagnostics in pairs(vim.lsp.util.diagnostics_by_buf) do
|
|
||||||
return diagnostics
|
|
||||||
end
|
|
||||||
]])
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
describe('lsp.util.show_line_diagnostics', function()
|
|
||||||
it('creates floating window and returns popup bufnr and winnr if current line contains diagnostics', function()
|
|
||||||
eq(3, exec_lua [[
|
|
||||||
local buffer = vim.api.nvim_create_buf(false, true)
|
|
||||||
vim.api.nvim_buf_set_lines(buffer, 0, -1, false, {
|
|
||||||
"testing";
|
|
||||||
"123";
|
|
||||||
})
|
|
||||||
local diagnostics = {
|
|
||||||
{
|
|
||||||
range = {
|
|
||||||
start = { line = 0; character = 1; };
|
|
||||||
["end"] = { line = 0; character = 3; };
|
|
||||||
};
|
|
||||||
severity = vim.lsp.protocol.DiagnosticSeverity.Error;
|
|
||||||
message = "Syntax error";
|
|
||||||
},
|
|
||||||
}
|
|
||||||
vim.api.nvim_win_set_buf(0, buffer)
|
|
||||||
vim.lsp.util.buf_diagnostics_save_positions(vim.fn.bufnr(buffer), diagnostics)
|
|
||||||
local popup_bufnr, winnr = vim.lsp.util.show_line_diagnostics()
|
|
||||||
return popup_bufnr
|
|
||||||
]])
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
describe('lsp.util.locations_to_items', function()
|
describe('lsp.util.locations_to_items', function()
|
||||||
it('Convert Location[] to items', function()
|
it('Convert Location[] to items', function()
|
||||||
local expected = {
|
local expected = {
|
||||||
@@ -1556,7 +1519,7 @@ describe('LSP', function()
|
|||||||
describe('vim.lsp.buf.outgoing_calls', function()
|
describe('vim.lsp.buf.outgoing_calls', function()
|
||||||
it('does nothing for an empty response', function()
|
it('does nothing for an empty response', function()
|
||||||
local qflist_count = exec_lua([=[
|
local qflist_count = exec_lua([=[
|
||||||
require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']()
|
require'vim.lsp.handlers'['callHierarchy/outgoingCalls']()
|
||||||
return #vim.fn.getqflist()
|
return #vim.fn.getqflist()
|
||||||
]=])
|
]=])
|
||||||
eq(0, qflist_count)
|
eq(0, qflist_count)
|
||||||
@@ -1602,7 +1565,7 @@ describe('LSP', function()
|
|||||||
uri = "file:///src/main.rs"
|
uri = "file:///src/main.rs"
|
||||||
}
|
}
|
||||||
} }
|
} }
|
||||||
local callback = require'vim.lsp.callbacks'['callHierarchy/outgoingCalls']
|
local callback = require'vim.lsp.handlers'['callHierarchy/outgoingCalls']
|
||||||
callback(nil, nil, rust_analyzer_response)
|
callback(nil, nil, rust_analyzer_response)
|
||||||
return vim.fn.getqflist()
|
return vim.fn.getqflist()
|
||||||
]=])
|
]=])
|
||||||
@@ -1627,7 +1590,7 @@ describe('LSP', function()
|
|||||||
describe('vim.lsp.buf.incoming_calls', function()
|
describe('vim.lsp.buf.incoming_calls', function()
|
||||||
it('does nothing for an empty response', function()
|
it('does nothing for an empty response', function()
|
||||||
local qflist_count = exec_lua([=[
|
local qflist_count = exec_lua([=[
|
||||||
require'vim.lsp.callbacks'['callHierarchy/incomingCalls']()
|
require'vim.lsp.handlers'['callHierarchy/incomingCalls']()
|
||||||
return #vim.fn.getqflist()
|
return #vim.fn.getqflist()
|
||||||
]=])
|
]=])
|
||||||
eq(0, qflist_count)
|
eq(0, qflist_count)
|
||||||
@@ -1674,7 +1637,7 @@ describe('LSP', function()
|
|||||||
} }
|
} }
|
||||||
} }
|
} }
|
||||||
|
|
||||||
local callback = require'vim.lsp.callbacks'['callHierarchy/incomingCalls']
|
local callback = require'vim.lsp.handlers'['callHierarchy/incomingCalls']
|
||||||
callback(nil, nil, rust_analyzer_response)
|
callback(nil, nil, rust_analyzer_response)
|
||||||
return vim.fn.getqflist()
|
return vim.fn.getqflist()
|
||||||
]=])
|
]=])
|
||||||
|
Reference in New Issue
Block a user