Jump to content

Zed

From NixOS Wiki
Revision as of 22:40, 13 May 2025 by Aaravchen (talk | contribs) (Remote Server: setup clarification, simplification, and pro/cons.)

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

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, this way you are able to make a reproducible Zed setup.

userSettings option will be translated directly to json file.

Note that home-manager configuration produces a read only settings.json. Zed assumes the settings.json is writable, and modifies it for all settings changed directly or indirectly from the GUI. Using a read-only version will prevent changing features like the current working model of the AI engine, or switching between AI engines.

You can see an example of the home-manager configuration:

{pkgs, lib, ... }:

{
    programs.zed-editor = {
        enable = true;

        ## This populates the userSettings "auto_install_extensions" which is a very odd optional Zed setting.
        ## It is not automatically populated or updated by Zed itself, and is only used at Zed startup to verify
        ## these are present in the set of installed extensions. Other extensions may have been installed from the
        ## GUI and Zed will do nothing to change those.  Removing an extension from this list will NOT remove it from
        ## Zed, that must be done manually from the GUI for every Zed instance using this config.
        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;

        };

    };
}