From e49a49044cca072c4aca1cb3a5013aa92ac3b4f9 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Mon, 3 Jun 2024 00:30:12 +0200 Subject: [PATCH] perf: use promises for fetching sections --- lua/trouble/sources/lsp.lua | 144 ++++++++++++++++++++--------------- lua/trouble/view/init.lua | 94 +++++++++-------------- lua/trouble/view/section.lua | 63 +++++++-------- 3 files changed, 148 insertions(+), 153 deletions(-) diff --git a/lua/trouble/sources/lsp.lua b/lua/trouble/sources/lsp.lua index 094f0dae..3efa1076 100644 --- a/lua/trouble/sources/lsp.lua +++ b/lua/trouble/sources/lsp.lua @@ -2,6 +2,7 @@ local Cache = require("trouble.cache") local Config = require("trouble.config") local Filter = require("trouble.filter") local Item = require("trouble.item") +local Promise = require("trouble.promise") local Util = require("trouble.util") ---@param line string line to be indexed @@ -105,11 +106,12 @@ for _, mode in ipairs({ "definitions", "references", "implementations", "type_de } end +---@class trouble.lsp.Response: {client: vim.lsp.Client, result: R, err: lsp.ResponseError, params: P} + ---@param method string ---@param params? table ---@param opts? {client?:vim.lsp.Client} ----@param cb fun(results: table) -function M.request(method, params, cb, opts) +function M.request(method, params, opts) opts = opts or {} local buf = vim.api.nvim_get_current_buf() ---@type vim.lsp.Client[] @@ -132,22 +134,19 @@ function M.request(method, params, cb, opts) end end - local results = {} ---@type table - local done = 0 - if #clients == 0 then - return cb(results) - end - for _, client in ipairs(clients) do - vim.lsp.buf_request(buf, method, params, function(_, result) - done = done + 1 - if result then - results[client] = result - end - if done == #clients then - cb(results) - end + ---@param client vim.lsp.Client + return Promise.all(vim.tbl_map(function(client) + return Promise.new(function(resolve) + vim.lsp.buf_request(buf, method, params, function(err, result) + resolve({ client = client, result = result, err = err, params = params }) + end) end) - end + end, clients)):next(function(results) + ---@param v trouble.lsp.Response + return vim.tbl_filter(function(v) + return v.result ~= nil + end, results) + end) end ---@param method string @@ -175,14 +174,17 @@ function M.get_locations(method, cb, context) return cb(Cache.locations[id]) end - M.request(method, params, function(results) - local items = {} ---@type trouble.Item[] - for client, result in pairs(results) do - vim.list_extend(items, M.get_items(client, result)) + M.request(method, params):next( + ---@param results trouble.lsp.Response[] + function(results) + local items = {} ---@type trouble.Item[] + for _, resp in ipairs(results) do + vim.list_extend(items, M.get_items(resp.client, resp.result)) + end + Cache.locations[id] = items + cb(items) end - Cache.locations[id] = items - cb(items) - end) + ) end M.get = {} @@ -202,55 +204,77 @@ function M.get.document_symbols(cb) ---@alias lsp.Symbol lsp.SymbolInformation|lsp.DocumentSymbol - M.request("textDocument/documentSymbol", params, function(results) - if not vim.api.nvim_buf_is_valid(buf) then - return - end - ---@cast results table - local items = {} ---@type trouble.Item[] + M.request("textDocument/documentSymbol", params):next( + ---@param results trouble.lsp.Response[] + function(results) + if not vim.api.nvim_buf_is_valid(buf) then + return + end + local items = {} ---@type trouble.Item[] - for client, result in pairs(results) do - vim.list_extend(items, M.results_to_items(client, result, params.textDocument.uri)) + for _, res in ipairs(results) do + vim.list_extend(items, M.results_to_items(res.client, res.result, params.textDocument.uri)) + end + Item.add_text(items, { mode = "after" }) + ---@diagnostic disable-next-line: no-unknown + Cache.symbols[buf] = items + cb(items) end - Item.add_text(items, { mode = "after" }) - ---@diagnostic disable-next-line: no-unknown - Cache.symbols[buf] = items - cb(items) - end) + ) end ---@param cb trouble.Source.Callback function M.call_hierarchy(cb, incoming) ---@type lsp.CallHierarchyPrepareParams local params = vim.lsp.util.make_position_params() - local items = {} ---@type trouble.Item[] - M.request("textDocument/prepareCallHierarchy", params, function(results) - for client, chis in pairs(results or {}) do - ---@cast chis lsp.CallHierarchyItem[] - for _, chi in ipairs(chis) do - M.request(("callHierarchy/%sCalls"):format(incoming and "incoming" or "outgoing"), { item = chi }, function(res) - local calls = res[client] or {} --[[@as (lsp.CallHierarchyIncomingCall|lsp.CallHierarchyOutgoingCall)[] ]] - local todo = {} ---@type lsp.ResultItem[] - - for _, call in ipairs(calls) do - if incoming then - for _, r in ipairs(call.fromRanges or {}) do - local t = vim.deepcopy(chi) --[[@as lsp.ResultItem]] - t.location = { range = r or call.from.selectionRange or call.from.range, uri = call.from.uri } - todo[#todo + 1] = t + M.request("textDocument/prepareCallHierarchy", params) + :next( + ---@param results trouble.lsp.Response[] + function(results) + local requests = {} ---@type trouble.Promise[] + for _, res in ipairs(results or {}) do + for _, chi in ipairs(res.result) do + requests[#requests + 1] = M.request( + ("callHierarchy/%sCalls"):format(incoming and "incoming" or "outgoing"), + { item = chi }, + { client = res.client } + ) + end + end + return Promise.all(requests) + end + ) + :next( + ---@param responses trouble.lsp.Response<(lsp.CallHierarchyIncomingCall|lsp.CallHierarchyOutgoingCall)[]>[][] + function(responses) + local items = {} ---@type trouble.Item[] + for _, results in ipairs(responses) do + for _, res in ipairs(results) do + local client = res.client + local calls = res.result + local todo = {} ---@type lsp.ResultItem[] + local chi = res.params.item + + for _, call in ipairs(calls) do + if incoming then + for _, r in ipairs(call.fromRanges or {}) do + local t = vim.deepcopy(chi) --[[@as lsp.ResultItem]] + t.location = { range = r or call.from.selectionRange or call.from.range, uri = call.from.uri } + todo[#todo + 1] = t + end + else + todo[#todo + 1] = call.to end - else - todo[#todo + 1] = call.to end + vim.list_extend(items, M.results_to_items(client, todo)) end - vim.list_extend(items, M.results_to_items(client, todo)) - Item.add_text(items, { mode = "after" }) - cb(items) - end, { client = client }) + end + Item.add_text(items, { mode = "after" }) + cb(items) end - end - end) + ) + -- :catch(Util.error) end ---@param cb trouble.Source.Callback diff --git a/lua/trouble/view/init.lua b/lua/trouble/view/init.lua index 0262afc6..ab9c3ffb 100644 --- a/lua/trouble/view/init.lua +++ b/lua/trouble/view/init.lua @@ -1,6 +1,7 @@ local Format = require("trouble.format") local Main = require("trouble.view.main") local Preview = require("trouble.view.preview") +local Promise = require("trouble.promise") local Render = require("trouble.view.render") local Section = require("trouble.view.section") local Spec = require("trouble.spec") @@ -16,10 +17,9 @@ local Window = require("trouble.view.window") ---@field renderer trouble.Render ---@field first_render? boolean ---@field moving uv_timer_t ----@field opening? boolean +---@field first_update trouble.Promise ---@field state table ---@field _filters table ----@field _waiting (fun())[] ---@field private _main? trouble.Main local M = {} M.__index = M @@ -39,9 +39,9 @@ function M.new(opts) M._views[self] = _idx self.state = {} self.opts = opts or {} - self._waiting = {} self._filters = {} self.first_render = true + self.first_update = Promise.new(function() end) self.opts.win = self.opts.win or {} self.opts.win.on_mount = function() self:on_mount() @@ -93,7 +93,7 @@ function M.get(filter) local ret = {} for view, idx in pairs(M._views) do local is_open = view.win:valid() - local ok = is_open or view.opts.auto_open or view.opening + local ok = is_open or view.opts.auto_open or view.first_update:is_pending() ok = ok and (not filter.mode or filter.mode == view.opts.mode) ok = ok and (not filter.open or is_open) if ok then @@ -177,8 +177,6 @@ function M:on_mount() for k, v in pairs(self.opts.keys) do self:map(k, v) end - - self.opening = false end ---@param node? trouble.Node @@ -251,11 +249,7 @@ function M:jump(item, opts) end function M:wait(fn) - if self.opening then - table.insert(self._waiting, fn) - else - fn() - end + self.first_update:next(fn) end ---@param item? trouble.Item @@ -373,13 +367,16 @@ function M:action(action, opts) end) end -function M:refresh() - if not (self.opening or self.win:valid() or self.opts.auto_open) then +---@param opts? {update?: boolean, opening?: boolean} +function M:refresh(opts) + opts = opts or {} + if not (opts.opening or self.win:valid() or self.opts.auto_open) then return end - for _, section in ipairs(self.sections) do - section:refresh() - end + ---@param section trouble.Section + return Promise.all(vim.tbl_map(function(section) + return section:refresh(opts) + end, self.sections)) end function M:help() @@ -424,14 +421,29 @@ function M:open() if self.win:valid() then return self end - self.opening = true - -- self.win:open() - self:refresh() + self + :refresh({ update = false, opening = true }) + :next(function() + local count = self:count() + if count == 0 then + if not self.opts.open_no_results then + if self.opts.warn_no_results then + Util.warn("No results for **" .. self.opts.mode .. "**") + end + return + end + elseif count == 1 and self.opts.auto_jump then + self:jump(self:flatten()[1]) + return self:close() + end + self.win:open() + self:update() + end) + :next(self.first_update.resolve) return self end function M:close() - self.opening = false self:goto_main() Preview.close() self.win:close() @@ -459,37 +471,10 @@ end -- called when results are updated function M:update() local is_open = self.win:valid() - self.opening = self.opening and not is_open local count = self:count() - local did_first_update = true - for _, section in ipairs(self.sections) do - if not section.first_update then - did_first_update = false - break - end - end - - if count == 0 then - if self.opening and not self.opts.open_no_results then - if did_first_update and self.opts.warn_no_results then - Util.warn("No results for **" .. self.opts.mode .. "**") - end - self.opening = not did_first_update - return - end - - if is_open and self.opts.auto_close then - return self:close() - end - end - - if self.opening and did_first_update then - if self.opts.auto_jump and count == 1 then - self.opening = false - self:jump(self:flatten()[1]) - return self:close() - end + if count == 0 and is_open and self.opts.auto_close then + return self:close() end if self.opts.auto_open and not is_open and count > 0 then @@ -497,20 +482,11 @@ function M:update() is_open = true end - if self.opening then - self.win:open() - is_open = true - end - - if not (self.opening or is_open) then + if not is_open then return end self:render() - - while #self._waiting > 0 do - Util.try(table.remove(self._waiting, 1)) - end end ---@param filter trouble.Filter diff --git a/lua/trouble/view/section.lua b/lua/trouble/view/section.lua index 5ba8f728..da118b40 100644 --- a/lua/trouble/view/section.lua +++ b/lua/trouble/view/section.lua @@ -1,6 +1,7 @@ local Filter = require("trouble.filter") local Main = require("trouble.view.main") local Preview = require("trouble.view.preview") +local Promise = require("trouble.promise") local Sort = require("trouble.sort") local Sources = require("trouble.sources") local Tree = require("trouble.tree") @@ -15,10 +16,9 @@ local Util = require("trouble.util") ---@field node? trouble.Node ---@field fetching boolean ---@field filter? trouble.Filter ----@field first_update boolean ---@field id number ----@field on_refresh? fun(self: trouble.Section) ---@field on_update? fun(self: trouble.Section) +---@field _refresh fun() local M = {} M._id = 0 @@ -36,7 +36,7 @@ function M.new(section, opts) local _self = Util.weak(self) - self.refresh = Util.throttle( + self._refresh = Util.throttle( M.refresh, Util.throttle_opts(opts.throttle.refresh, { ms = 20, @@ -50,7 +50,8 @@ function M.new(section, opts) return self end -function M:refresh() +---@param opts? {update?: boolean} +function M:refresh(opts) -- if self.section.source ~= "lsp.document_symbols" then -- Util.debug("Section Refresh", { -- id = self.id, @@ -58,35 +59,30 @@ function M:refresh() -- }) -- end self.fetching = true - if self.on_refresh then - self:on_refresh() - end - local done = false - local complete = function() - if done then - return - end - done = true - self.fetching = false - end - -- mark as completed after 2 seconds to avoid - -- errors staling the fetching count - vim.defer_fn(complete, 2000) - - self:main_call(function(main) - local ctx = { opts = self.opts, main = main } - self.finder(function(items) - items = Filter.filter(items, self.section.filter, ctx) - if self.filter then - items = Filter.filter(items, self.filter, ctx) - end - items = Sort.sort(items, self.section.sort, ctx) - self.items = items - self.node = Tree.build(items, self.section) - complete() - self:update() - end, ctx) + return Promise.new(function(resolve) + self:main_call(function(main) + local ctx = { opts = self.opts, main = main } + self.finder(function(items) + items = Filter.filter(items, self.section.filter, ctx) + if self.filter then + items = Filter.filter(items, self.filter, ctx) + end + items = Sort.sort(items, self.section.sort, ctx) + self.items = items + self.node = Tree.build(items, self.section) + if not (opts and opts.update == false) then + self:update() + end + resolve(self) + end, ctx) + end) end) + :catch(Util.error) + :timeout(2000) + :catch(function() end) + :finally(function() + self.fetching = false + end) end ---@param fn fun(main: trouble.Main) @@ -119,7 +115,6 @@ function M:main_call(fn) end function M:update() - self.first_update = true if self.on_update then self:on_update() end @@ -166,7 +161,7 @@ function M:listen() if e.event == "BufEnter" and vim.bo[e.buf].buftype ~= "" then return end - this:refresh() + this:_refresh() end, }) end