Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

NixOS module for Topiary configuration? #5

bglgwyng started this conversation in New BUDs
Discussion options

Topiary provides lib.prefetchLanguages, lib.wrapWithConfig, and lib.gitHook as Nix building blocks, but there's no NixOS or home-manager module that offers a declarative interface for configuring Topiary — including custom language registration, grammar sources, and query files — through Nix module options.

The docs note that building blocks are provided rather than complete solutions because the team is "unsure at this point of how people will use Topiary in their Nix code."

Two questions:

  1. Has a NixOS/home-manager module been considered and intentionally deferred? If so, what are the blockers — unclear use cases, the Nickel/Nix boundary for grammar config, the nixHash ergonomics, or something else?

  2. Are there any known examples of users composing lib.prefetchLanguages + lib.wrapWithConfig into a module-like wrapper for custom language extension?

My use case: when adding a custom language, you need to coordinate Nickel config (language mapping) and Nix (grammar compilation + prefetching). A module could unify both into a single Nix declaration. Happy to contribute if there's interest.

Thanks!

You must be logged in to vote

Replies: 9 comments 3 replies

Comment options

Thanks for your query, @bglgwyng 🙏 Let me try to answer your questions as best I can:

  1. Has a NixOS/home-manager module been considered and intentionally deferred? If so, what are the blockers — unclear use cases, the Nickel/Nix boundary for grammar config, the nixHash ergonomics, or something else?

I do not recall anyone mentioning a NixOS/home-manager module before, for Topiary. Regardless, the most significant blocker for us at the moment would be manpower. I've made some light Nix changes in #1200, recently, but otherwise changes to our Nix code haven't been a priority.

  1. Are there any known examples of users composing lib.prefetchLanguages + lib.wrapWithConfig into a module-like wrapper for custom language extension?

Not that I'm aware of, I'm afraid.

Happy to contribute if there's interest.

If this would be useful to you, then I suspect it would also be useful to others. We'd certainly encourage and welcome any contributions, if you have the time.

You must be logged in to vote
0 replies
Comment options

Keep an eye on

https://birdeehub.github.io/nix-wrapper-modules/

Maybe someone will come contribute a proper wrapper module if they want one.

Here's one from my config that I am messing with, but it needs a better set of queries options at least, it is kinda a sketch at the moment. https://github.com/BirdeeHub/birdeevim/blob/4f6a053a4424a127bbc8804d8de3e1a572d5994f/nix/topiary/module.nix

This would then work as a standalone package, but also a nixos or home manager module if you want. This is more useful, because it means you can use it in development shells and tests for packages without evaluating nixos or home manager. But then still use it there too, with the same options.

I might add it myself, but if I did it myself, I would probably end up spending way too much time on it and end up with a nix2nickel function and idk if im ready for all that just yet I already have a lot of stuff to do on this repo still XD Honestly what is at that link is probably almost good enough minus the queries option and maybe some prefetching changes if someone wanted to fix it up.

You must be logged in to vote
0 replies
Comment options

Here I made a PoC of embedding configuration generated with Nix.

You can test it with

nix run .#topiary-cli-portable -- format example

defaultEmbeddedConfig.nix is the Nix equivalent of languages.ncl. It contains Nix store paths of tree-sitter parser libraries and queries.

Possible follow-ups:

  • Write a NixOS module for this, which might improve configuration ergonomics.
  • Use objcopy to decouple configuration embedding from Topiary's compilation, so the entire crate doesn't have to be recompiled every time the configuration changes. objcopy writes the configuration into a separate section (e.g. .config) of an existing binary. Combined with a zero-copy serialization library like rkyv, this lets us embed the configuration in binary form and eliminate the parsing overhead on each execution.
You must be logged in to vote
0 replies
Comment options

I'm aware that the current implement is far from conventional one.
It's partially a result of prototyping.
The conventional implementaiton would be adding json config path argument(or enviornment variable) and binding with makeWrapper.

I'm willing to modify the current implementation to make it more conventional.
However, I also like this approach as it marginally reduce the start up time by avoiding reading the config file.

You must be logged in to vote
0 replies
Comment options

Apologies for the lack of communication from the Topiary core team on this effort, but just to say thank you for this 🙏

Otherwise, I suspect that this would be a useful feature, but I'm not equipped to review the details. Would anyone here (@bglgwyng, @BirdeeHub) object to me converting this into a GitHub discussion, where it might be better to talk about implementation strategies, before a PR is submitted?

