Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
666c81c
feat(utils): add discovery-first tool resolution helpers
charliie-dev Jul 16, 2026
fe6faa0
refactor(lsp): resolve language servers discovery-first
charliie-dev Jul 16, 2026
22c1c25
refactor(mason): resolve formatters and linters discovery-first
charliie-dev Jul 16, 2026
b23f4ad
refactor(dap): resolve debug adapters discovery-first
charliie-dev Jul 16, 2026
9bc4d08
fix(lsp): drop traefik v2 schema in favor of a v3 entry
charliie-dev Jul 16, 2026
81b241a
refactor(dap): drop adapter_meta and bare-name executable probing
charliie-dev Jul 16, 2026
6b1fa54
fix(dap): error on nil adapter config instead of silent default_setup…
charliie-dev Jul 16, 2026
4908db3
perf(tooling): keep blocking probes out of phase-1 resolution
charliie-dev Jul 16, 2026
5854685
fix(tooling): resolve and rewrite function-form commands consistently
charliie-dev Jul 16, 2026
923e4c7
refactor(tooling): consolidate duplicated module loading and registry…
charliie-dev Jul 16, 2026
e7510dd
fix(lint): relint all open buffers when a linter configures late
charliie-dev Jul 16, 2026
88ff458
refactor(tooling): drop the installing shadow cache for mason handle …
charliie-dev Jul 16, 2026
6f4e03d
docs(tooling): de-bloat discovery-first comments
charliie-dev Jul 16, 2026
92e8fe4
fix(tooling): avoid empty $path entry when appending mason bin dir
charliie-dev Jul 16, 2026
4eb8ccb
fix(dap): spawn debugpy fallback by the probed interpreter's exepath
charliie-dev Jul 16, 2026
bb37048
fix(dap): harden debugpy probe dedup and align probe with spawn
charliie-dev Jul 16, 2026
f080f0b
fix(lint): dedup the linters_for_ft union to mirror upstream
charliie-dev Jul 16, 2026
1ccdf09
fix(dap): count a broken client config as existing in has_client_config
charliie-dev Jul 16, 2026
f3f9e2e
fix(lint): only classify a linter as broken when its re-require fails
charliie-dev Jul 16, 2026
e3cd7b1
fix(tooling): state the mason bin probe in exepath_or_error's message
charliie-dev Jul 16, 2026
6602291
fix(dap): state the mason bin probe in debugpy error messages
charliie-dev Jul 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 21 additions & 19 deletions lua/core/settings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,15 @@ settings["external_browser"] = "chrome-cli open"
---@type boolean
settings["lsp_inlayhints"] = false

-- LSPs installed outside Mason (e.g. via system package manager).
-- These will be configured but not installed by Mason.
-- Key: lspconfig server name, Value: executable name to check availability.
---@type table<string, string>
settings["external_lsp_deps"] = {
nixd = "nixd",
nil_ls = "nil",
shuck = "shuck", -- shell linter/formatter/LSP (Rust); installed via mise, not Mason
-- dartls = "dart",
}

