Skip to content

Commit 6ff3ab9

Browse files
authored
chore(nix_flake_fmt): rework to use the new nix formatter subcommand (#279)
1 parent a49f5a7 commit 6ff3ab9

File tree

1 file changed

+105
-47
lines changed

1 file changed

+105
-47
lines changed

lua/null-ls/builtins/formatting/nix_flake_fmt.lua

Lines changed: 105 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,43 @@ local log = require("null-ls.logger")
44
local client = require("null-ls.client")
55

66
local 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

Comments
 (0)