refactor(lsp): consolidate the different floating window methods into open_floating_preview

This commit is contained in:
Folke Lemaitre 2021-05-26 23:47:53 +02:00
parent 192f89ea1b
commit 64da499ac2
No known key found for this signature in database
GPG Key ID: 707FE6FEB82F7984
4 changed files with 137 additions and 72 deletions

View File

@ -1151,6 +1151,7 @@ function M.show_line_diagnostics(opts, bufnr, line_nr, client_id)
end end
end end
opts.focus_id = "line_diagnostics"
local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts) local popup_bufnr, winnr = util.open_floating_preview(lines, 'plaintext', opts)
for i, hi in ipairs(highlights) do for i, hi in ipairs(highlights) do
local prefixlen, hiname = unpack(hi) local prefixlen, hiname = unpack(hi)

View File

@ -260,7 +260,7 @@ end
--- - See |vim.api.nvim_open_win()| --- - See |vim.api.nvim_open_win()|
function M.hover(_, method, result, _, _, config) function M.hover(_, method, result, _, _, config)
config = config or {} config = config or {}
local bufnr, winnr = util.focusable_float(method, function() config.focus_id = method
if not (result and result.contents) then if not (result and result.contents) then
-- return { 'No information available' } -- return { 'No information available' }
return return
@ -271,11 +271,7 @@ function M.hover(_, method, result, _, _, config)
-- return { 'No information available' } -- return { 'No information available' }
return return
end end
local bufnr, winnr = util.fancy_floating_markdown(markdown_lines, config) return util.open_floating_preview(markdown_lines, "markdown", config)
util.close_preview_autocmd({"CursorMoved", "BufHidden", "InsertCharPre"}, winnr)
return bufnr, winnr
end)
return bufnr, winnr
end end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_hover
@ -333,13 +329,13 @@ M['textDocument/implementation'] = location_handler
--- - See |vim.api.nvim_open_win()| --- - See |vim.api.nvim_open_win()|
function M.signature_help(_, method, result, _, bufnr, config) function M.signature_help(_, method, result, _, bufnr, config)
config = config or {} config = config or {}
config.focus_id = method
-- When use `autocmd CompleteDone <silent><buffer> lua vim.lsp.buf.signature_help()` to call signatureHelp handler -- 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 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 if not (result and result.signatures and result.signatures[1]) then
print('No signature help available') print('No signature help available')
return return
end end
local p_bufnr, winnr = util.focusable_float(method, function()
local ft = api.nvim_buf_get_option(bufnr, 'filetype') local ft = api.nvim_buf_get_option(bufnr, 'filetype')
local lines = util.convert_signature_help_to_markdown_lines(result, ft) local lines = util.convert_signature_help_to_markdown_lines(result, ft)
lines = util.trim_empty_lines(lines) lines = util.trim_empty_lines(lines)
@ -347,12 +343,7 @@ function M.signature_help(_, method, result, _, bufnr, config)
print('No signature help available') print('No signature help available')
return return
end end
local p_bufnr, p_winnr = util.fancy_floating_markdown(lines, config) return util.open_floating_preview(lines, "markdown", config)
util.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"}, p_winnr)
return p_bufnr, p_winnr
end)
return p_bufnr, winnr
end end
--@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp --@see https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_signatureHelp

View File

