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
|
||||
|
||||
M['textDocument/hover'] = function(_, method, result)
|
||||
util.focusable_preview(method, function()
|
||||
util.focusable_float(method, function()
|
||||
if not (result and result.contents) then
|
||||
return { 'No information available' }
|
||||
-- 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 { 'No information available' }
|
||||
return
|
||||
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
|
||||
|
||||
|
|
|
@ -19,11 +19,13 @@ local function npcall(fn, ...)
|
|||
return ok_or_nil(pcall(fn, ...))
|
||||
end
|
||||
|
||||
-- TODO(ashkan) @performance this could do less copying.
|
||||
function M.set_lines(lines, A, B, new_lines)
|
||||
-- 0-indexing to 1-indexing
|
||||
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
|
||||
error("Invalid range: "..vim.inspect{A = A; B = B; #lines, new_lines})
|
||||
end
|
||||
|
@ -88,7 +90,7 @@ function M.apply_text_edits(text_edits, bufnr)
|
|||
table.sort(cleaned, edit_sort_key)
|
||||
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 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
|
||||
table.insert(lines, '')
|
||||
end
|
||||
|
@ -315,9 +317,9 @@ 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
|
||||
-- fn()'s return bufnr, winnr
|
||||
-- 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
|
||||
return api.nvim_command("wincmd p")
|
||||
end
|
||||
|
@ -330,9 +332,127 @@ function M.focusable_preview(unique_name, fn)
|
|||
return
|
||||
end
|
||||
end
|
||||
local pbufnr, pwinnr = M.open_floating_preview(fn())
|
||||
api.nvim_win_set_var(pwinnr, unique_name, bufnr)
|
||||
return pbufnr, pwinnr
|
||||
local pbufnr, pwinnr = fn()
|
||||
if pbufnr then
|
||||
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
|
||||
|
||||
function M.open_floating_preview(contents, filetype, opts)
|
||||
|
|
Loading…
Reference in New Issue