conform.nvim icon indicating copy to clipboard operation
conform.nvim copied to clipboard

bug: treefmt seems to remove whole file in nix files

Open DrTeagle opened this issue 3 months ago • 3 comments

Neovim version (nvim -v)

NVIM v0.12.0-nightly+cf48741 Build type: Release LuaJIT 2.1.1741730670 Run "nvim -V1 -v" for more info

Operating system/version

nixos

Read debugging tips

Add the debug logs

  • [x] I have set log_level = vim.log.levels.DEBUG and pasted the log contents below.

Log file

treefmt root directory not found.

However, it seems that nixd formatter runs for God knows what reason, even though it is the exact same command.

Describe the bug

I made this beast, I am more asking if anybody else is able to reproduce it. whenever using treefmt on nix files+ nixd as lsp server. treefmt or something freaks the fuck out, and deletes the whole file.

What is the severity of this bug?

tolerable (can work around it)

Steps To Reproduce

  1. use treefmt as your everything formatter
  2. have this after/lsp/nixd.lua:
---@type vim.lsp.ClientConfig
return {
  root_markers = { "flake.nix", ".git" },
  cmd = {
    "nixd",
  },
  filetypes = { "nix" },
  settings = {
    nixd = {
      inlay_hints = true,
      formatting = {
        command = { "treefmt" },
      },
      nixpkgs = {
        expr = "(builtins.getFlake (builtins.toString ./.)).inputs.nixpkgs {}",
      },
      options = {
        nixOptions = {
          expr = '(builtins.getFlake "/home/cdockter/Documents/myNixOS").nixosConfigurations.nixos.options',
        },
        home_manager = {
          expr = '(builtins.getFlake "/home/cdockter/Documents/myNixOS").homeConfigurations."cdockter".options',
        },
        flake_parts = {
          expr = "(builtins.getFlake (builtins.toString ./.)).debug.options",
        },
        perSystem = {
          expr = "(builtins.getFlake (builtins.toString ./.)).currentSystem.options",
        },
      },
    },
  },
}

as well as this conform option set:

  formatters_by_ft = {
    markdown = { "injected" },
    ["*"] = { "treefmt" },
  },
  1. write seemingly to any nix file
  2. file is now blank

Expected Behavior

I would like treefmt(the module I introduced, so I will have to fix this sometime if it is not a massive skill issue) to not delete my entire nix file. I am using treefmt-nix so that might be the issue, I will try and use treefmt proper and see if it still occurs.

Minimal example file

No response

Minimal init.lua

-- DO NOT change the paths and don't remove the colorscheme
local root = vim.fn.fnamemodify("./.repro", ":p")

-- set stdpaths to use .repro
for _, name in ipairs({ "config", "data", "state", "cache" }) do
  vim.env[("XDG_%s_HOME"):format(name:upper())] = root .. "/" .. name
end

-- bootstrap lazy
local lazypath = root .. "/plugins/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
  vim.fn.system({
    "git",
    "clone",
    "--filter=blob:none",
    "--single-branch",
    "https://github.com/folke/lazy.nvim.git",
    lazypath,
  })
end
vim.opt.runtimepath:prepend(lazypath)

-- install plugins
local plugins = {
  "folke/tokyonight.nvim",
  {
    "stevearc/conform.nvim",
    config = function()
      require("conform").setup({
        log_level = vim.log.levels.DEBUG,
        -- add your config here
      })
    end,
  },
  -- add any other plugins here
}
require("lazy").setup(plugins, {
  root = root .. "/plugins",
})

vim.cmd.colorscheme("tokyonight")
-- add anything else here

Additional context

as far as I know conform calls the command; yet when I call the command, treefmt works w/o a hitch. it is possible that treefmt made it no longer possible to do it one file at a time. In which case I would remove the formatter, as I assume that running treefmt on your entire repo(even though it is very fast) can be a little annoying to have all the files change on a single write.


edit: I tested it and it seems to be a treefmt-nix issue. which makes no sense to me. I have exposed the treefmt config to the shell properly. the error that I do get is this: RPC[Error] code_name = UnknownErrorCode, message = "formatting treefmt command exited with 256"

I think it might have to do with how I am passing in formatting to treefmt, I will get back to you "tommorow" (even though it is already 01:26) when I sleep a tad bit more

DrTeagle avatar Oct 23 '25 06:10 DrTeagle

I think I might know what is going on, the most recent commit to this formatter added a check for treefmt-toml, I think this is causing the issue with treefmt-nix. As in this case(as well as simply using the nixpkgs version) the toml is in the nix store

DrTeagle avatar Oct 23 '25 12:10 DrTeagle

Fun fact -- after my previous PR was merged I later set up treefmt-nix for my personal dotfiles flake and did notice that, of course, not having an explicit treefmt.toml file in-source caused that flake-provided treefmt binary to not be called. However, that change still feels appropriate as the default behavior (as opposed to throwing errors).

I think there are two paths forward, and I'm not sure which is preferred from the perspective of this project.

  • You (and anyone else that has treefmt set up using treefmt-nix or similar so that it doesn't need an in-source config file) can tweak the definition of treefmt [1]. Most simply, that would just be overriding require_cwd to be false:
{
	"stevearc/conform.nvim",

	-- [other fields]

	---@module "conform"
	---@type conform.setupOpts
	opts = {
		formatters_by_ft = {
			["*"] = { "treefmt" },
		},

		formatters = {
			treefmt = {
				require_cwd = false,
			},
		},
	},
}
  • As you suggest in https://github.com/stevearc/conform.nvim/pull/777#issuecomment-3436644113, check whether treefmt is able to run successfully rather than using require_cwd. This is similar to what I ended up with in my own config, but felt a little specific for upstream since this is baking in specifics related to flake.nix still:
		formatters = {
			treefmt = (function()
				local cache = {}

				local function treefmt_works(cwd)
					local ok, handle = pcall(
						vim.system,
						{ "treefmt", "--stdin", "fake.txt" },
						{ cwd = cwd, stdin = "" }
					)

					if not ok then
						return false
					end

					local result = handle:wait()
					return result.code == 0
				end

				return function()
					return {
						cwd = require("conform.util").root_file({ "treefmt.toml", ".treefmt.toml", "flake.nix" }),
						condition = function(_, ctx)
							local cwd = vim.fs.root(ctx.dirname, { "treefmt.toml", ".treefmt.toml" })
							if cwd then
								return true
							end

							cwd = vim.fs.root(ctx.dirname, { "flake.nix" })
							if not cwd then
								return false
							end

							if cache[cwd] == nil then
								cache[cwd] = treefmt_works(cwd)
							end

							return cache[cwd]
						end,
					}
				end
			end)(),
		},

Perhaps that could be adapted to make sense in upstream, though.

[1]: the fact that the whole file is disappearing on you seems indicative of some larger bug somewhere else in your toolchain. While a severe side effect, I suspect it's not one that should drive the default behavior decision here?

zivarah avatar Oct 23 '25 14:10 zivarah

I think that nixd's treefmt is broken.(typical of nixd in my experience) I am trying to find a way to disable nixd's formatting entirely.

DrTeagle avatar Oct 23 '25 15:10 DrTeagle