Nixd home-manager completion and standalone home-manager outputs

The nixd configuration instructions require a nix expression to resolve option systems completions, e.g., as a json:

{
  // Tell the language server your desired option set, for completion
  // This is lazily evaluated.
  "options": { // Map of eval information
    // If this is ommited, default search path (<nixpkgs>) will be used.
    "nixos": { // This name "nixos" could be arbitary.
      // The expression to eval, intepret it as option declarations.
      "expr": "(builtins.getFlake \"/home/lyc/flakes\").nixosConfigurations.adrastea.options"
    },

    // By default there is no home-manager options completion, thus you can add this entry.
    "home-manager": {
      "expr": "(builtins.getFlake \"/home/lyc/flakes\").homeConfigurations.\"lyc@adrastea\".options"
    },

    // For flake-parts opitons.
    // Firstly read the docs here to enable "debugging", exposing declarations for nixd.
    // https://flake.parts/debug
    "flake-parts": {
      "expr": "(builtins.getFlake \"/path/to/your/flake\").debug.options"
    },
    // For a `perSystem` flake-parts option:
    "flake-parts2": {
      "expr": "(builtins.getFlake \"/path/to/your/flake\").currentSystem.options"
    }
  }
}

Firstly, it’s bizarre to me that I’m meant to put into my nvim config an absolute path to config source which may or may not exist on my machine and I may or may not be currently working on. Is that really how it has to be? I could instead write a nix expression which points to a remote repository instead but this still leads to completions which are potentially decoupled from the source I’m currently looking at. Why aren’t the completions resolved dynamically per project like uh, every other lsp does it?

Secondly, I didn’t write a flake with homeConfigurations as an output, so I can’t use the suggested expression above to expose home-manager options. This way of defining standalone home configs doesn’t seem to be documented in the home-manager manual or really anywhere, but so far as I can tell you’re not meant to define such an output say homeConfigurations.user and then a nixosConfiguration which depends on e.g. self.homeConfigurations.user. If you are, I can’t figure out how to do that (see above comment on documentation). Otherwise, I can make a nixosConfiguration with a home-manager module in the usual way by calling home-manager.nixosModules.home-manager and then I guess just have this dangling additional output homeConfigurations that I have lying around just to make nixd work, which frankly kind of sucks. Is this really how it’s intended to be done?

Another thing I tried was to find a nix expression which depends on home-manager only resolving the options, as nixpkgs#nixos-options does for nixos options, but I couldn’t figure that out either.

1 Like

In fact, creating configuration files for projects, such as a flake , is common for language servers. If a language server is unable to determine where to fetch home-manager options, how can it provide accurate code completion?

Because nix-lang too complex to resolve. There is no certain way to find out which structure this project in use.

No, actually you can do something like this:

home-manager.users.emanresu.programs.helix.languages.language-server = 
{
  nixd.config.nixd.options =
  let
    myOptions = "(builtins.getFlake \"${self}\").nixosConfigurations.MYHOSTNAME.options";
  in
  {
    nixos.expr = myOptions;
    home-manager.expr = myOptions + ".home-manager.users.type.getSubOptions []";
  };
};

On the other hand, if you prefer not to use configuration, you can simply skip it. However, doing so might result in the loss of home-manager completion capabilities, and it’s worth noting that other Nix LSPs also cannot provide this functionality anyway.

1 Like

Usually such config files live in the project, not on the system of the person using it. If there was a file (let’s say .nixd?) that could be put in the project dir, I think that’d be more intuitive, even if it ends up cluttering the project.

Thanks for your reply. I appreciate the solution you gave, which constructs a static editor config at build-time with the nix options I guess being resolved from the store at run time. I haven’t tested it, but the meat of it seems to be that

\"${self}\").nixosConfigurations.MYHOSTNAME.options.home-manager.users.type.getSubOptions []

is the expression I was looking for. This answers my last question but still leaves me confused as to why the documentation by default assumes you’ll have a flake which defines a standalone home manager config.

I’m not about to do this however, because I refuse to write a vim config in nix or have it otherwise depend on the evaluation of a nix expression. This just isn’t reasonable for a portable editor.

This also doesn’t solve the issue of resolving options for different projects at runtime. I will take your word that the problem’s intractable, although foisting the responsibility onto the user of finding a per-project per-module ad-hoc solution suggests that this isn’t really the case. At any rate, no other LSP I use requires per-project configs and I’m not about to start writing them for nixd I think. Edit: if I was, amusingly enough the nvim-lspconfig wiki advises

If you came across an LSP server that doesn’t provide a way to configure server via local files, please consider to fill an issue at its repo.

nixd is configurable via workspace/configuration request. Literally by “workspace”, I think it should be configurable from your editor’s configuration file in each project, not a custom one. For example, microsoft/vscode has .vscode/settings.json to support project-specific configuration.

1 Like

I think the best solution is performing some analysis on your flake.nix, then determine those expression automatically. However, nix-language it self is too complex to do so. I’ll elaborate how “other LSPs” address this problem.

clangd is another LSP I’ve contributed, it requires compile_commands.json per-project (this is generated automatically by build-systems, though).

rust-analyzer, normally you don’t need to configure it by cargo-based project, because “cargo.toml” is easy to analyze (it is not Turing-complete). However for other projects not based on “cargo”, it is still required to configure it by rust-project.json file per project, see the documentation in Fuchsia.

In conclusion I think: (1) it is common to have per-project configuration, although it may be auto-generated. (2) It is feasible to have heuristics for nix flake, since it is Turing-complete, I don’t think that heuristics will be complete to support all cases. (3) current situation is: heuristics for Nixpkgs, but not other module systems (e.g. Home-Manager, nix-darwin, nixvim, …).

Previously nixd did support such configuration file named .nixd.json, but normally configure nvim/vscode globally is enough for people just editing flake projects, also workspace/configuration is the standard way per LSP specifications, that’s why the feature is removed in nixd v2.0.0, with a large refactored code-base.

Open-source is all about choices, if you don’t like it, feel free to use other nix LSP (personally I recommend nil, it offers all static analysis, but with no eval-time features, I think that’s the best for your need).

2 Likes

Thank you for taking the time to explain this.

My opinion continues to be that making references to absolute paths to a user directory in a portable editor config is not at all correct. There doesn’t seem to be a reasonable work around for this, so I’ll take your advice and use a different LSP.