extension_api.rs

  1//! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust.
  2
  3pub mod settings;
  4
  5use core::fmt;
  6
  7use wit::*;
  8
  9pub use serde_json;
 10
 11// WIT re-exports.
 12//
 13// We explicitly enumerate the symbols we want to re-export, as there are some
 14// that we may want to shadow to provide a cleaner Rust API.
 15pub use wit::{
 16    download_file, make_file_executable,
 17    zed::extension::github::{
 18        latest_github_release, GithubRelease, GithubReleaseAsset, GithubReleaseOptions,
 19    },
 20    zed::extension::nodejs::{
 21        node_binary_path, npm_install_package, npm_package_installed_version,
 22        npm_package_latest_version,
 23    },
 24    zed::extension::platform::{current_platform, Architecture, Os},
 25    CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
 26    LanguageServerInstallationStatus, Range, Worktree,
 27};
 28
 29// Undocumented WIT re-exports.
 30//
 31// These are symbols that need to be public for the purposes of implementing
 32// the extension host, but aren't relevant to extension authors.
 33#[doc(hidden)]
 34pub use wit::Guest;
 35
 36/// Constructs for interacting with language servers over the
 37/// Language Server Protocol (LSP).
 38pub mod lsp {
 39    pub use crate::wit::zed::extension::lsp::{
 40        Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind,
 41    };
 42}
 43
 44/// A result returned from a Zed extension.
 45pub type Result<T, E = String> = core::result::Result<T, E>;
 46
 47/// Updates the installation status for the given language server.
 48pub fn set_language_server_installation_status(
 49    language_server_id: &LanguageServerId,
 50    status: &LanguageServerInstallationStatus,
 51) {
 52    wit::set_language_server_installation_status(&language_server_id.0, status)
 53}
 54
 55/// A Zed extension.
 56pub trait Extension: Send + Sync {
 57    /// Returns a new instance of the extension.
 58    fn new() -> Self
 59    where
 60        Self: Sized;
 61
 62    /// Returns the command used to start the language server for the specified
 63    /// language.
 64    fn language_server_command(
 65        &mut self,
 66        language_server_id: &LanguageServerId,
 67        worktree: &Worktree,
 68    ) -> Result<Command>;
 69
 70    /// Returns the initialization options to pass to the specified language server.
 71    fn language_server_initialization_options(
 72        &mut self,
 73        _language_server_id: &LanguageServerId,
 74        _worktree: &Worktree,
 75    ) -> Result<Option<serde_json::Value>> {
 76        Ok(None)
 77    }
 78
 79    /// Returns the workspace configuration options to pass to the language server.
 80    fn language_server_workspace_configuration(
 81        &mut self,
 82        _language_server_id: &LanguageServerId,
 83        _worktree: &Worktree,
 84    ) -> Result<Option<serde_json::Value>> {
 85        Ok(None)
 86    }
 87
 88    /// Returns the label for the given completion.
 89    fn label_for_completion(
 90        &self,
 91        _language_server_id: &LanguageServerId,
 92        _completion: Completion,
 93    ) -> Option<CodeLabel> {
 94        None
 95    }
 96
 97    /// Returns the label for the given symbol.
 98    fn label_for_symbol(
 99        &self,
100        _language_server_id: &LanguageServerId,
101        _symbol: Symbol,
102    ) -> Option<CodeLabel> {
103        None
104    }
105}
106
107/// Registers the provided type as a Zed extension.
108///
109/// The type must implement the [`Extension`] trait.
110#[macro_export]
111macro_rules! register_extension {
112    ($extension_type:ty) => {
113        #[export_name = "init-extension"]
114        pub extern "C" fn __init_extension() {
115            std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
116            zed_extension_api::register_extension(|| {
117                Box::new(<$extension_type as zed_extension_api::Extension>::new())
118            });
119        }
120    };
121}
122
123#[doc(hidden)]
124pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
125    unsafe { EXTENSION = Some((build_extension)()) }
126}
127
128fn extension() -> &'static mut dyn Extension {
129    unsafe { EXTENSION.as_deref_mut().unwrap() }
130}
131
132static mut EXTENSION: Option<Box<dyn Extension>> = None;
133
134#[cfg(target_arch = "wasm32")]
135#[link_section = "zed:api-version"]
136#[doc(hidden)]
137pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
138
139mod wit {
140    wit_bindgen::generate!({
141        skip: ["init-extension"],
142        path: "./wit/since_v0.0.6",
143    });
144}
145
146wit::export!(Component);
147
148struct Component;
149
150impl wit::Guest for Component {
151    fn language_server_command(
152        language_server_id: String,
153        worktree: &wit::Worktree,
154    ) -> Result<wit::Command> {
155        let language_server_id = LanguageServerId(language_server_id);
156        extension().language_server_command(&language_server_id, worktree)
157    }
158
159    fn language_server_initialization_options(
160        language_server_id: String,
161        worktree: &Worktree,
162    ) -> Result<Option<String>, String> {
163        let language_server_id = LanguageServerId(language_server_id);
164        Ok(extension()
165            .language_server_initialization_options(&language_server_id, worktree)?
166            .and_then(|value| serde_json::to_string(&value).ok()))
167    }
168
169    fn language_server_workspace_configuration(
170        language_server_id: String,
171        worktree: &Worktree,
172    ) -> Result<Option<String>, String> {
173        let language_server_id = LanguageServerId(language_server_id);
174        Ok(extension()
175            .language_server_workspace_configuration(&language_server_id, worktree)?
176            .and_then(|value| serde_json::to_string(&value).ok()))
177    }
178
179    fn labels_for_completions(
180        language_server_id: String,
181        completions: Vec<Completion>,
182    ) -> Result<Vec<Option<CodeLabel>>, String> {
183        let language_server_id = LanguageServerId(language_server_id);
184        let mut labels = Vec::new();
185        for (ix, completion) in completions.into_iter().enumerate() {
186            let label = extension().label_for_completion(&language_server_id, completion);
187            if let Some(label) = label {
188                labels.resize(ix + 1, None);
189                *labels.last_mut().unwrap() = Some(label);
190            }
191        }
192        Ok(labels)
193    }
194
195    fn labels_for_symbols(
196        language_server_id: String,
197        symbols: Vec<Symbol>,
198    ) -> Result<Vec<Option<CodeLabel>>, String> {
199        let language_server_id = LanguageServerId(language_server_id);
200        let mut labels = Vec::new();
201        for (ix, symbol) in symbols.into_iter().enumerate() {
202            let label = extension().label_for_symbol(&language_server_id, symbol);
203            if let Some(label) = label {
204                labels.resize(ix + 1, None);
205                *labels.last_mut().unwrap() = Some(label);
206            }
207        }
208        Ok(labels)
209    }
210}
211
212/// The ID of a language server.
213#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
214pub struct LanguageServerId(String);
215
216impl AsRef<str> for LanguageServerId {
217    fn as_ref(&self) -> &str {
218        &self.0
219    }
220}
221
222impl fmt::Display for LanguageServerId {
223    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
224        write!(f, "{}", self.0)
225    }
226}
227
228impl CodeLabelSpan {
229    /// Returns a [`CodeLabelSpan::CodeRange`].
230    pub fn code_range(range: impl Into<wit::Range>) -> Self {
231        Self::CodeRange(range.into())
232    }
233
234    /// Returns a [`CodeLabelSpan::Literal`].
235    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
236        Self::Literal(CodeLabelSpanLiteral {
237            text: text.into(),
238            highlight_name,
239        })
240    }
241}
242
243impl From<std::ops::Range<u32>> for wit::Range {
244    fn from(value: std::ops::Range<u32>) -> Self {
245        Self {
246            start: value.start,
247            end: value.end,
248        }
249    }
250}
251
252impl From<std::ops::Range<usize>> for wit::Range {
253    fn from(value: std::ops::Range<usize>) -> Self {
254        Self {
255            start: value.start as u32,
256            end: value.end as u32,
257        }
258    }
259}