@@ -40,6 +40,19 @@ return function()
4040 return false
4141 end
4242
43+ --- The gates a buffer must pass before an automatic format, grouped into one
44+ --- named predicate for the format_on_save callback's readability. The
45+ --- format_on_save SETTING is not re-checked here: its gate lives at the
46+ --- single place the callback is installed (format_on_save = enabled and …).
47+ --- @param bufnr integer
48+ --- @return boolean
49+ local function autoformat_allowed (bufnr )
50+ return block_list [vim .bo [bufnr ].filetype ] ~= true
51+ and not is_disabled_workspace (bufnr )
52+ and not vim .g .disable_autoformat
53+ and not vim .b [bufnr ].disable_autoformat
54+ end
55+
4356 --- Format only git-modified lines using gitsigns hunks + conform range format
4457 --- @param bufnr integer
4558 --- @return boolean @true if modifications were formatted
@@ -87,6 +100,8 @@ return function()
87100 return true
88101 end
89102
103+ local tools = require (" modules.utils.tools" )
104+
90105 require (" modules.utils" ).load_plugin (" conform" , {
91106 default_format_opts = {
92107 timeout_ms = format_timeout ,
@@ -132,31 +147,22 @@ return function()
132147 args = { " fix" , " --stdin" },
133148 stdin = true ,
134149 },
135- -- prettier: stdin mode does not work under bun's node shim,
136- -- so use --write (file-based) mode instead.
150+ -- prettier: the --write-on-temp-copy shape dates from the bun
151+ -- node-shim era (stdin was broken); mise ships real node now, so
152+ -- stdin likely works again — kept pending re-evaluation. `stdin =
153+ -- false` points $FILENAME at a `.conform.$RANDOM.*` copy; the real
154+ -- file is never touched.
137155 prettier = {
138156 command = " prettier" ,
139157 args = { " --write" , " $FILENAME" },
140158 stdin = false ,
141159 },
142160 },
143161 format_on_save = format_on_save_enabled and function (bufnr )
144- -- Check disabled filetypes
145- if block_list [vim .bo [bufnr ].filetype ] == true then
146- return
147- end
148-
149- -- Check disabled workspaces
150- if is_disabled_workspace (bufnr ) then
151- return
152- end
153-
154- -- Check global toggle
155- if vim .g .disable_autoformat or vim .b [bufnr ].disable_autoformat then
162+ if not autoformat_allowed (bufnr ) then
156163 return
157164 end
158165
159- -- Format only modified lines if enabled
160166 if format_modifications_only then
161167 if format_modifications (bufnr ) then
162168 return
@@ -168,6 +174,58 @@ return function()
168174 end or false ,
169175 })
170176
177+ -- Resolve `formatter_deps` (conform formatter names) discovery-first against
178+ -- conform's own registry, so a missing formatter is installed / reported.
179+ -- The probe only drives install/warn — nothing on the save path reads it —
180+ -- so `defer` moves the resolve off the BufWritePre tick that lazy-loaded
181+ -- conform; the resolver itself keeps the same-tick guarantee that Mason's
182+ -- bin dir is on $PATH before the replayed save's spawns.
183+ tools .resolve_runtime_tools (" conform.nvim" , settings .formatter_deps , function (name )
184+ -- get_formatter_config is conform's @private API; if it vanishes, every
185+ -- formatter is UNVERIFIABLE — report unresolved with the reason (missing
186+ -- bucket, immediate flush) instead of silently classifying them all as
187+ -- self-resolving, which would turn off installs and warnings wholesale.
188+ local conform = require (" conform" )
189+ if type (conform .get_formatter_config ) ~= " function" then
190+ return {
191+ unresolved = true ,
192+ reason = " conform.get_formatter_config is unavailable (conform API drift?) — formatters cannot be verified" ,
193+ }
194+ end
195+ -- get_formatter_config runs a function-form override directly, so pcall keeps a
196+ -- throwing override (a broken config) from being misread as an unknown name.
197+ local ok , config , err = pcall (conform .get_formatter_config , name )
198+ if not ok then
199+ return { broken = tostring (config ) }
200+ end
201+ if config then
202+ -- A function-form command resolves per buffer at format time (e.g. the
203+ -- builtin from_node_modules): treat it as self-resolving rather than
204+ -- evaluating it for a representative binary — a node_modules command
205+ -- shouldn't map to a Mason install anyway.
206+ if type (config .command ) == " function" then
207+ return { binary = nil }
208+ end
209+ return { binary = config .command }
210+ end
211+ -- (nil, err) is a real formatter with a broken config; bare nil is an unknown name.
212+ if type (err ) == " string" then
213+ return { broken = err }
214+ end
215+ -- A function-form override may legitimately return nil for the
216+ -- probe-time buffer (this probe runs on a scheduled tick against
217+ -- whatever buffer happens to be current): its existence proves the
218+ -- name real, but nothing is verifiable — report it unresolved
219+ -- (missing bucket, tailored reason) instead of a typo or a silent pass.
220+ local overrides = conform .formatters
221+ if type (overrides ) == " table" and type (overrides [name ]) == " function" then
222+ -- The reason rides on the probe result: the phrasing is conform's,
223+ -- not the shared resolver's (nvim-lint shares resolve_runtime_tools).
224+ return { unresolved = true , reason = " config resolves per buffer and could not be verified at startup" }
225+ end
226+ return nil
227+ end , nil , { defer = true })
228+
171229 -- User commands
172230 vim .api .nvim_create_user_command (" Format" , function (args )
173231 local range = nil
@@ -226,7 +284,7 @@ return function()
226284 end
227285 end , { nargs = 1 , complete = " filetype" })
228286
229- -- Auto stop shell LSPs for .env files (migrated from null-ls config) .
287+ -- Auto stop shell LSPs for .env files.
230288 -- Both bashls and shuck attach to .env's `sh` filetype and only add noise there.
231289 vim .api .nvim_create_autocmd (" LspAttach" , {
232290 callback = function (event )
0 commit comments