You must be logged in to vote
0 replies
Comment options

No problem!

You must be logged in to vote
1 reply
Comment options

Thanks, @bglgwyng: I have both converted this to a discussion and moved it to the "BUD" (blueprints for upcoming development) repository. I'll give it a shout out on our Discord to see if I can attract others into the discussion 👍

Comment options

Besides my quirk experiment, the easiest approach is to just generate the .ncl file from nix and wrap the binary with an environment variable using lib.makeWrapper. That way we don't need to modify the current implementation at all. Resolving the Nickel configuration at runtime still remains, which seems like a good place for a NixOS module to do its job. I'm fine with this approach though — my main concern is extensibility for supporting new languages (maybe ones still in development), and this satisfies that.

You must be logged in to vote
0 replies
Comment options

I think the first version of this should probably stay deliberately conventional: a Home Manager module that generates Topiary configuration as files, and builds any grammars it manages into Nix store paths.

interface along these lines:

programs.topiary = {
 enable = true;
 # Defaults to this flake's Topiary package.
 package = topiary.packages.${pkgs.system}.topiary-cli;
 # Defaults to true. The module starts from Topiary's shipped language set,
 # but rewrites grammar sources to store paths.
 includeDefaultLanguages = true;
 languages.<name> = {
 extensions = [ "..." ];
 indent = " "; # optional
 grammar = {
 symbol = "..."; # optional
 # Either use an already-built grammar from nixpkgs/user packages...
 package = pkgs.tree-sitter-grammars.tree-sitter-foo;
 # ...or let the module build it with tree-sitter.buildGrammar.
 source.git = {
 url = "https://github.com/example/tree-sitter-foo";
 rev = "...";
 hash = "sha256-...";
 subdir = null; # optional
 };
 };
 query.formatting = ./formatting.scm;
 # or:
 # query.formatting.text = ''
 # ...
 # '';
 };
};

The important bit is that the generated languages.ncl should contain grammar.source.path entries pointing at store paths, not grammar.source.git entries. For grammar declarations backed by Git, the module would call tree-sitter.buildGrammar during Nix evaluation/building and write the resulting parser path into the generated Topiary config. For grammars already packaged in nixpkgs, or supplied by the user as a derivation/path, the module would write that parser path directly.

The module should write:

~/.config/topiary/languages.ncl
~/.config/topiary/queries/<language>/formatting.scm

That keeps it aligned with Topiary's existing configuration model. By I would not wrap Topiary with -C, because writing the file as the normal XDG user config preserves Topiary's existing precedence rules: a project-local .topiary/languages.ncl can still override the user config. A wrapper mode could exist as an opt-in for people who want a specific configured Topiary package for dev shells, hooks, or tests.

This also makes the module useful without requiring Topiary changes. The existing Nix helpers already point in this direction: the current grammar-prefetching machinery effectively turns Git grammar sources into tree-sitter.buildGrammar outputs and rewrites the config to paths. The module would turn that into a user-facing, declarative API and produce Nickel source rather than asking users to manually coordinate Nix grammar builds with a separate languages.ncl.

The implementation shape I would aim for:

  • Add/reuse a Nix helper that converts a Topiary config attrset to Nickel source in the shape Topiary expects.
  • Start from Topiary's default language config when includeDefaultLanguages = true.
  • Normalize every managed grammar to grammar.source.path = "<store path>/parser" before emitting languages.ncl.
  • Generate query files under the matching XDG queries/<language>/formatting.scm layout.
  • Reject ambiguous language declarations, such as specifying both grammar.package and grammar.source.git.
  • Keep embedding/portable-binary work out of the first version. It is interesting, but the generated-file approach solves the custom-language ergonomics without changing Topiary itself.
You must be logged in to vote
0 replies
Comment options

I prototyped @ErinvanderVeen's suggestion here.
You can test it with nix run .#topiary-with-nix-config -- config. topiary-with-nix-config will be removed and we can make an equivalent PR in the home-manager repo.

You must be logged in to vote
2 replies
Comment options

Wow! I'm so impressed! I'm gonna look at it right away! Thanks for the amazing effort already!

Comment options

Looks great, this is definitely something we can keep! I have tqo suggestions.

  1. Before we make a pr to HM, we can actually add this module as an output to our flake under homeConfigurations, that way we can already test it out before we make a PR. This also gives us some credibility when we do ultimately make the PR!
  2. We can assert that a user can only define either a grammar package or source! assertion.patch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

AltStyle によって変換されたページ (->オリジナル) /