mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
LSP: Improve the display of the default hover callback. (#11576)
Strips the code blocks from markdown and does syntax highlighting.
This commit is contained in:
parent
d00c624ba4
commit
026ba804d1
@ -68,16 +68,22 @@ M['textDocument/completion'] = function(_, _, result)
|
|||||||
end
|
end
|
||||||
|
|
||||||
M['textDocument/hover'] = function(_, method, result)
|
M['textDocument/hover'] = function(_, method, result)
|
||||||
util.focusable_preview(method, function()
|
util.focusable_float(method, function()
|
||||||
if not (result and result.contents) then
|
if not (result and result.contents) then
|
||||||
return { 'No information available' }
|
-- return { 'No information available' }
|
||||||
|
return
|
||||||
end
|
end
|
||||||
local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
|
local markdown_lines = util.convert_input_to_markdown_lines(result.contents)
|
||||||
markdown_lines = util.trim_empty_lines(markdown_lines)
|
markdown_lines = util.trim_empty_lines(markdown_lines)
|
||||||
if vim.tbl_isempty(markdown_lines) then
|
if vim.tbl_isempty(markdown_lines) then
|
||||||
return { 'No information available' }
|
-- return { 'No information available' }
|
||||||
|
return
|
||||||
end
|
end
|
||||||
return markdown_lines, util.try_trim_markdown_code_blocks(markdown_lines)
|
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)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -19,11 +19,13 @@ local function npcall(fn, ...)
|
|||||||
return ok_or_nil(pcall(fn, ...))
|
return ok_or_nil(pcall(fn, ...))
|
||||||
end
|
end
|
||||||
|
|
||||||
-- TODO(ashkan) @performance this could do less copying.
|
|
||||||
function M.set_lines(lines, A, B, new_lines)
|
function M.set_lines(lines, A, B, new_lines)
|
||||||
-- 0-indexing to 1-indexing
|
-- 0-indexing to 1-indexing
|
||||||
local i_0 = A[1] + 1
|
local i_0 = A[1] + 1
|
||||||
local i_n = B[1] + 1
|
-- If it extends past the end, truncate it to the end. This is because the
|
||||||
|
-- way the LSP describes the range including the last newline is by
|
||||||
|
-- specifying a line number after what we would call the last line.
|
||||||
|
local i_n = math.min(B[1] + 1, #lines)
|
||||||
if not (i_0 >= 1 and i_0 <= #lines and i_n >= 1 and i_n <= #lines) then
|
if not (i_0 >= 1 and i_0 <= #lines and i_n >= 1 and i_n <= #lines) then
|
||||||
error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines})
|
error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines})
|
||||||
end
|
end
|
||||||
@ -88,7 +90,7 @@ function M.apply_text_edits(text_edits, bufnr)
|
|||||||
table.sort(cleaned, edit_sort_key)
|
table.sort(cleaned, edit_sort_key)
|
||||||
local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false)
|
local lines = api.nvim_buf_get_lines(bufnr, start_line, finish_line + 1, false)
|
||||||
local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol')
|
local fix_eol = api.nvim_buf_get_option(bufnr, 'fixeol')
|
||||||
local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) == finish_line + 1
|
local set_eol = fix_eol and api.nvim_buf_line_count(bufnr) <= finish_line + 1
|
||||||
if set_eol and #lines[#lines] ~= 0 then
|
if set_eol and #lines[#lines] ~= 0 then
|
||||||
table.insert(lines, '')
|
table.insert(lines, '')
|
||||||
end
|
end
|
||||||
@ -315,9 +317,9 @@ end
|
|||||||
-- Check if a window with `unique_name` tagged is associated with the current
|
-- Check if a window with `unique_name` tagged is associated with the current
|
||||||
-- buffer. If not, make a new preview.
|
-- buffer. If not, make a new preview.
|
||||||
--
|
--
|
||||||
-- fn()'s return values will be passed directly to open_floating_preview in the
|
-- fn()'s return bufnr, winnr
|
||||||
-- case that a new floating window should be created.
|
-- case that a new floating window should be created.
|
||||||
function M.focusable_preview(unique_name, fn)
|
function M.focusable_float(unique_name, fn)
|
||||||
if npcall(api.nvim_win_get_var, 0, unique_name) then
|
if npcall(api.nvim_win_get_var, 0, unique_name) then
|
||||||
return api.nvim_command("wincmd p")
|
return api.nvim_command("wincmd p")
|
||||||
end
|
end
|
||||||
@ -330,9 +332,127 @@ function M.focusable_preview(unique_name, fn)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local pbufnr, pwinnr = M.open_floating_preview(fn())
|
local pbufnr, pwinnr = fn()
|
||||||
api.nvim_win_set_var(pwinnr, unique_name, bufnr)
|
if pbufnr then
|
||||||
return pbufnr, pwinnr
|
api.nvim_win_set_var(pwinnr, unique_name, bufnr)
|
||||||
|
return pbufnr, pwinnr
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if a window with `unique_name` tagged is associated with the current
|
||||||
|
-- buffer. If not, make a new preview.
|
||||||
|
--
|
||||||
|
-- fn()'s return values will be passed directly to open_floating_preview in the
|
||||||
|
-- case that a new floating window should be created.
|
||||||
|
function M.focusable_preview(unique_name, fn)
|
||||||
|
return M.focusable_float(unique_name, function()
|
||||||
|
return M.open_floating_preview(fn())
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Convert markdown into syntax highlighted regions by stripping the code
|
||||||
|
-- blocks and converting them into highlighted code.
|
||||||
|
-- This will by default insert a blank line separator after those code block
|
||||||
|
-- regions to improve readability.
|
||||||
|
function M.fancy_floating_markdown(contents, opts)
|
||||||
|
local pad_left = opts and opts.pad_left
|
||||||
|
local pad_right = opts and opts.pad_right
|
||||||
|
local stripped = {}
|
||||||
|
local highlights = {}
|
||||||
|
do
|
||||||
|
local i = 1
|
||||||
|
while i <= #contents do
|
||||||
|
local line = contents[i]
|
||||||
|
-- TODO(ashkan): use a more strict regex for filetype?
|
||||||
|
local ft = line:match("^```([a-zA-Z0-9_]*)$")
|
||||||
|
-- local ft = line:match("^```(.*)$")
|
||||||
|
-- TODO(ashkan): validate the filetype here.
|
||||||
|
if ft then
|
||||||
|
local start = #stripped
|
||||||
|
i = i + 1
|
||||||
|
while i <= #contents do
|
||||||
|
line = contents[i]
|
||||||
|
if line == "```" then
|
||||||
|
i = i + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
table.insert(stripped, line)
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
table.insert(highlights, {
|
||||||
|
ft = ft;
|
||||||
|
start = start + 1;
|
||||||
|
finish = #stripped + 1 - 1;
|
||||||
|
})
|
||||||
|
else
|
||||||
|
table.insert(stripped, line)
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local width = 0
|
||||||
|
for i, v in ipairs(stripped) do
|
||||||
|
v = v:gsub("\r", "")
|
||||||
|
if pad_left then v = (" "):rep(pad_left)..v end
|
||||||
|
if pad_right then v = v..(" "):rep(pad_right) end
|
||||||
|
stripped[i] = v
|
||||||
|
width = math.max(width, #v)
|
||||||
|
end
|
||||||
|
if opts and opts.max_width then
|
||||||
|
width = math.min(opts.max_width, width)
|
||||||
|
end
|
||||||
|
-- TODO(ashkan): decide how to make this customizable.
|
||||||
|
local insert_separator = true
|
||||||
|
if insert_separator then
|
||||||
|
for i, h in ipairs(highlights) do
|
||||||
|
h.start = h.start + i - 1
|
||||||
|
h.finish = h.finish + i - 1
|
||||||
|
if h.finish + 1 <= #stripped then
|
||||||
|
table.insert(stripped, h.finish + 1, string.rep("─", width))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Make the floating window.
|
||||||
|
local height = #stripped
|
||||||
|
local bufnr = api.nvim_create_buf(false, true)
|
||||||
|
local winnr = api.nvim_open_win(bufnr, false, M.make_floating_popup_options(width, height, opts))
|
||||||
|
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
|
||||||
|
|
||||||
|
-- Switch to the floating window to apply the syntax highlighting.
|
||||||
|
-- This is because the syntax command doesn't accept a target.
|
||||||
|
local cwin = vim.api.nvim_get_current_win()
|
||||||
|
vim.api.nvim_set_current_win(winnr)
|
||||||
|
|
||||||
|
vim.cmd("ownsyntax markdown")
|
||||||
|
local idx = 1
|
||||||
|
local function highlight_region(ft, start, finish)
|
||||||
|
if ft == '' then return end
|
||||||
|
local name = ft..idx
|
||||||
|
idx = idx + 1
|
||||||
|
local lang = "@"..ft:upper()
|
||||||
|
-- TODO(ashkan): better validation before this.
|
||||||
|
if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s", name, start, finish + 1, lang))
|
||||||
|
end
|
||||||
|
-- Previous highlight region.
|
||||||
|
-- TODO(ashkan): this wasn't working for some reason, but I would like to
|
||||||
|
-- make sure that regions between code blocks are definitely markdown.
|
||||||
|
-- local ph = {start = 0; finish = 1;}
|
||||||
|
for _, h in ipairs(highlights) do
|
||||||
|
-- highlight_region('markdown', ph.finish, h.start)
|
||||||
|
highlight_region(h.ft, h.start, h.finish)
|
||||||
|
-- ph = h
|
||||||
|
end
|
||||||
|
|
||||||
|
vim.api.nvim_set_current_win(cwin)
|
||||||
|
return bufnr, winnr
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.close_preview_autocmd(events, winnr)
|
||||||
|
api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.open_floating_preview(contents, filetype, opts)
|
function M.open_floating_preview(contents, filetype, opts)
|
||||||
|
Loading…
Reference in New Issue
Block a user