nvf/modules/neovim/init/util.nix
2025-06-15 22:31:25 +01:00

178 lines
5.7 KiB
Nix

{
config,
lib,
...
}: let
inherit (builtins) filter;
inherit (lib.modules) mkIf mkMerge;
inherit (lib.nvim.dag) entryBefore;
cfg = config.vim.lsp;
in {
config = mkMerge [
(mkIf (cfg.servers != {}) {
vim.luaConfigRC.lsp-util =
entryBefore ["lsp-servers"]
/*
lua
*/
''
-- Port of nvim-lspconfig util
local util = { path = {} }
util.default_config = {
log_level = vim.lsp.protocol.MessageType.Warning,
message_level = vim.lsp.protocol.MessageType.Warning,
settings = vim.empty_dict(),
init_options = vim.empty_dict(),
handlers = {},
autostart = true,
capabilities = vim.lsp.protocol.make_client_capabilities(),
}
-- global on_setup hook
util.on_setup = nil
do
local validate = vim.validate
local api = vim.api
local lsp = vim.lsp
local nvim_eleven = vim.fn.has 'nvim-0.11' == 1
local iswin = vim.uv.os_uname().version:match 'Windows'
local function escape_wildcards(path)
return path:gsub('([%[%]%?%*])', '\\%1')
end
local function is_fs_root(path)
if iswin then
return path:match '^%a:$'
else
return path == '/'
end
end
local function traverse_parents(path, cb)
path = vim.uv.fs_realpath(path)
local dir = path
-- Just in case our algo is buggy, don't infinite loop.
for _ = 1, 100 do
dir = vim.fs.dirname(dir)
if not dir then
return
end
-- If we can't ascend further, then stop looking.
if cb(dir, path) then
return dir, path
end
if is_fs_root(dir) then
break
end
end
end
util.root_pattern = function(...)
local patterns = util.tbl_flatten { ... }
return function(startpath)
startpath = util.strip_archive_subpath(startpath)
for _, pattern in ipairs(patterns) do
local match = util.search_ancestors(startpath, function(path)
for _, p in ipairs(vim.fn.glob(table.concat({ escape_wildcards(path), pattern }, '/'), true, true)) do
if vim.uv.fs_stat(p) then
return path
end
end
end)
if match ~= nil then
return match
end
end
end
end
util.root_markers_with_field = function(root_files, new_names, field, fname)
local path = vim.fn.fnamemodify(fname, ':h')
local found = vim.fs.find(new_names, { path = path, upward = true })
for _, f in ipairs(found or {}) do
-- Match the given `field`.
for line in io.lines(f) do
if line:find(field) then
root_files[#root_files + 1] = vim.fs.basename(f)
break
end
end
end
return root_files
end
util.insert_package_json = function(root_files, field, fname)
return util.root_markers_with_field(root_files, { 'package.json', 'package.json5' }, field, fname)
end
util.strip_archive_subpath = function(path)
-- Matches regex from zip.vim / tar.vim
path = vim.fn.substitute(path, 'zipfile://\\(.\\{-}\\)::[^\\\\].*$', '\\1', ''')
path = vim.fn.substitute(path, 'tarfile:\\(.\\{-}\\)::.*$', '\\1', ''')
return path
end
util.get_typescript_server_path = function(root_dir)
local project_roots = vim.fs.find('node_modules', { path = root_dir, upward = true, limit = math.huge })
for _, project_root in ipairs(project_roots) do
local typescript_path = project_root .. '/typescript'
local stat = vim.loop.fs_stat(typescript_path)
if stat and stat.type == 'directory' then
return typescript_path .. '/lib'
end
end
return '''
end
util.search_ancestors = function(startpath, func)
if nvim_eleven then
validate('func', func, 'function')
end
if func(startpath) then
return startpath
end
local guard = 100
for path in vim.fs.parents(startpath) do
-- Prevent infinite recursion if our algorithm breaks
guard = guard - 1
if guard == 0 then
return
end
if func(path) then
return path
end
end
end
util.path.is_descendant = function(root, path)
if not path then
return false
end
local function cb(dir, _)
return dir == root
end
local dir, _ = traverse_parents(path, cb)
return dir == root
end
util.tbl_flatten = function(t)
--- @diagnostic disable-next-line:deprecated
return nvim_eleven and vim.iter(t):flatten(math.huge):totable() or vim.tbl_flatten(t)
end
end
'';
})
];
}