mirror of
https://github.com/folke/lazy.nvim.git
synced 2025-06-28 11:24:14 +00:00
I'm playing with https://github.com/folke/lazy.nvim/issues/978 So here's the thing. This is working for me, but I'm not 100% sure of why it's working. It's not hitting *most* of the code I touched since I didn't modify anything to touch that code. It is my suspicion that what I've done is make lazy.nvim more flexible at the cost of breaking LazyVim, which I don't use. So now I think the best course of action is to pull LazyVim and configure it to use this alternate loaders code to see how it falls over
440 lines
11 KiB
Lua
440 lines
11 KiB
Lua
---@class LazyUtilCore
|
|
local M = {}
|
|
|
|
---@alias LazyProfile {data: string|{[string]:string}, time: number, [number]:LazyProfile}
|
|
|
|
---@type LazyProfile[]
|
|
M._profiles = { { name = "lazy" } }
|
|
|
|
---@param data (string|{[string]:string})?
|
|
---@param time number?
|
|
function M.track(data, time)
|
|
if data then
|
|
local entry = {
|
|
data = data,
|
|
time = time or vim.loop.hrtime(),
|
|
}
|
|
table.insert(M._profiles[#M._profiles], entry)
|
|
|
|
if not time then
|
|
table.insert(M._profiles, entry)
|
|
end
|
|
return entry
|
|
else
|
|
---@type LazyProfile
|
|
local entry = table.remove(M._profiles)
|
|
entry.time = vim.loop.hrtime() - entry.time
|
|
return entry
|
|
end
|
|
end
|
|
|
|
---@generic F: fun()
|
|
---@param data (string|{[string]:string})?
|
|
---@param fn F
|
|
---@return F
|
|
function M.trackfn(data, fn)
|
|
return function(...)
|
|
M.track(data)
|
|
local ok, ret = pcall(fn, ...)
|
|
M.track()
|
|
if not ok then
|
|
error(ret)
|
|
end
|
|
return ret
|
|
end
|
|
end
|
|
|
|
---@param name string
|
|
---@return string
|
|
function M.normname(name)
|
|
local ret = name:lower():gsub("^n?vim%-", ""):gsub("%.n?vim$", ""):gsub("%.lua", ""):gsub("[^a-z]+", "")
|
|
return ret
|
|
end
|
|
|
|
---@return string
|
|
function M.norm(path)
|
|
if path:sub(1, 1) == "~" then
|
|
local home = vim.loop.os_homedir()
|
|
if home:sub(-1) == "\\" or home:sub(-1) == "/" then
|
|
home = home:sub(1, -2)
|
|
end
|
|
path = home .. path:sub(2)
|
|
end
|
|
path = path:gsub("\\", "/"):gsub("/+", "/")
|
|
return path:sub(-1) == "/" and path:sub(1, -2) or path
|
|
end
|
|
|
|
---@param opts? string|{msg:string, on_error:fun(msg)}
|
|
function M.try(fn, opts)
|
|
opts = type(opts) == "string" and { msg = opts } or opts or {}
|
|
local msg = opts.msg
|
|
-- error handler
|
|
local error_handler = function(err)
|
|
local Config = require("lazy.core.config")
|
|
local trace = {}
|
|
local level = 1
|
|
while true do
|
|
local info = debug.getinfo(level, "Sln")
|
|
if not info then
|
|
break
|
|
end
|
|
if info.what ~= "C" and not info.source:find("lazy.nvim") then
|
|
local source = info.source:sub(2)
|
|
if source:find(Config.options.root, 1, true) == 1 then
|
|
source = source:sub(#Config.options.root + 1)
|
|
end
|
|
source = vim.fn.fnamemodify(source, ":p:~:.")
|
|
local line = " - " .. source .. ":" .. info.currentline
|
|
if info.name then
|
|
line = line .. " _in_ **" .. info.name .. "**"
|
|
end
|
|
table.insert(trace, line)
|
|
end
|
|
level = level + 1
|
|
end
|
|
msg = (msg and (msg .. "\n\n") or "") .. err
|
|
if #trace > 0 then
|
|
msg = msg .. "\n\n# stacktrace:\n" .. table.concat(trace, "\n")
|
|
end
|
|
if opts.on_error then
|
|
opts.on_error(msg)
|
|
else
|
|
vim.schedule(function()
|
|
M.error(msg)
|
|
end)
|
|
end
|
|
return err
|
|
end
|
|
|
|
---@type boolean, any
|
|
local ok, result = xpcall(fn, error_handler)
|
|
return ok and result or nil
|
|
end
|
|
|
|
function M.get_source()
|
|
local f = 2
|
|
while true do
|
|
local info = debug.getinfo(f, "S")
|
|
if not info then
|
|
break
|
|
end
|
|
if info.what ~= "C" and not info.source:find("lazy.nvim", 1, true) then
|
|
return info.source:sub(2)
|
|
end
|
|
f = f + 1
|
|
end
|
|
end
|
|
|
|
-- Fast implementation to check if a table is a list
|
|
---@param t table
|
|
function M.is_list(t)
|
|
local i = 0
|
|
---@diagnostic disable-next-line: no-unknown
|
|
for _ in pairs(t) do
|
|
i = i + 1
|
|
if t[i] == nil then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function M.very_lazy()
|
|
local function _load()
|
|
vim.schedule(function()
|
|
if vim.v.exiting ~= vim.NIL then
|
|
return
|
|
end
|
|
vim.g.did_very_lazy = true
|
|
vim.api.nvim_exec_autocmds("User", { pattern = "VeryLazy", modeline = false })
|
|
end)
|
|
end
|
|
|
|
vim.api.nvim_create_autocmd("User", {
|
|
pattern = "LazyDone",
|
|
once = true,
|
|
callback = function()
|
|
if vim.v.vim_did_enter == 1 then
|
|
_load()
|
|
else
|
|
vim.api.nvim_create_autocmd("UIEnter", {
|
|
once = true,
|
|
callback = function()
|
|
_load()
|
|
end,
|
|
})
|
|
end
|
|
end,
|
|
})
|
|
end
|
|
|
|
---@alias FileType "file"|"directory"|"link"
|
|
---@param path string
|
|
---@param fn fun(path: string, name:string, type:FileType):boolean?
|
|
function M.ls(path, fn)
|
|
local handle = vim.loop.fs_scandir(path)
|
|
while handle do
|
|
local name, t = vim.loop.fs_scandir_next(handle)
|
|
if not name then
|
|
break
|
|
end
|
|
|
|
local fname = path .. "/" .. name
|
|
|
|
-- HACK: type is not always returned due to a bug in luv,
|
|
-- so fecth it with fs_stat instead when needed.
|
|
-- see https://github.com/folke/lazy.nvim/issues/306
|
|
if fn(fname, name, t or vim.loop.fs_stat(fname).type) == false then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
---@param path string
|
|
---@param fn fun(path: string, name:string, type:FileType)
|
|
function M.walk(path, fn)
|
|
M.ls(path, function(child, name, type)
|
|
if type == "directory" then
|
|
M.walk(child, fn)
|
|
end
|
|
fn(child, name, type)
|
|
end)
|
|
end
|
|
|
|
---@param delim string
|
|
function string:split(delim)
|
|
-- This default doesn't fully make sense, but I'm just replicating some python functionality
|
|
delim = delim or '%.'
|
|
if self:sub(-#delim ~= delim) then
|
|
self = self .. delim
|
|
end
|
|
|
|
return self:gmatch('(.-)' .. delim:gsub("[()%%.[^$%]*+%-?]", "%%%1"))
|
|
end
|
|
|
|
---@param value any
|
|
function table:contains(value)
|
|
for _, element in ipairs(self) do
|
|
if element == value then
|
|
return true
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
---@param root string
|
|
---@param fn fun(modname:string, modpath:string)
|
|
-- @param optionals? {modname:str, extensions:str[]}
|
|
function M.walkmods(root, fn, optionals)
|
|
optionals.modname = optionals.modname and (optionals.modname:gsub("%.$", "") .. ".") or ""
|
|
if not optionals.extensions:contains("lua") then
|
|
table.insert(optionals.extensions, "lua")
|
|
end
|
|
|
|
M.ls(root, function(path, name, type)
|
|
local name_components = name.split("%.")
|
|
local extension = name_components[#name_components]
|
|
if name == "init.lua" then
|
|
fn(optionals.modname:gsub("%.$", ""), path)
|
|
elseif (type == "file" or type == "link") and (optionals.extensions:contains(extension)) then
|
|
table.remove(name_components[#name_components])
|
|
fn(optionals.modname .. table.concat(name_components, ".", path))
|
|
elseif type == "directory" then
|
|
M.walkmods(path, fn, {
|
|
modname = optionals.modname .. name .. ".", extensions = optionals.extensions
|
|
})
|
|
end
|
|
end)
|
|
end
|
|
|
|
---@param modname string
|
|
function M.get_unloaded_rtp(modname)
|
|
modname = modname:gsub("/", ".")
|
|
local idx = modname:find(".", 1, true)
|
|
local topmod = idx and modname:sub(1, idx - 1) or modname
|
|
topmod = M.normname(topmod)
|
|
|
|
local rtp = {}
|
|
local Config = require("lazy.core.config")
|
|
if Config.spec then
|
|
for _, plugin in pairs(Config.spec.plugins) do
|
|
if not (plugin._.loaded or plugin.module == false) then
|
|
if topmod == M.normname(plugin.name) then
|
|
table.insert(rtp, 1, plugin.dir)
|
|
else
|
|
table.insert(rtp, plugin.dir)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return rtp
|
|
end
|
|
|
|
function M.find_root(modname)
|
|
local ret = require("lazy.core.cache").find(modname, {
|
|
rtp = true,
|
|
paths = M.get_unloaded_rtp(modname),
|
|
patterns = { "", ".lua" },
|
|
})[1]
|
|
if ret then
|
|
local root = ret.modpath:gsub("/init%.lua$", ""):gsub("%.lua$", "")
|
|
return root
|
|
end
|
|
end
|
|
|
|
---@param modname string
|
|
---@param fn fun(modname:string, modpath:string)
|
|
function M.lsmod(modname, fn)
|
|
local root = M.find_root(modname)
|
|
if not root then
|
|
return
|
|
end
|
|
|
|
if vim.loop.fs_stat(root .. ".lua") then
|
|
fn(modname, root .. ".lua")
|
|
end
|
|
|
|
M.ls(root, function(path, name, type)
|
|
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.loop.fs_stat(path .. "/init.lua") then
|
|
fn(modname .. "." .. name, path .. "/init.lua")
|
|
end
|
|
end)
|
|
end
|
|
|
|
---@generic T
|
|
---@param list T[]
|
|
---@param add T[]
|
|
---@return T[]
|
|
function M.extend(list, add)
|
|
local idx = {}
|
|
for _, v in ipairs(list) do
|
|
idx[v] = v
|
|
end
|
|
for _, a in ipairs(add) do
|
|
if not idx[a] then
|
|
table.insert(list, a)
|
|
end
|
|
end
|
|
return list
|
|
end
|
|
|
|
---@alias LazyNotifyOpts {lang?:string, title?:string, level?:number}
|
|
|
|
---@param msg string|string[]
|
|
---@param opts? LazyNotifyOpts
|
|
function M.notify(msg, opts)
|
|
if vim.in_fast_event() then
|
|
return vim.schedule(function()
|
|
M.notify(msg, opts)
|
|
end)
|
|
end
|
|
|
|
opts = opts or {}
|
|
if type(msg) == "table" then
|
|
msg = table.concat(
|
|
vim.tbl_filter(function(line)
|
|
return line or false
|
|
end, msg),
|
|
"\n"
|
|
)
|
|
end
|
|
local lang = opts.lang or "markdown"
|
|
vim.notify(msg, opts.level or vim.log.levels.INFO, {
|
|
on_open = function(win)
|
|
pcall(require, "nvim-treesitter")
|
|
vim.wo[win].conceallevel = 3
|
|
vim.wo[win].concealcursor = ""
|
|
vim.wo[win].spell = false
|
|
local buf = vim.api.nvim_win_get_buf(win)
|
|
if not pcall(vim.treesitter.start, buf, lang) then
|
|
vim.bo[buf].filetype = lang
|
|
vim.bo[buf].syntax = lang
|
|
end
|
|
end,
|
|
title = opts.title or "lazy.nvim",
|
|
})
|
|
end
|
|
|
|
---@param msg string|string[]
|
|
---@param opts? LazyNotifyOpts
|
|
function M.error(msg, opts)
|
|
opts = opts or {}
|
|
opts.level = vim.log.levels.ERROR
|
|
M.notify(msg, opts)
|
|
end
|
|
|
|
---@param msg string|string[]
|
|
---@param opts? LazyNotifyOpts
|
|
function M.info(msg, opts)
|
|
opts = opts or {}
|
|
opts.level = vim.log.levels.INFO
|
|
M.notify(msg, opts)
|
|
end
|
|
|
|
---@param msg string|string[]
|
|
---@param opts? LazyNotifyOpts
|
|
function M.warn(msg, opts)
|
|
opts = opts or {}
|
|
opts.level = vim.log.levels.WARN
|
|
M.notify(msg, opts)
|
|
end
|
|
|
|
---@param msg string|table
|
|
---@param opts? LazyNotifyOpts
|
|
function M.debug(msg, opts)
|
|
if not require("lazy.core.config").options.debug then
|
|
return
|
|
end
|
|
opts = opts or {}
|
|
if opts.title then
|
|
opts.title = "lazy.nvim: " .. opts.title
|
|
end
|
|
if type(msg) == "string" then
|
|
M.notify(msg, opts)
|
|
else
|
|
opts.lang = "lua"
|
|
M.notify(vim.inspect(msg), opts)
|
|
end
|
|
end
|
|
|
|
local function can_merge(v)
|
|
return type(v) == "table" and (vim.tbl_isempty(v) or not M.is_list(v))
|
|
end
|
|
|
|
--- Merges the values similar to vim.tbl_deep_extend with the **force** behavior,
|
|
--- but the values can be any type, in which case they override the values on the left.
|
|
--- Values will me merged in-place in the first left-most table. If you want the result to be in
|
|
--- a new table, then simply pass an empty table as the first argument `vim.merge({}, ...)`
|
|
--- Supports clearing values by setting a key to `vim.NIL`
|
|
---@generic T
|
|
---@param ... T
|
|
---@return T
|
|
function M.merge(...)
|
|
local values = { ... }
|
|
local ret = values[1]
|
|
|
|
if ret == vim.NIL then
|
|
ret = nil
|
|
end
|
|
|
|
for i = 2, #values, 1 do
|
|
local value = values[i]
|
|
if can_merge(ret) and can_merge(value) then
|
|
for k, v in pairs(value) do
|
|
ret[k] = M.merge(ret[k], v)
|
|
end
|
|
elseif value == vim.NIL then
|
|
ret = nil
|
|
else
|
|
ret = value
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
return M
|