Update generated nvim config
This commit is contained in:
@ -0,0 +1,175 @@
|
||||
local Config = require("lazydev.config")
|
||||
local Lsp = require("lazydev.lsp")
|
||||
local Pkg = require("lazydev.pkg")
|
||||
local Util = require("lazydev.util")
|
||||
local Workspace = require("lazydev.workspace")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<number,number>
|
||||
M.attached = {}
|
||||
|
||||
---@type table<string, vim.loader.ModuleInfo|false>
|
||||
M.modules = {}
|
||||
|
||||
function M.setup()
|
||||
for _, lib in ipairs(Config.libs) do
|
||||
if #lib.words == 0 and #lib.mods == 0 then
|
||||
Workspace.global():add(lib.path)
|
||||
end
|
||||
end
|
||||
|
||||
-- debounce updates
|
||||
local update = vim.schedule_wrap(M.update)
|
||||
local timer = assert(vim.uv.new_timer())
|
||||
M.update = function()
|
||||
timer:start(100, 0, update)
|
||||
end
|
||||
|
||||
local group = vim.api.nvim_create_augroup("lazydev", { clear = true })
|
||||
|
||||
vim.api.nvim_create_autocmd("LspAttach", {
|
||||
group = group,
|
||||
callback = function(ev)
|
||||
local client = vim.lsp.get_client_by_id(ev.data.client_id)
|
||||
if client and client.name == "lua_ls" then
|
||||
M.on_attach(client, ev.buf)
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
-- Attach to all existing clients
|
||||
for _, client in ipairs(M.get_clients()) do
|
||||
for buf in pairs(client.attached_buffers) do
|
||||
M.on_attach(client, buf)
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for library changes
|
||||
M.update()
|
||||
end
|
||||
|
||||
--- Gets all LuaLS clients that are enabled
|
||||
function M.get_clients()
|
||||
return Util.get_clients({ name = "lua_ls" })
|
||||
end
|
||||
|
||||
--- Attach to the buffer
|
||||
---@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
|
||||
M.attached[buf] = buf
|
||||
-- Attach to buffer events
|
||||
vim.api.nvim_buf_attach(buf, false, {
|
||||
on_lines = function(_, b, _, first, _, last)
|
||||
M.on_lines(b, first, last)
|
||||
end,
|
||||
on_detach = function()
|
||||
M.attached[buf] = nil
|
||||
end,
|
||||
on_reload = function()
|
||||
M.on_lines(buf, 0, vim.api.nvim_buf_line_count(buf))
|
||||
end,
|
||||
})
|
||||
-- Trigger initial scan
|
||||
M.on_lines(buf, 0, vim.api.nvim_buf_line_count(buf))
|
||||
M.update()
|
||||
end
|
||||
|
||||
--- Triggered when lines are changed
|
||||
---@param buf number
|
||||
---@param first number
|
||||
---@param last number
|
||||
function M.on_lines(buf, first, last)
|
||||
local lines = vim.api.nvim_buf_get_lines(buf, first, last, false)
|
||||
for _, line in ipairs(lines) do
|
||||
M.on_line(buf, line)
|
||||
end
|
||||
end
|
||||
|
||||
---@param buf number
|
||||
---@param line string
|
||||
function M.on_line(buf, line)
|
||||
-- Check for words
|
||||
for word, paths in pairs(Config.words) do
|
||||
if line:find(word) then
|
||||
Workspace.find(buf):add(paths)
|
||||
end
|
||||
end
|
||||
-- Check for modules
|
||||
local module = Pkg.get_module(line)
|
||||
if module then
|
||||
M.on_mod(buf, module)
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if a module is available and add it to the library
|
||||
---@param buf number
|
||||
---@param modname string
|
||||
function M.on_mod(buf, modname)
|
||||
-- Check for configured modules
|
||||
if Config.mods[modname] then
|
||||
Workspace.find(buf):add(Config.mods[modname])
|
||||
end
|
||||
|
||||
-- Check for modules in Neovim plugins
|
||||
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
|
||||
|
||||
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 then
|
||||
Workspace.find(buf):add(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Update LuaLS settings with the current library
|
||||
function M.update()
|
||||
if package.loaded["neodev"] then
|
||||
vim.notify_once(
|
||||
"Please disable `neodev.nvim` in your config.\nThis is no longer needed when you use `lazydev.nvim`",
|
||||
vim.log.levels.WARN
|
||||
)
|
||||
end
|
||||
for _, client in ipairs(M.get_clients()) do
|
||||
local update = false
|
||||
|
||||
---@param ws lsp.WorkspaceFolder
|
||||
local folders = vim.tbl_map(function(ws)
|
||||
return Workspace.get(client.id, ws.name)
|
||||
end, client.workspace_folders or {})
|
||||
|
||||
if #folders == 0 then
|
||||
folders = { Workspace.single(client) }
|
||||
end
|
||||
|
||||
for _, w in ipairs(folders) do
|
||||
if w:update() then
|
||||
update = true
|
||||
end
|
||||
end
|
||||
|
||||
if update then
|
||||
Lsp.attach(client)
|
||||
Lsp.update(client)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,62 @@
|
||||
local Util = require("lazydev.util")
|
||||
local Workspace = require("lazydev.workspace")
|
||||
|
||||
local M = {}
|
||||
|
||||
---@type table<string, fun(args: string[])>
|
||||
M.commands = {
|
||||
debug = function()
|
||||
local buf = vim.api.nvim_get_current_buf()
|
||||
local ws = Workspace.find(buf)
|
||||
if not ws then
|
||||
return Util.warn("No **LuaLS** workspace found.\nUse `:LazyDev lsp` to see settings of attached LSP clients.")
|
||||
end
|
||||
ws:debug({ details = true })
|
||||
end,
|
||||
lsp = function()
|
||||
local clients = Util.get_clients({ bufnr = 0 })
|
||||
local lines = {} ---@type string[]
|
||||
for _, client in ipairs(clients) do
|
||||
lines[#lines + 1] = "## " .. client.name
|
||||
lines[#lines + 1] = "```lua"
|
||||
lines[#lines + 1] = "settings = " .. vim.inspect(client.settings)
|
||||
lines[#lines + 1] = "```"
|
||||
end
|
||||
Util.info(lines)
|
||||
end,
|
||||
}
|
||||
|
||||
function M.execute(input)
|
||||
local prefix, args = M.parse(input.args)
|
||||
prefix = prefix and prefix ~= "" and prefix or "debug"
|
||||
if not M.commands[prefix or ""] then
|
||||
return Util.error("Invalid command")
|
||||
end
|
||||
M.commands[prefix](args)
|
||||
end
|
||||
|
||||
function M.complete(_, line)
|
||||
local prefix, args = M.parse(line)
|
||||
if #args > 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
---@param key string
|
||||
return vim.tbl_filter(function(key)
|
||||
return key:find(prefix, 1, true) == 1
|
||||
end, vim.tbl_keys(M.commands))
|
||||
end
|
||||
|
||||
---@return string, string[]
|
||||
function M.parse(args)
|
||||
local parts = vim.split(vim.trim(args), "%s+")
|
||||
if parts[1]:find("LazyDev") then
|
||||
table.remove(parts, 1)
|
||||
end
|
||||
if args:sub(-1) == " " then
|
||||
parts[#parts + 1] = ""
|
||||
end
|
||||
return table.remove(parts, 1) or "", parts
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,73 @@
|
||||
local Buf = require("lazydev.buf")
|
||||
local Config = require("lazydev.config")
|
||||
local Pkg = require("lazydev.pkg")
|
||||
|
||||
local Source = {}
|
||||
|
||||
function Source:get_trigger_characters()
|
||||
return { '"', "'", ".", "/" }
|
||||
end
|
||||
|
||||
---@param params cmp.SourceCompletionApiParams
|
||||
---@param callback fun(items: lsp.CompletionItem[])
|
||||
function Source:complete(params, callback)
|
||||
local cmp = require("cmp")
|
||||
local before = params.context.cursor_before_line
|
||||
---@type string?
|
||||
local req, forward_slash = Pkg.get_module(before, { before = true })
|
||||
if not req then
|
||||
return callback({})
|
||||
end
|
||||
local items = {} ---@type table<string,lsp.CompletionItem>
|
||||
|
||||
---@param modname string
|
||||
---@param modpath string
|
||||
local function add(modname, modpath)
|
||||
items[modname] = items[modname]
|
||||
or {
|
||||
label = forward_slash and modname:gsub("%.", "/") or modname,
|
||||
kind = cmp.lsp.CompletionItemKind.Module,
|
||||
}
|
||||
local item = items[modname]
|
||||
|
||||
local plugin = Pkg.get_plugin_name(modpath)
|
||||
if plugin then
|
||||
if item.documentation then
|
||||
item.documentation.value = item.documentation.value .. "\n- `" .. plugin .. "`"
|
||||
else
|
||||
item.documentation = {
|
||||
kind = cmp.lsp.MarkupKind.Markdown,
|
||||
value = "# Plugins:\n" .. "- `" .. plugin .. "`",
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not req:find(".", 1, true) then
|
||||
Pkg.topmods(add)
|
||||
for _, lib in ipairs(Config.libs) do
|
||||
for _, mod in ipairs(lib.mods) do
|
||||
add(mod, lib.path)
|
||||
end
|
||||
end
|
||||
else
|
||||
Pkg.lsmod(req:gsub("%.[^%.]*$", ""), add)
|
||||
end
|
||||
|
||||
callback(vim.tbl_values(items))
|
||||
end
|
||||
|
||||
function Source:is_available()
|
||||
return Buf.attached[vim.api.nvim_get_current_buf()]
|
||||
end
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.setup()
|
||||
local ok, cmp = pcall(require, "cmp")
|
||||
if ok then
|
||||
cmp.register_source("lazydev", Source)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,99 @@
|
||||
---@class lazydev.Config.mod: lazydev.Config
|
||||
local M = {}
|
||||
|
||||
---@alias lazydev.Library {path:string, words:string[], mods:string[]}
|
||||
---@alias lazydev.Library.spec string|{path:string, words?:string[], mods?:string[]}
|
||||
---@class lazydev.Config
|
||||
local defaults = {
|
||||
runtime = vim.env.VIMRUNTIME --[[@as string]],
|
||||
library = {}, ---@type lazydev.Library.spec[]
|
||||
---@type boolean|(fun(root:string):boolean?)
|
||||
enabled = function(root_dir)
|
||||
return vim.g.lazydev_enabled == nil and true or vim.g.lazydev_enabled
|
||||
end,
|
||||
-- add the cmp source for completion of:
|
||||
-- `require "modname"`
|
||||
-- `---@module "modname"`
|
||||
cmp = true,
|
||||
debug = false,
|
||||
}
|
||||
|
||||
M.libs = {} ---@type lazydev.Library[]
|
||||
M.words = {} ---@type table<string, string[]>
|
||||
M.mods = {} ---@type table<string, string[]>
|
||||
|
||||
---@type lazydev.Config
|
||||
local options
|
||||
|
||||
---@param root string
|
||||
---@return boolean
|
||||
function M.is_enabled(root)
|
||||
local enabled = M.enabled
|
||||
if type(enabled) == "function" then
|
||||
return enabled(root) and true or false
|
||||
end
|
||||
return enabled
|
||||
end
|
||||
|
||||
M.have_0_10 = vim.fn.has("nvim-0.10") == 1
|
||||
|
||||
---@param opts? lazydev.Config
|
||||
function M.setup(opts)
|
||||
if not M.have_0_10 then
|
||||
local msg = "lazydev.nvim requires Neovim >= 0.10"
|
||||
vim.notify_once(msg, vim.log.levels.ERROR, { title = "lazydev.nvim" })
|
||||
error(msg)
|
||||
return
|
||||
end
|
||||
|
||||
options = vim.tbl_deep_extend("force", {}, options or defaults, opts or {})
|
||||
|
||||
M.libs, M.words, M.mods = {}, {}, {}
|
||||
local runtime = require("lazydev.util").norm(options.runtime)
|
||||
table.insert(M.libs, {
|
||||
path = vim.uv.fs_stat(runtime) and runtime or vim.env.VIMRUNTIME,
|
||||
words = {},
|
||||
mods = {},
|
||||
})
|
||||
for _, lib in pairs(M.library) do
|
||||
table.insert(M.libs, {
|
||||
path = type(lib) == "table" and lib.path or lib,
|
||||
words = type(lib) == "table" and lib.words or {},
|
||||
mods = type(lib) == "table" and lib.mods or {},
|
||||
})
|
||||
end
|
||||
|
||||
for _, lib in ipairs(M.libs) do
|
||||
for _, word in ipairs(lib.words) do
|
||||
M.words[word] = M.words[word] or {}
|
||||
table.insert(M.words[word], lib.path)
|
||||
end
|
||||
for _, mod in ipairs(lib.mods) do
|
||||
M.mods[mod] = M.mods[mod] or {}
|
||||
table.insert(M.mods[mod], lib.path)
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_create_user_command("LazyDev", function(...)
|
||||
require("lazydev.cmd").execute(...)
|
||||
end, {
|
||||
nargs = "*",
|
||||
complete = function(...)
|
||||
return require("lazydev.cmd").complete(...)
|
||||
end,
|
||||
desc = "lazydev.nvim",
|
||||
})
|
||||
|
||||
vim.schedule(function()
|
||||
require("lazydev.buf").setup()
|
||||
require("lazydev.cmp").setup()
|
||||
end)
|
||||
return options
|
||||
end
|
||||
|
||||
return setmetatable(M, {
|
||||
__index = function(_, key)
|
||||
options = options or M.setup()
|
||||
return options[key]
|
||||
end,
|
||||
})
|
||||
@ -0,0 +1,8 @@
|
||||
local M = {}
|
||||
|
||||
---@param opts? lazydev.Config
|
||||
function M.setup(opts)
|
||||
require("lazydev.config").setup(opts)
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,58 @@
|
||||
local Workspace = require("lazydev.workspace")
|
||||
|
||||
local M = {}
|
||||
M.attached = {} ---@type table<number,number>
|
||||
|
||||
---@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 or #params.items == 0 then
|
||||
return {}
|
||||
end
|
||||
|
||||
-- fallback scope
|
||||
if #(client.workspace_folders or {}) > 0 and not params.items[1].scopeUri then
|
||||
return {}
|
||||
end
|
||||
|
||||
local response = {}
|
||||
for _, item in ipairs(params.items) do
|
||||
if item.section then
|
||||
local settings = client.settings
|
||||
if item.section == "Lua" then
|
||||
local ws = item.scopeUri and Workspace.get(client, vim.uri_to_fname(item.scopeUri))
|
||||
or Workspace.single(client)
|
||||
if ws:enabled() then
|
||||
settings = ws.settings
|
||||
end
|
||||
end
|
||||
|
||||
local keys = vim.split(item.section, ".", { plain = true }) --- @type string[]
|
||||
local value = vim.tbl_get(settings or {}, unpack(keys))
|
||||
-- For empty sections with no explicit '' key, return settings as is
|
||||
if value == nil and item.section == "" then
|
||||
value = settings
|
||||
end
|
||||
if value == nil then
|
||||
value = vim.NIL
|
||||
end
|
||||
table.insert(response, value)
|
||||
end
|
||||
end
|
||||
return response
|
||||
end
|
||||
end
|
||||
|
||||
---@param client vim.lsp.Client
|
||||
function M.update(client)
|
||||
client.notify("workspace/didChangeConfiguration", {
|
||||
settings = { Lua = {} },
|
||||
})
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,156 @@
|
||||
---@class lazydev.Pkg
|
||||
local M = {}
|
||||
|
||||
M.PAT_MODULE_BASE = "%-%-%-%s*@module%s*[\"']([%w%.%-_/]+)"
|
||||
M.PAT_REQUIRE_BASE = "require%s*%(?%s*['\"]([%w%.%-_/]+)"
|
||||
M.PAT_MODULE_BEFORE = M.PAT_MODULE_BASE .. "$"
|
||||
M.PAT_REQUIRE_BEFORE = M.PAT_REQUIRE_BASE .. "$"
|
||||
M.PAT_MODULE = M.PAT_MODULE_BASE .. "[\"']"
|
||||
M.PAT_REQUIRE = M.PAT_REQUIRE_BASE .. "[\"']"
|
||||
|
||||
local is_lazy = type(package.loaded.lazy) == "table"
|
||||
|
||||
---@param modname string
|
||||
---@return string[]
|
||||
function M.lazy_unloaded(modname)
|
||||
local Util = require("lazy.core.util")
|
||||
return Util.get_unloaded_rtp(modname)
|
||||
end
|
||||
|
||||
---@type string[]
|
||||
local packs = nil
|
||||
|
||||
function M.pack_unloaded()
|
||||
if packs then
|
||||
return packs
|
||||
end
|
||||
|
||||
local sites = vim.opt.packpath:get() ---@type string[]
|
||||
local default_site = vim.fn.stdpath("data") .. "/site"
|
||||
if not vim.tbl_contains(sites, default_site) then
|
||||
sites[#sites + 1] = default_site
|
||||
end
|
||||
local Util = require("lazydev.util")
|
||||
packs = {} ---@type string[]
|
||||
for _, site in pairs(sites) do
|
||||
for _, pack in ipairs(vim.fn.expand(site .. "/pack/*/opt/*", false, true)) do
|
||||
if not pack:find("*", 1, true) then
|
||||
packs[#packs + 1] = Util.norm(pack)
|
||||
end
|
||||
end
|
||||
end
|
||||
return packs
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
---@return string[]
|
||||
function M.get_unloaded(modname)
|
||||
return is_lazy and M.lazy_unloaded(modname) or M.pack_unloaded()
|
||||
end
|
||||
|
||||
function M.get_plugin_path(name)
|
||||
if is_lazy then
|
||||
local Config = require("lazy.core.config")
|
||||
local plugin = Config.spec.plugins[name]
|
||||
return plugin and plugin.dir
|
||||
else
|
||||
for _, dir in ipairs(vim.opt.rtp:get()) do
|
||||
local basename = vim.fs.basename(dir)
|
||||
if basename == name then
|
||||
return dir
|
||||
end
|
||||
end
|
||||
for _, dir in ipairs(M.pack_unloaded()) do
|
||||
local basename = vim.fs.basename(dir)
|
||||
if basename == name then
|
||||
return dir
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
---@return string[]
|
||||
function M.find_roots(modname)
|
||||
local ret = vim.loader.find(modname, {
|
||||
rtp = true,
|
||||
paths = M.get_unloaded(modname),
|
||||
patterns = { "", ".lua" },
|
||||
all = true,
|
||||
})
|
||||
return vim.tbl_map(
|
||||
---@param mod vim.loader.ModuleInfo
|
||||
function(mod)
|
||||
local path = mod.modpath:gsub("/init%.lua$", ""):gsub("%.lua$", "")
|
||||
return path
|
||||
end,
|
||||
ret
|
||||
)
|
||||
end
|
||||
|
||||
---@param fn fun(modname:string, modpath:string)
|
||||
function M.topmods(fn)
|
||||
local ret = vim.loader.find("*", {
|
||||
all = true,
|
||||
rtp = true,
|
||||
paths = M.get_unloaded(""),
|
||||
})
|
||||
for _, mod in ipairs(ret) do
|
||||
fn(mod.modname, mod.modpath)
|
||||
end
|
||||
end
|
||||
|
||||
--- Get the module name from a line,
|
||||
--- either `---@module "modname"` or `require "modname"`
|
||||
---@param line string
|
||||
---@param opts? {before?:boolean}
|
||||
---@return string?, boolean? forward_slash
|
||||
function M.get_module(line, opts)
|
||||
local patterns = opts and opts.before and {
|
||||
M.PAT_MODULE_BEFORE,
|
||||
M.PAT_REQUIRE_BEFORE,
|
||||
} or {
|
||||
M.PAT_MODULE,
|
||||
M.PAT_REQUIRE,
|
||||
}
|
||||
for _, pat in ipairs(patterns) do
|
||||
local match = line:match(pat)
|
||||
if match then
|
||||
return match:gsub("/", "."), match:find("/", 1, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param path string
|
||||
---@return string?
|
||||
function M.get_plugin_name(path)
|
||||
local lua = path:find("/lua/", 1, true)
|
||||
if lua then
|
||||
local name = path:sub(1, lua - 1)
|
||||
local slash = name:reverse():find("/", 1, true)
|
||||
if slash then
|
||||
name = name:sub(#name - slash + 2)
|
||||
return name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
---@param modname string
|
||||
---@param fn fun(modname:string, modpath:string)
|
||||
function M.lsmod(modname, fn)
|
||||
local roots = M.find_roots(modname)
|
||||
for _, root in ipairs(roots) do
|
||||
for name, type in vim.fs.dir(root) do
|
||||
local path = vim.fs.joinpath(root, name)
|
||||
if name == "init.lua" then
|
||||
fn(modname, path)
|
||||
elseif (type == "file" or type == "link") and name:sub(-4) == ".lua" then
|
||||
fn(modname .. "." .. name:sub(1, -5), path)
|
||||
elseif type == "directory" and vim.uv.fs_stat(path .. "/init.lua") then
|
||||
fn(modname .. "." .. name, path .. "/init.lua")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,59 @@
|
||||
local M = {}
|
||||
|
||||
---@alias NotifyOpts {level?: number, title?: string, once?: boolean}
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.notify(msg, opts)
|
||||
opts = opts or {}
|
||||
msg = type(msg) == "table" and table.concat(msg, "\n") or msg
|
||||
---@cast msg string
|
||||
msg = vim.trim(msg)
|
||||
return vim[opts.once and "notify_once" or "notify"](msg, opts.level, {
|
||||
title = opts.title or "lazydev.nvim",
|
||||
on_open = function(win)
|
||||
vim.wo.conceallevel = 3
|
||||
vim.wo.concealcursor = "n"
|
||||
vim.wo.spell = false
|
||||
vim.treesitter.start(vim.api.nvim_win_get_buf(win), "markdown")
|
||||
end,
|
||||
})
|
||||
end
|
||||
|
||||
M.get_clients = vim.lsp.get_clients or vim.lsp.get_active_clients
|
||||
|
||||
---@param path string
|
||||
function M.is_absolute(path)
|
||||
return path:sub(1, 1) == "/" or path:sub(2, 2) == ":"
|
||||
end
|
||||
|
||||
---@param path string
|
||||
function M.norm(path)
|
||||
path = vim.fs.normalize(path)
|
||||
-- Special case for Windows drive letters
|
||||
-- vim.fs.normalize doesn't handle them correctly
|
||||
if path:sub(2, 2) == ":" then
|
||||
path = path:sub(1, 1):lower() .. path:sub(2)
|
||||
end
|
||||
return path
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.warn(msg, opts)
|
||||
M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.WARN }, opts or {}))
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.error(msg, opts)
|
||||
M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.ERROR }, opts or {}))
|
||||
end
|
||||
|
||||
---@param msg string|string[]
|
||||
---@param opts? NotifyOpts
|
||||
function M.info(msg, opts)
|
||||
M.notify(msg, vim.tbl_extend("keep", { level = vim.log.levels.INFO }, opts or {}))
|
||||
end
|
||||
|
||||
return M
|
||||
@ -0,0 +1,188 @@
|
||||
local Config = require("lazydev.config")
|
||||
local Pkg = require("lazydev.pkg")
|
||||
local Util = require("lazydev.util")
|
||||
|
||||
---@class lazydev.Workspace
|
||||
---@field root string
|
||||
---@field client_id number
|
||||
---@field settings table
|
||||
---@field library string[]
|
||||
local M = {}
|
||||
M.__index = M
|
||||
M.SINGLE = "single"
|
||||
M.GLOBAL = "global"
|
||||
|
||||
---@type table<string,lazydev.Workspace>
|
||||
M.workspaces = {}
|
||||
|
||||
function M.is_special(root)
|
||||
return root == M.SINGLE or root == M.GLOBAL
|
||||
end
|
||||
|
||||
---@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)
|
||||
if not M.is_special(root) then
|
||||
self:add(root)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
---@param client vim.lsp.Client|number
|
||||
---@param root string
|
||||
function M.get(client, root)
|
||||
root = M.is_special(root) and root or Util.norm(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, M.GLOBAL)
|
||||
end
|
||||
|
||||
---@param client vim.lsp.Client
|
||||
function M.single(client)
|
||||
return M.get(client, M.SINGLE)
|
||||
end
|
||||
|
||||
function M.find(buf)
|
||||
local client = Util.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 "single"
|
||||
end
|
||||
|
||||
---@param path string|string[]
|
||||
function M:add(path)
|
||||
if type(path) == "table" then
|
||||
for _, p in ipairs(path) do
|
||||
self:add(p)
|
||||
end
|
||||
return
|
||||
end
|
||||
---@cast path string
|
||||
-- ignore special workspaces
|
||||
if M.is_special(path) then
|
||||
return
|
||||
end
|
||||
-- normalize
|
||||
path = Util.norm(path)
|
||||
-- try to resolve to a plugin path
|
||||
if not Util.is_absolute(path) and not vim.uv.fs_stat(path) then
|
||||
local name, extra = path:match("([^/]+)(/?.*)")
|
||||
if name then
|
||||
local pp = Pkg.get_plugin_path(name)
|
||||
path = pp and (pp .. extra) or path
|
||||
end
|
||||
end
|
||||
path = vim.uv.fs_realpath(path) or path
|
||||
path = Util.norm(path) -- normalize again
|
||||
-- append /lua if it exists
|
||||
if not path:find("/lua/?$") and vim.uv.fs_stat(path .. "/lua") then
|
||||
path = path .. "/lua"
|
||||
end
|
||||
if path ~= self.root and not vim.tbl_contains(self.library, path) then
|
||||
table.insert(self.library, path)
|
||||
if self.root ~= M.GLOBAL then
|
||||
require("lazydev.buf").update()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:client()
|
||||
return vim.lsp.get_client_by_id(self.client_id)
|
||||
end
|
||||
|
||||
function M:enabled()
|
||||
return self.root == M.GLOBAL or Config.is_enabled(self.root)
|
||||
end
|
||||
|
||||
function M:update()
|
||||
local client = self:client()
|
||||
if not client then
|
||||
return
|
||||
end
|
||||
if not self:enabled() 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
|
||||
if Config.debug then
|
||||
self:debug()
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
---@param opts? {details: boolean}
|
||||
function M:debug(opts)
|
||||
opts = opts or {}
|
||||
local root = M.is_special(self.root) and "[" .. self.root .. "]" or vim.fn.fnamemodify(self.root, ":~")
|
||||
local lines = { "## " .. root }
|
||||
---@type string[]
|
||||
local library = vim.tbl_get(self.settings, "Lua", "workspace", "library") or {}
|
||||
for _, lib in ipairs(library) do
|
||||
lib = vim.fn.fnamemodify(lib, ":~")
|
||||
local plugin = Pkg.get_plugin_name(lib .. "/")
|
||||
table.insert(lines, "- " .. (plugin and "**" .. plugin .. "** " or "") .. ("`" .. lib .. "`"))
|
||||
end
|
||||
if opts.details then
|
||||
lines[#lines + 1] = "```lua"
|
||||
lines[#lines + 1] = "settings = " .. vim.inspect(self.settings)
|
||||
lines[#lines + 1] = "```"
|
||||
end
|
||||
Util.info(lines)
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user