Skip to content
Merged
Show file tree
Hide file tree
Changes from 44 commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
0392395
feat(tooling): add a shared discovery-first tool resolver
charliie-dev Jul 20, 2026
8316907
feat(settings): move dep lists to resolver-native names
charliie-dev Jul 20, 2026
4e933b3
feat(lsp): resolve language servers discovery-first
charliie-dev Jul 20, 2026
229ec3c
feat(conform): resolve formatters discovery-first
charliie-dev Jul 20, 2026
a72c766
feat(lint): resolve linters discovery-first
charliie-dev Jul 20, 2026
5642ed3
feat(dap): resolve debug adapters discovery-first
charliie-dev Jul 20, 2026
7bb2678
refactor(mason): drop the bootstrap ensure-install loop
charliie-dev Jul 20, 2026
9ed2a05
refactor(lsp): resolve the traefik schema conflict by file pattern
charliie-dev Jul 20, 2026
5cf756d
feat(tooling): hand off mid-session installs to pending configures
charliie-dev Jul 20, 2026
cf90b27
fix(lsp): make user override reads install-timing independent
charliie-dev Jul 20, 2026
cfeefa4
fix(lint): surface the real load error for a broken linter module
charliie-dev Jul 20, 2026
8a911df
fix(tooling): keep concrete failure reasons out of the dedup sink
charliie-dev Jul 20, 2026
c2fa84b
fix(lsp): consult vim.lsp.config as the server discovery truth source
charliie-dev Jul 20, 2026
b74e53d
fix(tooling): report invalid dep entries and unresolvable tools honestly
charliie-dev Jul 20, 2026
a75823a
fix(conform): stop reporting buffer-conditional overrides as typos
charliie-dev Jul 20, 2026
d67d14e
fix(lsp): scope the traefik schema prune to the claimed files
charliie-dev Jul 20, 2026
5e1c478
fix(dap): survive partial mason-nvim-dap mapping drift
charliie-dev Jul 20, 2026
252ebd5
refactor(tooling): sink the no-fall-through config contract into tools
charliie-dev Jul 20, 2026
df782e2
perf(tooling): move deferral ownership into the resolver
charliie-dev Jul 20, 2026
a20707b
perf(dap): keep mason off the :Dap tick for provisioned sessions
charliie-dev Jul 20, 2026
4a33b30
perf(lsp): defer registry classification off the first file open
charliie-dev Jul 20, 2026
88be21c
perf(dap): cache the negative debugpy import probe for the session
charliie-dev Jul 20, 2026
b543856
perf(tooling): memoize hot registry and mason-root lookups
charliie-dev Jul 20, 2026
4958f0f
fix(lint): propagate a failed post-install linter reload
charliie-dev Jul 20, 2026
ee9aafa
refactor(lint): remove the unreachable factory-wrapper machinery
charliie-dev Jul 20, 2026
535b8e9
refactor(dap): probe debugpy through the supported mason surface
charliie-dev Jul 20, 2026
8961821
refactor(conform): drop the dead format-on-save re-check
charliie-dev Jul 20, 2026
1f1d5b7
fix(settings): warn when a stale external_lsp_deps override survives
charliie-dev Jul 20, 2026
953b2be
docs(settings): correct the linter_deps resolution timing
charliie-dev Jul 20, 2026
798151c
docs(tooling): include the unresolved state in the probe annotation
charliie-dev Jul 20, 2026
0c0a436
fix(lsp): use the documented gh-dash schema url
charliie-dev Jul 20, 2026
17dfa3a
fix(dap): re-probe debugpy on a ttl with a finite retry budget
charliie-dev Jul 20, 2026
89201ab
fix(dap): restore enumerable opts for user overrides and name mapping…
charliie-dev Jul 20, 2026
4260acd
fix(settings): classify external_lsp_deps residue before advising
charliie-dev Jul 20, 2026
9f40f9e
fix(tooling): strip only real chunkname prefixes from error reasons
charliie-dev Jul 20, 2026
0c24473
refactor(tooling): carry the unresolved reason on the probe result
charliie-dev Jul 20, 2026
5a6aaf3
fix(conform): report a vanished get_formatter_config as unresolved
charliie-dev Jul 20, 2026
41c1dba
fix(lsp): bound the traefik prune's brace matcher and fail closed pas…
charliie-dev Jul 20, 2026
8cf62af
perf(lsp): resolve language servers per filetype
charliie-dev Jul 20, 2026
9f56985
docs(settings): describe the per-filetype lsp_deps timing
charliie-dev Jul 20, 2026
25bf650
fix(tooling): drop the bin-to-package index on registry updates
charliie-dev Jul 20, 2026
39936df
fix(lsp): refetch the mason mapping after a registry update
charliie-dev Jul 20, 2026
d87a337
refactor(tooling): consolidate resolver bookkeeping into single sources
charliie-dev Jul 20, 2026
f9d1114
refactor(dap): inline the single-caller client-config loader
charliie-dev Jul 20, 2026
3712ef3
fix(lint): add an idle-gated parity sweep for never-opened filetypes
charliie-dev Jul 21, 2026
e6f08f8
fix(dap): raise spawn-free at config time when mason can provision de…
charliie-dev Jul 21, 2026
0e7e391
fix(dap): guard the venv fallback against a nil cwd
charliie-dev Jul 21, 2026
84edddd
fix(lsp): guard get_mappings against mason-lspconfig api drift
charliie-dev Jul 21, 2026
e14f445
fix(tooling): flag each registry event subscription separately
charliie-dev Jul 21, 2026
84c5855
fix(dap): validate the delve remote-attach host shape
charliie-dev Jul 21, 2026
c9ac3d0
fix(tooling): let a set-up mason.settings root win over a cached $MASON
charliie-dev Jul 21, 2026
ea74de8
fix(tooling): re-check $PATH membership on every ensure_mason_on_path…
charliie-dev Jul 21, 2026
87ad594
refactor(tooling): share the lazy mason-registry thunk across resolvers
charliie-dev Jul 21, 2026
8daf5be
fix(tooling): normalize a drifted registry thunk result in phase 2
charliie-dev Jul 21, 2026
6242a03
refactor(lsp): classify lsp_deps through split_dep_names
charliie-dev Jul 21, 2026
aad58a4
refactor(lsp): extract the one cmd-to-binary rule
charliie-dev Jul 21, 2026
6c6fe32
refactor(tooling): share the pending-binaries probe across hand-off p…
charliie-dev Jul 21, 2026
fc00806
refactor(tooling): extract the string-or-nil sanitizer
charliie-dev Jul 21, 2026
9656317
refactor(tooling): drop the normalize_names wrapper
charliie-dev Jul 21, 2026
3642567
docs(tooling): label mason_root as the tested root-priority contract
charliie-dev Jul 21, 2026
6601542
refactor(dap): return the override verdict instead of the winning module
charliie-dev Jul 21, 2026
bff4a12
docs(lsp): label the harness-facing exports
charliie-dev Jul 21, 2026
2fa8a0d
docs(tooling): de-rot and reattach the resolver docstrings
charliie-dev Jul 21, 2026
e04e565
docs(lsp): de-rot the replay, sweep, and shuck comments
charliie-dev Jul 21, 2026
fe9d466
docs(lint): de-rot the override and sweep comments
charliie-dev Jul 21, 2026
31f20ec
docs(conform): de-rot the prettier override and drop restating comments
charliie-dev Jul 21, 2026
e181a23
docs(dap): correct the loader and adapter comments
charliie-dev Jul 21, 2026
26e0618
docs(settings): de-rot dep-list comments and fix the palette type
charliie-dev Jul 21, 2026
e92954c
chore(tool): drop the commented-out remote-nvim spec
charliie-dev Jul 21, 2026
6df2354
docs(settings): suppress missing-fields on the partial palette override
charliie-dev Jul 21, 2026
c53bd6e
fix(dap): bound the debugpy import probe with a timeout
charliie-dev Jul 21, 2026
5b13fe4
fix(lsp): close the override-recording window after user.configs.lsp …
charliie-dev Jul 21, 2026
dc78a40
fix(lsp): apply one container-shape policy to lsp_deps at both consumers
charliie-dev Jul 21, 2026
20dc184
fix(tooling): keep phase-1 configure failures reachable by the retry …
charliie-dev Jul 21, 2026
e621a9c
fix(tooling): report retry failures with their fresh reasons
charliie-dev Jul 21, 2026
bd0bc45
fix(tooling): retract the timeout placeholder when an install settles…
charliie-dev Jul 21, 2026
55abcce
fix(dap): drop the spawn-free raise; always probe before provisioning…
charliie-dev Jul 21, 2026
9e39551
fix(dap): degrade the debugpy probe latch to a slow ttl instead of pe…
charliie-dev Jul 21, 2026
3c5f827
fix(lint): detect github workflow files as yaml.github
charliie-dev Jul 21, 2026
ed482ac
fix(lsp): claim yaml.github for the workflow-facing servers
charliie-dev Jul 21, 2026
7e42251
fix(lint): bound the parity sweep with a fallback timer when idle pre…
charliie-dev Jul 21, 2026
13417d4
fix(lint): memoize the broken-linter verdict so the retry's side effe…
charliie-dev Jul 21, 2026
f19103e
fix(lsp): guard the parity-sweep timer with a re-source generation token
charliie-dev Jul 21, 2026
88a879a
fix(lsp): skip the read trigger when the rollback snapshot cannot be …
charliie-dev Jul 21, 2026
883eacb
fix(tooling): never migrate an attempted-and-failed tool to the typo …
charliie-dev Jul 21, 2026
0c0aada
fix(tooling): read $MASON live instead of latching the first value
charliie-dev Jul 21, 2026
bc2b596
refactor(dap): share the remote-attach endpoint validation
charliie-dev Jul 21, 2026
8d2a09e
fix(dap): surface a broken mason-nvim-dap load instead of reading it …
charliie-dev Jul 21, 2026
7ef3c23
refactor(lsp): fold the yamlls brace expansion into one worklist helper
charliie-dev Jul 21, 2026
8b7b5d8
perf(lsp): stem-gate the traefik prune before the brace matcher
charliie-dev Jul 21, 2026
9a61036
perf(lint): re-lint only the late-configured linter
charliie-dev Jul 21, 2026
ad6ccb4
docs(lint): reattach resolve_batch's docstring to its function
charliie-dev Jul 21, 2026
d83223b
refactor(tooling): hoist the shared parity-sweep delay into tools
charliie-dev Jul 21, 2026
81ebf2f
refactor(tooling): consolidate collector bookkeeping into one entries…
charliie-dev Jul 21, 2026
62379cc
docs(tooling): de-rot two comments the group-3 gate flagged
charliie-dev Jul 21, 2026
d3e157a
refactor(tooling): adopt the stdlib idioms for dep-list forwarding an…
charliie-dev Jul 22, 2026
280cd91
refactor(tooling): sanitize hook reasons through str_or_nil
charliie-dev Jul 22, 2026
9c86c57
refactor(lsp): hoist one resolved_config helper for the truth-source …
charliie-dev Jul 22, 2026
efb8bea
refactor(lsp): drain resolve_remaining through resolve_ft
charliie-dev Jul 22, 2026
e02a1a6
fix(lsp): warn when the resolve bridges run before setup
charliie-dev Jul 22, 2026
a4a7573
refactor(dap): build the eager handler opts through one constructor
charliie-dev Jul 22, 2026
9ba25cb
refactor(lsp): resolve filetype overrides by event instead of a hardc…
charliie-dev Jul 22, 2026
6a18bb3
fix(lint): give the plugin-loading buffer its initial lint
charliie-dev Jul 22, 2026
7d143b1
docs(tooling): correct two LuaDoc annotations from copilot review
charliie-dev Jul 22, 2026
aaea9fe
fix(dap): validate attach_endpoint's arguments up front
charliie-dev Jul 22, 2026
afb1ff1
fix(dap): validate opts.default_port to the same 1-65535 contract as …
charliie-dev Jul 22, 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
111 changes: 91 additions & 20 deletions lua/core/settings.lua
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,19 @@ 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. Names whose filetypes lspconfig knows resolve on that
-- filetype's FIRST buffer (a late sweep classifies the rest once per session); names
-- with user overrides, repo modules that override filetypes, or no filetype data
-- resolve on the first file open. 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 +107,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,39 +119,56 @@ 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",
"mdsf",
"nixfmt", -- Nix formatter; prefer the $PATH binary (Nix)
"prettier",
"superhtml",
"shellharden",
"statix", -- Nix linter, its `fix` mode doubles as a conform formatter; from Nix ($PATH)
"stylua",
}

