Jump to content

Zed: Difference between revisions

From NixOS Wiki
Mention home-manager remote server option
Remote Server: setup clarification, simplification, and pro/cons.
Line 30: Line 30:


== Remote Server ==
== Remote Server ==
When connecting to a remote server running NixOS, Zed will automatically upload a static server binary matching its version (provided and built by upstream Zed) and connect to it:
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 <code>"upload_binary_over_ssh": true</code>) to the remote host, before connecting to it :


:<syntaxhighlight lang="console">
:<syntaxhighlight lang="console">
Line 41: Line 41:
</syntaxhighlight>
</syntaxhighlight>


While this static binary works fine on NixOS, you may want to consider using the nixpkgs provided server instead if you have custom patches to apply or mistrust whatever binary upstream Zed is downloading to your server.
In either case, the binary is placed in the <code>~/.zed_server</code> folder on the remote system, and must match the client Zed version exactly or the connection will fail.  This same <code>~/.zed_server</code> location on the client system is also where locally downloaded binaries are stored before being uploaded via SSH to a remote system.


The binary name of the <code>remote_server</code> output of the <code>zed-editor</code> package matches what Zed expects on the remote, meaning you can symlink <code>"${pkgs.zed-editor.remote_server}/bin"</code> to <code>~/.zed_server</code> on the remote host to make use of it. As an end user, you have to ensure the remote uses the same version of the zed-editor package as the local client.
While the default prebuilt binaries from the upstream Zed website work fine with the default nixpkgs definition of <code>zed-editor</code>, you may want to provide your own, whether for reasons related to custom patching or purely mistrust of prebuilt upstream binaries.


In home-manager, this can be used as follows:
The <code>zed-editor</code> provides an additional output, <code>remote_server</code>, for the server binary matching the client version.  To make use of it you need the binary in <code>"${pkgs.zed-editor.remote_server}/bin"</code> placed/symlinked into your <code>~/.zed_server</code> folder (and <code>"upload_binary_over_ssh": true</code> included in the Zed client settings that specify each remote system). 


:<syntaxhighlight lang="nix">
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";
}
</syntaxhighlight>
 
If the version does not match, Zed will try to upload the binary itself (as the existing remote binary has a version-specific name), and in this particular example fail to do so because we symlinked <code>.zed_server</code> to a read-only directory in the Nix store.
 
Alternatively, you may choose to only symlink the binary and not the directory, so Zed will successfully fall back to its own binary upload if the remote package version does not match:


:<syntaxhighlight lang="nix">
:<syntaxhighlight lang="nix">
{ pkgs, ... }:
{ pkgs, ... }:
let
  inherit (pkgs.zed-editor) version remote_server;
  binary_name = "zed-remote-server-stable-${version}";
in
{
{
   home.file.".zed_server/${binary_name}".source = "${remote_server}/bin/${binary_name}";
   home.file.".zed_server" = {
    source = "${pkgs.zed-editor.remote_server}/bin";
    # keeps the folder writable, but symlinks the binaries into it
    recursive = true;
  };
}
}
</syntaxhighlight>
</syntaxhighlight>


A more convenient home-manager option that does the same exists as well:
Because the <code>~/.zed_server</code> folder is also used for external clients connecting to the current system as a remote, it's necessary to use the <code>recursive = true;</code> 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 <code>programs.zed-editor.installRemoteServer = true;</code> (the <code>programs.zed-editor.enable = true</code> must also be enabled for this to take effect, see below for further details about using the home-manager <code>programs.zed-editor</code>). 
 
:<syntaxhighlight lang="nix">
{
  programs.zed-editor = {
    enable = true;
    installRemoteServer = true;
  };
}
</syntaxhighlight>


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 <code>~/.zed_server</code> 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 <code>zed-editor</code>, 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 <code>recursive = true;</code> or explicitly set it to <code>recursive = false;</code> (the default when not specified) on <code>home.file.".zed_server"</code>.
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 ==
== Home manager support ==
Line 86: Line 70:
'''userSettings''' option will be translated directly to '''json''' file.
'''userSettings''' option will be translated directly to '''json''' file.


note that home-manager configuration produces a '''read only `settings.json`''' this means some features which requires to write this file might not work. For example change model at runtime. Changing AI model at runtime tries to write in the configuration.
Note that home-manager configuration produces a '''read only <code>settings.json</code>'''. Zed assumes the <code>settings.json</code> 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.
You can see an example of the home-manager configuration:
   
   
:<syntaxhighlight lang="nix">
:<syntaxhighlight lang="nix">
Line 96: Line 80:
     programs.zed-editor = {
     programs.zed-editor = {
         enable = true;
         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"];
         extensions = ["nix" "toml" "elixir" "make"];



Revision as of 22:40, 13 May 2025

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;

        };

    };
}