-
Notifications
You must be signed in to change notification settings - Fork 86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add "nix flake fmt" builtin formatter #192
base: main
Are you sure you want to change the base?
Conversation
-- `--walk` is specific to treefmt, and this formatter is supposed | ||
-- to be generic. | ||
-- Note: this could get converted to the new `TREEFMT_WALK` | ||
-- environment variable once | ||
-- <https://github.com/numtide/treefmt/commit/bac4a0d102e1142406d3a7d15106e5ba108bfcf8> | ||
-- is fixed, which would at least play nicely with other types of | ||
-- `nix fmt` entrypoints. | ||
-- | ||
-- However, IMO, the real fix is to change treefmt itself to be | ||
-- willing to format files passed explicitly, even if they're | ||
-- gitignored: | ||
-- https://github.com/numtide/treefmt/issues/435 | ||
"--walk=filesystem", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the record, this is the thing I'd like to figure out before we merge this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized I said "gitignored" in this comment. What I really meant was "even if they're not tracked by git".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the review, @Mic92!
3ba73f7
to
ec16dd4
Compare
I actually just learned that neovim seems to have a tempdir with automatic cleanup already builtin. So all you have to do is doing this once and save it in temporary variable:
And pass this as an argument for the gcroot. This logic seems much easier than having to check store paths with |
I'm building a [`nix fmt`](https://nix.dev/manual/nix/latest/command-ref/new-cli/nix3-fmt) formatter. The tricky thing about `nix fmt` is that for nontrivial projects, the entrypoint (`nix fmt`) can be slow enough to be a bad experience to run on every save, and can even trigger [neovim's lsp timeout](https://github.com/nvimtools/none-ls.nvim?tab=readme-ov-file#i-am-seeing-a-formatting-timeout-error-message). It's not the that underlying formatter it invokes are slow, it's that it can take 1-2 seconds for nix to evaluate the `flake.nix` and determine what command `nix fmt` should run under the hood. Since the underlying command shouldn't change very often for a given project, I feel like it would be reasonable to cache this result per project, hence this new `by_bufroot` callback.
This uses `nix fmt` under the hood. I introduced some caching make it performant, as `nix fmt` can take a little while to evaluate all your nix code to figure out the formatter entrypoint.
This protects us against garbage collection so long as vim is running.
-- Dirty hack to check if the flake actually defines a formatter. | ||
-- | ||
-- I cannot for the *life* of me figure out a less hacky way of | ||
-- checking if a flake defines a formatter. Things I've tried: | ||
-- | ||
-- - `nix eval . --apply '...'`: This doesn't not give me the flake | ||
-- itself, it gives me the default package. | ||
-- - `builtins.getFlake`: Every incantation I've tried requires | ||
-- `--impure`, which has the performance downside described above. | ||
-- - `nix flake show --json .`: This works, but it can be quite slow: | ||
-- we end up evaluating all outputs, which can take a while for | ||
-- `nixosConfigurations`. | ||
if cp.stderr:find("error: flake .+ does not provide attribute .+ or 'formatter'") then | ||
log:warn("this flake does not define a `nix fmt` entrypoint") | ||
else | ||
log:error(string.format("unable discover 'nix fmt' command. stderr: %s", cp.stderr)) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know of any better way to check if a flake exposes an attr. but can't we just forward the error as warning in any case this fails and hope that nix provides a good enough warning so we don't need to handle it explicitly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could, but how would I choose the appropriate log level (warn vs error)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably warn everytime?
This implements a builtin formatter that (essentially) invokes
nix fmt
on the given buffer. I called it"nix flake fmt"
, even though the underlying command isnix fmt
, because I didn't want people to confuse it with none-ls's pre-existingnixfmt
builtin formatter. Naming is hard.This was pretty tricky to build. Issues I ran into:
nix fmt
is slow enough that it's unbearable to run every time you save a buffer. The slowness is the time spent evaluating all the relevant nix code each time, so I opted to cache the result.by_bufroot
cache helper (inspired by the existingby_bufnr
cache helper). That's the first commit in this PR, and could be merged up independently, but it might be silly to do so without any builtins using it.nix eval
,nix build
, etc implementation makes me cry 😭. I'm hoping a Nix expert swoops in and shows me a better way. Or, perhaps we can use this as motivation to add a feature to thenix
cli to make this easier. I have a few ideas about things that could make this simpler.nix fmt
entrypoint takes a--walk=filesystem
parameter. That's a bogus assumption. I've left a comment in the code explaining how I think we should fix this (it'll require a change totreefmt
itself). I'd like to address this before we merge this PR up, so I've marked this as a draft.