-- Linters to install during bootstrap (Mason package names).
-- These are managed by Mason and used by nvim-lint.
-- Linters to resolve discovery-first (nvim-lint linter names). A name mapped
-- to a filetype resolves on that filetype's FIRST matching event after
-- nvim-lint lazy-loads (the resolve-only FileType autocmd or a lint event) —
-- nothing is installed or warned about before such a buffer opens; unmapped
-- names (typos, manual-only linters) get an immediate deferred pass instead.
---@type string[]
settings["linter_deps"] = {
"actionlint",
"deadnix", -- Nix dead-code linter; prefer the $PATH binary (Nix)
"hadolint",
"markdownlint-cli2",
"oxlint",
-- "rumdl", -- markdownlint Rust rewrite; waiting for rule coverage to mature
"golangci-lint",
"golangcilint",
"selene",
"shellcheck",
"shuck", -- shell linter for yaml.github `run:` blocks; no Mason package, installed via mise
"statix", -- Nix linter; prefer the $PATH binary (Nix)
"systemdlint",
"zsh", -- `zsh -n` syntax check via the system shell itself
}

-- Debug Adapter Protocol (DAP) clients to install and configure during bootstrap.
-- Deadline (ms) for background Mason work before the aggregated missing-tool warning
-- flushes anyway. Gates each tracked install (its own window) AND the registry refresh
-- wait; late completions still recover. Missing or non-positive values fall back to
-- the resolver's DEFAULT_TOOL_INSTALL_TIMEOUT_MS (300000) in `modules/utils/tools.lua`.
---@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 Expand Up @@ -242,4 +258,59 @@ settings["dashboard_image"] = {
[[⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠿⠿⢿⠿⠷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀]],
}

