# Zed Extension API The Zed Rust Extension API (version 0.7.0) allows developers to write extensions for [Zed](https://zed.dev/) in Rust. This crate provides a comprehensive framework for extending Zed's functionality, including support for language servers, debug adapters, slash commands for the AI assistant, context servers, and documentation indexing. Extensions are compiled to WebAssembly and run in a sandboxed environment within Zed. The API is organized into several modules: the core `Extension` trait that all extensions must implement, an HTTP client for making web requests, process execution utilities, LSP (Language Server Protocol) constructs, and settings access. Extensions can integrate with external tools, download and manage binaries, interact with the filesystem through worktrees, and provide custom functionality to enhance the Zed editing experience. ## Extension Trait - Core Extension Interface The `Extension` trait is the primary interface that all Zed extensions must implement. It provides hooks for language server management, debug adapter configuration, slash commands, context servers, and documentation indexing. Only the `new()` method is required; all other methods have default implementations. ```rust use zed_extension_api::{self as zed, Extension, Result, Command, LanguageServerId, Worktree}; use zed_extension_api::serde_json::{self, json}; struct MyExtension { cached_binary_path: Option, } impl Extension for MyExtension { fn new() -> Self { MyExtension { cached_binary_path: None, } } fn language_server_command( &mut self, language_server_id: &LanguageServerId, worktree: &Worktree, ) -> Result { // Find or download the language server binary let binary_path = worktree .which("my-language-server") .ok_or_else(|| "my-language-server not found in PATH".to_string())?; Ok(Command { command: binary_path, args: vec!["--stdio".to_string()], env: vec![("MY_ENV_VAR".to_string(), "value".to_string())], }) } fn language_server_initialization_options( &mut self, _language_server_id: &LanguageServerId, _worktree: &Worktree, ) -> Result> { Ok(Some(json!({ "typescript": { "tsdk": "./node_modules/typescript/lib" }, "diagnostics": { "enable": true } }))) } } zed::register_extension!(MyExtension); ``` ## register_extension! Macro - Extension Registration The `register_extension!` macro registers your type as a Zed extension. It must be called exactly once in your extension crate with a type that implements the `Extension` trait. This macro generates the necessary WebAssembly exports for Zed to load and interact with your extension. ```rust use zed_extension_api::{self as zed, Extension}; struct SimpleExtension; impl Extension for SimpleExtension { fn new() -> Self { SimpleExtension } } // Register the extension - this is required for Zed to load it zed::register_extension!(SimpleExtension); ``` ## current_platform - Platform Detection Returns the current operating system and architecture as a tuple of `(Os, Architecture)` enums. This is essential for downloading platform-specific binaries for language servers or debug adapters. ```rust use zed_extension_api::{current_platform, Os, Architecture}; fn get_binary_name() -> String { let (os, arch) = current_platform(); let os_str = match os { Os::Mac => "darwin", Os::Linux => "linux", Os::Windows => "windows", }; let arch_str = match arch { Architecture::Aarch64 => "arm64", Architecture::X8664 => "x64", Architecture::X86 => "x86", }; format!("my-server-{}-{}", os_str, arch_str) } // Example output: "my-server-darwin-arm64" on Apple Silicon Mac ``` ## latest_github_release - GitHub Release Fetching Fetches the latest release information from a GitHub repository. Use this to check for and download new versions of language servers or other tools. The repo parameter should be in "owner/repo" format. ```rust use zed_extension_api::{latest_github_release, GithubReleaseOptions}; fn check_for_updates() -> Result { let release = latest_github_release( "rust-lang/rust-analyzer", GithubReleaseOptions { require_assets: true, pre_release: false, }, )?; println!("Latest version: {}", release.version); println!("Release name: {}", release.name); // Find the appropriate asset for the current platform for asset in &release.assets { if asset.name.contains("x86_64-apple-darwin") { println!("Download URL: {}", asset.download_url); return Ok(asset.download_url.clone()); } } Err("No suitable asset found".to_string()) } ``` ## download_file - File Download and Extraction Downloads a file from a URL and saves it to the extension's working directory. Supports automatic extraction for common archive formats including gzip, zip, and tar archives. ```rust use zed_extension_api::{download_file, DownloadedFileType}; fn download_language_server(version: &str) -> Result<(), String> { let url = format!( "https://github.com/rust-lang/rust-analyzer/releases/download/{}/rust-analyzer-x86_64-apple-darwin.gz", version ); // Download and extract a gzip file download_file( &url, "rust-analyzer", DownloadedFileType::Gzip, )?; // For zip archives download_file( "https://example.com/server.zip", "server", DownloadedFileType::Zip, )?; // For tar.gz archives download_file( "https://example.com/server.tar.gz", "server-dir", DownloadedFileType::GzipTar, )?; // For uncompressed files download_file( "https://example.com/binary", "binary", DownloadedFileType::Uncompressed, )?; Ok(()) } ``` ## make_file_executable - Set Execute Permissions Makes a file executable after downloading. This is necessary on Unix-like systems for binary files that need to be run. ```rust use zed_extension_api::{download_file, make_file_executable, DownloadedFileType}; fn setup_binary() -> Result<(), String> { // Download the binary download_file( "https://example.com/my-server", "my-server", DownloadedFileType::Uncompressed, )?; // Make it executable make_file_executable("my-server")?; Ok(()) } ``` ## http_client::fetch - HTTP Requests Performs HTTP requests and returns the response. Supports various HTTP methods, custom headers, request bodies, and redirect policies. Use this for API calls, downloading data, or checking service availability. ```rust use zed_extension_api::http_client::{fetch, HttpRequest, HttpMethod, RedirectPolicy}; fn fetch_api_data() -> Result { let request = HttpRequest { url: "https://api.example.com/data".to_string(), method: HttpMethod::Get, headers: vec![ ("Authorization".to_string(), "Bearer token123".to_string()), ("Accept".to_string(), "application/json".to_string()), ], body: None, redirect_policy: RedirectPolicy::FollowAll, }; let response = fetch(&request)?; if response.status >= 200 && response.status < 300 { let body = String::from_utf8(response.body) .map_err(|e| e.to_string())?; Ok(body) } else { Err(format!("HTTP error: {}", response.status)) } } fn post_json_data() -> Result<(), String> { let request = HttpRequest { url: "https://api.example.com/submit".to_string(), method: HttpMethod::Post, headers: vec![ ("Content-Type".to_string(), "application/json".to_string()), ], body: Some(r#"{"key": "value"}"#.as_bytes().to_vec()), redirect_policy: RedirectPolicy::NoFollow, }; let response = fetch(&request)?; println!("Response status: {}", response.status); Ok(()) } ``` ## Worktree - Project File Access The `Worktree` struct provides access to the project's file system and environment. It allows reading files, finding binaries on PATH, and accessing shell environment variables within the context of a specific project. ```rust use zed_extension_api::{Worktree, Result, Command}; fn configure_from_worktree(worktree: &Worktree) -> Result { // Get the worktree root path let root = worktree.root_path(); println!("Project root: {}", root); // Read a configuration file let config = worktree.read_text_file("package.json") .unwrap_or_else(|_| "{}".to_string()); // Find a binary on PATH let node_path = worktree.which("node") .ok_or_else(|| "Node.js not found".to_string())?; // Get shell environment variables let env_vars = worktree.shell_env(); // Build a command with the discovered paths Ok(Command { command: node_path, args: vec![ format!("{}/node_modules/.bin/typescript-language-server", root), "--stdio".to_string(), ], env: env_vars, }) } ``` ## Slash Commands - AI Assistant Integration Extensions can provide custom slash commands for Zed's AI Assistant. These commands can generate dynamic content, fetch external data, or provide specialized functionality. Implement `complete_slash_command_argument` for argument completions and `run_slash_command` to execute the command. ```rust use zed_extension_api::{ Extension, SlashCommand, SlashCommandOutput, SlashCommandOutputSection, SlashCommandArgumentCompletion, Worktree, Result, }; struct DocsExtension; impl Extension for DocsExtension { fn new() -> Self { DocsExtension } fn complete_slash_command_argument( &self, command: SlashCommand, args: Vec, ) -> Result, String> { if command.name == "docs" { Ok(vec![ SlashCommandArgumentCompletion { label: "rust".to_string(), new_text: "rust".to_string(), run_command: true, }, SlashCommandArgumentCompletion { label: "typescript".to_string(), new_text: "typescript".to_string(), run_command: true, }, ]) } else { Ok(vec![]) } } fn run_slash_command( &self, command: SlashCommand, args: Vec, worktree: Option<&Worktree>, ) -> Result { let topic = args.first().cloned().unwrap_or_default(); let content = match topic.as_str() { "rust" => "# Rust Documentation\n\nRust is a systems programming language...".to_string(), "typescript" => "# TypeScript Documentation\n\nTypeScript is a typed superset of JavaScript...".to_string(), _ => format!("Documentation for '{}' not found.", topic), }; Ok(SlashCommandOutput { sections: vec![SlashCommandOutputSection { range: (0..content.len()).into(), label: format!("Docs: {}", topic), }], text: content, }) } } ``` ## NPM Package Management Helper functions for managing NPM packages within extensions. These are useful for extensions that depend on Node.js-based language servers or tools. ```rust use zed_extension_api::{ node_binary_path, npm_install_package, npm_package_installed_version, npm_package_latest_version, Result, Command, }; fn setup_typescript_server() -> Result { let package = "typescript-language-server"; // Check installed version let installed = npm_package_installed_version(package)?; let latest = npm_package_latest_version(package)?; // Install or update if needed if installed.as_ref() != Some(&latest) { npm_install_package(package, &latest)?; } // Get the Node.js binary path let node = node_binary_path()?; Ok(Command { command: node, args: vec![ "node_modules/.bin/typescript-language-server".to_string(), "--stdio".to_string(), ], env: vec![], }) } ``` ## Language Server Installation Status Update and display the installation status of language servers to provide feedback to users during setup. ```rust use zed_extension_api::{ set_language_server_installation_status, LanguageServerInstallationStatus, LanguageServerId, }; fn install_server(server_id: &LanguageServerId) -> Result<(), String> { // Show downloading status set_language_server_installation_status( server_id, &LanguageServerInstallationStatus::Downloading, ); // Download the server... // download_file(...)?; // Show extraction/installation status set_language_server_installation_status( server_id, &LanguageServerInstallationStatus::CheckingForUpdate, ); // Verify installation... // Clear status on success (None means installed and ready) set_language_server_installation_status( server_id, &LanguageServerInstallationStatus::None, ); Ok(()) } fn handle_install_failure(server_id: &LanguageServerId, error: &str) { set_language_server_installation_status( server_id, &LanguageServerInstallationStatus::Failed(error.to_string()), ); } ``` ## Debug Adapter Support Extensions can provide debug adapter (DAP) support for debugging various languages. This involves implementing methods to locate debug binaries, convert configurations to debug scenarios, and handle debug requests. ```rust use zed_extension_api::{ Extension, DebugAdapterBinary, DebugTaskDefinition, DebugScenario, DebugConfig, DebugRequest, Worktree, Result, StartDebuggingRequestArgumentsRequest, }; use zed_extension_api::serde_json::{json, Value}; struct DebugExtension; impl Extension for DebugExtension { fn new() -> Self { DebugExtension } fn get_dap_binary( &mut self, adapter_name: String, config: DebugTaskDefinition, user_provided_path: Option, worktree: &Worktree, ) -> Result { let binary_path = user_provided_path .or_else(|| worktree.which("lldb-vscode")) .ok_or_else(|| "Debug adapter not found".to_string())?; Ok(DebugAdapterBinary { command: binary_path, args: vec![], envs: vec![], cwd: Some(worktree.root_path()), connection: None, }) } fn dap_request_kind( &mut self, adapter_name: String, config: Value, ) -> Result { // Determine if this is a launch or attach request if config.get("processId").is_some() || config.get("pid").is_some() { Ok(StartDebuggingRequestArgumentsRequest::Attach) } else { Ok(StartDebuggingRequestArgumentsRequest::Launch) } } fn dap_config_to_scenario( &mut self, config: DebugConfig, ) -> Result { Ok(DebugScenario { adapter: "lldb".to_string(), config: json!({ "program": config.program, "args": config.args, "cwd": config.cwd, "env": config.env, }), build: None, tcp_connection: None, }) } } ``` ## KeyValueStore - Documentation Indexing The `KeyValueStore` provides persistent storage for indexed documentation. Use it in the `index_docs` method to store searchable documentation entries. ```rust use zed_extension_api::{Extension, KeyValueStore, Result}; struct DocsIndexer; impl Extension for DocsIndexer { fn new() -> Self { DocsIndexer } fn suggest_docs_packages(&self, provider: String) -> Result, String> { // Suggest packages that can be indexed Ok(vec![ "react".to_string(), "vue".to_string(), "angular".to_string(), ]) } fn index_docs( &self, provider: String, package: String, database: &KeyValueStore, ) -> Result<(), String> { // Fetch documentation for the package let docs = fetch_package_docs(&package)?; // Index each documentation entry for (key, content) in docs { database.insert(&key, &content)?; } Ok(()) } } fn fetch_package_docs(package: &str) -> Result, String> { // Fetch and parse documentation... Ok(vec![ (format!("{}/getting-started", package), "# Getting Started\n...".to_string()), (format!("{}/api-reference", package), "# API Reference\n...".to_string()), ]) } ``` ## Summary The Zed Extension API provides a powerful and flexible framework for extending the Zed editor. The most common use cases include adding language server support for new programming languages, integrating debug adapters for debugging sessions, creating custom slash commands for the AI assistant, and indexing documentation for quick reference. Extensions can leverage the HTTP client for external API communication, the process module for running external tools, and the settings module for reading user configuration. Integration patterns typically involve implementing the `Extension` trait with the necessary method overrides, using platform detection to download appropriate binaries, managing installation status for user feedback, and utilizing the worktree API for project-aware file access. Extensions are distributed as WebAssembly modules, ensuring they run safely within Zed's sandboxed environment while still providing full access to the extension API's capabilities for enhancing the editing experience.