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