feat: add linter config and clean up codebase
This commit is contained in:
+6
-11
@@ -26,11 +26,11 @@ end
|
||||
|
||||
local reload_server_config = utils.debounce_with_id(function(name, events)
|
||||
utils.info(("Reloading server with new config"):format(name), name)
|
||||
---@type Server?
|
||||
---@type Server|nil
|
||||
local server = servers[name]
|
||||
|
||||
if server and server.config.enable then
|
||||
server:unload()
|
||||
server:deinit()
|
||||
servers[name] = nil
|
||||
end
|
||||
|
||||
@@ -54,11 +54,11 @@ local reload_server_config = utils.debounce_with_id(function(name, events)
|
||||
if #server:get_ft_buffers() ~= 0 then
|
||||
server:setup()
|
||||
else
|
||||
server:register()
|
||||
server:init()
|
||||
end
|
||||
|
||||
servers[name] = server
|
||||
end, 100)
|
||||
end, 1000)
|
||||
|
||||
local function process_change(error, filename, events)
|
||||
if error then
|
||||
@@ -103,12 +103,7 @@ local function load_configs()
|
||||
::continue::
|
||||
end
|
||||
|
||||
vim.uv.fs_event_start(
|
||||
vim.uv.new_fs_event(),
|
||||
CONFIG_DIR,
|
||||
{},
|
||||
vim.schedule_wrap(process_change)
|
||||
)
|
||||
vim.uv.fs_event_start(vim.uv.new_fs_event(), CONFIG_DIR, {}, vim.schedule_wrap(process_change))
|
||||
end
|
||||
|
||||
--- Setup diagnostics UI
|
||||
@@ -148,7 +143,7 @@ function M.setup()
|
||||
|
||||
for _, server in pairs(servers) do
|
||||
if server.config.enable then
|
||||
server:register()
|
||||
server:init()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+17
-17
@@ -21,22 +21,22 @@ M.new = {}
|
||||
|
||||
--- Load LSP keybinds
|
||||
---@param server Server
|
||||
function M:load(server, bufnr)
|
||||
function M:init(server, bufnr)
|
||||
self.old[bufnr] = {}
|
||||
for _, mode in ipairs(MODE_TYPES) do
|
||||
vim.tbl_extend("error", self.old[bufnr], vim.api.nvim_buf_get_keymap(bufnr, mode))
|
||||
end
|
||||
|
||||
self.new[bufnr] = {
|
||||
{ mode = { "n" }, lhs = "<leader>df", rhs = vim.diagnostic.open_float },
|
||||
{ mode = { "n" }, lhs = "[d", rhs = vim.diagnostic.goto_prev },
|
||||
{ mode = { "n" }, lhs = "]d", rhs = vim.diagnostic.goto_next },
|
||||
{ mode = { "n" }, lhs = "gD", rhs = vim.lsp.buf.declaration },
|
||||
{ mode = { "n", "i" }, lhs = "<C-k>", rhs = vim.lsp.buf.hover },
|
||||
{ mode = { "n", "i" }, lhs = "<C-j>", rhs = vim.lsp.buf.signature_help },
|
||||
{ mode = { "n", "i" }, lhs = "<C-h>", rhs = vim.lsp.buf.document_highlight },
|
||||
{ mode = { "n" }, lhs = "<leader>lr", rhs = server.ca_rename },
|
||||
{ mode = { "n" }, lhs = "<leader>la", rhs = vim.lsp.buf.code_action },
|
||||
{ mode = { "n" }, lhs = "<leader>df", rhs = vim.diagnostic.open_float },
|
||||
{ mode = { "n" }, lhs = "[d", rhs = vim.diagnostic.goto_prev },
|
||||
{ mode = { "n" }, lhs = "]d", rhs = vim.diagnostic.goto_next },
|
||||
{ mode = { "n" }, lhs = "gD", rhs = vim.lsp.buf.declaration },
|
||||
{ mode = { "n", "i" }, lhs = "<C-k>", rhs = vim.lsp.buf.hover },
|
||||
{ mode = { "n", "i" }, lhs = "<C-j>", rhs = vim.lsp.buf.signature_help },
|
||||
{ mode = { "n", "i" }, lhs = "<C-h>", rhs = vim.lsp.buf.document_highlight },
|
||||
{ mode = { "n" }, lhs = "<leader>lr", rhs = server.ca_rename },
|
||||
{ mode = { "n" }, lhs = "<leader>la", rhs = vim.lsp.buf.code_action },
|
||||
{ mode = { "n", "x" }, lhs = "<leader>lf", rhs = vim.lsp.buf.format },
|
||||
{
|
||||
mode = { "n", "i" },
|
||||
@@ -57,17 +57,17 @@ function M:load(server, bufnr)
|
||||
vim.list_extend(self.new[bufnr], {
|
||||
{ mode = "n", lhs = "<leader>dl", rhs = telescope.diagnostics },
|
||||
{ mode = "n", lhs = "<leader>lD", rhs = telescope.lsp_type_definitions },
|
||||
{ mode = "n", lhs = "gd", rhs = telescope.lsp_definitions },
|
||||
{ mode = "n", lhs = "gi", rhs = telescope.lsp_implementations },
|
||||
{ mode = "n", lhs = "gr", rhs = telescope.lsp_references },
|
||||
{ mode = "n", lhs = "gd", rhs = telescope.lsp_definitions },
|
||||
{ mode = "n", lhs = "gi", rhs = telescope.lsp_implementations },
|
||||
{ mode = "n", lhs = "gr", rhs = telescope.lsp_references },
|
||||
})
|
||||
else
|
||||
vim.list_extend(self.new[bufnr], {
|
||||
{ mode = "n", lhs = "<leader>dl", rhs = vim.diagnostic.setloclist },
|
||||
{ mode = "n", lhs = "<leader>ld", rhs = vim.lsp.buf.type_definition },
|
||||
{ mode = "n", lhs = "gd", rhs = vim.lsp.buf.definition },
|
||||
{ mode = "n", lhs = "gi", rhs = vim.lsp.buf.implementation },
|
||||
{ mode = "n", lhs = "gr", rhs = vim.lsp.buf.references },
|
||||
{ mode = "n", lhs = "gd", rhs = vim.lsp.buf.definition },
|
||||
{ mode = "n", lhs = "gi", rhs = vim.lsp.buf.implementation },
|
||||
{ mode = "n", lhs = "gr", rhs = vim.lsp.buf.references },
|
||||
})
|
||||
end
|
||||
|
||||
@@ -81,7 +81,7 @@ function M:load(server, bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
function M:unload(bufnr)
|
||||
function M:deinit(bufnr)
|
||||
if self.new[bufnr] then
|
||||
for _, keymap in ipairs(self.new[bufnr]) do
|
||||
-- pcall to avoid error if keymap was already removed,
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
local utils = require("utils")
|
||||
|
||||
---@class Linter
|
||||
---@field name string
|
||||
---@field namespace number
|
||||
---@field augroup number
|
||||
---@field buffers number[]
|
||||
---@field config LinterConfig
|
||||
M = {}
|
||||
M.__index = M
|
||||
|
||||
---@alias Group
|
||||
---| "lnum"
|
||||
---| "col"
|
||||
---| "severity"
|
||||
---| "message"
|
||||
|
||||
---@class LinterConfig
|
||||
---@field cmd string[]
|
||||
---@field stdin? boolean
|
||||
---@field stdout? boolean
|
||||
---@field stderr? boolean
|
||||
---@field pattern string
|
||||
---@field groups Group[]
|
||||
---@field severity_map table<string, vim.diagnostic.Severity>
|
||||
---@field source? string
|
||||
---@field debounce? number
|
||||
M.config = {}
|
||||
|
||||
function M.validate(name, config)
|
||||
local ok, resp = pcall(vim.validate, {
|
||||
name = { name, "string" },
|
||||
config = { config, "table" },
|
||||
})
|
||||
|
||||
if ok then
|
||||
ok, resp = pcall(vim.validate, {
|
||||
cmd = {
|
||||
config.cmd,
|
||||
function(t)
|
||||
return utils.is_list(t, "string")
|
||||
end,
|
||||
"list of strings",
|
||||
},
|
||||
stdin = { config.stdin, "boolean", true },
|
||||
stdout = { config.stdout, "boolean", true },
|
||||
stderr = { config.stderr, "boolean", true },
|
||||
pattern = { config.pattern, "string" },
|
||||
groups = {
|
||||
config.groups,
|
||||
function(t)
|
||||
return utils.is_list(t, "string")
|
||||
end,
|
||||
"list of strings",
|
||||
},
|
||||
severity_map = {
|
||||
config.severity_map,
|
||||
function(t)
|
||||
return utils.is_map(t, "string", "number")
|
||||
end,
|
||||
"map of string and number",
|
||||
},
|
||||
debounce = { config.debounce, "number", true },
|
||||
source = { config.source, "string", true },
|
||||
})
|
||||
end
|
||||
|
||||
if not ok then
|
||||
utils.err(("Invalid config for linter:\n%s"):format(resp))
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function M:run(bufnr)
|
||||
local input
|
||||
-- TODO: add placeholder variables for when not using stdin
|
||||
if self.config.stdin then
|
||||
input = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
end
|
||||
|
||||
vim.system(
|
||||
self.config.cmd,
|
||||
{ stdin = input },
|
||||
vim.schedule_wrap(function(out)
|
||||
local output
|
||||
if self.config.stdout then
|
||||
output = out.stdout or ""
|
||||
end
|
||||
if self.config.stderr then
|
||||
output = out.stderr or ""
|
||||
end
|
||||
|
||||
local output_lines = vim.fn.split(output, "\n", false)
|
||||
local diagnostics = {}
|
||||
for _, line in ipairs(output_lines) do
|
||||
local ok, resp = pcall(
|
||||
vim.diagnostic.match,
|
||||
line,
|
||||
self.config.pattern,
|
||||
self.config.groups,
|
||||
self.config.severity_map
|
||||
)
|
||||
if not ok then
|
||||
utils.err(tostring(resp))
|
||||
return
|
||||
elseif not resp then
|
||||
utils.err(("Failed to parse linter output:\n%s"):format(line))
|
||||
end
|
||||
|
||||
resp.source = self.config.source
|
||||
table.insert(diagnostics, resp)
|
||||
end
|
||||
vim.diagnostic.set(self.namespace, bufnr, diagnostics)
|
||||
end)
|
||||
)
|
||||
end
|
||||
|
||||
function M:init(bufnr)
|
||||
table.insert(self.buffers, bufnr)
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI" }, {
|
||||
buffer = bufnr,
|
||||
callback = utils.debounce(function()
|
||||
self:run(bufnr)
|
||||
end, self.config.debounce),
|
||||
group = self.augroup,
|
||||
})
|
||||
self:run(bufnr)
|
||||
end
|
||||
|
||||
function M:deinit()
|
||||
for _, bufnr in ipairs(self.buffers) do
|
||||
vim.api.nvim_buf_clear_namespace(bufnr, self.namespace, 0, -1)
|
||||
end
|
||||
self.buffers = {}
|
||||
|
||||
vim.api.nvim_clear_autocmds({ group = self.augroup })
|
||||
end
|
||||
|
||||
--- Create a new instance
|
||||
---@param name string
|
||||
---@param config LinterConfig
|
||||
---@return Linter|nil
|
||||
function M.new(name, config)
|
||||
if not M.validate(name, config) then
|
||||
return
|
||||
end
|
||||
|
||||
config.debounce = config.debounce or 100
|
||||
|
||||
local linter = {
|
||||
name = name,
|
||||
namespace = vim.api.nvim_create_namespace(name),
|
||||
augroup = vim.api.nvim_create_augroup(name, {}),
|
||||
buffers = {},
|
||||
config = config,
|
||||
}
|
||||
|
||||
return setmetatable(linter, M)
|
||||
end
|
||||
|
||||
return M
|
||||
+118
-125
@@ -1,100 +1,96 @@
|
||||
local utils = require("utils")
|
||||
|
||||
---@class PostInstallStep
|
||||
---@field command string
|
||||
---@field args string[]
|
||||
---@field cmd string[]
|
||||
|
||||
---@class MasonPackage
|
||||
---@field dependencies MasonPackage[]?
|
||||
---@field dependencies? MasonPackage[]
|
||||
---@field config MasonPackageConfig
|
||||
local M = {}
|
||||
M.__index = M
|
||||
|
||||
---@class MasonPackageConfig
|
||||
---@field name string?
|
||||
---@field version string?
|
||||
---@field dependencies MasonPackageConfig[]?
|
||||
---@field post_install PostInstallStep[]?
|
||||
---@field [1]? string
|
||||
---@field name? string
|
||||
---@field version? string
|
||||
---@field dependencies? string[]|MasonPackageConfig[]
|
||||
---@field post_install? PostInstallStep[]
|
||||
M.config = {}
|
||||
|
||||
--- Validate MasonPackageConfig
|
||||
---@param config MasonPackageConfig
|
||||
---@param config string|MasonPackageConfig
|
||||
---@return boolean
|
||||
function M.validate(config)
|
||||
local ok, resp = pcall(vim.validate, { config = { config, { "table" } } })
|
||||
if not ok then
|
||||
goto check_resp
|
||||
end
|
||||
|
||||
ok, resp = pcall(
|
||||
vim.validate, {
|
||||
name = { config.name, { "string" }, true },
|
||||
if ok then
|
||||
ok, resp = pcall(vim.validate, {
|
||||
name = { config.name or config[1], { "string" }, true },
|
||||
version = { config.version, { "string" }, true },
|
||||
dependencies = {
|
||||
config.dependencies, function(f)
|
||||
if not f then
|
||||
return true
|
||||
end
|
||||
config.dependencies,
|
||||
function(f)
|
||||
if not f then
|
||||
return true
|
||||
end
|
||||
|
||||
if not utils.is_list(f) then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, dep in ipairs(f) do
|
||||
if not M.validate(dep) then
|
||||
if utils.is_list(f, "string") then
|
||||
return true
|
||||
elseif not utils.is_list(f) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
for _, dep in ipairs(f) do
|
||||
if not M.validate(dep) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
"list of dependencies",
|
||||
},
|
||||
post_install = {
|
||||
config.post_install, function(field)
|
||||
if not field then
|
||||
return true
|
||||
end
|
||||
|
||||
if not utils.is_list(field) then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, step in ipairs(field) do
|
||||
local o, r = pcall(vim.validate, { step = { step, { "table" } } })
|
||||
if not ok then
|
||||
goto check_r
|
||||
config.post_install,
|
||||
function(field)
|
||||
if not field then
|
||||
return true
|
||||
end
|
||||
|
||||
o, r = pcall(
|
||||
vim.validate, {
|
||||
command = { step.command, { "string" } },
|
||||
args = {
|
||||
step.args, function(f)
|
||||
return utils.is_list_or_nil(f, "string")
|
||||
end, "list of strings or nil",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
::check_r::
|
||||
|
||||
if not o then
|
||||
utils.err(("Invalid config for post_install step: %s"):format(r))
|
||||
if not utils.is_list(field) then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, step in ipairs(field) do
|
||||
local o, r = pcall(vim.validate, { step = { step, { "table" } } })
|
||||
if o then
|
||||
o, r = pcall(vim.validate, {
|
||||
cmd = {
|
||||
step.cmd,
|
||||
function(f)
|
||||
return utils.is_list(f, "string")
|
||||
end,
|
||||
"list of strings or nil",
|
||||
},
|
||||
})
|
||||
end
|
||||
|
||||
if not o then
|
||||
utils.err(("Invalid config for post_install step: %s"):format(r))
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
end,
|
||||
"list of steps",
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
end
|
||||
|
||||
::check_resp::
|
||||
if not ok then
|
||||
utils.err(("Invalid config for %s:\n%s"):format(config.name, resp))
|
||||
utils.err(("Invalid config for %s:\n%s"):format(config.name or config[1] or config, resp))
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -107,52 +103,47 @@ function M:run_post_install(pkg)
|
||||
---@param step PostInstallStep
|
||||
---@param msg string
|
||||
local function log_err(step, msg)
|
||||
local cmd = step.command
|
||||
|
||||
if step.args then
|
||||
cmd = cmd .. " " .. table.concat(step.args, " ")
|
||||
end
|
||||
local cmd = table.concat(step.cmd, " ")
|
||||
|
||||
utils.err(
|
||||
("Post installation step for %s:\n`%s`\nfailed with:\n%s"):format(
|
||||
self.config.name, cmd, msg
|
||||
), "lsp.package:run_post_install"
|
||||
self.config.name,
|
||||
cmd,
|
||||
msg
|
||||
),
|
||||
"lsp.package:run_post_install"
|
||||
)
|
||||
end
|
||||
|
||||
if self.config.post_install then
|
||||
utils.info("running post install")
|
||||
for _, step in ipairs(self.config.post_install) do
|
||||
utils.info("running post install step")
|
||||
local cwd = pkg:get_install_path()
|
||||
local command = step.command
|
||||
local args = step.cmd
|
||||
local prog = table.remove(args, 1)
|
||||
|
||||
if command:find("[/\\]") then
|
||||
command = vim.fn.resolve(("%s/%s"):format(cwd, command))
|
||||
if prog:find("[/\\]") then
|
||||
prog = vim.fn.resolve(("%s/%s"):format(cwd, prog))
|
||||
end
|
||||
|
||||
if not utils.is_executable(command) then
|
||||
if not utils.is_executable(prog) then
|
||||
log_err(step, "command not executable")
|
||||
return
|
||||
end
|
||||
|
||||
local job = require("plenary.job"):new(
|
||||
{
|
||||
command = step.command,
|
||||
args = step.args,
|
||||
cwd = pkg:get_install_path(),
|
||||
enabled_recording = true,
|
||||
on_exit = function(job, code, signal)
|
||||
if code ~= 0 or signal ~= 0 then
|
||||
local cmd = command
|
||||
if step.args then
|
||||
cmd = cmd .. " " .. table.concat(step.args, " ")
|
||||
end
|
||||
|
||||
log_err(step, table.concat(job:stderr_result(), "\n"))
|
||||
end
|
||||
end,
|
||||
}
|
||||
)
|
||||
local job = require("plenary.job"):new({
|
||||
command = prog,
|
||||
args = args,
|
||||
cwd = pkg:get_install_path(),
|
||||
enabled_recording = true,
|
||||
on_exit = vim.schedule_wrap(function(job, code, signal)
|
||||
if code ~= 0 or signal ~= 0 then
|
||||
log_err(step, table.concat(job:stderr_result(), "\n"))
|
||||
return
|
||||
end
|
||||
utils.info("post install step done")
|
||||
end),
|
||||
})
|
||||
job:start()
|
||||
end
|
||||
end
|
||||
@@ -186,42 +177,40 @@ function M:mason_install(on_done)
|
||||
|
||||
local err
|
||||
handle:on(
|
||||
"stderr", vim.schedule_wrap(
|
||||
function(msg)
|
||||
err = (err or "") .. msg
|
||||
end
|
||||
)
|
||||
"stderr",
|
||||
vim.schedule_wrap(function(msg)
|
||||
err = (err or "") .. msg
|
||||
end)
|
||||
)
|
||||
|
||||
handle:once(
|
||||
"closed", vim.schedule_wrap(
|
||||
function()
|
||||
local is_installed = pkg:is_installed()
|
||||
"closed",
|
||||
vim.schedule_wrap(function()
|
||||
local is_installed = pkg:is_installed()
|
||||
|
||||
if is_installed then
|
||||
utils.info(
|
||||
("Successfully installed %s"):format(self.config.name),
|
||||
"lsp.package:mason_install"
|
||||
)
|
||||
self:run_post_install(pkg)
|
||||
if is_installed then
|
||||
utils.info(
|
||||
("Successfully installed %s"):format(self.config.name),
|
||||
"lsp.package:mason_install"
|
||||
)
|
||||
self:run_post_install(pkg)
|
||||
else
|
||||
if err then
|
||||
err = ":\n" .. err
|
||||
else
|
||||
if err then
|
||||
err = ":\n" .. err
|
||||
else
|
||||
err = ""
|
||||
end
|
||||
|
||||
utils.err(
|
||||
("Failed to install %s%s"):format(self.config.name, err),
|
||||
"lsp.package:mason_install"
|
||||
)
|
||||
err = ""
|
||||
end
|
||||
|
||||
if on_done then
|
||||
on_done(is_installed)
|
||||
end
|
||||
utils.err(
|
||||
("Failed to install %s%s"):format(self.config.name, err),
|
||||
"lsp.package:mason_install"
|
||||
)
|
||||
end
|
||||
)
|
||||
|
||||
if on_done then
|
||||
on_done(is_installed)
|
||||
end
|
||||
end)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -276,15 +265,19 @@ function M:install(on_done)
|
||||
end
|
||||
|
||||
--- Create a new instance
|
||||
---@param config MasonPackageConfig?
|
||||
---@return MasonPackage?
|
||||
---@param config MasonPackageConfig|string
|
||||
---@return MasonPackage|nil
|
||||
function M.new(config)
|
||||
config = config or {}
|
||||
if type(config) == "string" then
|
||||
config = { config }
|
||||
end
|
||||
|
||||
if not M.validate(config) then
|
||||
return
|
||||
end
|
||||
|
||||
config.name = config.name or config[1]
|
||||
|
||||
local pkg = { config = config }
|
||||
|
||||
if pkg.config.dependencies then
|
||||
|
||||
+149
-91
@@ -1,33 +1,44 @@
|
||||
local utils = require("utils")
|
||||
local keymap = require("lsp.keymap")
|
||||
local utils = require("utils")
|
||||
|
||||
---@class Linter
|
||||
local Linter = require("lsp.linter")
|
||||
|
||||
---@class MasonPackage
|
||||
local MasonPackage = require("lsp.package")
|
||||
|
||||
-- override type, seems to be incorrect in either lspconfig or vim.lsp
|
||||
---@class lspconfig.Config
|
||||
---@field root_dir function
|
||||
|
||||
---@class Server
|
||||
---@field name string?
|
||||
---@field mason MasonPackage?
|
||||
---@field client vim.lsp.Client?
|
||||
---@field attached_buffers number[]?
|
||||
---@field name? string
|
||||
---@field mason? MasonPackage
|
||||
---@field client? vim.lsp.Client
|
||||
---@field attached_buffers? number[]
|
||||
---@field manager lspconfig.Manager
|
||||
---@field linters? Linter[]
|
||||
---@field config ServerConfig
|
||||
local M = {}
|
||||
|
||||
M.__index = M
|
||||
|
||||
---@class ServerConfig
|
||||
---@field enable boolean?
|
||||
---@field dependencies string[]?
|
||||
---@field mason MasonPackageConfig?
|
||||
---@field root_patterns string[]?
|
||||
---@field keymaps Keymap[]?
|
||||
---@field lspconfig lspconfig.Config?
|
||||
---@field enable? boolean
|
||||
---@field dependencies? string[]
|
||||
---@field mason? string|MasonPackageConfig
|
||||
---@field root_patterns? string[]
|
||||
---@field keymaps? Keymap[]
|
||||
---@field linters? LinterConfig[]
|
||||
---@field lspconfig? lspconfig.Config
|
||||
M.config = {}
|
||||
|
||||
--- Validate ServerConfig
|
||||
---@param config ServerConfig
|
||||
---@return boolean
|
||||
function M.validate(name, config)
|
||||
local ok, resp = pcall(vim.validate, { config = { config, { "table" } } })
|
||||
|
||||
if ok then
|
||||
ok, resp = pcall(vim.validate, {
|
||||
enable = { config.enable, { "boolean" }, true },
|
||||
@@ -35,57 +46,69 @@ function M.validate(name, config)
|
||||
config.dependencies,
|
||||
function(f)
|
||||
return utils.is_list_or_nil(f, "string")
|
||||
end, "list of strings or nil",
|
||||
},
|
||||
mason = {
|
||||
config.mason, function(f)
|
||||
if f == nil then return true end
|
||||
return MasonPackage.validate(f)
|
||||
end,
|
||||
end,
|
||||
"list of strings or nil",
|
||||
},
|
||||
mason = { config.mason, { "string", "table" }, true },
|
||||
root_patterns = {
|
||||
config.root_patterns,
|
||||
function(f)
|
||||
return utils.is_list_or_nil(f, "string")
|
||||
end, "list of strings or nil",
|
||||
end,
|
||||
"list of strings or nil",
|
||||
},
|
||||
keymaps = {
|
||||
config.keymaps, function(f)
|
||||
if not f then return true end
|
||||
if not utils.is_list(f, "table") then
|
||||
return false
|
||||
end
|
||||
for _, key in ipairs(f) do
|
||||
local o, r = pcall(vim.validate, {
|
||||
mode = { key.mode, { "s", "t" } },
|
||||
lhs = { key.lhs, "s" },
|
||||
rhs = { key.rhs, { "s", "f" } },
|
||||
opts = { key.opts, "t", true },
|
||||
})
|
||||
if not o then
|
||||
utils.err(("Invalid keymap:\n%s"):format(r))
|
||||
config.keymaps,
|
||||
function(f)
|
||||
if not f then
|
||||
return true
|
||||
end
|
||||
|
||||
if not utils.is_list(f, "table") then
|
||||
return false
|
||||
end
|
||||
end
|
||||
return true
|
||||
end, "list of keymaps",
|
||||
|
||||
for _, key in ipairs(f) do
|
||||
local o, r = pcall(vim.validate, {
|
||||
mode = { key.mode, { "s", "t" } },
|
||||
lhs = { key.lhs, "s" },
|
||||
rhs = { key.rhs, { "s", "f" } },
|
||||
opts = { key.opts, "t", true },
|
||||
})
|
||||
|
||||
if not o then
|
||||
utils.err(("Invalid keymap:\n%s"):format(r))
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end,
|
||||
"list of keymaps",
|
||||
},
|
||||
lspconfig = { config.lspconfig, { "table" }, true },
|
||||
})
|
||||
end
|
||||
|
||||
if not ok then
|
||||
utils.err(("Invalid config for %s:\n%s"):format(name, resp))
|
||||
return false
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Rename Code Action
|
||||
function M.ca_rename()
|
||||
local ts_utils = utils.try_require("nvim-treesitter.ts_utils")
|
||||
if not ts_utils then return end
|
||||
if not ts_utils then
|
||||
return
|
||||
end
|
||||
local identifier_types = {
|
||||
"IDENTIFIER", "identifier", "variable_name", "word",
|
||||
"IDENTIFIER",
|
||||
"identifier",
|
||||
"variable_name",
|
||||
"word",
|
||||
}
|
||||
local node = ts_utils.get_node_at_cursor()
|
||||
if not node or not vim.list_contains(identifier_types, node:type()) then
|
||||
@@ -98,8 +121,7 @@ function M.ca_rename()
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
local min_width = 10
|
||||
local max_width = 50
|
||||
local default_width = math.min(max_width, math.max(min_width,
|
||||
vim.str_utfindex(old) + 1))
|
||||
local default_width = math.min(max_width, math.max(min_width, vim.str_utfindex(old) + 1))
|
||||
local row, col, _, _ = node:range()
|
||||
local win = vim.api.nvim_open_win(buf, true, {
|
||||
relative = "win",
|
||||
@@ -116,22 +138,20 @@ function M.ca_rename()
|
||||
})
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { old })
|
||||
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "TextChangedP" },
|
||||
{
|
||||
buffer = buf,
|
||||
callback = function()
|
||||
local win_width = vim.api.nvim_win_get_width(win)
|
||||
local content = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
if #content > 0 then
|
||||
local cwidth = vim.str_utfindex(content[1] or "") + 1
|
||||
local new_width = math.min(max_width,
|
||||
math.max(min_width, cwidth))
|
||||
if new_width ~= win_width then
|
||||
vim.api.nvim_win_set_width(win, new_width)
|
||||
end
|
||||
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "TextChangedP" }, {
|
||||
buffer = buf,
|
||||
callback = function()
|
||||
local win_width = vim.api.nvim_win_get_width(win)
|
||||
local content = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
if #content > 0 then
|
||||
local cwidth = vim.str_utfindex(content[1] or "") + 1
|
||||
local new_width = math.min(max_width, math.max(min_width, cwidth))
|
||||
if new_width ~= win_width then
|
||||
vim.api.nvim_win_set_width(win, new_width)
|
||||
end
|
||||
end,
|
||||
})
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.keymap.set({ "n", "i", "x" }, "<cr>", function()
|
||||
local content = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
||||
@@ -146,15 +166,14 @@ function M.ca_rename()
|
||||
vim.api.nvim_win_close(win, true)
|
||||
vim.cmd.stopinsert()
|
||||
end, { buffer = buf })
|
||||
vim.keymap.set({ "n", "x" }, "<esc>",
|
||||
function() vim.api.nvim_win_close(win, true) end,
|
||||
{ buffer = buf })
|
||||
vim.keymap.set({ "n", "x" }, "q",
|
||||
function() vim.api.nvim_win_close(win, true) end,
|
||||
{ buffer = buf })
|
||||
vim.keymap.set({ "n", "x" }, "<esc>", function()
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end, { buffer = buf })
|
||||
vim.keymap.set({ "n", "x" }, "q", function()
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end, { buffer = buf })
|
||||
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("^v$<C-g>", true,
|
||||
false, true), "n", true)
|
||||
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes("^v$<C-g>", true, false, true), "n", true)
|
||||
end
|
||||
|
||||
--- Called when language server attaches
|
||||
@@ -168,7 +187,12 @@ function M:on_attach(client, bufnr)
|
||||
self.attached_buffers = self.attached_buffers or {}
|
||||
table.insert(self.attached_buffers, bufnr)
|
||||
|
||||
keymap:load(self, bufnr)
|
||||
keymap:init(self, bufnr)
|
||||
if self.linters then
|
||||
for _, linter in ipairs(self.linters) do
|
||||
linter:init(bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
-- For document highlight
|
||||
vim.cmd.highlight({ "link LspReferenceRead Visual", bang = true })
|
||||
@@ -178,9 +202,8 @@ function M:on_attach(client, bufnr)
|
||||
vim.opt.updatetime = 300
|
||||
require("lsp-inlayhints").on_attach(client, bufnr, false)
|
||||
|
||||
vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(
|
||||
vim.lsp.handlers.hover,
|
||||
{ border = "single" })
|
||||
vim.lsp.handlers["textDocument/hover"] =
|
||||
vim.lsp.with(vim.lsp.handlers.hover, { border = "single" })
|
||||
vim.lsp.handlers["textDocument/signatureHelp"] =
|
||||
vim.lsp.with(vim.lsp.handlers.signature_help, { border = "single" })
|
||||
|
||||
@@ -205,8 +228,8 @@ function M:configure_client()
|
||||
|
||||
local cmp_nvim_lsp = utils.try_require("cmp_nvim_lsp")
|
||||
if cmp_nvim_lsp then
|
||||
capabilities = vim.tbl_deep_extend("force", capabilities,
|
||||
cmp_nvim_lsp.default_capabilities())
|
||||
capabilities =
|
||||
vim.tbl_deep_extend("force", capabilities, cmp_nvim_lsp.default_capabilities())
|
||||
end
|
||||
|
||||
-- local epo = utils.try_require("epo")
|
||||
@@ -230,19 +253,31 @@ function M:configure_client()
|
||||
if not ok then
|
||||
utils.err(
|
||||
("Failed to load on_attach for %s:\n%s"):format(self.name, ret),
|
||||
"lsp.server:configure_client")
|
||||
"lsp.server:configure_client"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
local ok, ret = pcall(lspconfig[self.name].setup, self.config.lspconfig)
|
||||
if not ok then
|
||||
utils.err(("Failed to setup LSP server %s with lspconfig: %s"):format(
|
||||
self.name, ret))
|
||||
utils.err(("Failed to setup LSP server %s with lspconfig: %s"):format(self.name, ret))
|
||||
return
|
||||
end
|
||||
|
||||
self.manager = lspconfig[self.name].manager
|
||||
for _, bufnr in ipairs(self:get_ft_buffers()) do
|
||||
self.manager:try_add_wrapper(bufnr)
|
||||
end
|
||||
|
||||
if self.config.linters then
|
||||
self.linters = {}
|
||||
for i, config in ipairs(self.config.linters) do
|
||||
local linter = Linter.new(("%s_linter%d"):format(self.name, i), config)
|
||||
if linter then
|
||||
table.insert(self.linters, linter)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function M:get_ft_buffers()
|
||||
@@ -278,8 +313,12 @@ function M:install(on_done)
|
||||
--- Handle install result
|
||||
---@param success boolean
|
||||
local function handle_result(success)
|
||||
if not success then self.config.enable = false end
|
||||
if on_done then on_done(success) end
|
||||
if not success then
|
||||
self.config.enable = false
|
||||
end
|
||||
if on_done then
|
||||
on_done(success)
|
||||
end
|
||||
end
|
||||
|
||||
self.mason:install(handle_result)
|
||||
@@ -291,13 +330,18 @@ function M:setup()
|
||||
if #missing_deps > 0 then
|
||||
utils.warn(
|
||||
("Disabling %s because the following package(s) are not installed: %s"):format(
|
||||
self.name, table.concat(missing_deps, ", ")))
|
||||
self.name,
|
||||
table.concat(missing_deps, ", ")
|
||||
)
|
||||
)
|
||||
self.config.enable = false
|
||||
return
|
||||
end
|
||||
if self.mason then
|
||||
self:install(function(success)
|
||||
if success then self:configure_client() end
|
||||
if success then
|
||||
self:configure_client()
|
||||
end
|
||||
end)
|
||||
elseif vim.fn.executable(self.config.lspconfig.cmd[1]) == 1 then
|
||||
self:configure_client()
|
||||
@@ -307,51 +351,65 @@ function M:setup()
|
||||
end
|
||||
end
|
||||
|
||||
--- Register autocmd for setting up LSP server upon entering a buffer of related filetype
|
||||
function M:register()
|
||||
--- Load autocmd for setting up LSP server upon entering a buffer of related filetype
|
||||
function M:init()
|
||||
local group = vim.api.nvim_create_augroup("lsp_bootstrap_" .. self.name, {})
|
||||
vim.api.nvim_create_autocmd("FileType", {
|
||||
once = true,
|
||||
pattern = self.config.lspconfig.filetypes or {},
|
||||
callback = function() self:setup() end,
|
||||
callback = function()
|
||||
self:setup()
|
||||
end,
|
||||
group = group,
|
||||
})
|
||||
end
|
||||
|
||||
function M:unload()
|
||||
function M:deinit()
|
||||
if self.attached_buffers then
|
||||
for _, bufnr in ipairs(self.attached_buffers) do
|
||||
keymap:unload(bufnr)
|
||||
keymap:deinit(bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
if self.client then
|
||||
self.client.stop()
|
||||
self.client = nil
|
||||
end
|
||||
|
||||
vim.api.nvim_clear_autocmds({ group = "lsp_bootstrap_" .. self.name })
|
||||
|
||||
if self.linters then
|
||||
for _, linter in ipairs(self.linters) do
|
||||
linter:deinit()
|
||||
end
|
||||
|
||||
self.linters = nil
|
||||
end
|
||||
|
||||
require("lspconfig")[self.name] = nil
|
||||
end
|
||||
|
||||
--- Create a new instance
|
||||
---@param name string
|
||||
---@param config ServerConfig?
|
||||
---@return Server?
|
||||
---@param config? ServerConfig
|
||||
---@return Server|nil
|
||||
function M.new(name, config)
|
||||
config = config or {}
|
||||
if not M.validate(name, config) then return end
|
||||
local ok, resp = pcall(require, "lspconfig.server_configurations." .. name)
|
||||
if not ok then
|
||||
utils.err(("Server with name %s does not exist in lspconfig"):format(
|
||||
name))
|
||||
if not M.validate(name, config) then
|
||||
return
|
||||
end
|
||||
config.lspconfig = vim.tbl_deep_extend("keep", config.lspconfig or {},
|
||||
resp.default_config)
|
||||
local ok, resp = pcall(require, "lspconfig.server_configurations." .. name)
|
||||
if not ok then
|
||||
utils.err(("Server with name %s does not exist in lspconfig"):format(name))
|
||||
return
|
||||
end
|
||||
config.lspconfig = vim.tbl_deep_extend("keep", config.lspconfig or {}, resp.default_config)
|
||||
local server = { name = name, config = config }
|
||||
if server.config.mason then
|
||||
local pkg = MasonPackage.new(server.config.mason)
|
||||
if pkg then server.mason = pkg end
|
||||
if pkg then
|
||||
server.mason = pkg
|
||||
end
|
||||
end
|
||||
return setmetatable(server, M)
|
||||
end
|
||||
|
||||
+117
-69
@@ -4,7 +4,7 @@ M.os_name = vim.uv.os_uname().sysname
|
||||
|
||||
--- Get the module path of a file
|
||||
---@param file string
|
||||
---@return string?
|
||||
---@return string|nil
|
||||
local function get_module_path(file)
|
||||
for _, rtp in ipairs(vim.api.nvim_list_runtime_paths()) do
|
||||
if file:sub(1, #rtp) == rtp then
|
||||
@@ -21,13 +21,13 @@ end
|
||||
|
||||
--- Send a notification
|
||||
---@param msg string Message to send
|
||||
---@param title string? Title of notification
|
||||
---@param title? string Title of notification
|
||||
---@param level integer Log level
|
||||
local function notify(msg, title, level)
|
||||
if not title then
|
||||
local info = debug.getinfo(3)
|
||||
local file = info.source
|
||||
and (info.source:sub(1, 1) == "@" and info.source:sub(2) or info.source)
|
||||
and (info.source:sub(1, 1) == "@" and info.source:sub(2) or info.source)
|
||||
or nil
|
||||
local module = file and (get_module_path(file) or file) or nil
|
||||
title = module and module .. (info.name and info.name ~= "" and ":" .. info.name or "")
|
||||
@@ -97,28 +97,28 @@ end
|
||||
|
||||
--- Send a debug notification
|
||||
---@param msg string Message to send
|
||||
---@param title string? Title of notification
|
||||
---@param title? string Title of notification
|
||||
function M.debug(msg, title)
|
||||
notify(msg, title, vim.log.levels.DEBUG)
|
||||
end
|
||||
|
||||
--- Send an info notification
|
||||
---@param msg string Message to send
|
||||
---@param title string? Title of notification
|
||||
---@param title? string Title of notification
|
||||
function M.info(msg, title)
|
||||
notify(msg, title, vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
--- Send a warning notification
|
||||
---@param msg string Message to send
|
||||
---@param title string? Title of notification
|
||||
---@param title? string Title of notification
|
||||
function M.warn(msg, title)
|
||||
notify(msg, title, vim.log.levels.WARN)
|
||||
end
|
||||
|
||||
--- Send an error notification
|
||||
---@param msg string Message to send
|
||||
---@param title string? Title of notification
|
||||
---@param title? string Title of notification
|
||||
function M.err(msg, title)
|
||||
notify(msg, title, vim.log.levels.ERROR)
|
||||
end
|
||||
@@ -136,21 +136,29 @@ function M.try_require(module)
|
||||
M.err(("Failed to load module %s:\n%s"):format(module, resp))
|
||||
end
|
||||
|
||||
--- Checks if it is possible to require a module
|
||||
---@param module string
|
||||
---@return boolean
|
||||
function M.has_module(module)
|
||||
local has_module, _ = pcall(require, module)
|
||||
return has_module
|
||||
end
|
||||
|
||||
---@class FormatOptions
|
||||
---@field cmd string[] Command to run. The following keywords get replaces by the specified values:
|
||||
--- * %file% - path to the current file
|
||||
--- * %filename% - name of the current file
|
||||
--- * %row_start% - first row of selection
|
||||
--- * %row_end% - last row of selection
|
||||
--- * %col_start% - first column position of selection
|
||||
--- * %col_end% - last column position of selection
|
||||
--- * %byte_start% - byte count of first cell in selection
|
||||
--- * %byte_end% - byte count of last cell in selection
|
||||
---@field stdin boolean? Pass text to stdin. False by default.
|
||||
---@field stdout boolean? Use stdout as the result. False by default.
|
||||
---@field stderr boolean? Use stderr as the result. False by default.
|
||||
---@field in_place boolean? The file is formatted in-place by `cmd`. False by default.
|
||||
---@field auto_indent boolean? Perform auto indent on formatted range. False by default.
|
||||
---@field selection boolean? Only format the currently selected lines. False by default.
|
||||
---@field stdin? boolean Pass text to stdin. Assumes in-place formatting on False. False by default.
|
||||
---@field stdout? boolean Use stdout as the result. False by default.
|
||||
---@field stderr? boolean Use stderr as the result. False by default.
|
||||
---@field auto_indent? boolean Perform auto indent on formatted range. False by default.
|
||||
---@field only_selection? boolean Only send the selected lines to `stdin`. False by default.
|
||||
|
||||
--- Format buffer
|
||||
---@param opts FormatOptions
|
||||
@@ -160,23 +168,20 @@ function M.format(opts)
|
||||
stdin = opts.stdin or false,
|
||||
stdout = opts.stdout or false,
|
||||
stderr = opts.stderr or false,
|
||||
in_place = opts.in_place or false,
|
||||
auto_indent = opts.auto_indent or false,
|
||||
selection = opts.selection or false,
|
||||
only_selection = opts.only_selection or false,
|
||||
}
|
||||
|
||||
if not opts.in_place and not opts.stdout and not opts.stderr then
|
||||
M.err("One of `in_place`, `stdout` or `stderr` must be true.")
|
||||
if opts.stdin and not (opts.stdout or opts.stderr) then
|
||||
M.err("`stdin` requires that one of `stdout` or `stderr` is set")
|
||||
return
|
||||
elseif opts.in_place and (opts.selection or opts.stdin or opts.stdout or opts.stderr) then
|
||||
M.err(
|
||||
"`in_place` is not valid together with any of "
|
||||
.. "`selection`, `stdin`, `stdout` or `stderr`"
|
||||
)
|
||||
elseif (opts.only_selection or opts.stdout or opts.stderr) and not opts.stdin then
|
||||
M.err("`stdout`, `stderr` and `only_selection` requires `stdin` to be set")
|
||||
return
|
||||
end
|
||||
|
||||
local file = vim.fn.expand("%")
|
||||
local filename = vim.fn.expand("%:t")
|
||||
local mode = vim.fn.mode()
|
||||
local is_visual = mode == "v" or mode == "V" or mode == ""
|
||||
|
||||
@@ -199,18 +204,19 @@ function M.format(opts)
|
||||
end
|
||||
|
||||
local input
|
||||
if opts.selection then
|
||||
if opts.only_selection then
|
||||
input = vim.api.nvim_buf_get_lines(0, row_start - 1, row_end, false)
|
||||
else
|
||||
elseif opts.stdin then
|
||||
input = vim.api.nvim_buf_get_lines(0, 0, -1, false)
|
||||
end
|
||||
|
||||
if opts.in_place then
|
||||
if not opts.stdin then
|
||||
vim.api.nvim_buf_call(0, vim.cmd.write)
|
||||
end
|
||||
|
||||
for i, arg in ipairs(opts.cmd) do
|
||||
arg = arg:gsub("%%file%%", file)
|
||||
arg = arg:gsub("%%filename%%", filename)
|
||||
if is_visual then
|
||||
arg = arg:gsub("%%row_start%%", row_start)
|
||||
arg = arg:gsub("%%row_end%%", row_end)
|
||||
@@ -222,58 +228,100 @@ function M.format(opts)
|
||||
opts.cmd[i] = arg
|
||||
end
|
||||
|
||||
vim.system(
|
||||
opts.cmd,
|
||||
{
|
||||
stdin = opts.stdin and input or nil,
|
||||
},
|
||||
vim.schedule_wrap(function(out)
|
||||
if out.code ~= 0 or out.signal ~= 0 then
|
||||
local err = out.stderr or ""
|
||||
M.err(("Failed to format:\n%s"):format(err))
|
||||
return
|
||||
local stdout, stderr, err
|
||||
local resp = vim.system(opts.cmd, {
|
||||
stdin = opts.stdin and input or nil,
|
||||
stdout = opts.stdout and function(e, data)
|
||||
if data then
|
||||
stdout = stdout and stdout .. data or data
|
||||
end
|
||||
|
||||
if opts.in_place then
|
||||
vim.api.nvim_buf_call(0, vim.cmd.edit)
|
||||
if e then
|
||||
err = err and err .. e or e
|
||||
end
|
||||
end,
|
||||
stderr = opts.stderr and function(e, data)
|
||||
if data then
|
||||
stderr = stderr and stderr .. data or data
|
||||
end
|
||||
|
||||
if e then
|
||||
err = err and err .. e or e
|
||||
end
|
||||
end,
|
||||
}):wait()
|
||||
|
||||
if err then
|
||||
M.err("Error during formatting:\n%s" .. err)
|
||||
return
|
||||
end
|
||||
|
||||
if resp.code ~= 0 or resp.signal ~= 0 then
|
||||
M.err(("Failed to format:\n%s"):format(stderr or ""))
|
||||
return
|
||||
end
|
||||
|
||||
if not opts.stdin then
|
||||
vim.api.nvim_buf_call(0, vim.cmd.edit)
|
||||
else
|
||||
local output
|
||||
if opts.stdout then
|
||||
output = stdout or ""
|
||||
end
|
||||
if opts.stderr then
|
||||
output = stderr or ""
|
||||
end
|
||||
|
||||
output = output:gsub("\n$", "")
|
||||
local output_lines = vim.fn.split(output, "\n", true)
|
||||
|
||||
if opts.only_selection then
|
||||
vim.api.nvim_buf_set_lines(0, row_start - 1, row_end, false, output_lines)
|
||||
else
|
||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, output_lines)
|
||||
end
|
||||
|
||||
if opts.auto_indent then
|
||||
if is_visual then
|
||||
vim.api.nvim_command(
|
||||
("%d,%dnormal! =="):format(row_start, row_start + #output_lines)
|
||||
)
|
||||
else
|
||||
local output
|
||||
if opts.stdout then
|
||||
output = out.stdout or ""
|
||||
end
|
||||
if opts.stderr then
|
||||
output = out.stderr or ""
|
||||
end
|
||||
|
||||
output = output:gsub("\n$", "")
|
||||
local output_lines = vim.fn.split(output, "\n", true)
|
||||
|
||||
if opts.selection then
|
||||
vim.api.nvim_buf_set_lines(0, row_start - 1, row_end, false, output_lines)
|
||||
else
|
||||
vim.api.nvim_buf_set_lines(0, 0, -1, false, output_lines)
|
||||
end
|
||||
|
||||
if opts.auto_indent then
|
||||
if is_visual then
|
||||
vim.api.nvim_command(
|
||||
("%d,%dnormal! =="):format(row_start, row_start + #output_lines)
|
||||
)
|
||||
else
|
||||
vim.api.nvim_command("normal! gg=G")
|
||||
end
|
||||
end
|
||||
vim.api.nvim_command("normal! gg=G")
|
||||
end
|
||||
end)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Check if `val` is a list of type `t` (if given)
|
||||
---@param val any
|
||||
---@param t type?
|
||||
---@param kt type
|
||||
---@param vt type
|
||||
---@return boolean
|
||||
function M.is_map(val, kt, vt)
|
||||
if type(val) ~= "table" then
|
||||
return false
|
||||
end
|
||||
|
||||
for k, v in pairs(val) do
|
||||
if type(k) ~= kt then
|
||||
return false
|
||||
end
|
||||
|
||||
if type(v) ~= vt then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
--- Check if `val` is a list of type `t` (if given)
|
||||
---@param val any
|
||||
---@param t? type
|
||||
---@return boolean
|
||||
function M.is_list(val, t)
|
||||
if type(val) ~= "table" then
|
||||
if not vim.tbl_islist(val) then
|
||||
return false
|
||||
end
|
||||
|
||||
@@ -291,8 +339,8 @@ function M.is_list(val, t)
|
||||
end
|
||||
|
||||
--- Check if `val` is a list of type `t` (if given), or nil
|
||||
---@param val any?
|
||||
---@param t type?
|
||||
---@param val? any
|
||||
---@param t? type
|
||||
---@return boolean
|
||||
function M.is_list_or_nil(val, t)
|
||||
if val == nil then
|
||||
|
||||
Reference in New Issue
Block a user