From 48267c807750610f3f8f7cb1469308f68243f081 Mon Sep 17 00:00:00 2001 From: Folke Lemaitre Date: Sun, 2 Jun 2024 16:30:10 +0200 Subject: [PATCH] perf: properly track library usage per workspace folder --- README.md | 4 +- lua/lazydev/buf.lua | 119 +++++++++++------------------------- lua/lazydev/config.lua | 9 +-- lua/lazydev/lsp.lua | 56 +++++++++++++++++ lua/lazydev/workspace.lua | 125 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 222 insertions(+), 91 deletions(-) create mode 100644 lua/lazydev/lsp.lua create mode 100644 lua/lazydev/workspace.lua diff --git a/README.md b/README.md index 47ae635..4a7410c 100644 --- a/README.md +++ b/README.md @@ -83,12 +83,12 @@ Default settings: { runtime = vim.env.VIMRUNTIME --[[@as string]], library = {}, ---@type string[]|table - ---@type boolean|(fun(root_dir):boolean?) + ---@type boolean|(fun(root:string):boolean?) enabled = function(root_dir) if vim.g.lazydev_enabled ~= nil then return vim.g.lazydev_enabled end - return vim.uv.fs_stat(root_dir .. "/lua") and true or false + return true end, -- add the cmp source for completion of: -- `require "modname"` diff --git a/lua/lazydev/buf.lua b/lua/lazydev/buf.lua index 5f08a61..039603b 100644 --- a/lua/lazydev/buf.lua +++ b/lua/lazydev/buf.lua @@ -1,5 +1,7 @@ local Config = require("lazydev.config") +local Lsp = require("lazydev.lsp") local Pkg = require("lazydev.pkg") +local Workspace = require("lazydev.workspace") local M = {} @@ -9,10 +11,6 @@ M.attached = {} ---@type table M.modules = {} ---- Mapping library name to path ----@type string[] -M.library = {} - function M.setup() M.add(Config.runtime) for _, lib in pairs(Config.library) do @@ -31,10 +29,9 @@ function M.setup() vim.api.nvim_create_autocmd("LspAttach", { group = group, callback = function(ev) - local buffer = ev.buf ---@type number local client = vim.lsp.get_client_by_id(ev.data.client_id) - if client and client.name == "lua_ls" and Config.is_enabled(client) then - M.on_attach(buffer) + if client and client.name == "lua_ls" then + M.on_attach(client, ev.buf) end end, }) @@ -42,7 +39,7 @@ function M.setup() -- Attach to all existing clients for _, client in ipairs(M.get_clients()) do for buf in pairs(client.attached_buffers) do - M.on_attach(buf) + M.on_attach(client, buf) end end @@ -68,20 +65,20 @@ function M.add(path) if not path:find("/lua/?$") and vim.uv.fs_stat(path .. "/lua") then path = path .. "/lua" end - if not vim.tbl_contains(M.library, path) then - table.insert(M.library, path) - end + Workspace:global():add(path) end --- Gets all LuaLS clients that are enabled function M.get_clients() - ---@param client vim.lsp.Client - return vim.tbl_filter(function(client) - return Config.is_enabled(client) - end, vim.lsp.get_clients({ name = "lua_ls" })) + return vim.lsp.get_clients({ name = "lua_ls" }) end -function M.on_attach(buf) +---@param client vim.lsp.Client +function M.on_attach(client, buf) + local root = Workspace.get_root(client, buf) + if not Config.is_enabled(root) then + return + end if M.attached[buf] then return end @@ -112,49 +109,35 @@ function M.on_lines(buf, first, last) for _, line in ipairs(lines) do local module = Pkg.get_module(line) if module then - M.on_require(module) + M.on_require(buf, module) end end end --- Check if a module is available and add it to the library +---@param buf number ---@param modname string -function M.on_require(modname) - local mod = vim.loader.find(modname)[1] - if not mod then - local paths = Pkg.get_unloaded(modname) - mod = vim.loader.find(modname, { rtp = false, paths = paths })[1] +function M.on_require(buf, modname) + local mod = M.modules[modname] + + if mod == nil then + mod = vim.loader.find(modname)[1] + if not mod then + local paths = Pkg.get_unloaded(modname) + mod = vim.loader.find(modname, { rtp = false, paths = paths })[1] + end + M.modules[modname] = mod or false end - M.modules[modname] = mod or false - if mod then local lua = mod.modpath:find("/lua/", 1, true) local path = lua and mod.modpath:sub(1, lua + 3) or mod.modpath - if path and not vim.tbl_contains(M.library, path) then - table.insert(M.library, path) + if path and Workspace.find(buf):add(path) then M.update() end end end ----@param client vim.lsp.Client -function M.set_handlers(client) - client.handlers["workspace/configuration"] = client.handlers["workspace/configuration"] - ---@param params lsp.ConfigurationParams - or function(err, params, ctx, cfg) - ---@type any[] - local ret = vim.deepcopy(vim.lsp.handlers["workspace/configuration"](err, params, ctx, cfg)) - -- Don't set workspace libraries for the fallback scope - for i, item in ipairs(params.items) do - if item.section == "Lua" and not item.scopeUri and type(ret[i]) == "table" and ret[i].workspace then - ret[i].workspace.library = nil - end - end - return ret - end -end - --- Update LuaLS settings with the current library function M.update() if package.loaded["neodev"] then @@ -164,52 +147,18 @@ function M.update() ) end for _, client in ipairs(M.get_clients()) do - -- M.set_handlers(client) - local settings = vim.deepcopy(client.settings or {}) - - ---@type string[] - local library = vim.tbl_get(settings, "Lua", "workspace", "library") or {} - for _, path in ipairs(M.library) do - if not vim.tbl_contains(library, path) then - table.insert(library, path) + local update = false + for _, ws in ipairs(client.workspace_folders) do + local w = Workspace.get(client.id, ws.name) + if Config.is_enabled(w.root) and w:update() then + update = true end end - - settings = vim.tbl_deep_extend("force", settings, { - Lua = { - runtime = { - version = "LuaJIT", - path = { "?.lua", "?/init.lua" }, - pathStrict = true, - }, - workspace = { - checkThirdParty = false, - library = library, - }, - }, - }) - - if not vim.deep_equal(settings, client.settings) then - if Config.debug then - M.debug() - end - client.settings = settings - client.notify("workspace/didChangeConfiguration", { - settings = settings, - }) + if update then + Lsp.attach(client) + Lsp.update(client) end end end -function M.debug() - local Util = require("lazy.core.util") - local Plugin = require("lazy.core.plugin") - local lines = {} - for _, lib in ipairs(M.library) do - local plugin = Plugin.find(lib .. "/") - table.insert(lines, "- " .. (plugin and "**" .. plugin.name .. "** " or "") .. ("`" .. lib .. "`")) - end - Util.info(lines, { title = "lazydev.nvim" }) -end - return M diff --git a/lua/lazydev/config.lua b/lua/lazydev/config.lua index 232af26..2e46a19 100644 --- a/lua/lazydev/config.lua +++ b/lua/lazydev/config.lua @@ -5,12 +5,12 @@ local M = {} local defaults = { runtime = vim.env.VIMRUNTIME --[[@as string]], library = {}, ---@type string[]|table - ---@type boolean|(fun(root_dir):boolean?) + ---@type boolean|(fun(root:string):boolean?) enabled = function(root_dir) if vim.g.lazydev_enabled ~= nil then return vim.g.lazydev_enabled end - return vim.uv.fs_stat(root_dir .. "/lua") and true or false + return true end, debug = false, -- add the cmp source for completion of: @@ -22,11 +22,12 @@ local defaults = { ---@type lazydev.Config local options +---@param root string ---@return boolean -function M.is_enabled(client) +function M.is_enabled(root) local enabled = M.enabled if type(enabled) == "function" then - return enabled(client.root_dir) and true or false + return enabled(root) and true or false end return enabled end diff --git a/lua/lazydev/lsp.lua b/lua/lazydev/lsp.lua new file mode 100644 index 0000000..74c0f00 --- /dev/null +++ b/lua/lazydev/lsp.lua @@ -0,0 +1,56 @@ +local Config = require("lazydev.config") +local Workspace = require("lazydev.workspace") + +local M = {} +M.attached = {} ---@type table + +---@param client vim.lsp.Client +function M.attach(client) + if M.attached[client.id] then + return + end + M.attached[client.id] = client.id + ---@param params lsp.ConfigurationParams + client.handlers["workspace/configuration"] = function(err, params, ctx, cfg) + if not params.items then + return {} + end + + local response = {} + for _, item in ipairs(params.items) do + if item.section then + local settings = client.settings + if item.scopeUri and item.section == "Lua" then + local root = vim.uri_to_fname(item.scopeUri) + if Config.is_enabled(root) then + local ws = Workspace.get(client, root) + settings = ws.settings + if Config.debug then + ws:debug() + end + end + end + + local keys = vim.split(item.section, ".", { plain = true }) --- @type string[] + local value = vim.tbl_get(settings, unpack(keys)) + -- For empty sections with no explicit '' key, return settings as is + if value == nil and item.section == "" then + value = client.settings + end + if value == nil then + value = vim.NIL + end + table.insert(response, value) + end + end + return response + end +end + +function M.update(client) + client.notify("workspace/didChangeConfiguration", { + settings = { Lua = {} }, + }) +end + +return M diff --git a/lua/lazydev/workspace.lua b/lua/lazydev/workspace.lua new file mode 100644 index 0000000..c17c348 --- /dev/null +++ b/lua/lazydev/workspace.lua @@ -0,0 +1,125 @@ +local Config = require("lazydev.config") + +---@class lazydev.Workspace +---@field root string +---@field client_id number +---@field settings table +---@field library string[] +local M = {} +M.__index = M + +---@type table +M.workspaces = {} + +---@param client_id number +---@param root string +function M.new(client_id, root) + local self = setmetatable({ + root = root, + client_id = client_id, + settings = {}, + library = {}, + }, M) + return self +end + +---@param client vim.lsp.Client|number +---@param root string +function M.get(client, root) + local client_id = type(client) == "number" and client or client.id + local id = client_id .. root + if not M.workspaces[id] then + M.workspaces[id] = M.new(client_id, root) + end + return M.workspaces[id] +end + +function M.global() + return M.get(-1, "global") +end + +function M.find(buf) + local client = vim.lsp.get_clients({ + name = "lua_ls", + bufnr = buf, + })[1] + return client and M.get(client.id, M.get_root(client, buf)) +end + +---@param client vim.lsp.Client +---@param buf number +function M.get_root(client, buf) + local uri = vim.uri_from_bufnr(buf) + for _, ws in ipairs(client.workspace_folders or {}) do + if (uri .. "/"):sub(1, #ws.uri + 1) == ws.uri .. "/" then + return ws.name + end + end + return client.root_dir or vim.api.nvim_buf_get_name(buf) +end + +---@param path string +function M:add(path) + if not vim.tbl_contains(self.library, path) then + table.insert(self.library, path) + return true + end +end + +function M:client() + return vim.lsp.get_client_by_id(self.client_id) +end + +function M:update() + local client = self:client() + if not client then + return + end + local settings = vim.deepcopy(client.settings or {}) + + local libs = {} ---@type string[] + vim.list_extend(libs, M.global().library) + vim.list_extend(libs, self.library) + + ---@type string[] + local library = vim.tbl_get(settings, "Lua", "workspace", "library") or {} + for _, path in ipairs(libs) do + if not vim.tbl_contains(library, path) then + table.insert(library, path) + end + end + + settings = vim.tbl_deep_extend("force", settings, { + Lua = { + runtime = { + version = "LuaJIT", + path = { "?.lua", "?/init.lua" }, + pathStrict = true, + }, + workspace = { + checkThirdParty = false, + library = library, + }, + }, + }) + + if not vim.deep_equal(settings, self.settings) then + self.settings = settings + return true + end +end + +function M:debug() + local Util = require("lazy.core.util") + local Plugin = require("lazy.core.plugin") + local lines = { "# " .. self.root } + ---@type string[] + local library = vim.tbl_get(self.settings, "Lua", "workspace", "library") or {} + for _, lib in ipairs(library) do + local plugin = Plugin.find(lib .. "/") + table.insert(lines, "- " .. (plugin and "**" .. plugin.name .. "** " or "") .. ("`" .. lib .. "`")) + end + Util.info(lines, { title = "lazydev.nvim" }) +end + +return M