diff options
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r-- | runtime/lua/vim/lsp/callbacks.lua | 14 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/util.lua | 136 |
2 files changed, 138 insertions, 12 deletions
diff --git a/runtime/lua/vim/lsp/callbacks.lua b/runtime/lua/vim/lsp/callbacks.lua index 4fc3f74519..a3cd521b86 100644 --- a/runtime/lua/vim/lsp/callbacks.lua +++ b/runtime/lua/vim/lsp/callbacks.lua @@ -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 diff --git a/runtime/lua/vim/lsp/util.lua b/runtime/lua/vim/lsp/util.lua index 8f27353698..4a781359d4 100644 --- a/runtime/lua/vim/lsp/util.lua +++ b/runtime/lua/vim/lsp/util.lua @@ -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) |