Regenerate nvim config
This commit is contained in:
383
config/neovim/store/lazy-plugins/nvim-lint/lua/lint.lua
Normal file
383
config/neovim/store/lazy-plugins/nvim-lint/lua/lint.lua
Normal file
@ -0,0 +1,383 @@
|
||||
local uv = vim.loop
|
||||
local api = vim.api
|
||||
local notify = vim.notify_once or vim.notify
|
||||
local M = {}
|
||||
|
||||
|
||||
---@class lint.Parser
|
||||
---@field on_chunk fun(chunk: string)
|
||||
---@field on_done fun(publish: fun(diagnostics: vim.Diagnostic[]), bufnr: number, linter_cwd: string)
|
||||
|
||||
|
||||
---@class lint.Linter
|
||||
---@field name string
|
||||
---@field cmd string
|
||||
---@field args? (string|fun():string)[] command arguments
|
||||
---@field stdin? boolean send content via stdin. Defaults to false
|
||||
---@field append_fname? boolean add current file name to the commands arguments
|
||||
---@field stream? "stdout"|"stderr"|"both" result stream. Defaults to stdout
|
||||
---@field ignore_exitcode? boolean if exit code != 1 should be ignored or result in a warning. Defaults to false
|
||||
---@field env? table
|
||||
---@field cwd? string
|
||||
---@field parser lint.Parser|fun(output:string, bufnr:number, linter_cwd:string):vim.Diagnostic[]
|
||||
|
||||
|
||||
---@class lint.LintProc
|
||||
---@field bufnr integer
|
||||
---@field handle uv.uv_process_t
|
||||
---@field stdout uv.uv_pipe_t
|
||||
---@field stderr uv.uv_pipe_t
|
||||
---@field linter lint.Linter
|
||||
---@field cwd string
|
||||
---@field ns integer
|
||||
---@field stream? "stdout"|"stderr"|"both"
|
||||
---@field cancelled boolean
|
||||
|
||||
|
||||
---@type table<string, lint.Linter|fun():lint.Linter>
|
||||
M.linters = setmetatable({}, {
|
||||
__index = function(_, key)
|
||||
local ok, linter = pcall(require, 'lint.linters.' .. key)
|
||||
if ok then
|
||||
return linter
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
M.linters_by_ft = {
|
||||
text = {'vale',},
|
||||
json = {'jsonlint',},
|
||||
markdown = {'vale',},
|
||||
rst = {'vale',},
|
||||
ruby = {'ruby',},
|
||||
janet = {'janet',},
|
||||
inko = {'inko',},
|
||||
clojure = {'clj-kondo',},
|
||||
dockerfile = {'hadolint',},
|
||||
terraform = {'tflint'},
|
||||
}
|
||||
|
||||
local namespaces = setmetatable({}, {
|
||||
__index = function(tbl, key)
|
||||
local ns = api.nvim_create_namespace(key)
|
||||
rawset(tbl, key, ns)
|
||||
return ns
|
||||
end
|
||||
})
|
||||
|
||||
|
||||
local function read_output(cwd, bufnr, parser, publish_fn)
|
||||
return function(err, chunk)
|
||||
assert(not err, err)
|
||||
if chunk then
|
||||
parser.on_chunk(chunk, bufnr)
|
||||
else
|
||||
parser.on_done(publish_fn, bufnr, cwd)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function M._resolve_linter_by_ft(ft)
|
||||
local names = M.linters_by_ft[ft]
|
||||
if names then
|
||||
return names
|
||||
end
|
||||
local dedup_linters = {}
|
||||
local filetypes = vim.split(ft, '.', { plain = true })
|
||||
for _, ft_ in pairs(filetypes) do
|
||||
local linters = M.linters_by_ft[ft_]
|
||||
if linters then
|
||||
for _, linter in ipairs(linters) do
|
||||
dedup_linters[linter] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return vim.tbl_keys(dedup_linters)
|
||||
end
|
||||
|
||||
|
||||
---@class lint.LintProc
|
||||
local LintProc = {}
|
||||
local linter_proc_mt = {
|
||||
__index = LintProc
|
||||
}
|
||||
|
||||
|
||||
function LintProc:publish(diagnostics)
|
||||
-- By the time the linter is finished the user might have deleted the buffer
|
||||
if api.nvim_buf_is_valid(self.bufnr) and not self.cancelled then
|
||||
vim.diagnostic.set(self.ns, self.bufnr, diagnostics)
|
||||
end
|
||||
self.stdout:shutdown()
|
||||
self.stdout:close()
|
||||
self.stderr:shutdown()
|
||||
self.stderr:close()
|
||||
end
|
||||
|
||||
|
||||
function LintProc:start_read()
|
||||
local linter_proc = self
|
||||
local publish = function(diagnostics)
|
||||
linter_proc:publish(diagnostics)
|
||||
end
|
||||
local parser = self.linter.parser
|
||||
if type(parser) == 'function' then
|
||||
parser = require('lint.parser').accumulate_chunks(parser)
|
||||
end
|
||||
assert(
|
||||
parser.on_chunk and type(parser.on_chunk == 'function'),
|
||||
'Parser requires a `on_chunk` function'
|
||||
)
|
||||
assert(
|
||||
parser.on_done and type(parser.on_done == 'function'),
|
||||
'Parser requires a `on_done` function'
|
||||
)
|
||||
local stream = self.linter.stream
|
||||
local cwd = self.cwd
|
||||
local bufnr = self.bufnr
|
||||
if not stream or stream == 'stdout' then
|
||||
self.stdout:read_start(read_output(cwd, bufnr, parser, publish))
|
||||
elseif stream == 'stderr' then
|
||||
self.stderr:read_start(read_output(cwd, bufnr, parser, publish))
|
||||
elseif stream == 'both' then
|
||||
local parser1, parser2 = require('lint.parser').split(parser)
|
||||
self.stdout:read_start(read_output(cwd, bufnr, parser1, publish))
|
||||
self.stderr:read_start(read_output(cwd, bufnr, parser2, publish))
|
||||
else
|
||||
error('Invalid `stream` setting: ' .. stream)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function LintProc:cancel()
|
||||
self.cancelled = true
|
||||
local handle = self.handle
|
||||
if not handle or handle:is_closing() then
|
||||
return
|
||||
end
|
||||
|
||||
-- Use sigint so the process can safely close any child processes.
|
||||
-- This is mostly useful for when `cmd` is a script with a shebang.
|
||||
handle:kill('sigint')
|
||||
|
||||
vim.wait(10000, function()
|
||||
return handle:is_closing() or false
|
||||
end)
|
||||
|
||||
if not handle:is_closing() then
|
||||
-- 'sigint' didn't work, hit it with a 'sigkill'.
|
||||
-- This should also kill any attached child processes since
|
||||
-- handle is a process group leader (due to it being detached).
|
||||
handle:kill('sigkill')
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--- Return the namespace for a given linter.
|
||||
---
|
||||
--- Can be used to configure diagnostics for a given linter. For example:
|
||||
---
|
||||
--- ```lua
|
||||
--- local ns = require("lint").get_namespace("my_linter_name")
|
||||
--- vim.diagnostic.config({ virtual_text = true }, ns)
|
||||
---
|
||||
--- ```
|
||||
---
|
||||
---@param name string linter
|
||||
function M.get_namespace(name)
|
||||
return namespaces[name]
|
||||
end
|
||||
|
||||
|
||||
--- Running processes by buffer -> by linter name
|
||||
---@type table<integer, table<string, lint.LintProc>> bufnr: {linter: handle}
|
||||
local running_procs_by_buf = {}
|
||||
|
||||
|
||||
--- Returns the names of the running linters
|
||||
---
|
||||
---@param bufnr? integer buffer for which to get the running linters. nil=all buffers
|
||||
---@return string[]
|
||||
function M.get_running(bufnr)
|
||||
local linters = {}
|
||||
if bufnr then
|
||||
bufnr = bufnr == 0 and api.nvim_get_current_buf() or bufnr
|
||||
local running_procs = (running_procs_by_buf[bufnr] or {})
|
||||
for linter_name, _ in pairs(running_procs) do
|
||||
table.insert(linters, linter_name)
|
||||
end
|
||||
else
|
||||
for _, running_procs in pairs(running_procs_by_buf) do
|
||||
for linter_name, _ in pairs(running_procs) do
|
||||
table.insert(linters, linter_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
return linters
|
||||
end
|
||||
|
||||
|
||||
---@param names? string|string[] name of the linter
|
||||
---@param opts? {cwd?: string, ignore_errors?: boolean} options
|
||||
function M.try_lint(names, opts)
|
||||
assert(
|
||||
vim.diagnostic,
|
||||
"nvim-lint requires neovim 0.6.0+. If you're using an older version, use the `nvim-05` tag of nvim-lint'"
|
||||
)
|
||||
opts = opts or {}
|
||||
if type(names) == "string" then
|
||||
names = { names }
|
||||
end
|
||||
if not names then
|
||||
names = M._resolve_linter_by_ft(vim.bo.filetype)
|
||||
end
|
||||
|
||||
local lookup_linter = function(name)
|
||||
local linter = M.linters[name]
|
||||
assert(linter, 'Linter with name `' .. name .. '` not available')
|
||||
if type(linter) == "function" then
|
||||
linter = linter()
|
||||
end
|
||||
linter.name = linter.name or name
|
||||
return linter
|
||||
end
|
||||
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
local running_procs = running_procs_by_buf[bufnr] or {}
|
||||
for _, linter_name in pairs(names) do
|
||||
local linter = lookup_linter(linter_name)
|
||||
local proc = running_procs[linter.name]
|
||||
if proc then
|
||||
proc:cancel()
|
||||
end
|
||||
running_procs[linter.name] = nil
|
||||
local ok, lintproc_or_error = pcall(M.lint, linter, opts)
|
||||
if ok then
|
||||
running_procs[linter.name] = lintproc_or_error
|
||||
elseif not opts.ignore_errors then
|
||||
notify(lintproc_or_error --[[@as string]], vim.log.levels.WARN)
|
||||
end
|
||||
end
|
||||
running_procs_by_buf[bufnr] = running_procs
|
||||
end
|
||||
|
||||
local function eval_fn_or_id(x)
|
||||
if type(x) == 'function' then
|
||||
return x()
|
||||
else
|
||||
return x
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
---@param linter lint.Linter
|
||||
---@param opts? {cwd?: string, ignore_errors?: boolean}
|
||||
---@return lint.LintProc|nil
|
||||
function M.lint(linter, opts)
|
||||
assert(linter, 'lint must be called with a linter')
|
||||
local stdin = assert(uv.new_pipe(false), "Must be able to create pipe")
|
||||
local stdout = assert(uv.new_pipe(false), "Must be able to create pipe")
|
||||
local stderr = assert(uv.new_pipe(false), "Must be able to create pipe")
|
||||
local handle
|
||||
local env
|
||||
local pid_or_err
|
||||
local args = {}
|
||||
local bufnr = api.nvim_get_current_buf()
|
||||
local iswin = vim.fn.has("win32") == 1
|
||||
if iswin then
|
||||
linter = vim.tbl_extend("force", linter, {
|
||||
cmd = "cmd.exe",
|
||||
args = { "/C", linter.cmd, unpack(linter.args or {}) },
|
||||
})
|
||||
end
|
||||
opts = opts or {}
|
||||
if linter.args then
|
||||
vim.list_extend(args, vim.tbl_map(eval_fn_or_id, linter.args))
|
||||
end
|
||||
if not linter.stdin and linter.append_fname ~= false then
|
||||
table.insert(args, api.nvim_buf_get_name(bufnr))
|
||||
end
|
||||
if linter.env then
|
||||
env = {}
|
||||
if not linter.env["PATH"] then
|
||||
-- Always include PATH as we need it to execute the linter command
|
||||
table.insert(env, "PATH=" .. os.getenv("PATH"))
|
||||
end
|
||||
for k, v in pairs(linter.env) do
|
||||
table.insert(env, k .. "=" .. v)
|
||||
end
|
||||
end
|
||||
local linter_opts = {
|
||||
args = args,
|
||||
stdio = { stdin, stdout, stderr },
|
||||
env = env,
|
||||
cwd = opts.cwd or linter.cwd or vim.fn.getcwd(),
|
||||
-- Linter may launch child processes so set this as a group leader and
|
||||
-- manually track and kill processes as we need to.
|
||||
-- Don't detach on windows since that may cause shells to
|
||||
-- pop up shortly.
|
||||
detached = not iswin
|
||||
}
|
||||
local cmd = eval_fn_or_id(linter.cmd)
|
||||
assert(cmd, 'Linter definition must have a `cmd` set: ' .. vim.inspect(linter))
|
||||
handle, pid_or_err = uv.spawn(cmd, linter_opts, function(code)
|
||||
if handle and not handle:is_closing() then
|
||||
local procs = (running_procs_by_buf[bufnr] or {})
|
||||
-- Only cleanup if there has not been another procs in between
|
||||
local proc = procs[linter.name] or {}
|
||||
if handle == proc.handle then
|
||||
procs[linter.name] = nil
|
||||
if not next(procs) then
|
||||
running_procs_by_buf[bufnr] = nil
|
||||
end
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
if code ~= 0 and not linter.ignore_exitcode then
|
||||
vim.schedule(function()
|
||||
vim.notify('Linter command `' .. cmd .. '` exited with code: ' .. code, vim.log.levels.WARN)
|
||||
end)
|
||||
end
|
||||
end)
|
||||
if not handle then
|
||||
stdout:close()
|
||||
stderr:close()
|
||||
stdin:close()
|
||||
if not opts.ignore_errors then
|
||||
vim.notify('Error running ' .. cmd .. ': ' .. pid_or_err, vim.log.levels.ERROR)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
local state = {
|
||||
bufnr = bufnr,
|
||||
stdout = stdout,
|
||||
stderr = stderr,
|
||||
handle = handle,
|
||||
linter = linter,
|
||||
cwd = linter_opts.cwd,
|
||||
ns = namespaces[linter.name],
|
||||
cancelled = false,
|
||||
}
|
||||
local linter_proc = setmetatable(state, linter_proc_mt)
|
||||
linter_proc:start_read()
|
||||
if linter.stdin then
|
||||
local lines = api.nvim_buf_get_lines(0, 0, -1, true)
|
||||
for _, line in ipairs(lines) do
|
||||
stdin:write(line .. '\n')
|
||||
end
|
||||
stdin:write('', function()
|
||||
stdin:shutdown(function()
|
||||
stdin:close()
|
||||
end)
|
||||
end)
|
||||
else
|
||||
stdin:close()
|
||||
end
|
||||
return linter_proc
|
||||
end
|
||||
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user