local api = vim.api
local lsp = vim.lsp
local validate = vim.validate
local util = require('vim.lsp.util')
local npcall = vim.F.npcall
local ms = require('vim.lsp.protocol').Methods
local M = {}
--- @param params? table
--- @return fun(client: vim.lsp.Client): lsp.TextDocumentPositionParams
local function client_positional_params(params)
local win = api.nvim_get_current_win()
return function(client)
local ret = util.make_position_params(win, client.offset_encoding)
if params then
ret = vim.tbl_extend('force', ret, params)
end
return ret
end
end
--- @class vim.lsp.buf.hover.Opts : vim.lsp.util.open_floating_preview.Opts
--- @field silent? boolean
--- Displays hover information about the symbol under the cursor in a floating
--- window. The window will be dismissed on cursor move.
--- Calling the function twice will jump into the floating window
--- (thus by default, "KK" will open the hover window and focus it).
--- In the floating window, all commands and mappings are available as usual,
--- except that "q" dismisses the window.
--- You can scroll the contents the same as you would any other buffer.
--- @param config? vim.lsp.buf.hover.Opts
function M.hover(config)
config = config or {}
config.focus_id = ms.textDocument_hover
lsp.buf_request_all(0, ms.textDocument_hover, client_positional_params(), function(results, ctx)
if api.nvim_get_current_buf() ~= ctx.bufnr then
-- Ignore result since buffer changed. This happens for slow language servers.
return
end
-- Filter errors from results
local results1 = {} --- @type table<integer,lsp.Hover>
for client_id, resp in pairs(results) do
local err, result = resp.err, resp.result
if err then
lsp.log.error(err.code, err.message)
elseif result then
results1[client_id] = result
end
end
if vim.tbl_isempty(results1) then
if config.silent ~= true then
vim.notify('No information available')
end
return
end
local contents = {} --- @type string[]
local nresults = #vim.tbl_keys(results1)
local format = 'markdown'
for client_id, result in pairs(results1) do
if nresults > 1 then
-- Show client name if there are multiple clients
contents[#contents + 1] = string.format('# %s', lsp.get_client_by_id(client_id).name)
end
if type(result.contents) == 'table' and result.contents.kind == 'plaintext' then
if #results1 == 1 then
format = 'plaintext'
contents = vim.split(result.contents.value or '', '\n', { trimempty = true })
else
-- Surround plaintext with ``` to get correct formatting
contents[#contents + 1] = '```'
vim.list_extend(
contents,
vim.split(result.contents.value or '', '\n', { trimempty = true })
)
contents[#contents + 1] = '```'
end
else
vim.list_extend(contents, util.convert_input_to_markdown_lines(result.contents))
end
contents[#contents + 1] = '---'
end
-- Remove last linebreak ('---')
contents[#contents] = nil
if vim.tbl_isempty(contents) then
if config.silent ~= true then
vim.notify('No information available')
end
return
end
lsp.util.open_floating_preview(contents, format, config)
end)
end
local function request_with_opts(name, params, opts)
local req_handler --- @type function?
if opts then
req_handler = function(err, result, ctx, config)
local client = assert(lsp.get_client_by_id(ctx.client_id))
local handler = client.handlers[name] or lsp.handlers[name]
handler(err, result, ctx, vim.tbl_extend('force', config or {}, opts))
end
end
lsp.buf_request(0, name, params, req_handler)
end
---@param method string
---@param opts? vim.lsp.LocationOpts
local function get_locations(method, opts)
opts = opts or {}
local bufnr = api.nvim_get_current_buf()
local clients = lsp.get_clients({ method = method, bufnr = bufnr })
if not next(clients) then
vim.notify(lsp._unsupported_method(method), vim.log.levels.WARN)
return
end
local win = api.nvim_get_current_win()
local from = vim.fn.getpos('.')
from[1] = bufnr
local tagname = vim.fn.expand('<cword>')
local remaining = #clients
---@type vim.quickfix.entry[]
local all_items = {}
---@param result nil|lsp.Location|lsp.Location[]
---@param client vim.lsp.Client
local function on_response(_, result, client)
local locations = {}
if result then
locations = vim.islist(result) and result or { result }
end
local items = util.locations_to_items(locations, client.offset_encoding)
vim.list_extend(all_items, items)
remaining = remaining - 1
if remaining == 0 then
if vim.tbl_isempty(all_items) then
vim.notify('No locations found', vim.log.levels.INFO)
return
end
local title = 'LSP locations'
if opts.on_list then
assert(vim.is_callable(opts.on_list), 'on_list is not a function')
opts.on_list({
title = title,
items = all_items,
context = { bufnr = bufnr, method = method },
})
return
end
if #all_items == 1 then
local item = all_items[1]
local b = item.bufnr or vim.fn.bufadd(item.filename)
-- Save position in jumplist
vim.cmd("normal! m'")
-- Push a new item into tagstack
local tagstack = { { tagname = tagname, from = from } }
vim.fn.settagstack(vim.fn.win_getid(win), { items = tagstack }, 't')
vim.bo
|