lazy.nvim/lua/lazy/core/util.lua

361 lines
8.6 KiB
Lua
Raw Normal View History

---@class LazyUtilCore
2022-11-20 21:33:47 +00:00
local M = {}
2022-11-29 11:02:25 +00:00
---@alias LazyProfile {data: string|{[string]:string}, time: number, [number]:LazyProfile}
2022-11-20 21:33:47 +00:00
---@type LazyProfile[]
M._profiles = { { name = "lazy" } }
2022-11-29 11:02:25 +00:00
---@param data (string|{[string]:string})?
2022-11-20 21:33:47 +00:00
---@param time number?
2022-11-29 11:02:25 +00:00
function M.track(data, time)
if data then
2022-11-20 21:34:55 +00:00
local entry = {
2022-11-29 11:02:25 +00:00
data = data,
2022-11-23 15:10:16 +00:00
time = time or vim.loop.hrtime(),
2022-11-20 21:34:55 +00:00
}
table.insert(M._profiles[#M._profiles], entry)
if not time then
table.insert(M._profiles, entry)
end
2022-11-23 15:10:16 +00:00
return entry
2022-11-20 21:34:55 +00:00
else
2022-11-24 21:03:00 +00:00
---@type LazyProfile
2022-11-20 21:34:55 +00:00
local entry = table.remove(M._profiles)
2022-11-23 15:10:16 +00:00
entry.time = vim.loop.hrtime() - entry.time
return entry
2022-11-20 21:34:55 +00:00
end
2022-11-20 21:33:47 +00:00
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
2022-12-23 09:18:19 +00:00
---@return string
function M.normname(name)
2022-12-23 09:18:19 +00:00
local ret = name:lower():gsub("^n?vim%-", ""):gsub("%.n?vim$", ""):gsub("%.lua", ""):gsub("[^a-z]+", "")
return ret
end
2022-12-23 09:18:19 +00:00
---@return string
function M.norm(path)
if path:sub(1, 1) == "~" then
2022-12-20 07:47:01 +00:00
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("/+", "/")
2022-12-21 09:17:10 +00:00
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
2022-11-28 06:36:32 +00:00
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
2022-11-20 21:33:47 +00:00
function M.very_lazy()
2022-11-20 21:34:55 +00:00
local function _load()
vim.defer_fn(function()
vim.g.did_very_lazy = true
2022-11-20 21:34:55 +00:00
vim.cmd("do User VeryLazy")
end, 50)
2022-11-20 21:34:55 +00:00
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("VimEnter", {
once = true,
callback = function()
_load()
end,
})
end
end,
})
2022-11-20 21:33:47 +00:00
end
2022-11-24 21:03:00 +00:00
---@alias FileType "file"|"directory"|"link"
---@param path string
---@param fn fun(path: string, name:string, type:FileType):boolean?
2022-11-24 21:03:00 +00:00
function M.ls(path, fn)
local handle = vim.loop.fs_scandir(path)
while handle do
local name, t = vim.loop.fs_scandir_next(handle)
-- HACK: assume type is a file if no type returned
-- see https://github.com/folke/lazy.nvim/issues/306
t = t or "file"
2022-11-24 21:03:00 +00:00
if not name then
break
2022-11-20 21:34:55 +00:00
end
if fn(path .. "/" .. name, name, t) == false then
break
end
2022-11-20 21:34:55 +00:00
end
2022-11-20 21:33:47 +00:00
end
2022-11-24 21:03:00 +00:00
---@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)
2022-11-20 21:34:55 +00:00
end
2022-11-24 21:03:00 +00:00
fn(child, name, type)
end)
2022-11-20 21:33:47 +00:00
end
---@param modname string
2022-11-24 21:03:00 +00:00
---@param fn fun(modname:string, modpath:string)
function M.lsmod(modname, fn)
local Cache = require("lazy.core.cache")
local root = Cache.find_root(modname)
if not root then
return
end
if vim.loop.fs_stat(root .. ".lua") then
fn(modname, root .. ".lua")
end
2022-11-24 21:03:00 +00:00
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)
2022-11-24 21:03:00 +00:00
elseif type == "directory" and vim.loop.fs_stat(path .. "/init.lua") then
fn(modname .. "." .. name, path .. "/init.lua")
2022-11-24 21:03:00 +00:00
end
end)
2022-11-20 21:33:47 +00:00
end
---@alias LazyNotifyOpts {lang?:string, title?:string, level?:number}
2022-12-02 15:52:22 +00:00
---@param msg string|string[]
---@param opts? LazyNotifyOpts
function M.notify(msg, opts)
2023-01-03 15:17:15 +00:00
if vim.in_fast_event() then
return vim.schedule(function()
M.notify(msg, opts)
2023-01-03 15:17:15 +00:00
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
2023-01-03 15:17:15 +00:00
local lang = opts.lang or "markdown"
vim.notify(msg, opts.level or vim.log.levels.INFO, {
on_open = function(win)
2023-01-03 15:17:15 +00:00
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)
2023-01-03 15:17:15 +00:00
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",
2022-11-20 21:34:55 +00:00
})
2022-11-20 21:33:47 +00:00
end
2022-12-02 15:52:22 +00:00
---@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
2022-12-02 15:52:22 +00:00
---@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)
2022-11-22 21:35:06 +00:00
end
2022-12-02 15:52:22 +00:00
---@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)
2022-12-02 15:52:22 +00:00
end
2023-01-03 15:17:15 +00:00
---@param msg string|table
---@param opts? LazyNotifyOpts
function M.debug(msg, opts)
2023-01-03 15:17:15 +00:00
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
2023-01-03 15:17:15 +00:00
if type(msg) == "string" then
M.notify(msg, opts)
2023-01-03 15:17:15 +00:00
else
opts.lang = "lua"
M.notify(vim.inspect(msg), opts)
2023-01-03 15:17:15 +00:00
end
end
local MERGE = "__merge"
local function can_merge(v)
return type(v) == "table" and (vim.tbl_isempty(v) or not M.is_list(v))
end
local function can_extend(v)
return type(v) == "table" and (vim.tbl_isempty(v) or M.is_list(v))
end
---@param v any|{__merge:boolean}
---@param merge? boolean
---@return boolean?
local function check_merge(v, merge)
if type(v) == "table" and v[MERGE] ~= nil then
merge = v[MERGE]
v[MERGE] = nil
end
return merge
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
local merge = check_merge(ret)
for i = 2, #values, 1 do
local value = values[i]
merge = check_merge(value, merge)
if can_merge(ret) and can_merge(value) and merge ~= false then
for k, v in pairs(value) do
ret[k] = M.merge(ret[k], v)
end
elseif can_extend(ret) and can_extend(value) and merge then
for _, v in ipairs(value) do
ret[#ret + 1] = v
end
elseif value == vim.NIL then
ret = nil
else
ret = value
end
end
return ret
end
2022-11-20 21:33:47 +00:00
return M