fix: namespace all local packages and modules

This commit is contained in:
2025-04-16 23:16:58 +02:00
parent 0c327701a1
commit 16ccb1d107
45 changed files with 43 additions and 32 deletions
-36
View File
@@ -1,36 +0,0 @@
local utils = require("utils")
return {
enable = true,
dependencies = {
"npm",
},
mason = {
"bash-language-server",
dependencies = {
{ "shellcheck" },
{ "shfmt" },
},
},
keymaps = {
{
mode = "n",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = { "shfmt", "-s", "-i", "4", "-" },
output = "stdout",
})
end,
},
},
lspconfig = {
filetypes = {
"sh",
"bash",
"zsh",
},
cmd = { "bash-language-server", "start" },
single_file_support = true,
},
}
-28
View File
@@ -1,28 +0,0 @@
---@type ServerConfig
return {
enable = true,
mason = { "clangd" },
keymaps = {
{
mode = "n",
lhs = "gs",
rhs = vim.cmd.ClangdSwitchSourceHeader,
},
},
lspconfig = {
filetypes = {
"c",
"cpp",
},
cmd = {
"clangd",
"--clang-tidy",
"--enable-config",
-- Fix for errors in files outside of project
-- https://clangd.llvm.org/faq#how-do-i-fix-errors-i-get-when-opening-headers-outside-of-my-project-directory
"--compile-commands-dir=build",
},
single_file_support = true,
},
}
-20
View File
@@ -1,20 +0,0 @@
return {
enable = true,
dependencies = {
"python3",
},
mason = {
name = "cmake-language-server",
-- version = "",
},
lspconfig = {
filetypes = {
"cmake",
},
cmd = { "cmake-language-server", },
single_file_support = true,
init_options = {
buildDirectory = "build",
},
},
}
-36
View File
@@ -1,36 +0,0 @@
local utils = require("utils")
---@type ServerConfig
return {
enable = true,
mason = { "gopls", dependencies = { "golines" } },
keymaps = {
{
mode = "n",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = { "golines", "-m", "80", "--shorten-comments" },
output = "stdout",
})
vim.lsp.buf.format({ async = true })
end,
},
},
lspconfig = {
filetypes = {
"go",
"gomod",
"gowork",
"gotmpl",
},
cmd = { "gopls" },
single_file_support = true,
settings = {
gopls = {
staticcheck = true,
semanticTokens = true,
},
},
},
}
-30
View File
@@ -1,30 +0,0 @@
--[[
Copyright 2023 Oscar Wallberg
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
]]
return {
enable = false,
dependencies = {
"java",
},
mason = {
"groovy-language-server",
},
lspconfig = {
cmd = {
"groovy-language-server",
},
},
}
-18
View File
@@ -1,18 +0,0 @@
return {
enable = true,
mason = {
name = "haskell-language-server",
-- version = "",
},
lspconfig = {
filetypes = { "haskell", "lhaskell", "cabal", },
cmd = { "haskell-language-server-wrapper", "--lsp", },
single_file_support = true,
settings = {
haskell = {
cabalFormattingProvider = "cabalfmt",
formattingProvider = "ormolu",
},
},
},
}
-85
View File
@@ -1,85 +0,0 @@
-- spec:
-- https://github.com/bmewburn/intelephense-docs/blob/master/installation.md
-- https://github.com/bmewburn/vscode-intelephense/blob/master/package.json
local utils = require("utils")
return {
enable = true,
dependencies = {
"npm",
},
mason = { "intelephense", dependencies = { {"phpcs", "pretty-php" } } },
linters = {
{
cmd = {
"phpcs",
"--standard=PSR12",
"--report=emacs",
"-s",
"-q",
"-",
},
stdin = true,
stdout = true,
pattern = "^.+:(%d+):(%d+): (%w+) %- (.*) %((.*)%)$",
groups = { "lnum", "col", "severity", "message", "source" },
source = "phpcs",
severity_map = {
error = vim.diagnostic.severity.ERROR,
warning = vim.diagnostic.severity.WARN,
},
},
},
keymaps = {
{
mode = "n",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = {
"pretty-php",
"--psr12",
"--enable=align-comments",
"-qq",
"-",
},
output = "stdout",
})
end,
},
{
mode = "x",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = {
"pretty-php",
"--psr12",
"--enable=align-comments",
"-qq",
"-",
},
output = "stdout",
})
end,
},
},
lspconfig = {
filetypes = {
"php",
},
cmd = { "intelephense", "--stdio" },
single_file_support = true,
settings = {
intelephense = {
environment = {
phpVersion = "7.4",
},
format = {
enable = false,
},
},
},
},
}
-162
View File
@@ -1,162 +0,0 @@
local utils = require("utils")
local ERROR = vim.diagnostic.severity.ERROR
local WARN = vim.diagnostic.severity.WARN
local INFO = vim.diagnostic.severity.INFO
local HINT = vim.diagnostic.severity.HINT
local SEVERITY_MAP = {
YTT = WARN,
ANN = WARN,
ASYNC = WARN,
B = WARN,
A = WARN,
COM = WARN,
C = WARN,
DTZ = WARN,
T = WARN,
FIX = WARN,
FA = WARN,
ISC = WARN,
PIE = WARN,
PYI = WARN,
PT = WARN,
RET = WARN,
SIM = WARN,
TC = WARN,
I = WARN,
E = ERROR,
W = WARN,
DOC = WARN,
D = INFO,
F = WARN,
PLC = WARN,
PLE = ERROR,
PLR = WARN,
PLW = WARN,
UP = WARN,
RUF = WARN,
}
---@type ServerConfig
return {
enable = true,
mason = { "jedi-language-server", dependencies = { "ruff" } },
linters = {
{
cmd = {
"ruff",
"check",
"--output-format=json",
"-q",
"-",
},
stdin = true,
stdout = true,
json = {
lnum = "location.row",
end_lnum = "end_location.row",
col = "location.column",
end_col = "end_location.column",
code = "code",
message = "message",
callback = function(diag)
if diag.severity or not diag.code then
return
end
diag.severity = SEVERITY_MAP[diag.code:match("^(%u+)")]
end,
},
source = "ruff",
tags = {
deprecated = {
"PYI057",
"PT020",
"UP005",
"UP019",
"UP021",
"UP023",
"UP026",
"UP035",
},
unnecessary = {
"ARG001",
"ARG002",
"ARG003",
"ARG004",
"ARG005",
"F401",
"F504",
"F522",
"F811",
"F841",
"F842",
"RUF029",
"RUF059",
"RUF100",
},
},
},
},
keymaps = {
{
mode = "n",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = {
"ruff",
"format",
"--stdin-filename=%filename%",
"--quiet",
"-",
},
output = "stdout",
})
utils.format({
cmd = {
"ruff",
"check",
"--select=I",
"--fix",
"--quiet",
"-",
},
output = "stdout",
})
end,
},
{
mode = "x",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = {
"ruff",
"format",
"--stdin-filename=%filename%",
"--quiet",
"--range=%row_start%:%col_start%-%row_end%:%col_end%",
"-",
},
output = "stdout",
})
end,
},
},
lspconfig = {
filetypes = {
"python",
},
cmd = { "jedi-language-server" },
single_file_support = true,
init_options = {
completion = {
disableSnippets = true,
},
diagnostics = {
enable = true,
},
},
},
}
-42
View File
@@ -1,42 +0,0 @@
-- spec: https://github.com/eclipse/lemminx/blob/main/docs/Configuration.md
local utils = require("utils")
return {
enable = true,
mason = {
name = "lemminx",
dependencies = { "xmlformatter" },
},
lspconfig = {
filetypes = {
"xml",
"xsd",
"xsl",
"xslt",
"svg",
},
cmd = { "lemminx", },
single_file_support = true,
init_options = {
settings = {
xml = {
format = {
enabled = true, -- is able to format document
splitAttributes = true, -- each attribute is formatted onto new line
joinCDATALines = false, -- normalize content inside CDATA
joinCommentLines = false, -- normalize content inside comments
formatComments = true, -- keep comment in relative position
joinContentLines = false, -- normalize content inside elements
spaceBeforeEmptyCloseLine = true, -- insert whitespace before self closing tag end bracket
},
validation = {
noGrammar = "ignore",
enabled = true,
schema = true,
}
},
},
},
},
}
-87
View File
@@ -1,87 +0,0 @@
-- spec: https://luals.github.io/wiki/settings/
local utils = require("utils")
---@type ServerConfig
return {
enable = true,
dependencies = { "cargo" },
mason = {
"lua-language-server",
post_install = { { cmd = { "cargo", "install", "stylua", "--features", "lua54" } } },
},
keymaps = {
{
mode = "n",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = { "stylua", "-" },
output = "stdout",
})
end,
},
{
mode = "x",
lhs = "<leader>lf",
rhs = function()
utils.format({
cmd = {
"stylua",
"-",
"--range-start",
"%byte_start%",
"--range-end",
"%byte_end%",
},
output = "stdout",
})
end,
},
},
lspconfig = {
filetypes = { "lua" },
cmd = { "lua-language-server" },
single_file_support = true,
settings = {
Lua = {
completion = { showWord = "Disable" },
diagnostics = {
-- disable = { "missing-fields" },
},
runtime = {
version = "LuaJIT",
path = {
-- "?.lua",
"?.lua",
"?/init.lua",
-- "?.lua",
-- "lua/?.lua",
-- "lua/?/init.lua",
-- "?/lua/?.lua",
-- "?/lua/?/init.lua",
},
-- pathStrict = true,
},
workspace = {
library = {
vim.env.VIMRUNTIME,
"~/repos/awesome-code-doc",
"/usr/share/awesome/lib",
vim.fn.stdpath("data") .. "/lazy",
},
checkThirdParty = false,
},
hint = {
enable = false,
arrayIndex = "Disable",
await = true,
paramName = "All",
paramType = true,
semicolon = "Disable",
setType = true,
},
telemetry = { enable = false },
},
},
},
}
-21
View File
@@ -1,21 +0,0 @@
---@type ServerConfig
return {
enable = true,
mason = { "mesonlsp" },
lspconfig = {
---@type fun(filename: string, bufnr: number): string?
root_dir = function(filename, bufnr)
local parent = require("lspconfig").util.find_git_ancestor(filename)
if not parent then
local win = vim.fn.bufwinid(bufnr)
parent = vim.fn.getcwd(win)
end
return parent
end,
settings = {
others = {
disableInlayHints = true,
},
},
},
}
-26
View File
@@ -1,26 +0,0 @@
---@type ServerConfig
return {
enable = true,
mason = { "pyright" },
lspconfig = {
filetypes = {
"python",
},
cmd = { "pyright-langserver", "--stdio" },
single_file_support = true,
-- https://microsoft.github.io/pyright/#/settings
settings = {
python = {
analysis = {
autoSearchPaths = true,
diagnosticMode = "openFilesOnly",
useLibraryCodeForTypes = true,
typeCheckingMode = "strict",
},
},
pyright = {
disableLanguageServices = true,
},
},
},
}
-191
View File
@@ -1,191 +0,0 @@
-- spec: https://rust-analyzer.github.io/manual.html#configuration
return {
enable = true,
mason = {
name = "rust-analyzer",
-- version = "",
},
lspconfig = {
filetypes = {
"rust",
},
cmd = { "rust-analyzer", },
single_file_support = true,
settings = {
["rust-analyzer"] = {
inlayHints = {
chainingHints = {
enable = false,
},
parameterHints = {
enable = false,
},
typeHints = {
enable = false,
},
},
--[[ assist = {
emitMustUse = false,
expressionFillDefault = false,
},
cachePriming = {
enable = false,
},
cargo = {
autoreload = false,
buildScripts = {
enable = false,
},
},
checkOnSave = false,
completion = {
autoimport = {
enable = false,
},
autoself = {
enable = false,
},
callable = {
snippets = false,
},
fullFunctionSignatures = {
enable = false,
},
postfix = {
enable = false,
},
privateEditable = {
enable = false,
},
},
diagnostics = {
enable = false,
},
highlightRelated = {
breakPoints = {
enable = false,
},
closureCaptures = {
enable = false,
},
exitPoints = {
enable = false,
},
references = {
enable = false,
},
yieldPoints = {
enable = false,
},
},
hover = {
actions = {
enable = false,
},
documentation = {
enable = true,
},
links = {
enable = false,
},
memoryLayout = {
enable = false,
},
},
imports = {
group = {
enable = false,
},
},
inlayHints = {
bindingModeHints = {
enable = false,
},
chainingHints = {
enable = false,
},
closingBraceHints = {
enable = false,
},
closureCaptureHints = {
enable = false,
},
closureReturnTypeHints = {
enable = false,
},
discriminantHints = {
enable = false,
},
expressionAdjustmentHints = {
enable = false,
},
lifetimeElisionHints = {
enable = false,
},
parameterHints = {
enable = false,
},
reborrowHints = {
enable = false,
},
typeHints = {
enable = false,
},
},
joinLines = {
joinAssignments = false,
joinElseIf = false,
removeTrailingComma = false,
unwrapTrivialBlock = false,
},
lens = {
enable = false,
},
notifications = {
cargoTomlNotFound = false,
},
procMacro = {
enable = false,
},
references = {
excludeImports = false,
},
rustfmt = {
rangeFormatting = {
enable = false,
},
},
semanticHighlighting = {
doc = {
comment = {
inject = {
enable = false,
},
},
},
nonStandardTokens = false,
operator = {
enable = false,
},
punctuation = {
enable = false,
},
strings = {
enable = false,
},
},
signatureInfo = {
documentation = {
enable = true,
},
},
typing = {
autoClosingAngleBrackets = {
enable = false,
},
}, ]]
},
},
},
}
-31
View File
@@ -1,31 +0,0 @@
-- spec: https://github.com/zigtools/zls#configuration-options
---@type ServerConfig
return {
enable = true,
dependencies = {
"zig",
},
-- mason = {
-- name = "zls",
-- -- version = "",
-- },
lspconfig = {
filetypes = {
"zig",
"zir",
},
cmd = { "zls", },
single_file_support = true,
settings = {
zls = {
warn_style = true,
highlight_global_var_declarations = true,
inlay_hints_show_variable_type_hints = false,
inlay_hints_show_struct_literal_field_type = false,
inlay_hints_show_parameter_name = false,
inlay_hints_show_builtin = false,
},
},
},
}
-141
View File
@@ -1,141 +0,0 @@
-- Mappings.
-- See `:help vim.lsp.*` for documentation on any of the below functions
local utils = require("utils")
---@class Keymap
---@field mode string|string[]
---@field lhs string
---@field rhs string|function
---@field opts? vim.keymap.set.Opts
local MODE_TYPES = { "n", "v", "s", "x", "o", "i", "l", "c" }
local M = {}
---@type table<number, vim.api.keyset.keymap[]>
M.old = {}
---@type table<number, Keymap[]>
M.new = {}
--- Load LSP keybinds
---@param server Server
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 = function()
vim.diagnostic.jump({ count = -1, float = true })
end,
},
{
mode = { "n" },
lhs = "]d",
rhs = function()
vim.diagnostic.jump({ count = 1, float = true })
end,
},
{ mode = { "n" }, lhs = "gD", rhs = vim.lsp.buf.declaration },
{
mode = { "n", "i" },
lhs = "<C-k>",
rhs = function()
vim.lsp.buf.hover({ border = "single" })
end,
},
{
mode = { "n", "i" },
lhs = "<C-j>",
rhs = function()
vim.lsp.buf.signature_help({ border = "single" })
end,
},
{
mode = { "n", "i" },
lhs = "<C-h>",
rhs = vim.lsp.buf.document_highlight,
},
{ mode = { "n", "x" }, lhs = "<leader>lf", rhs = vim.lsp.buf.format },
{
mode = { "n" },
lhs = "<leader>ld",
rhs = function()
vim.diagnostic.enable(not vim.diagnostic.is_enabled())
end,
},
{
mode = { "n", "i" },
lhs = "<C-l>",
rhs = function()
vim.lsp.buf.clear_references()
vim.cmd.nohlsearch()
vim.schedule(vim.cmd.diffupdate)
return "<C-l>"
end,
opts = { expr = true },
},
}
local telescope = utils.try_require("telescope.builtin")
if telescope then
vim.list_extend(self.new[bufnr], {
{ mode = "n", lhs = "<leader>dl", rhs = telescope.diagnostics },
{ mode = "n", lhs = "grt", rhs = telescope.lsp_type_definitions, },
{ mode = "n", lhs = "gd", rhs = telescope.lsp_definitions },
{ mode = "n", lhs = "gri", rhs = telescope.lsp_implementations },
{ mode = "n", lhs = "grr", rhs = telescope.lsp_references },
})
else
vim.list_extend(self.new[bufnr], {
{ mode = "n", lhs = "<leader>dl", rhs = vim.diagnostic.setloclist },
{ mode = "n", lhs = "grt", rhs = vim.lsp.buf.type_definition, },
{ mode = "n", lhs = "gd", rhs = vim.lsp.buf.definition },
})
end
if server.config.keymaps then
vim.list_extend(self.new[bufnr], server.config.keymaps)
end
for _, keymap in ipairs(self.new[bufnr]) do
keymap.opts = vim.tbl_extend(
"force",
keymap.opts or {},
{ buffer = bufnr, remap = true }
)
vim.keymap.set(keymap.mode, keymap.lhs, keymap.rhs, keymap.opts)
end
end
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,
-- for example if server.config.keymaps overrides a default LSP keymap
pcall(vim.keymap.del, keymap.mode, keymap.lhs, { buffer = bufnr })
end
self.new[bufnr] = nil
end
if self.old[bufnr] then
for _, keymap in ipairs(self.old[bufnr]) do
vim.cmd.mapset(keymap)
end
self.old[bufnr] = nil
end
end
return M
-401
View File
@@ -1,401 +0,0 @@
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"
---| "end_lnum"
---| "col"
---| "end_col"
---| "severity"
---| "message"
---| "source"
---| "code"
---@class JsonConfig
---@field diagnostics_root? string
---@field lnum? string
---@field end_lnum? string
---@field col? string
---@field end_col? string
---@field severity? string
---@field message? string
---@field source? string
---@field code? string
---@field callback? fun(diag: vim.Diagnostic)
---@class DiagnosticTagMap
---@field unnecessary? string[]
---@field deprecated? string[]
---@class LinterConfig
---@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
---@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
---@field json? JsonConfig
---@field tags? DiagnosticTagMap
---@field zero_idx_lnum? boolean
---@field zero_idx_col? boolean
M.config = {}
-- Extract a value from a JSON object using a path
---@param obj table The JSON object
---@param path string Path to the value (dot notation string)
---@return any The value at the specified path, or nil if not found
function M.get_json_value(obj, path)
if not obj then
return nil
end
local current = obj
local parts = {}
for part in path:gmatch("[^%.]+") do
table.insert(parts, part)
end
for _, key in ipairs(parts) do
if tonumber(key) ~= nil then
key = tonumber(key)
end
if type(current) ~= "table" then
return nil
end
current = current[key]
if current == nil then
return nil
end
end
return current
end
--- Clamp column to line length
---@param diag vim.Diagnostic
---@param bufnr number
function M:clamp_col(diag, bufnr)
local lines =
vim.api.nvim_buf_get_lines(bufnr, diag.lnum, diag.lnum + 1, false)
if #lines == 0 then
return
end
local line_len = #lines[1] - 1
if diag.col > line_len then
diag.col = line_len
end
end
--- Add diagnostic tags
---@param diag vim.Diagnostic
function M:add_tags(diag)
if not self.config.tags then
return
end
local have_unnecessary = vim.islist(self.config.tags.unnecessary)
local have_deprecated = vim.islist(self.config.tags.deprecated)
if not have_unnecessary and not have_deprecated then
return
end
diag._tags = {}
if
have_unnecessary
and vim.list_contains(self.config.tags.unnecessary, diag.code)
then
diag._tags.unnecessary = true
diag.severity = vim.diagnostic.severity.HINT
end
if
have_deprecated
and vim.list_contains(self.config.tags.deprecated, diag.code)
then
diag._tags.deprecated = true
diag.severity = vim.diagnostic.severity.WARN
end
end
--- Resolve 0/1-based indexing for lnum/col
---@param diag vim.Diagnostic
function M:fix_indexing(diag)
if not self.config.zero_idx_lnum then
if diag.lnum then
diag.lnum = diag.lnum - 1
end
if diag.end_lnum then
diag.end_lnum = diag.end_lnum - 1
end
end
if not self.config.zero_idx_col then
if diag.col then
diag.col = diag.col - 1
end
if diag.end_col then
diag.end_col = diag.end_col - 1
end
end
end
function M:process_json_output(json, bufnr)
---@type vim.Diagnostic[]
local diagnostics = {}
local items = json
if self.config.json.diagnostics_root then
items = M.get_json_value(json, self.config.json.diagnostics_root)
end
if type(items) ~= "table" then
utils.err("diagnostics root is not an array or object")
return
end
if not vim.islist(items) then
items = { items }
end
for _, item in ipairs(items) do
local diag = {}
for field, path in pairs(self.config.json) do
if field ~= "diagnostics_root" and field ~= "callback" then
diag[field] = M.get_json_value(item, path)
end
end
diag.source = diag.source or self.config.source
if
diag.severity
and self.config.severity_map
and self.config.severity_map[diag.severity]
then
diag.severity = self.config.severity_map[diag.severity]
end
self:fix_indexing(diag)
self:clamp_col(diag, bufnr)
self:add_tags(diag)
if type(self.config.json.callback) == "function" then
self.config.json.callback(diag)
end
table.insert(diagnostics, diag)
end
return diagnostics
end
--- Validate input
---@param name string
---@param config LinterConfig
---@return boolean
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", true },
groups = {
config.groups,
function(t)
return utils.is_list(t, "string")
end,
true,
"list of strings",
},
severity_map = {
config.severity_map,
function(t)
return utils.is_map(t, "string", "number")
end,
true,
"map of string and number",
},
debounce = { config.debounce, "number", true },
source = { config.source, "string", true },
json = { config.json, "table", true },
tags = { config.tags, "table", true },
})
end
if not ok then
utils.err(("Invalid config for linter:\n%s"):format(resp))
return false
end
if not config.json and (not config.pattern or not config.groups) then
utils.err("Either json or pattern and groups must be provided")
return false
end
return true
end
function M:run(bufnr)
local input
if self.config.stdin then
input = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
end
local cmd = vim.fn.copy(self.config.cmd)
local file = vim.fn.expand("%:.")
local filename = vim.fn.expand("%:t")
for i, arg in ipairs(cmd) do
arg = arg:gsub("%%file%%", file)
arg = arg:gsub("%%filename%%", filename)
cmd[i] = arg
end
local ok, resp = pcall(
vim.system,
cmd,
{ stdin = input },
---@param out vim.SystemCompleted
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 ""
elseif out.stderr and out.stderr ~= "" then
utils.err(out.stderr)
end
if self.config.json then
local ok, json = pcall(
vim.json.decode,
output,
{ luanil = { object = true, array = true } }
)
if not ok then
utils.err("Failed to parse JSON: " .. json)
return
end
local diagnostics = self:process_json_output(json, bufnr)
if diagnostics then
vim.diagnostic.set(self.namespace, bufnr, diagnostics)
end
return
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 resp then
resp.source = resp.source or self.config.source
self:clamp_col(resp, bufnr)
self:add_tags(resp)
self:fix_indexing(resp)
table.insert(diagnostics, resp)
end
end
vim.diagnostic.set(self.namespace, bufnr, diagnostics)
end)
)
if not ok then
utils.err(("Failed to run %s:\n%s"):format(self.config.cmd[1], resp))
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
-341
View File
@@ -1,341 +0,0 @@
local utils = require("utils")
---@class PostInstallStep
---@field cmd string[]
---@class MasonPackage
---@field dependencies? MasonPackage[]
---@field config MasonPackageConfig
local M = {}
M.__index = M
---@class MasonPackageConfig
---@field [1]? string
---@field name? string
---@field version? string
---@field dependencies? string[]|MasonPackageConfig[]
---@field post_install? PostInstallStep[]
M.config = {}
--- Validate MasonPackageConfig
---@param config string|MasonPackageConfig
---@return boolean
function M.validate(config)
local ok, resp = pcall(vim.validate, { config = { config, { "table" } } })
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
for _, dep in ipairs(f) do
local t = type(dep)
if
(t ~= "string" and t ~= "table")
or (type(dep) == "table" and not M.validate(dep))
then
return false
end
end
return true
end,
"list of string|MasonPackageConfig",
},
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 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), "LSP")
return false
end
return true
end
return true
end,
"list of steps",
},
})
end
if not ok then
utils.err(
("Invalid config for %s:\n%s"):format(config.name or config[1] or config, resp),
"LSP"
)
return false
end
return true
end
--- Run post installation steps
---@param pkg Package
function M:post_install(pkg)
---@param step PostInstallStep
---@param msg string
local function log_err(step, msg)
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"
)
end
if self.config.post_install then
for _, step in ipairs(self.config.post_install) do
utils.info("running post install step", "LSP")
local cwd = pkg:get_install_path()
local args = step.cmd
local prog = table.remove(args, 1)
if prog:find("[/\\]") then
prog = vim.fn.resolve(("%s/%s"):format(cwd, prog))
end
if not utils.is_executable(prog) then
log_err(step, "command not executable")
return
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", "LSP")
end),
})
job:start()
end
end
end
--- Handle installation
---@param pkg Package
---@param old_version? string
---@param new_version? string
---@param on_done? fun(success: boolean)
function M:handle_install(pkg, old_version, new_version, on_done)
local version_str = ""
if old_version and new_version then
version_str = (" %s -> %s"):format(old_version, new_version)
elseif new_version then
version_str = " " .. new_version
end
utils.info(("Installing %s%s"):format(self.config.name, version_str), "LSP")
local handle = pkg:install({ version = new_version })
local err
handle:on(
"stderr",
vim.schedule_wrap(function(msg)
err = (err or "") .. msg
end)
)
handle:once(
"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")
self:post_install(pkg)
else
if err then
err = ":\n" .. err
else
err = ""
end
utils.err(("Failed to install %s%s"):format(self.config.name, err), "LSP")
end
if on_done then
on_done(is_installed)
end
end)
)
end
--- Perform installation
---@param on_done fun(success: boolean)?
function M:install(on_done)
local registry = require("mason-registry")
local success, result = pcall(registry.get_package, self.config.name)
if not success then
utils.err(("Failed to get package %s:\n%s"):format(self.config.name, result), "LSP")
if on_done then
on_done(false)
end
return
end
local pkg = result
if not pkg:is_installed() then
self:handle_install(pkg, nil, self.config.version, on_done)
elseif self.config.version then
pkg:get_installed_version(function(ok, version_or_err)
if not ok then
utils.err(
("Failed to check currently installed version for %s:\n%s"):format(
self.config.name,
version_or_err
),
"LSP"
)
elseif self.config.version ~= version_or_err then
self:handle_install(pkg, version_or_err, self.config.version, on_done)
return
end
if on_done then
on_done(ok)
end
end)
else
pkg:check_new_version(function(update_available, res)
local ok = type(res) == "table" or res == "Package is not outdated."
if not ok then
utils.err(
("Failed to check for update for %s:\n%s"):format(self.config.name, res),
"LSP"
)
elseif update_available then
self:handle_install(pkg, res.current_version, res.latest_version, on_done)
return
end
if on_done then
on_done(ok)
end
end)
return
end
end
--- Install package dependencies
---@param on_done fun(success: boolean)?
function M:install_dependencies(on_done)
local total = #self.dependencies
local completed = 0
local all_successful = true
--- Handle install result
---@param success boolean
local function handle_result(success)
completed = completed + 1
if not success then
all_successful = false
end
if completed == total and on_done then
on_done(all_successful)
end
end
for _, dep in ipairs(self.dependencies) do
dep:install_with_dependencies(handle_result)
end
end
--- Install package and any defined dependencies
---@param on_done fun(success: boolean)?
function M:install_with_dependencies(on_done)
--- Handle install result
---@param success boolean
local function handle_result(success)
if success then
self:install(on_done)
elseif on_done then
on_done(success)
end
end
require("mason-registry").refresh(function()
if self.dependencies and #self.dependencies ~= 0 then
self:install_dependencies(handle_result)
else
self:install(on_done)
end
end)
end
--- Create a new instance
---@param config MasonPackageConfig|string
---@return MasonPackage|nil
function M.new(config)
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
pkg.dependencies = {}
for _, dep_cfg in ipairs(config.dependencies) do
local dep = M.new(dep_cfg)
if dep then
table.insert(pkg.dependencies, dep)
end
end
end
return setmetatable(pkg, M)
end
return M
-344
View File
@@ -1,344 +0,0 @@
local keymap = require("lsp.keymap")
local utils = require("utils")
---@class Linter
local Linter = require("lsp.linter")
---@class MasonPackage
local MasonPackage = require("lsp.package")
---@class Server
---@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? string|MasonPackageConfig
---@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 },
dependencies = {
config.dependencies,
function(f)
return utils.is_list_or_nil(f, "string")
end,
"list of strings or nil",
},
mason = { config.mason, { "string", "table" }, true },
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, { "string", "table" } },
lhs = { key.lhs, "string" },
rhs = { key.rhs, { "string", "function" } },
opts = { key.opts, "table", 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
--- Called when language server attaches
---@param client vim.lsp.Client
---@param bufnr integer
function M:on_attach(client, bufnr)
if self.client and self.client.id ~= client.id then
self.client:stop(true)
end
self.client = client
self.attached_buffers = self.attached_buffers or {}
table.insert(self.attached_buffers, bufnr)
keymap:init(self, bufnr)
if self.linters then
for _, linter in ipairs(self.linters) do
local bin = linter.config.cmd[1]
if utils.is_executable(bin) then
linter:init(bufnr)
else
utils.warn(
("Not adding %s because it is not installed"):format(bin)
)
end
end
end
-- For document highlight
vim.cmd.highlight({ "link LspReferenceRead Visual", bang = true })
vim.cmd.highlight({ "link LspReferenceText Visual", bang = true })
vim.cmd.highlight({ "link LspReferenceWrite Visual", bang = true })
---@alias lsp.Client vim.lsp.Client
-- require("lsp_compl").attach(client, bufnr, {
-- server_side_fuzzy_completion = true,
-- })
end
--- Configure the LSP client
function M:configure_client()
local lspconfig = require("lspconfig")
local capabilities = vim.lsp.protocol.make_client_capabilities()
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()
)
end
-- local epo = utils.try_require("epo")
-- if epo then
-- capabilities = vim.tbl_deep_extend(
-- "force",
-- capabilities,
-- epo.register_cap()
-- )
-- end
-- local lsp_compl = utils.try_require("lsp_compl")
-- if lsp_compl then
-- capabilities = vim.tbl_deep_extend("force", capabilities, lsp_compl.capabilities())
-- end
--
self.config.lspconfig.capabilities = capabilities
self.config.lspconfig.on_attach = function(client, bufnr)
local ok, ret = pcall(self.on_attach, self, client, bufnr)
if not ok then
utils.err(
("Failed to load on_attach for %s:\n%s"):format(self.name, ret),
"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
)
)
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()
local filetypes = self.config.lspconfig.filetypes or {}
if not vim.list_contains(filetypes, self.config.lspconfig.filetype) then
table.insert(filetypes, self.config.lspconfig.filetype)
end
if #filetypes == 0 then
return {}
end
return vim.tbl_filter(function(bufnr)
return vim.list_contains(filetypes, vim.bo[bufnr].filetype)
end, vim.api.nvim_list_bufs())
end
--- Check for and return missing dependencies
---@return table<string>
function M:get_missing_unmanaged_deps()
local missing_deps = {}
if self.config.dependencies ~= nil then
for _, dep in ipairs(self.config.dependencies) do
if not utils.is_executable(dep) then
table.insert(missing_deps, dep)
end
end
end
return missing_deps
end
--- Install LSP server
---@param on_done fun(success: boolean)?
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
end
self.mason:install_with_dependencies(handle_result)
end
--- Setup LSP server
function M:setup(on_done)
local missing_deps = self:get_missing_unmanaged_deps()
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.config.enable = false
return
end
if self.mason then
self:install(function(success)
if success then
self:configure_client()
end
if on_done then
on_done(success)
end
end)
elseif vim.fn.executable(self.config.lspconfig.cmd[1]) == 1 then
self:configure_client()
else
utils.warn(self.name .. " not installed, disabling")
self.config.enable = false
end
end
--- Load autocmd for setting up LSP server upon entering a buffer of related
--- filetype
function M:init(on_done)
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(on_done)
end,
group = group,
})
end
function M:deinit()
if self.attached_buffers then
for _, bufnr in ipairs(self.attached_buffers) do
keymap:deinit(bufnr)
end
end
if self.client then
self.client:stop(true)
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|nil
function M.new(name, config)
config = config or {}
if not M.validate(name, config) then
return
end
local ok, resp = pcall(require, "lspconfig.configs." .. 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
end
return setmetatable(server, M)
end
return M