return require("modules.utils").extend_config(settings, "user.settings")
local merged = require("modules.utils").extend_config(settings, "user.settings")

-- Migration guard: the discovery-first refactor removed this key; a stale
-- user/settings.lua would merge it in and feed nothing — its servers would
-- vanish without a word. (Scheduled: the notifier plugin isn't loaded this
-- early; the default notify still lands in :messages.)
if merged.external_lsp_deps ~= nil then
-- The removed setting was a MAP of server name -> executable name, but a
-- stale override can survive in any shape: classify before advising so the
-- guidance never presents numeric indices as keys, never drops entries a
-- half-migrated LIST residue still carries, and always names the final
-- step (deleting the dead key). Neither group suppresses the other.
local string_keys, list_items = {}, {}
if type(merged.external_lsp_deps) == "table" then
for k, v in pairs(merged.external_lsp_deps) do
if type(k) == "string" then
string_keys[#string_keys + 1] = k
elseif type(k) == "number" and type(v) == "string" then
list_items[#list_items + 1] = v
end
end
table.sort(string_keys)
table.sort(list_items)
end
local guidance
if #string_keys > 0 and #list_items > 0 then
guidance = "Move its KEYS ("
.. table.concat(string_keys, ", ")
.. ") AND its list entries ("
.. table.concat(list_items, ", ")
.. ")\n— all of them server names — into `lsp_deps`, then delete `external_lsp_deps`."
elseif #string_keys > 0 then
guidance = "Move its KEYS ("
.. table.concat(string_keys, ", ")
.. ") — the server names, not the\n"
.. "executable values — into `lsp_deps`, then delete `external_lsp_deps`."
elseif #list_items > 0 then
guidance = "It now holds a LIST ("
.. table.concat(list_items, ", ")
.. ") — those are already the\n"
.. "server names; move them into `lsp_deps` and delete `external_lsp_deps`."
else
guidance = "It is empty or not a map — delete `external_lsp_deps` from user/settings.lua."
end
-- (Scheduled: the notifier plugin isn't loaded this early; the default
-- notify still lands in :messages.)
vim.schedule(function()
vim.notify(
"`external_lsp_deps` was removed: non-Mason servers are now discovered\nfrom $PATH. " .. guidance,
vim.log.levels.WARN,
{ title = "core.settings" }
)
end)
end

return merged
86 changes: 72 additions & 14 deletions lua/modules/configs/completion/conform.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,19 @@ return function()
return false
end

---The gates a buffer must pass before an automatic format, grouped into one
---named predicate for the format_on_save callback's readability. The
---format_on_save SETTING is not re-checked here: its gate lives at the
---single place the callback is installed (format_on_save = enabled and …).
---@param bufnr integer
---@return boolean
local function autoformat_allowed(bufnr)
return block_list[vim.bo[bufnr].filetype] ~= true
and not is_disabled_workspace(bufnr)
and not vim.g.disable_autoformat
and not vim.b[bufnr].disable_autoformat
end

---Format only git-modified lines using gitsigns hunks + conform range format
---@param bufnr integer
---@return boolean @true if modifications were formatted
Expand Down Expand Up @@ -87,6 +100,8 @@ return function()
return true
end

local tools = require("modules.utils.tools")

require("modules.utils").load_plugin("conform", {
default_format_opts = {
timeout_ms = format_timeout,
Expand Down Expand Up @@ -132,27 +147,18 @@ return function()
args = { "fix", "--stdin" },
stdin = true,
},
-- prettier: stdin mode does not work under bun's node shim,
-- so use --write (file-based) mode instead.
-- prettier: stdin is broken under bun's node shim, so use --write on
-- conform's temp copy (`stdin = false` points $FILENAME at a
-- `.conform.$RANDOM.*` copy; the real file is never touched).
prettier = {
command = "prettier",
args = { "--write", "$FILENAME" },
stdin = false,
},
},
format_on_save = format_on_save_enabled and function(bufnr)
-- Check disabled filetypes
if block_list[vim.bo[bufnr].filetype] == true then
return
end

-- Check disabled workspaces
if is_disabled_workspace(bufnr) then
return
end

-- Check global toggle
if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then
-- Disabled filetypes, disabled workspaces, and the global/buffer toggles
if not autoformat_allowed(bufnr) then
return
end

Expand All @@ -168,6 +174,58 @@ return function()
end or false,
})

