extension_api.rs

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