-- LSPs to install during bootstrap.
-- Language servers to enable, resolved discovery-first at runtime: binary on $PATH is
-- used as-is; else Mason installs it when it ships a package; else an aggregated warning
-- asks you to provision it. See `modules.utils.tools` and `completion/mason-lspconfig.lua`.
-- Full list: https://github.com/neovim/nvim-lspconfig/tree/master/lsp
---@type string[]
settings["lsp_deps"] = {
"bashls",
"clangd",
-- "dartls", -- Dart LSP (ships with the Dart SDK)
"dockerls",
"gh_actions_ls",
-- "gitlab_ci_ls",
Expand All @@ -111,7 +103,10 @@ settings["lsp_deps"] = {
"lua_ls",
"marksman",
"neocmake",
"nil_ls", -- Nix LSP; prefer the $PATH binary (Nix), else Mason installs it (package `nil`)
"nixd", -- Nix LSP (Rust); no Mason package, comes from Nix ($PATH)
"ruff",
"shuck", -- shell linter/formatter/LSP (Rust); no Mason package, installed via mise
"systemd_lsp",
"terraformls",
"tflint",
Expand All @@ -120,13 +115,13 @@ settings["lsp_deps"] = {
"zuban",
}

-- Formatters to install during bootstrap (Mason package names).
-- These are managed by Mason and used by conform.nvim.
-- Formatters to resolve when conform.nvim lazy-loads (first BufWritePre /
-- :Format). conform formatter names, resolved discovery-first like lsp_deps.
---@type string[]
settings["formatter_deps"] = {
"beautysh",
"clang-format",
"cmakelang",
"cmake_format",
"fixjson",
"gofumpt",
"goimports",
Expand All @@ -137,22 +132,29 @@ settings["formatter_deps"] = {
"stylua",
}

-- Linters to install during bootstrap (Mason package names).
-- These are managed by Mason and used by nvim-lint.
-- Linters to resolve when nvim-lint lazy-loads (first BufReadPost). nvim-lint
-- linter names, resolved discovery-first like formatter_deps.
---@type string[]
settings["linter_deps"] = {
"actionlint",
"hadolint",
"markdownlint-cli2",
"oxlint",
-- "rumdl", -- markdownlint Rust rewrite; waiting for rule coverage to mature
"golangci-lint",
"golangcilint",
"selene",
"shellcheck",
"systemdlint",
}

-- Debug Adapter Protocol (DAP) clients to install and configure during bootstrap.
-- Deadline (ms) for background Mason work before the aggregated missing-tool warning is
-- flushed anyway, so one stalled download can't suppress it all session. A finished
-- install past the deadline is still configured; non-positive values use the default.
---@type number
settings["tool_install_timeout"] = 300000

-- DAP adapters to enable (mason-nvim-dap adapter names), resolved
-- discovery-first when nvim-dap lazy-loads (first :Dap* command).
-- Supported DAPs: https://github.com/jay-babu/mason-nvim-dap.nvim/blob/main/lua/mason-nvim-dap/mappings/source.lua
---@type string[]
settings["dap_deps"] = {
Expand Down
79 changes: 79 additions & 0 deletions lua/modules/configs/completion/conform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,85 @@ return function()
end or false,
})

-- Resolve `formatter_deps` (conform formatter names) discovery-first against conform's
-- own registry. Runs synchronously before lazy.nvim replays BufWritePre so the
-- off-$PATH rewrite is in place for the first format-on-save under Mason PATH = "skip".
local tools = require("modules.utils.tools")
tools.resolve_runtime_tools("conform.nvim", settings.formatter_deps, function(name)
-- get_formatter_config is conform's @private API; if dropped, degrade to
-- "resolves itself" rather than misreporting every formatter as unknown.
local conform = require("conform")
if type(conform.get_formatter_config) ~= "function" then
return { binary = nil }
end
-- get_formatter_config runs a function-form override directly, so pcall keeps a
-- throwing override (a broken config) from being misread as an unknown name.
local ok, config, err = pcall(conform.get_formatter_config, name)
if not ok then
return { broken = tostring(config) }
end
if config then
if type(config.command) == "function" then
-- Function-form command (e.g. builtin `from_node_modules`): evaluate it with
-- a hand-built ctx for a representative answer; per-buffer differences are
-- covered by configure's wrapper below.
local buf = vim.api.nvim_get_current_buf()
local filename = vim.api.nvim_buf_get_name(buf)
local ok_cmd, cmd = pcall(config.command, config, {
buf = buf,
filename = filename,
dirname = vim.fs.dirname(filename),
})
if not ok_cmd then
return { broken = tostring(cmd) }
end
return { binary = type(cmd) == "string" and cmd or nil }
end
return { binary = config.command }
end
-- (nil, err) is a real formatter with a broken config; bare nil is an unknown name.
if type(err) == "string" then
return { broken = err }
end
return nil
end, function(name)
-- Under Mason PATH = "skip" the binary is off $PATH; rewrite it to an absolute
-- path so conform doesn't spawn a bare name that fails. No-op on $PATH.
local conform = require("conform")
local prev = conform.formatters[name]
if type(prev) == "function" then
-- Function-form override resolves per buffer: wrap it to rewrite an off-$PATH
-- result, preserving the override's args/cwd.
conform.formatters[name] = tools.wrap_off_path(prev, "command")
return
end
if type(conform.get_formatter_config) ~= "function" then
return
end
local config = conform.get_formatter_config(name)
if not config then
return
end
if type(config.command) == "function" then
-- Function-form command resolves per buffer: wrap it to rewrite an off-$PATH
-- result at format time; (self, ctx) pass through.
conform.formatters[name] = vim.tbl_deep_extend(
"force",
type(prev) == "table" and prev or {},
{ command = tools.wrap_off_path(config.command) }
)
return
end
if type(config.command) ~= "string" then
return
end
local abs = tools.off_path_command(config.command)
if abs then
conform.formatters[name] =
vim.tbl_deep_extend("force", type(prev) == "table" and prev or {}, { command = abs })
end
end)

-- User commands
vim.api.nvim_create_user_command("Format", function(args)
local range = nil
Expand Down
28 changes: 5 additions & 23 deletions lua/modules/configs/completion/lsp.lua
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
return function()
-- Server resolution (Mason-installed / on $PATH / installable / missing) is handled
-- discovery-first in `mason-lspconfig.setup`, driven by `settings.lsp_deps`.
require("completion.mason-lspconfig").setup()

local opts = {
capabilities = require("modules.utils").get_lsp_capabilities(),
}
-- Configure LSPs that are not managed by Mason but are available in `nvim-lspconfig`.
-- Servers are defined in `settings.external_lsp_deps` as { server_name = "executable" }.
for lsp_name, exe in pairs(require("core.settings").external_lsp_deps) do
if vim.fn.executable(exe) == 1 then
local ok, _opts = pcall(require, "user.configs.lsp-servers." .. lsp_name)
if not ok then
local default_ok, default_opts = pcall(require, "completion.servers." .. lsp_name)
if default_ok then
_opts = default_opts
end
end
if type(_opts) == "table" then
local final_opts = vim.tbl_deep_extend("keep", _opts, opts)
require("modules.utils").register_server(lsp_name, final_opts)
else
require("modules.utils").register_server(lsp_name, opts)
end
end
end

-- A server Mason installs mid-session registers only when its install finishes (after
-- this point), so a `vim.lsp.config` override here gets overwritten by the repo spec.
-- Overrides that must always win belong in `user.configs.lsp-servers.<name>`.
pcall(require, "user.configs.lsp")

-- Start LSPs
Expand Down
Loading
Loading