Zed
Zed is a graphical text editor focusing on speed and collaborative editing.
Its Linux support is fairly recent, the NixOS support even more so.
Installation
The package zed-editor
is available only from channel 24.11 onward.
CLI support is installed and aliased to zeditor
Zed requires a GPU with Vulkan support, otherwise it will prompt you to use a very inefficient "emulated GPU" mode. This is available thru `nixGL` by using the Vulkan wrappers, or via home-manager nixGL integration by setting the nixGL.vulkan.enable = true
.
On non-NixOS systems this method of providing Vulkan support via NixGL has even been demonstrated that it can be more consistent/stable than relying on native host system support. The Vulkan libraries of Ubuntu 24.04 for example don't provide the same level of support between X11 and Wayland environments, and don't meet the Zed minimum Vulkan API requirements under X11, but the nixGL wrapper with Vulkan support does (for a GPU that actually supports the features).
LSP Support
By default, Zed will try to download pre-built LSP servers in ~/.local/share/zed/languages/
. This does not work for NixOS.
There's sadly no way to inject those from $PATH
for now. Worse, the way to point to the language server is language-specific, there is no global configuration flag for now.
The following sections contain some language-specific setup working on NixOS.
rust-analyzer
Here, we'll assume rust-analyzer is globally installed in your system profile at /run/current-system/sw/bin/rust-analyzer
. You may want to adapt this path in the following code snippet to something more relevant to your use case.
Add the following snippet to your zed configuration file:
"lsp": {
"rust-analyzer": {
"binary": {
"path": "/run/current-system/sw/bin/rust-analyzer",
},
}
}
Remote Server
When connecting to a remote server running NixOS, Zed will either automatically download a static server binary matching its version from the upstream Zed website onto the remote host, or will locally obtain then upload a static server binary (if "upload_binary_over_ssh": true
) to the remote host, before connecting to it :
$ ls ~/.zed_server zed-remote-server-stable-0.169.2 $ file ~/.zed_server/zed-remote-server-stable-0.169.2 .zed_server/zed-remote-server-stable-0.169.2: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), static-pie linked, BuildID[sha1]=b1ac2c83127e7a9a072840cd7c24d7d125c1b655, not stripped $ ldd ~/.zed_server/zed-remote-server-stable-0.169.2 statically linked
In either case, the binary is placed in the ~/.zed_server
folder on the remote system, and must match the client Zed version exactly or the connection will fail. This same ~/.zed_server
location on the client system is also where locally downloaded binaries are stored before being uploaded via SSH to a remote system.
While the default prebuilt binaries from the upstream Zed website work fine with the default nixpkgs definition of zed-editor
, you may want to provide your own, whether for reasons related to custom patching or purely mistrust of prebuilt upstream binaries.
The zed-editor
provides an additional output, remote_server
, for the server binary matching the client version. To make use of it you need the binary in "${pkgs.zed-editor.remote_server}/bin"
placed/symlinked into your ~/.zed_server
folder (and "upload_binary_over_ssh": true
included in the Zed client settings that specify each remote system).
In a simple case, this can be setup with home-manager as follows:
{ pkgs, ... }: { home.file.".zed_server" = { source = "${pkgs.zed-editor.remote_server}/bin"; # keeps the folder writable, but symlinks the binaries into it recursive = true; }; }
Because the ~/.zed_server
folder is also used for external clients connecting to the current system as a remote, it's necessary to use the recursive = true;
setting so only individual binaries are symlinked from the nix store into the folder, and the folder itself remains writable. When a client connects using a version that doesn't already have a matching server binary available, it will use its configured method to add the missing server binary. This same configuration may be set more easily via home-manager if you're managing your entire Zed config thru home-manager by setting programs.zed-editor.installRemoteServer = true;
(the programs.zed-editor.enable = true
must also be enabled for this to take effect, see below for further details about using the home-manager programs.zed-editor
).
If you want to prohibit clients from managing the Zed server binaries on your system when connecting to it as a remote, you can make the whole ~/.zed_server
folder read-only by symlinking the whole folder to the nix store. This will restrict clients to only be able to use one Zed server binary provided from your nixpkgs zed-editor
, which has the indirect effect of also limiting to a single Zed client version that's allowed to connect. To do this, simply remove the recursive = true;
or explicitly set it to recursive = false;
(the default when not specified) on home.file.".zed_server"
.
Be aware however that Zed will refuse to connect to a remote if there is no remote server binary matching the exact Zed client version and it's unable to populate a matching one!
Home manager support
Zed is supported by Home Manager, which allows you to make a reproducible initial Zed setup. However, due to the method Zed uses for installing, running, and managing Extensions, only the initial set of preinstalled extensions can currently be defined in home-manager. It's possible to install additional extensions from the Zed GUI on any system without including it in the home-manager config. This is partially a technical limitation of the format of extensions (which follow a similar model to VSCode and require external binaries to be downloaded and used at run-time}, and a Zed extensions management design (that doesn't track the installed extensions in the settings but does allow a list to be manually added that will be auto-installed if not already present).
The userSettings
and userkeyMaps
options will be translated directly into JSON.
The extensions
currently just defines the userSettings.auto_install_extensions
(see Zed documentation).
The extraPackages
includes extra nixpkgs in the environment Zed executes in (an FHS), so extra LSP server packages (e.g. pkgs.nixd
) or optional tools for LSP servers (e.g. pkgs.shellcheck
for the "Basher" LSP) should be included.
Note that home-manager configuration produces a read only settings.json
, but Zed assumes/requires the settings.json
to be writable. Using home-manager configuration will prevent you from changing most settings in the GUI since Zed will be unable to modify the read-only settings.json
accordingly. This includes the current/default AI engine to use, or even switching the model of the AI engine (as well as most non-AI settings).
You can see an example of the home-manager configuration:
{pkgs, lib, ... }: { programs.zed-editor = { enable = true; ## This populates the userSettings "auto_install_extensions" extensions = ["nix" "toml" "elixir" "make"]; ## everything inside of these brackets are Zed options. userSettings = { assistant = { enabled = true; version = "2"; default_open_ai_model = null; ### PROVIDER OPTIONS ### zed.dev models { claude-3-5-sonnet-latest } requires github connected ### anthropic models { claude-3-5-sonnet-latest claude-3-haiku-latest claude-3-opus-latest } requires API_KEY ### copilot_chat models { gpt-4o gpt-4 gpt-3.5-turbo o1-preview } requires github connected default_model = { provider = "zed.dev"; model = "claude-3-5-sonnet-latest"; }; # inline_alternatives = [ # { # provider = "copilot_chat"; # model = "gpt-3.5-turbo"; # } # ]; }; node = { path = lib.getExe pkgs.nodejs; npm_path = lib.getExe' pkgs.nodejs "npm"; }; hour_format = "hour24"; auto_update = false; terminal = { alternate_scroll = "off"; blinking = "off"; copy_on_select = false; dock = "bottom"; detect_venv = { on = { directories = [".env" "env" ".venv" "venv"]; activate_script = "default"; }; }; env = { TERM = "alacritty"; }; font_family = "FiraCode Nerd Font"; font_features = null; font_size = null; line_height = "comfortable"; option_as_meta = false; button = false; shell = "system"; #{ # program = "zsh"; #}; toolbar = { title = true; }; working_directory = "current_project_directory"; }; lsp = { rust-analyzer = { binary = { # path = lib.getExe pkgs.rust-analyzer; path_lookup = true; }; }; nix = { binary = { path_lookup = true; }; }; elixir-ls = { binary = { path_lookup = true; }; settings = { dialyzerEnabled = true; }; }; }; languages = { "Elixir" = { language_servers = ["!lexical" "elixir-ls" "!next-ls"]; format_on_save = { external = { command = "mix"; arguments = ["format" "--stdin-filename" "{buffer_path}" "-"]; }; }; }; "HEEX" = { language_servers = ["!lexical" "elixir-ls" "!next-ls"]; format_on_save = { external = { command = "mix"; arguments = ["format" "--stdin-filename" "{buffer_path}" "-"]; }; }; }; }; vim_mode = true; ## tell zed to use direnv and direnv can use a flake.nix enviroment. load_direnv = "shell_hook"; base_keymap = "VSCode"; theme = { mode = "system"; light = "One Light"; dark = "One Dark"; }; show_whitespaces = "all" ; ui_font_size = 16; buffer_font_size = 16; }; }; }