feat: add linter config and clean up codebase

This commit is contained in:
2024-06-12 09:05:53 +02:00
parent 186e95d6b5
commit b404648928
7 changed files with 580 additions and 313 deletions
+6 -11
View File
@@ -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
+2 -2
View File
@@ -21,7 +21,7 @@ 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))
@@ -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,
+163
View File
@@ -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
+66 -73
View File
@@ -1,42 +1,41 @@
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)
config.dependencies,
function(f)
if not f then
return true
end
if not utils.is_list(f) then
if utils.is_list(f, "string") then
return true
elseif not utils.is_list(f) then
return false
end
@@ -48,9 +47,11 @@ function M.validate(config)
return true
end,
"list of dependencies",
},
post_install = {
config.post_install, function(field)
config.post_install,
function(field)
if not field then
return true
end
@@ -61,22 +62,17 @@ function M.validate(config)
for _, step in ipairs(field) do
local o, r = pcall(vim.validate, { step = { step, { "table" } } })
if not ok then
goto check_r
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",
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",
},
}
)
::check_r::
})
end
if not o then
utils.err(("Invalid config for post_install step: %s"):format(r))
@@ -88,13 +84,13 @@ function M.validate(config)
return true
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,
local job = require("plenary.job"):new({
command = prog,
args = args,
cwd = pkg:get_install_path(),
enabled_recording = true,
on_exit = function(job, code, signal)
on_exit = vim.schedule_wrap(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"))
return
end
end,
}
)
utils.info("post install step done")
end),
})
job:start()
end
end
@@ -186,16 +177,15 @@ function M:mason_install(on_done)
local err
handle:on(
"stderr", vim.schedule_wrap(
function(msg)
"stderr",
vim.schedule_wrap(function(msg)
err = (err or "") .. msg
end
)
end)
)
handle:once(
"closed", vim.schedule_wrap(
function()
"closed",
vim.schedule_wrap(function()
local is_installed = pkg:is_installed()
if is_installed then
@@ -220,8 +210,7 @@ function M:mason_install(on_done)
if on_done then
on_done(is_installed)
end
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
+123 -65
View File
@@ -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,26 +46,28 @@ 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,
"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
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" } },
@@ -62,30 +75,40 @@ function M.validate(name, config)
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",
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,16 +138,14 @@ function M.ca_rename()
})
vim.api.nvim_buf_set_lines(buf, 0, -1, false, { old })
vim.api.nvim_create_autocmd({ "TextChanged", "TextChangedI", "TextChangedP" },
{
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))
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
@@ -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
+90 -42
View File
@@ -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,7 +21,7 @@ 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
@@ -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,33 +228,54 @@ function M.format(opts)
opts.cmd[i] = arg
end
vim.system(
opts.cmd,
{
local stdout, stderr, err
local resp = 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))
stdout = opts.stdout and function(e, data)
if data then
stdout = stdout and stdout .. data or data
end
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 opts.in_place then
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 = out.stdout or ""
output = stdout or ""
end
if opts.stderr then
output = out.stderr or ""
output = stderr or ""
end
output = output:gsub("\n$", "")
local output_lines = vim.fn.split(output, "\n", true)
if opts.selection then
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)
@@ -264,16 +291,37 @@ function M.format(opts)
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
+10
View File
@@ -0,0 +1,10 @@
column_width = 100
line_endings = "Unix"
indent_type = "Spaces"
indent_width = 4
quote_style = "AutoPreferDouble"
call_parentheses = "Always"
collapse_simple_statement = "Never"
[sort_requires]
enabled = true