Gram Extensions the Nix Way #
One of the core ideas of Gram, next to stripping Zed of any generative AI features, is that extensions should not be installed from automatically updated binary blobs, and that these extensions should not be able to arbitrarily download and install other binaries.
In Zed, the editor will automatically update itself, downloading binaries from the Zed website vetted only by Zed. Its extensions are binary blobs hosted by Zed, automatically updated and downloaded without user control. Those extensions will, in turn, download and install anything they feel like.
This comes at an inconvenience for the user, but there is another way! Through the power of Nix, we can easily share build definitions for a wide range of extensions and make installing them as easy as declaring a list of extensions to link into our home directory. The extensions are built from source, the user may easily control what versions to install (either by pinning their clone of this repository to a specific rev, or by overriding the version and source hash of an extension), and optionally, users can take advantage of an input addressed binary cache for prebuilt versions of extensions.
Installation #
This repository is easily consumable through npins, lon, pomni, flakes or whatever other pinning mechanism you prefer (including builtins.fetchTarball, if you feel so inclined).
The canonical forges for this repository are Codeberg and Tangled, from which you can access them as https://codeberg.org/niklaskorz/nix-gram-extensions/archive/main.tar.gz or https://tangled.org/niklaskorz.eu/nix-gram-extensions/archive/main.tar.gz (or replace main with the specific rev you wish to fetch).
Both work fine as flake inputs and are automatically locked to a specific commit hash in your flake.lock.
The default.nix by default fetches a version of nixpkgs that includes the WASIp2 changes required to build Gram extensions from source.
You can optionally pass your own instance of pkgs instead, but are then responsible for ensuring this includes the wasm32-wasip2 cross target, which has not yet landed in any upstream nixpkgs channel.
You can even pass it your own WASIp2 cross pkgs instance as wasip2Pkgs.
From default.nix, you get a set of extensions defined in this repository along with three useful functions buildGramExtension, buildGramRustExtension and linkGramExtensions.
For convenience, these are also exposed by a flake.nix.
One caveat to look out for is that Gram does not invalidate its cached list of installed extensions when the installed extensions directory changes.
You must delete $XDG_DATA_HOME/gram/extensions/index.json whenever the list of installed extensions changes.
Depending on how you manage your home directory, this can be automated.
Wrapping the executable #
This method is only available in release 3.3.0 or higher.
let
extensions = with gram-extensions; [
bearded-icons
catppuccin
catppuccin-icons
deno
helm
graphql
gdscript
git-firefly
night-owlz
typst
];
gram = gram-extensions.wrapGramWithExtensions pkgs.gram extensions;
in
{
environment.systemPackages = [ gram ];
}
home-manager #
Assuming you have imported this repository's default.nix as gram-extensions, you can create and link a directory containing all your desired extensions in home-manager like this:
let
extensions = with gram-extensions; [
bearded-icons
catppuccin
catppuccin-icons
deno
helm
graphql
gdscript
git-firefly
night-owlz
typst
];
extensions-dir = gram-extensions.linkGramExtensions extensions;
in
{
home.file."Library/Application Support/Gram/extensions/installed" = {
enable = pkgs.stdenv.hostPlatform.isDarwin;
source = extensions-dir;
onChange = ''
cd "Library/Application Support/Gram/extensions"
mv index.json index.json.backup
'';
};
xdg.dataFile."gram/extensions/installed" = {
enable = pkgs.stdenv.hostPlatform.isLinux;
source = extensions-dir;
onChange = ''
cd "${config.xdg.dataHome}/gram/extensions"
mv index.json index.json.backup
'';
};
}
When consumed as a flake, the extensions and linkGramExtensions helper would be contained in gram-extensions.packages.${system}.
hjem #
I have no experience in using hjem, but creating and linking the extensions directory should work quite similar to home-manager.
Running a hook on change is not supported by hjem though, see the aforementioned caveat of having to manually delete $XDG_DATA_HOME/gram/extensions/index.json for changes to the extension list to apply.
A better solution for the future would be to also generate this index.json through Nix instead (PRs welcome).
let
extensions = with gram-extensions; [
bearded-icons
deno
git-firefly
];
extensions-dir = gram-extensions.linkGramExtensions extensions;
in
{
hjem.users.example.xdg.data.files = {
"gram/extensions/installed".source = extensions-dir;
};
}
Contributing #
Pull requests are welcome on both Codeberg and Tangled. I keep them in sync to the best of my abilities.
Adding extensions #
To make initialization of new extensions easier, this repository's development shell includes a fork of nix-init for our Gram extension builders. It even prefetches all grammars listed in the extension's manifest. As an example, adding the GDScript extension would work like this:
nix-init --url https://github.com/GDQuest/zed-gdscript extensions/gdscript/package.nix
Feel free to report any issues with my nix-init fork directly here on Codeberg or Tangled. I may also move it there from Github in the future.
Future ideas #
Right now this repository only covers building extensions, but not configuring them or providing the binaries they need.
If you installed the deno or typst extension from this repository, you still need to install deno or tinymist (the language server used by the typst extension) from nixpkgs yourself.
There are two alternative solutions to this problem.
One of them is a home-manager module that exposes every extension as an option and automatically installs the relevant packages they need through home.packages.
The other is directly integrating the nix store paths to the binaries an extension needs as part of the extension build, thus making them a direct dependency during substitution and then accessing them through their nix store path.
Another problem to address is automatically updating extensions in this repository, or at least providing update scripts for them all. I'm not sure yet whether this repository should aim to cover the whole Zed extension registry or just add popular extension on demand, or when someone bothers to make a pull request for them.