@@ -4,43 +4,43 @@ local log = require("null-ls.logger")
44local client = require (" null-ls.client" )
55
66local FORMATTING = methods .internal .FORMATTING
7- local NOTIFICATION_TITLE = " discovering `nix fmt` entrypoint"
8- local NOTIFICATION_TOKEN = " nix-flake-fmt-discovery"
9-
10- --- Asynchronously computes the command that `nix fmt` would run, or nil if
11- --- we're not in a flake with a formatter, or if we fail to discover the
12- --- formatter somehow. When finished, it invokes the `done` callback with a
13- --- single string|nil parameter identifier the `nix fmt` entrypoint if found.
14- ---
15- --- The formatter must follow treefmt's [formatter
16- --- spec](https://github.com/numtide/treefmt/blob/main/docs/formatter-spec.md).
17- ---
18- --- This basically re-implements the "entrypoint discovery" that `nix fmt` does.
19- --- So why are we doing this ourselves rather than just invoking `nix fmt`?
20- --- Unfortunately, it can take a few moments to evaluate all your nix code to
21- --- figure out the formatter entrypoint. It can even be slow enough to exceed
22- --- Neovim's default LSP timeout.
23- --- By doing this ourselves, we can cache the result.
24- local find_nix_fmt = function (opts , done )
25- done = vim .schedule_wrap (done )
267
8+ local run_job = function (opts )
279 local async = require (" plenary.async" )
2810 local Job = require (" plenary.job" )
2911
30- local run_job = async .wrap (function (_opts , _done )
12+ local _run_job = async .wrap (function (_opts , _done )
3113 _opts .on_exit = function (j , status )
3214 _done (status , j :result (), j :stderr_result ())
3315 end
3416
3517 Job :new (_opts ):start ()
3618 end , 2 )
3719
38- local tmpname = async .wrap (function (_done )
20+ return _run_job (opts )
21+ end
22+
23+ local tmpname = function ()
24+ local async = require (" plenary.async" )
25+
26+ local mktemp = async .wrap (function (_done )
3927 vim .defer_fn (function ()
4028 _done (vim .fn .tempname ())
4129 end , 0 )
4230 end , 1 )
31+ return mktemp ()
32+ end
4333
34+ --- Asynchronously build and return the formatter for the flake located at {root},
35+ --- If {root} is not a flake, or does not have a formatter, or we cannot build the formatter, return `nil`.
36+ --- This legacy codepath is quite complicated, and unnecessary now that `nix` has core support for
37+ --- returning the fromatter command.
38+ --- TODO: remove after the `nix formatter` subcommand has been released for a while.
39+ --- The command was introduced in https://github.com/NixOS/nix/commit/d155bb901241441149c701b9efc92f5785c2e1c3
40+ ---
41+ --- @param root string
42+ --- @return string | nil
43+ local legacy_find_nix_fmt = function (root )
4444 local get_current_system = function ()
4545 local status , stdout_lines , stderr_lines = run_job ({
4646 command = " nix" ,
@@ -65,7 +65,7 @@ local find_nix_fmt = function(opts, done)
6565 return nix_current_system
6666 end
6767
68- local get_flake_ref = function (root )
68+ local get_flake_ref = function (_root )
6969 local status , stdout_lines , stderr_lines = run_job ({
7070 command = " nix" ,
7171 args = {
@@ -74,7 +74,7 @@ local find_nix_fmt = function(opts, done)
7474 " flake" ,
7575 " metadata" ,
7676 " --json" ,
77- root ,
77+ _root ,
7878 },
7979 })
8080
@@ -101,12 +101,12 @@ local find_nix_fmt = function(opts, done)
101101 return flake_ref
102102 end
103103
104- local evaluate_flake_formatter = function (root )
104+ local evaluate_flake_formatter = function (_root )
105105 local nix_current_system = get_current_system ()
106106 if nix_current_system == nil then
107107 return
108108 end
109- local flake_ref = get_flake_ref (root )
109+ local flake_ref = get_flake_ref (_root )
110110 local eval_nix_formatter = [[
111111 let
112112 system = "]] .. nix_current_system .. [[ ";
@@ -141,12 +141,6 @@ local find_nix_fmt = function(opts, done)
141141 builtins.toJSON result
142142 ]]
143143
144- client .send_progress_notification (NOTIFICATION_TOKEN , {
145- kind = " report" ,
146- title = NOTIFICATION_TITLE ,
147- message = " evaluating" ,
148- })
149-
150144 local status , stdout_lines , stderr_lines = run_job ({
151145 command = " nix" ,
152146 args = {
@@ -218,34 +212,98 @@ local find_nix_fmt = function(opts, done)
218212 return true
219213 end
220214
215+ local drv_path , nix_fmt_path = evaluate_flake_formatter (root )
216+ if drv_path == nil then
217+ return nil
218+ end
219+
220+ -- Build the derivation. This ensures that `nix_fmt_path` exists.
221+ if not build_derivation ({ drv = drv_path , out_link = tmpname () }) then
222+ return nil
223+ end
224+
225+ return nix_fmt_path
226+ end
227+
228+ local nix_has_formatter_subcommand = function ()
229+ local status , _ , _ = run_job ({
230+ command = " nix" ,
231+ args = {
232+ " --extra-experimental-features" ,
233+ " nix-command flakes" ,
234+ " formatter" ,
235+ " --help" ,
236+ },
237+ })
238+
239+ return status == 0
240+ end
241+
242+ --- Asynchronously computes the command that `nix fmt` would run, or nil if
243+ --- we're not in a flake with a formatter, or if we fail to discover the
244+ --- formatter somehow. When finished, it invokes the `done` callback with a
245+ --- single string|nil parameter identifier the `nix fmt` entrypoint if found.
246+ ---
247+ --- The formatter must follow treefmt's [formatter
248+ --- spec](https://github.com/numtide/treefmt/blob/main/docs/formatter-spec.md).
249+ ---
250+ --- This basically re-implements the "entrypoint discovery" that `nix fmt` does.
251+ --- So why are we doing this ourselves rather than just invoking `nix fmt`?
252+ --- Unfortunately, it can take a few moments to evaluate all your nix code to
253+ --- figure out the formatter entrypoint. It can even be slow enough to exceed
254+ --- Neovim's default LSP timeout.
255+ --- By doing this ourselves, we can cache the result.
256+ local find_nix_fmt = function (opts , done )
257+ done = vim .schedule_wrap (done )
258+
259+ local async = require (" plenary.async" )
260+
261+ local notification_title = " discovering `nix fmt` entrypoint"
262+ local notification_token = " nix-flake-fmt-discovery"
263+
221264 async .run (function ()
222- client .send_progress_notification (NOTIFICATION_TOKEN , {
265+ client .send_progress_notification (notification_token , {
223266 kind = " begin" ,
224- title = NOTIFICATION_TITLE ,
267+ title = notification_title ,
225268 })
226269
227270 local _done = function (result )
228271 done (result )
229- client .send_progress_notification (NOTIFICATION_TOKEN , {
272+ client .send_progress_notification (notification_token , {
230273 kind = " end" ,
231- title = NOTIFICATION_TITLE ,
274+ title = notification_title ,
232275 message = " done" ,
233276 })
234277 end
235278
236- local drv_path , nix_fmt_path = evaluate_flake_formatter (opts .root )
237- if drv_path == nil then
238- return _done (nil )
239- end
279+ local nix_fmt_path --- @type string | nil
280+ local is_legacy = not nix_has_formatter_subcommand ()
281+ if is_legacy then
282+ nix_fmt_path = legacy_find_nix_fmt (opts .root )
283+ else
284+ local status , stdout_lines , stderr_lines = run_job ({
285+ command = " nix" ,
286+ args = {
287+ " --extra-experimental-features" ,
288+ " nix-command" ,
289+ " formatter" ,
290+ " build" ,
291+ " --out-link" ,
292+ tmpname (),
293+ },
294+ cwd = opts .root ,
295+ })
240296
241- -- Build the derivation. This ensures that `nix_fmt_path` exists.
242- client .send_progress_notification (NOTIFICATION_TOKEN , {
243- kind = " report" ,
244- title = NOTIFICATION_TITLE ,
245- message = " building" ,
246- })
247- if not build_derivation ({ drv = drv_path , out_link = tmpname () }) then
248- return _done (nil )
297+ if status ~= 0 then
298+ local stderr = table.concat (stderr_lines , " \n " )
299+ vim .defer_fn (function ()
300+ log :warn (string.format (" unable to build 'nix fmt' entrypoint. stderr: %s" , stderr ))
301+ end , 0 )
302+ return false
303+ end
304+
305+ local stdout = table.concat (stdout_lines , " \n " )
306+ nix_fmt_path = stdout
249307 end
250308
251309 return _done (nix_fmt_path )
0 commit comments