Detailed changes
@@ -7,6 +7,8 @@
/script/node_modules
/crates/theme/schemas/theme.json
/crates/collab/seed.json
+/crates/zed/resources/flatpak/flatpak-cargo-sources.json
+/dev.zed.Zed*.json
/assets/*licenses.md
**/venv
.build
@@ -25,3 +27,4 @@ DerivedData/
.blob_store
.vscode
.wrangler
+.flatpak-builder
@@ -342,6 +342,16 @@ impl AutoUpdater {
}
async fn update(this: Model<Self>, mut cx: AsyncAppContext) -> Result<()> {
+ // Skip auto-update for flatpaks
+ #[cfg(target_os = "linux")]
+ if matches!(std::env::var("ZED_IS_FLATPAK_INSTALL"), Ok(_)) {
+ this.update(&mut cx, |this, cx| {
+ this.status = AutoUpdateStatus::Idle;
+ cx.notify();
+ })?;
+ return Ok(());
+ }
+
let (client, current_version) = this.read_with(&cx, |this, _| {
(this.http_client.clone(), this.current_version)
})?;
@@ -60,6 +60,13 @@ fn parse_path_with_position(
}
fn main() -> Result<()> {
+ // Exit flatpak sandbox if needed
+ #[cfg(target_os = "linux")]
+ {
+ flatpak::try_restart_to_host();
+ flatpak::ld_extra_libs();
+ }
+
// Intercept version designators
#[cfg(target_os = "macos")]
if let Some(channel) = std::env::args().nth(1).filter(|arg| arg.starts_with("--")) {
@@ -72,6 +79,9 @@ fn main() -> Result<()> {
}
let args = Args::parse();
+ #[cfg(target_os = "linux")]
+ let args = flatpak::set_bin_if_no_escape(args);
+
let app = Detect::detect(args.zed.as_deref()).context("Bundle detection")?;
if args.version {
@@ -275,6 +285,114 @@ mod linux {
}
}
+#[cfg(target_os = "linux")]
+mod flatpak {
+ use std::ffi::OsString;
+ use std::path::PathBuf;
+ use std::process::Command;
+ use std::{env, process};
+
+ const EXTRA_LIB_ENV_NAME: &'static str = "ZED_FLATPAK_LIB_PATH";
+ const NO_ESCAPE_ENV_NAME: &'static str = "ZED_FLATPAK_NO_ESCAPE";
+
+ /// Adds bundled libraries to LD_LIBRARY_PATH if running under flatpak
+ pub fn ld_extra_libs() {
+ let mut paths = if let Ok(paths) = env::var("LD_LIBRARY_PATH") {
+ env::split_paths(&paths).collect()
+ } else {
+ Vec::new()
+ };
+
+ if let Ok(extra_path) = env::var(EXTRA_LIB_ENV_NAME) {
+ paths.push(extra_path.into());
+ }
+
+ env::set_var("LD_LIBRARY_PATH", env::join_paths(paths).unwrap());
+ }
+
+ /// Restarts outside of the sandbox if currently running within it
+ pub fn try_restart_to_host() {
+ if let Some(flatpak_dir) = get_flatpak_dir() {
+ let mut args = vec!["/usr/bin/flatpak-spawn".into(), "--host".into()];
+ args.append(&mut get_xdg_env_args());
+ args.push("--env=ZED_IS_FLATPAK_INSTALL=1".into());
+ args.push(
+ format!(
+ "--env={EXTRA_LIB_ENV_NAME}={}",
+ flatpak_dir.join("lib").to_str().unwrap()
+ )
+ .into(),
+ );
+ args.push(flatpak_dir.join("bin").join("zed").into());
+
+ let mut is_app_location_set = false;
+ for arg in &env::args_os().collect::<Vec<_>>()[1..] {
+ args.push(arg.clone());
+ is_app_location_set |= arg == "--zed";
+ }
+
+ if !is_app_location_set {
+ args.push("--zed".into());
+ args.push(flatpak_dir.join("bin").join("zed-app").into());
+ }
+
+ let error = exec::execvp("/usr/bin/flatpak-spawn", args);
+ eprintln!("failed restart cli on host: {:?}", error);
+ process::exit(1);
+ }
+ }
+
+ pub fn set_bin_if_no_escape(mut args: super::Args) -> super::Args {
+ if env::var(NO_ESCAPE_ENV_NAME).is_ok()
+ && env::var("FLATPAK_ID").map_or(false, |id| id.starts_with("dev.zed.Zed"))
+ {
+ if args.zed.is_none() {
+ args.zed = Some("/app/bin/zed-app".into());
+ env::set_var("ZED_IS_FLATPAK_INSTALL", "1");
+ }
+ }
+ args
+ }
+
+ fn get_flatpak_dir() -> Option<PathBuf> {
+ if env::var(NO_ESCAPE_ENV_NAME).is_ok() {
+ return None;
+ }
+
+ if let Ok(flatpak_id) = env::var("FLATPAK_ID") {
+ if !flatpak_id.starts_with("dev.zed.Zed") {
+ return None;
+ }
+
+ let install_dir = Command::new("/usr/bin/flatpak-spawn")
+ .arg("--host")
+ .arg("flatpak")
+ .arg("info")
+ .arg("--show-location")
+ .arg(flatpak_id)
+ .output()
+ .unwrap();
+ let install_dir = PathBuf::from(String::from_utf8(install_dir.stdout).unwrap().trim());
+ Some(install_dir.join("files"))
+ } else {
+ None
+ }
+ }
+
+ fn get_xdg_env_args() -> Vec<OsString> {
+ let xdg_keys = [
+ "XDG_DATA_HOME",
+ "XDG_CONFIG_HOME",
+ "XDG_CACHE_HOME",
+ "XDG_STATE_HOME",
+ ];
+ env::vars()
+ .filter(|(key, _)| xdg_keys.contains(&key.as_str()))
+ .map(|(key, val)| format!("--env=FLATPAK_{}={}", key, val).into())
+ .collect()
+ }
+}
+
// todo("windows")
#[cfg(target_os = "windows")]
mod windows {
@@ -13,9 +13,11 @@ lazy_static::lazy_static! {
.expect("failed to determine RoamingAppData directory")
.join("Zed")
} else if cfg!(target_os = "linux") {
- dirs::config_dir()
- .expect("failed to determine XDG_CONFIG_HOME directory")
- .join("zed")
+ if let Ok(flatpak_xdg_config) = std::env::var("FLATPAK_XDG_CONFIG_HOME") {
+ flatpak_xdg_config.into()
+ } else {
+ dirs::config_dir().expect("failed to determine XDG_CONFIG_HOME directory")
+ }.join("zed")
} else {
HOME.join(".config").join("zed")
};
@@ -39,9 +41,11 @@ lazy_static::lazy_static! {
pub static ref SUPPORT_DIR: PathBuf = if cfg!(target_os = "macos") {
HOME.join("Library/Application Support/Zed")
} else if cfg!(target_os = "linux") {
- dirs::data_local_dir()
- .expect("failed to determine XDG_DATA_DIR directory")
- .join("zed")
+ if let Ok(flatpak_xdg_data) = std::env::var("FLATPAK_XDG_DATA_HOME") {
+ flatpak_xdg_data.into()
+ } else {
+ dirs::data_local_dir().expect("failed to determine XDG_DATA_HOME directory")
+ }.join("zed")
} else if cfg!(target_os = "windows") {
dirs::data_local_dir()
.expect("failed to determine LocalAppData directory")
@@ -80,9 +84,11 @@ lazy_static::lazy_static! {
.expect("failed to determine LocalAppData directory")
.join("Zed")
} else if cfg!(target_os = "linux") {
- dirs::cache_dir()
- .expect("failed to determine XDG_CACHE_HOME directory")
- .join("zed")
+ if let Ok(flatpak_xdg_cache) = std::env::var("FLATPAK_XDG_CACHE_HOME") {
+ flatpak_xdg_cache.into()
+ } else {
+ dirs::cache_dir().expect("failed to determine XDG_CACHE_HOME directory")
+ }.join("zed")
} else {
HOME.join(".cache").join("zed")
};
@@ -0,0 +1,59 @@
+{
+ "id": "$APP_ID",
+ "runtime": "org.freedesktop.Platform",
+ "runtime-version": "23.08",
+ "sdk": "org.freedesktop.Sdk",
+ "sdk-extensions": [
+ "org.freedesktop.Sdk.Extension.rust-stable"
+ ],
+ "command": "zed",
+ "finish-args": [
+ "--talk-name=org.freedesktop.Flatpak",
+ "--device=dri",
+ "--share=ipc",
+ "--share=network",
+ "--socket=wayland",
+ "--socket=fallback-x11",
+ "--socket=pulseaudio",
+ "--filesystem=host"
+ ],
+ "build-options": {
+ "append-path": "/usr/lib/sdk/rust-stable/bin"
+ },
+ "modules": [
+ {
+ "name": "zed",
+ "buildsystem": "simple",
+ "build-options": {
+ "env": {
+ "APP_ID": "$APP_ID",
+ "APP_ICON": "$APP_ID",
+ "APP_NAME": "$APP_NAME",
+ "BRANDING_LIGHT": "$BRANDING_LIGHT",
+ "BRANDING_DARK": "$BRANDING_DARK",
+ "APP_ARGS": "--foreground",
+ "DO_STARTUP_NOTIFY": "false"
+ }
+ },
+ "build-commands": [
+ "install -Dm644 $ICON_FILE.png /app/share/icons/hicolor/512x512/apps/$APP_ID.png",
+ "envsubst < zed.desktop.in > zed.desktop && install -Dm644 zed.desktop /app/share/applications/$APP_ID.desktop",
+ "envsubst < flatpak/zed.metainfo.xml.in > zed.metainfo.xml && install -Dm644 zed.metainfo.xml /app/share/metainfo/$APP_ID.metainfo.xml",
+ "sed -i -e '/@release_info@/{r flatpak/release-info/$CHANNEL' -e 'd}' /app/share/metainfo/$APP_ID.metainfo.xml",
+ "install -Dm755 bin/cli /app/bin/zed",
+ "install -Dm755 bin/zed /app/bin/zed-app",
+ "install -Dm755 lib/* -t /app/lib"
+ ],
+ "sources": [
+ {
+ "type": "archive",
+ "path": "./target/release/$ARCHIVE"
+ },
+ {
+ "type": "dir",
+ "path": "./crates/zed/resources"
+ }
+ ]
+ }
+ ]
+}
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop-application">
+ <id>$APP_ID</id>
+ <metadata_license>MIT</metadata_license>
+ <project_license>AGPL-3.0-or-later and Apache-2.0 and GPL-3.0-or-later</project_license>
+
+ <name>$APP_NAME</name>
+ <summary>High-performance, multiplayer code editor</summary>
+ <developer id="dev.zed">
+ <name translate="no">Zed Industries, Inc.</name>
+ </developer>
+ <description>
+ <p>
+ Productive coding starts with a tool that stays out of your way. Zed blends the power of an IDE with the speed of a lightweight editor for productivity you can feel under your fingertips.
+ </p>
+ <p>Features:</p>
+ <ul>
+ <li>Performance: Efficiently uses every CPU core and your GPU for instant startup, quick file loading, and responsive keystrokes.</li>
+ <li>Language-aware: Maintains a syntax tree for precise highlighting, and auto-indent, with LSP support for autocompletion and refactoring.</li>
+ <li>Collaboration: Real-time editing and navigation for multiple developers in a shared workspace.</li>
+ <li>AI Integration: Integrates GitHub Copilot and GPT-4 for natural language code generation.</li>
+ </ul>
+ </description>
+
+ <launchable type="desktop-id">$APP_ID.desktop</launchable>
+
+ <branding>
+ <color type="primary" scheme_preference="light">$BRANDING_LIGHT</color>
+ <color type="primary" scheme_preference="dark">$BRANDING_DARK</color>
+ </branding>
+
+ <content_rating type="oars-1.1">
+ <content_attribute id="social-chat">intense</content_attribute>
+ <content_attribute id="social-audio">intense</content_attribute>
+ </content_rating>
+
+ <url type="homepage">https://zed.dev</url>
+ <url type="bugtracker">https://github.com/zed-industries/zed/issues</url>
+ <url type="faq">https://zed.dev/faq</url>
+ <url type="help">https://zed.dev/docs/getting-started</url>
+ <url type="contact">https://zed.dev/docs/feedback-and-support</url>
+ <url type="vcs-browser">https://github.com/zed-industries/zed</url>
+ <url type="contribute">https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md</url>
+
+ <supports>
+ <internet>offline-only</internet>
+ </supports>
+ <recommends>
+ <control>pointing</control>
+ <control>keyboard</control>
+ <display_length compare="ge">768</display_length>
+ </recommends>
+
+ <screenshots>
+ <screenshot type="default">
+ <caption>Zed with a large project open, showing language server and gitblame support</caption>
+ <image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-1.png</image>
+ </screenshot>
+ <screenshot>
+ <caption>Zed with a file open and a channel message thread in the right sidebar</caption>
+ <image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-2.png</image>
+ </screenshot>
+ <screenshot>
+ <caption>Example of a channel's shared document</caption>
+ <image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-3.png</image>
+ </screenshot>
+ <screenshot>
+ <caption>Zed's extension list</caption>
+ <image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-4.png</image>
+ </screenshot>
+ <screenshot>
+ <caption>Theme switcher UI and example theme</caption>
+ <image type="source" width="1122" height="859" xml:lang="en">https://zed.dev/img/flatpak/flatpak-5.png</image>
+ </screenshot>
+ </screenshots>
+
+ <releases>
+ @release_info@
+ </releases>
+</component>
@@ -1,13 +1,13 @@
[Desktop Entry]
Version=1.0
Type=Application
-Name=Zed
+Name=$APP_NAME
GenericName=Text Editor
Comment=A high-performance, multiplayer code editor.
TryExec=zed
-StartupNotify=true
-Exec=zed
-Icon=zed
-Categories=TextEditor;Development;IDE;
+StartupNotify=$DO_STARTUP_NOTIFY
+Exec=zed $APP_ARGS
+Icon=$APP_ICON
+Categories=Utility;TextEditor;Development;IDE;
Keywords=zed;
MimeType=text/plain;inode/directory;
@@ -70,6 +70,18 @@ cargo test --workspace
Zed has basic support for both modes. The mode is selected at runtime. If you're on wayland and want to run in X11 mode, you can set `WAYLAND_DISPLAY='' cargo run` to do so.
+## Flatpak
+
+> [!WARNING]
+> Zed's current Flatpak integration simply exits the sandbox on startup. Workflows that rely on Flatpak's sandboxing may not work as expected.
+
+To build & install the Flatpak package locally follow the steps below:
+
+1. Install Flatpak for your distribution as outlined [here](https://flathub.org/setup).
+2. Run the `script/flatpak/deps` script to install the required dependencies.
+3. Run `script/flatpak/bundle-flatpak`.
+4. Now the package has been installed and has a bundle available at `target/release/{app-id}.flatpak`.
+
## Troubleshooting
### Can't compile zed
@@ -79,14 +79,21 @@ mkdir -p "${zed_dir}/share/icons/hicolor/1024x1024/apps"
cp "crates/zed/resources/app-icon$suffix@2x.png" "${zed_dir}/share/icons/hicolor/1024x1024/apps/zed.png"
# .desktop
-mkdir -p "${zed_dir}/share/applications"
-cp "crates/zed/resources/zed.desktop" "${zed_dir}/share/applications/zed$suffix.desktop"
+export DO_STARTUP_NOTIFY="true"
+export APP_ICON="zed"
if [[ "$channel" == "preview" ]]; then
- sed -i "s|Name=Zed|Name=Zed Preview|g" "${zed_dir}/share/applications/zed$suffix.desktop"
+ export APP_NAME="Zed Preview"
elif [[ "$channel" == "nightly" ]]; then
- sed -i "s|Name=Zed|Name=Zed Nightly|g" "${zed_dir}/share/applications/zed$suffix.desktop"
+ export APP_NAME="Zed Nightly"
+elif [[ "$channel" == "dev" ]]; then
+ export APP_NAME="Zed Devel"
+else
+ export APP_NAME="Zed"
fi
+mkdir -p "${zed_dir}/share/applications"
+envsubst < "crates/zed/resources/zed.desktop.in" > "${zed_dir}/share/applications/zed$suffix.desktop"
+
# Licenses
cp "assets/licenses.md" "${zed_dir}/licenses.md"
@@ -102,4 +109,6 @@ else
fi
rm -rf "${archive}"
+remove_match="zed(-[a-zA-Z0-9]+)?-linux-$(uname -m)\.tar\.gz"
+ls target/release | grep -E ${remove_match} | xargs -d "\n" -I {} rm -f target/release/{} || true
tar -czvf target/release/$archive -C ${temp_dir} "zed$suffix.app"
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+cd "$(dirname "$0")/../.."
+shopt -s extglob
+
+script/bundle-linux
+archive_match="zed(-[a-zA-Z0-9]+)?-linux-$(uname -m)\.tar\.gz"
+archive=$(ls "target/release" | grep -E ${archive_match})
+channel=$(<crates/zed/RELEASE_CHANNEL)
+
+export CHANNEL="$channel"
+export ARCHIVE="$archive"
+if [[ "$channel" == "dev" ]]; then
+ export APP_ID="dev.zed.Zed-Dev"
+ export APP_NAME="Zed Devel"
+ export BRANDING_LIGHT="#99c1f1"
+ export BRANDING_DARK="#1a5fb4"
+ export ICON_FILE="app-icon-dev"
+elif [[ "$channel" == "nightly" ]]; then
+ export APP_ID="dev.zed.Zed-Nightly"
+ export APP_NAME="Zed Nightly"
+ export BRANDING_LIGHT="#e9aa6a"
+ export BRANDING_DARK="#1a5fb4"
+ export ICON_FILE="app-icon-nightly"
+elif [[ "$channel" == "preview" ]]; then
+ export APP_ID="dev.zed.Zed-Preview"
+ export APP_NAME="Zed Preview"
+ export BRANDING_LIGHT="#99c1f1"
+ export BRANDING_DARK="#1a5fb4"
+ export ICON_FILE="app-icon-preview"
+elif [[ "$channel" == "stable" ]]; then
+ export APP_ID="dev.zed.Zed"
+ export APP_NAME="Zed"
+ export BRANDING_LIGHT="#99c1f1"
+ export BRANDING_DARK="#1a5fb4"
+ export ICON_FILE="app-icon"
+else
+ echo "Invalid channel: '$channel'"
+ exit
+fi
+
+envsubst < "crates/zed/resources/flatpak/manifest-template.json" > "$APP_ID.json"
+flatpak-builder --user --install --force-clean build "$APP_ID.json"
+flatpak build-bundle ~/.local/share/flatpak/repo "target/release/$APP_ID.flatpak" "$APP_ID"
+echo "Created 'target/release/$APP_ID.flatpak'"
@@ -0,0 +1,93 @@
+import re
+import requests
+import sys
+import textwrap
+import os
+
+def clean_line(line: str, in_code_fence: bool) -> str:
+ line = re.sub(r"<", "<", line)
+ line = re.sub(r">", ">", line)
+ line = re.sub(r"\(\[(#\d+)\]\([\w|\d\:|\/|\.|\-|_]*\)\)", lambda match: f"[{match.group(1)}]", line)
+ line = re.sub(r"\[(#\d+)\]\([\w|\d\:|\/|\.|\-|_]*\)", lambda match: f"[{match.group(1)}]", line)
+ if not in_code_fence:
+ line = line.strip()
+
+ return line
+
+
+def convert_body(body: str) -> str:
+ formatted = ""
+
+ in_code_fence = False
+ in_list = False
+ for line in body.splitlines():
+ line = clean_line(line, in_code_fence)
+ if not line:
+ continue
+ if re.search(r'\[[\w|\d|:|\/|\.|\-|_]*\]\([\w|\d|:|\/|\.|\-|_]*\)', line):
+ continue
+ line = re.sub(r"(?<!\`)`([^`\n]+)`(?!`)", lambda match: f"<code>{match.group(1)}</code>", line)
+
+ contains_code_fence = bool(re.search(r"```", line))
+ is_list = bool(re.search(r"^-\s*", line))
+
+ if in_list and not is_list:
+ formatted += "</ul>\n"
+ if (not in_code_fence and contains_code_fence) or (not in_list and is_list):
+ formatted += "<ul>\n"
+ in_list = is_list
+ in_code_fence = contains_code_fence != in_code_fence
+
+ if is_list:
+ line = re.sub(r"^-\s*", "", line)
+ line = f" <li>{line}</li>"
+ elif in_code_fence or contains_code_fence:
+ line = f" <li><code> {line}</code></li>"
+ else:
+ line = f"<p>{line}</p>"
+ formatted += f"{line}\n"
+
+ if (not in_code_fence and contains_code_fence):
+ formatted += "</ul>\n"
+ if in_code_fence or in_list:
+ formatted += "</ul>\n"
+ return formatted
+
+def get_release_info(tag: str):
+ url = f"https://api.github.com/repos/zed-industries/zed/releases/tags/{tag}"
+ response = requests.get(url)
+ if response.status_code == 200:
+ return response.json()
+ else:
+ print(f"Failed to fetch release info for tag '{tag}'. Status code: {response.status_code}")
+ quit()
+
+
+if __name__ == "__main__":
+ os.chdir(sys.path[0])
+
+ if len(sys.argv) != 3:
+ print("Usage: python convert-release-notes.py <tag> <channel>")
+ sys.exit(1)
+
+ tag = sys.argv[1]
+ channel = sys.argv[2]
+
+ release_info = get_release_info(tag)
+ body = convert_body(release_info["body"])
+ version = tag.removeprefix("v").removesuffix("-pre")
+ date = release_info["published_at"]
+
+ release_info_str = f"<release version=\"{version}\" date=\"{date}\">\n"
+ release_info_str += f" <description>\n"
+ release_info_str += textwrap.indent(body, " " * 8)
+ release_info_str += f" </description>\n"
+ release_info_str += f" <url>https://github.com/zed-industries/zed/releases/tag/{tag}</url>\n"
+ release_info_str += "</release>\n"
+
+ channel_releases_file = f"../../crates/zed/resources/flatpak/release-info/{channel}"
+ with open(channel_releases_file) as f:
+ old_release_info = f.read()
+ with open(channel_releases_file, "w") as f:
+ f.write(textwrap.indent(release_info_str, " " * 8) + old_release_info)
+ print(f"Added release notes from {tag} to '{channel_releases_file}'")
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+flatpak remote-add --if-not-exists --user flathub https://dl.flathub.org/repo/flathub.flatpakrepo
+
+arch=$(arch)
+fd_version=23.08
+flatpak install -y --user org.freedesktop.Platform/${arch}/${fd_version}
+flatpak install -y --user org.freedesktop.Sdk/${arch}/${fd_version}
+flatpak install -y --user org.freedesktop.Sdk.Extension.rust-stable/${arch}/${fd_version}