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