@ -975,6 +975,8 @@ function M.preview_location(location, opts)
-- An empty syntax is more common now with TreeSitter, since TS disables syntax. -- An empty syntax is more common now with TreeSitter, since TS disables syntax.
syntax = api.nvim_buf_get_option(bufnr, 'filetype') syntax = api.nvim_buf_get_option(bufnr, 'filetype')
end end
opts = opts or {}
opts.focus_id = "location"
return M.open_floating_preview(contents, syntax, opts) return M.open_floating_preview(contents, syntax, opts)
end end
@ -997,7 +999,9 @@ end
---buffer id, the newly created window will be the new focus associated with ---buffer id, the newly created window will be the new focus associated with
---the current buffer via the tag `unique_name`. ---the current buffer via the tag `unique_name`.
--@returns (pbufnr, pwinnr) if `fn()` has created a new window; nil otherwise --@returns (pbufnr, pwinnr) if `fn()` has created a new window; nil otherwise
---@deprecated please use open_floating_preview directly
function M.focusable_float(unique_name, fn) function M.focusable_float(unique_name, fn)
vim.notify("focusable_float is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
-- Go back to previous window if we are in a focusable one -- Go back to previous window if we are in a focusable one
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")
@ -1026,10 +1030,10 @@ end
--@param fn (function) The return values of this function will be passed --@param fn (function) The return values of this function will be passed
---directly to |vim.lsp.util.open_floating_preview()|, in the case that a new ---directly to |vim.lsp.util.open_floating_preview()|, in the case that a new
---floating window should be created ---floating window should be created
---@deprecated please use open_floating_preview directly
function M.focusable_preview(unique_name, fn) function M.focusable_preview(unique_name, fn)
return M.focusable_float(unique_name, function() vim.notify("focusable_preview is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
return M.open_floating_preview(fn()) return M.open_floating_preview(fn(), {focus_id = unique_name})
end)
end end
--- Trims empty lines from input and pad top and bottom with empty lines --- Trims empty lines from input and pad top and bottom with empty lines
@ -1061,13 +1065,20 @@ end
-- TODO: refactor to separate stripping/converting and make use of open_floating_preview --- @deprecated please use open_floating_preview directly
-- function M.fancy_floating_markdown(contents, opts)
vim.notify("fancy_floating_markdown is deprecated. Please use open_floating_preview and pass focus_id = [unique_name] instead", vim.log.levels.WARN)
return M.open_floating_preview(contents, "markdown", opts)
end
--- Converts markdown into syntax highlighted regions by stripping the code --- Converts markdown into syntax highlighted regions by stripping the code
--- blocks and converting them into highlighted code. --- blocks and converting them into highlighted code.
--- This will by default insert a blank line separator after those code block --- This will by default insert a blank line separator after those code block
--- regions to improve readability. --- regions to improve readability.
--- The result is shown in a floating preview. ---
--- This method configures the given buffer and returns the lines to set.
---
--- If you want to open a popup with fancy markdown, use `open_floating_preview` instead
--- ---
---@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
@ -1082,7 +1093,7 @@ end
--- - 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.stylize_markdown(bufnr, contents, opts)
validate { validate {
contents = { contents, 't' }; contents = { contents, 't' };
opts = { opts, 't', true }; opts = { opts, 't', true };
@ -1154,24 +1165,13 @@ function M.fancy_floating_markdown(contents, opts)
end end
end end
-- Make the floating window.
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) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, stripped)
api.nvim_buf_set_option(bufnr, 'modifiable', false)
api.nvim_buf_set_option(bufnr, 'bufhidden', 'wipe')
-- 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)
api.nvim_win_set_option(winnr, 'conceallevel', 2)
api.nvim_win_set_option(winnr, 'concealcursor', 'n')
vim.cmd("ownsyntax lsp_markdown")
local idx = 1 local idx = 1
--@private --@private
-- keep track of syntaxes we already inlcuded.
-- no need to include the same syntax more than once
local langs = {}
local function apply_syntax_to_region(ft, start, finish) local function apply_syntax_to_region(ft, start, finish)
if ft == "" then if ft == "" then
vim.cmd(string.format("syntax region markdownCode start=+\\%%%dl+ end=+\\%%%dl+ keepend extend", start, finish + 1)) vim.cmd(string.format("syntax region markdownCode start=+\\%%%dl+ end=+\\%%%dl+ keepend extend", start, finish + 1))
@ -1180,21 +1180,37 @@ function M.fancy_floating_markdown(contents, opts)
local name = ft..idx local name = ft..idx
idx = idx + 1 idx = idx + 1
local lang = "@"..ft:upper() local lang = "@"..ft:upper()
if not langs[lang] then
-- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set -- HACK: reset current_syntax, since some syntax files like markdown won't load if it is already set
pcall(vim.api.nvim_buf_del_var, bufnr, "current_syntax") pcall(vim.api.nvim_buf_del_var, bufnr, "current_syntax")
-- TODO(ashkan): better validation before this. -- TODO(ashkan): better validation before this.
if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then if not pcall(vim.cmd, string.format("syntax include %s syntax/%s.vim", lang, ft)) then
return return
end end
langs[lang] = true
end
vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s keepend", name, start, finish + 1, lang)) vim.cmd(string.format("syntax region %s start=+\\%%%dl+ end=+\\%%%dl+ contains=%s keepend", name, start, finish + 1, lang))
end end
-- needs to run in the buffer for the regions to work
api.nvim_buf_call(bufnr, function()
-- we need to apply lsp_markdown regions speperately, since otherwise
-- markdown regions can "bleed" through the other syntax regions
-- and mess up the formatting
local last = 1
for _, h in ipairs(highlights) do for _, h in ipairs(highlights) do
apply_syntax_to_region(h.ft, h.start, h.finish) if last < h.start then
apply_syntax_to_region("lsp_markdown", last, h.start - 1)
end end
apply_syntax_to_region(h.ft, h.start, h.finish)
last = h.finish + 1
end
if last < #stripped then
apply_syntax_to_region("lsp_markdown", last, #stripped)
end
end)
vim.api.nvim_set_current_win(cwin) return stripped
return bufnr, winnr
end end
--- Creates autocommands to close a preview window when events happen. --- Creates autocommands to close a preview window when events happen.
@ -1203,7 +1219,9 @@ end
--@param winnr (number) window id of preview window --@param winnr (number) window id of preview window
--@see |autocmd-events| --@see |autocmd-events|
function M.close_preview_autocmd(events, winnr) function M.close_preview_autocmd(events, winnr)
if #events > 0 then
api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)") api.nvim_command("autocmd "..table.concat(events, ',').." <buffer> ++once lua pcall(vim.api.nvim_win_close, "..winnr..", true)")
end
end end
--@internal --@internal
@ -1288,13 +1306,16 @@ end
--@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 boolean enable wrapping of long lines (defaults to true)
-- - wrap_at character to wrap at for computing height when wrap is enabled
-- - 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
-- - focus_id if a popup with this id is opened, then focus it
-- - close_events list of events that closes the floating window
--@returns bufnr,winnr buffer and window number of the newly created floating --@returns bufnr,winnr buffer and window number of the newly created floating
---preview window ---preview window
function M.open_floating_preview(contents, syntax, opts) function M.open_floating_preview(contents, syntax, opts)
@ -1304,27 +1325,77 @@ function M.open_floating_preview(contents, syntax, opts)
opts = { opts, 't', true }; opts = { opts, 't', true };
} }
opts = opts or {} opts = opts or {}
opts.wrap = opts.wrap ~= false -- wrapping by default
opts.stylize_markdown = opts.stylize_markdown ~= false
opts.close_events = opts.close_events or {"CursorMoved", "CursorMovedI", "BufHidden", "InsertCharPre"}
local bufnr = api.nvim_get_current_buf()
-- check if this popup is focusable and we need to focus
if opts.focus_id then
-- Go back to previous window if we are in a focusable one
local current_winnr = api.nvim_get_current_win()
if npcall(api.nvim_win_get_var, current_winnr, opts.focus_id) then
api.nvim_command("wincmd p")
return bufnr, current_winnr
end
do
local win = find_window_by_var(opts.focus_id, bufnr)
if win and api.nvim_win_is_valid(win) and vim.fn.pumvisible() == 0 then
-- focus and return the existing buf, win
api.nvim_set_current_win(win)
api.nvim_command("stopinsert")
return api.nvim_win_get_buf(win), win
end
end
end
local floating_bufnr = api.nvim_create_buf(false, true)
local do_stylize = syntax == "markdown" and opts.stylize_markdown
-- Clean up input: trim empty lines from the end, pad -- Clean up input: trim empty lines from the end, pad
contents = M._trim(contents, opts) contents = M._trim(contents, opts)
-- Compute size of float needed to show (wrapped) lines if do_stylize then
opts.wrap_at = opts.wrap_at or (vim.wo["wrap"] and api.nvim_win_get_width(0)) -- applies the syntax and sets the lines to the buffer
local width, height = M._make_floating_popup_size(contents, opts) contents = M.stylize_markdown(floating_bufnr, contents, opts)
else
local floating_bufnr = api.nvim_create_buf(false, true)
if syntax then if syntax then
api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax) api.nvim_buf_set_option(floating_bufnr, 'syntax', syntax)
end end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents)
end
-- Compute size of float needed to show (wrapped) lines
if opts.wrap then
opts.wrap_at = opts.wrap_at or api.nvim_win_get_width(0)
else
opts.wrap_at = nil
end
local width, height = M._make_floating_popup_size(contents, opts)
local float_option = M.make_floating_popup_options(width, height, opts) local float_option = M.make_floating_popup_options(width, height, opts)
local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option) local floating_winnr = api.nvim_open_win(floating_bufnr, false, float_option)
if syntax == 'markdown' then if do_stylize then
api.nvim_win_set_option(floating_winnr, 'conceallevel', 2) api.nvim_win_set_option(floating_winnr, 'conceallevel', 2)
api.nvim_win_set_option(floating_winnr, 'concealcursor', 'n')
end end
api.nvim_buf_set_lines(floating_bufnr, 0, -1, true, contents) -- disable folding
api.nvim_win_set_option(floating_winnr, 'foldenable', false)
-- soft wrapping
api.nvim_win_set_option(floating_winnr, 'wrap', opts.wrap)
api.nvim_buf_set_option(floating_bufnr, 'modifiable', false) api.nvim_buf_set_option(floating_bufnr, 'modifiable', false)
api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe') api.nvim_buf_set_option(floating_bufnr, 'bufhidden', 'wipe')
M.close_preview_autocmd({"CursorMoved", "CursorMovedI", "BufHidden", "BufLeave"}, floating_winnr) api.nvim_buf_set_keymap(floating_bufnr, "n", "q", "<cmd>bdelete<cr>", {silent = true, noremap = true})
M.close_preview_autocmd(opts.close_events, floating_winnr)
-- save focus_id
if opts.focus_id then
api.nvim_win_set_var(floating_winnr, opts.focus_id, bufnr)
end
return floating_bufnr, floating_winnr return floating_bufnr, floating_winnr
end end

View File

@ -4,7 +4,9 @@
" URL: http://neovim.io " URL: http://neovim.io
" Remark: Uses markdown syntax file " Remark: Uses markdown syntax file
runtime! syntax/markdown.vim " always source the system included markdown instead of any other installed
" markdown.vim syntax files
execute 'source' expand('<sfile>:p:h') .. '/markdown.vim'
syn cluster mkdNonListItem add=mkdEscape,mkdNbsp syn cluster mkdNonListItem add=mkdEscape,mkdNbsp