1

Update generated nvim config

This commit is contained in:
2024-06-05 01:48:19 +02:00
parent fa0d2d57b9
commit aeec0fe400
21 changed files with 1565 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,
})

View File

@ -0,0 +1,8 @@
local M = {}
---@param opts? lazydev.Config
function M.setup(opts)
require("lazydev.config").setup(opts)
end
return M

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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