-
Notifications
You must be signed in to change notification settings - Fork 0
-
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:
-
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
nixHashergonomics, or something else? -
Are there any known examples of users composing
lib.prefetchLanguages+lib.wrapWithConfiginto 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!
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 9 comments 3 replies
-
Thanks for your query, @bglgwyng 🙏 Let me try to answer your questions as best I can:
- 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
nixHashergonomics, 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.
- Are there any known examples of users composing
lib.prefetchLanguages+lib.wrapWithConfiginto 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.
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 2
-
Here I made a PoC of embedding configuration generated with Nix.
You can test it with
nix run .#topiary-cli-portable -- format exampledefaultEmbeddedConfig.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
objcopyto decouple configuration embedding from Topiary's compilation, so the entire crate doesn't have to be recompiled every time the configuration changes.objcopywrites the configuration into a separate section (e.g..config) of an existing binary. Combined with a zero-copy serialization library likerkyv, this lets us embed the configuration in binary form and eliminate the parsing overhead on each execution.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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?
Beta Was this translation helpful? Give feedback.
All reactions
-
No problem!
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
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 👍
Beta Was this translation helpful? Give feedback.
All reactions
-
👍 1
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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 emittinglanguages.ncl. - Generate query files under the matching XDG
queries/<language>/formatting.scmlayout. - Reject ambiguous language declarations, such as specifying both
grammar.packageandgrammar.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.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 2
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
Wow! I'm so impressed! I'm gonna look at it right away! Thanks for the amazing effort already!
Beta Was this translation helpful? Give feedback.
All reactions
-
Looks great, this is definitely something we can keep! I have tqo suggestions.
- 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! - We can assert that a user can only define either a grammar package or source! assertion.patch
Beta Was this translation helpful? Give feedback.