-- Resolve `formatter_deps` (conform formatter names) discovery-first against
-- conform's own registry, so a missing formatter is installed / reported.
-- The probe only drives install/warn — nothing on the save path reads it —
-- so `defer` moves the resolve off the BufWritePre tick that lazy-loaded
-- conform; the resolver itself keeps the same-tick guarantee that Mason's
-- bin dir is on $PATH before the replayed save's spawns.
tools.resolve_runtime_tools("conform.nvim", settings.formatter_deps, function(name)
-- get_formatter_config is conform's @private API; if it vanishes, every
-- formatter is UNVERIFIABLE — report unresolved with the reason (missing
-- bucket, immediate flush) instead of silently classifying them all as
-- self-resolving, which would turn off installs and warnings wholesale.
local conform = require("conform")
if type(conform.get_formatter_config) ~= "function" then
return {
unresolved = true,
reason = "conform.get_formatter_config is unavailable (conform API drift?) — formatters cannot be verified",
}
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
-- A function-form command resolves per buffer at format time (e.g. the
-- builtin from_node_modules): treat it as self-resolving rather than
-- evaluating it for a representative binary — a node_modules command
-- shouldn't map to a Mason install anyway.
if type(config.command) == "function" then
return { binary = 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
-- A function-form override may legitimately return nil for the
-- probe-time buffer (this probe runs on a scheduled tick against
-- whatever buffer happens to be current): its existence proves the
-- name real, but nothing is verifiable — report it unresolved
-- (missing bucket, tailored reason) instead of a typo or a silent pass.
local overrides = conform.formatters
if type(overrides) == "table" and type(overrides[name]) == "function" then
-- The reason rides on the probe result: the phrasing is conform's,
-- not the shared resolver's (nvim-lint shares resolve_runtime_tools).
return { unresolved = true, reason = "config resolves per buffer and could not be verified at startup" }
end
return nil
end, nil, { defer = true })

-- User commands
vim.api.nvim_create_user_command("Format", function(args)
local range = nil
Expand Down
34 changes: 11 additions & 23 deletions lua/modules/configs/completion/lsp.lua
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
return function()
-- Handler/probe machinery first (no discovery yet): sets up the read
-- trigger the override pass below relies on.
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
-- Run `user.configs.lsp` with its vim.lsp.config registrations recorded: a
-- mid-session install registers after this point, and the replay keeps the
-- user's overrides on top regardless of timing.
-- `user.configs.lsp-servers.<name>` remains the richer per-server hook.
require("completion.mason-lspconfig").run_user_lsp_overrides()

pcall(require, "user.configs.lsp")
-- Discovery LAST (Mason-installed / on $PATH / installable / missing,
-- driven by `settings.lsp_deps`): user runtime registrations above must be
-- visible to the unknown/binary classification.
require("completion.mason-lspconfig").resolve_deps()

-- Start LSPs
pcall(vim.cmd.LspStart)
Expand Down
Loading
Loading