From 1e074b02c32987ac78611ff67fd2b90fdaeef7ca Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 5 Nov 2024 21:51:36 +0100 Subject: [PATCH] perf: new logic for docs and throttle fixes https://github.com/max397574/care.nvim/issues/111 fixes https://github.com/max397574/care.nvim/issues/112 fixes https://github.com/max397574/care.nvim/issues/98 --- lua/care/config.lua | 1 + lua/care/entry.lua | 48 ++++++++++++++++++++----------------- lua/care/menu/init.lua | 43 +++++++++++++++++---------------- lua/care/types/config.lua | 4 ++++ lua/care/utils/async.lua | 50 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 104 insertions(+), 42 deletions(-) diff --git a/lua/care/config.lua b/lua/care/config.lua index d2ade09..27f7ccb 100644 --- a/lua/care/config.lua +++ b/lua/care/config.lua @@ -23,6 +23,7 @@ config.defaults = { border = "rounded", scrollbar = { enabled = true, character = "┃", offset = 0 }, position = "auto", + advanced_styling = false, }, type_icons = { Class = "󰠱", diff --git a/lua/care/entry.lua b/lua/care/entry.lua index 64fa638..f222ffc 100644 --- a/lua/care/entry.lua +++ b/lua/care/entry.lua @@ -170,33 +170,37 @@ end function Entry:get_documentation() local completion_item = self.completion_item - local documentation = completion_item.documentation - ---@diagnostic disable-next-line: param-type-mismatch - local documentation_text = vim.trim(type(documentation) == "table" and documentation.value or documentation or "") - if (documentation_text):match("^%s*$") and (completion_item.detail or ""):match("^%s*$") then - return nil, nil + + local documents = {} + + if completion_item.detail and completion_item.detail ~= "" then + local ft = vim.bo.ft + table.insert(documents, { + kind = "markdown", + value = ("```%s\n%s\n```"):format(ft, vim.trim(completion_item.detail)), + }) end - local format = "markdown" - local contents - if documentation_text ~= "" then - if type(documentation) == "table" and documentation.kind == "plaintext" then - format = "plaintext" - contents = vim.split(documentation.value or "", "\n", { trimempty = true }) - else - contents = vim.lsp.util.convert_input_to_markdown_lines(documentation_text) + + local docs = completion_item.documentation + if type(docs) == "string" and vim.trim(docs) ~= "" then + table.insert(documents, { + kind = "plaintext", + value = vim.trim(docs), + }) + elseif type(docs) == "table" and #docs.value > 0 then + if docs.value ~= "" then + table.insert(documents, { + kind = docs.kind, + value = vim.trim(docs.value), + }) end end - if completion_item.detail and completion_item.detail ~= "" then - if not contents then - contents = {} - end - table.insert(contents, 1, vim.trim(completion_item.detail)) - if documentation_text ~= "" then - table.insert(contents, 2, "---") - end + if #documents == 2 then + documents[1].value = documents[1].value .. "\n---" end - return contents, format + + return vim.lsp.util.convert_input_to_markdown_lines(documents) end return Entry diff --git a/lua/care/menu/init.lua b/lua/care/menu/init.lua index 15958f7..dd24eeb 100644 --- a/lua/care/menu/init.lua +++ b/lua/care/menu/init.lua @@ -34,7 +34,7 @@ function Menu:close() end end -function Menu:draw_docs(entry) +Menu.draw_docs = require("care.utils.async").throttle(function(self, entry) if not entry or self.index == 0 or self.menu_window.winnr == nil then if self:docs_visible() then self.docs_window:close() @@ -82,36 +82,36 @@ function Menu:draw_docs(entry) width = width - (has_border and 2 or 0) - local contents, format = doc_entry:get_documentation() + local docs = doc_entry:get_documentation() - if contents == nil then + if docs == nil or #docs == 0 then self.docs_window:close() return end - local do_stylize = format == "markdown" and vim.g.syntax_on ~= nil - if do_stylize then - contents = vim.lsp.util._normalize_markdown( - vim.split(table.concat(contents, "\n"), "\n", { trimempty = true }), + if self.config.ui.docs_view.advanced_styling then + docs = vim.lsp.util._normalize_markdown( + vim.split(table.concat(docs, "\n"), "\n", { trimempty = true }), { width = width } ) vim.bo[self.docs_window.buf].filetype = "markdown" vim.treesitter.start(self.docs_window.buf) - vim.api.nvim_buf_set_lines(self.docs_window.buf, 0, -1, false, contents) + vim.api.nvim_buf_set_lines(self.docs_window.buf, 0, -1, false, docs) else - -- Clean up input: trim empty lines - contents = vim.split(table.concat(contents, "\n"), "\n", { trimempty = true }) + docs = vim.lsp.util._normalize_markdown( + vim.split(table.concat(docs, "\n"), "\n", { trimempty = true }), + { width = width } + ) - if format then - vim.bo[self.docs_window.buf].syntax = format - end - vim.api.nvim_buf_set_lines(self.docs_window.buf, 0, -1, true, contents) - end - if #contents == 0 then - self.docs_window:close() - return + vim.api.nvim_buf_call(self.docs_window.buf, function() + vim.cmd([[syntax clear]]) + vim.api.nvim_buf_set_lines(self.docs_window.buf, 0, -1, false, {}) + end) + + vim.lsp.util.stylize_markdown(self.docs_window.buf, docs, { max_width = width }) end - width = math.min(width, require("care.utils").longest(contents)) + + width = math.min(width, require("care.utils").longest(docs)) local win_offset if position == "right" then @@ -141,6 +141,9 @@ function Menu:draw_docs(entry) }) end + vim.wo[self.docs_window.winnr][0].conceallevel = 2 + vim.wo[self.docs_window.winnr][0].concealcursor = "n" + self.docs_window:set_scroll(1, 1, false) self.docs_window:draw_scrollbar() end @@ -158,7 +161,7 @@ function Menu:draw_docs(entry) else open_docs_window(entry) end -end +end, 10) local function preselect(menu) if not menu.config.preselect then diff --git a/lua/care/types/config.lua b/lua/care/types/config.lua index 8e67e8b..7de605d 100644 --- a/lua/care/types/config.lua +++ b/lua/care/types/config.lua @@ -172,6 +172,10 @@ --- Position of docs view. --- Auto will prefer right if there is enough space ---@field position? "auto"|"left"|"right" +--- Use advanced styling for the documentation. +--- This will use treesitter to make the menu prettier which comes with quite a big +--- performance cost though. +---@field advanced_styling? boolean --- Additional data passed to format function to allow more advanced formatting ---@class care.format_data diff --git a/lua/care/utils/async.lua b/lua/care/utils/async.lua index 0c98c8a..bd4d35e 100644 --- a/lua/care/utils/async.lua +++ b/lua/care/utils/async.lua @@ -1,12 +1,62 @@ +-- some parts are adapted from https://github.com/folke/trouble.nvim local async = {} local uv = vim.loop or vim.uv +local Queue = {} +Queue.queue = {} +Queue.checker = uv.new_check() + +function Queue.step() + local budget = 1 * 1e6 + local start = uv.hrtime() + while #Queue.queue > 0 and uv.hrtime() - start < budget do + local a = table.remove(Queue.queue, 1) + a:step() + if a.running then + table.insert(Queue._queue, a) + end + end + if #Queue.queue == 0 then + return Queue.checker:stop() + end +end + +function Queue.add(a) + table.insert(Queue.queue, a) + if not Queue.checker:is_active() then + Queue.checker:start(vim.schedule_wrap(Queue.step)) + end +end + +local AsyncTask = {} + +function AsyncTask.new(fn) + local self = setmetatable({}, { __index = AsyncTask }) + self.callbacks = {} + self.running = true + self.thread = coroutine.create(fn) + Queue.add(self) + return self +end + +function AsyncTask:step() + local ok, res = coroutine.resume(self.thread) + if not ok then + return self:_done(nil, res) + elseif res == "abort" then + return self:_done(nil, "abort") + elseif coroutine.status(self.thread) == "dead" then + return self:_done(res) + end +end + --- Returns a function which can be called to execute the callback --- After the first time executes earliest after `timeout` ms again ---@param fn function ---@param timeout integer ---@return table? +---@overload fun(...): any? function async.throttle(fn, timeout) local timer = vim.uv.new_timer() local last_executed = nil