extension_api.rs

  1//! The Zed Rust Extension API allows you write extensions for [Zed](https://zed.dev/) in Rust.
  2
  3pub mod http_client;
  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::{
 28        SlashCommand, SlashCommandArgumentCompletion, SlashCommandOutput, SlashCommandOutputSection,
 29    },
 30    CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command, DownloadedFileType, EnvVars,
 31    KeyValueStore, LanguageServerInstallationStatus, Project, Range, Worktree,
 32};
 33
 34// Undocumented WIT re-exports.
 35//
 36// These are symbols that need to be public for the purposes of implementing
 37// the extension host, but aren't relevant to extension authors.
 38#[doc(hidden)]
 39pub use wit::Guest;
 40
 41/// Constructs for interacting with language servers over the
 42/// Language Server Protocol (LSP).
 43pub mod lsp {
 44    pub use crate::wit::zed::extension::lsp::{
 45        Completion, CompletionKind, InsertTextFormat, Symbol, SymbolKind,
 46    };
 47}
 48
 49/// A result returned from a Zed extension.
 50pub type Result<T, E = String> = core::result::Result<T, E>;
 51
 52/// Updates the installation status for the given language server.
 53pub fn set_language_server_installation_status(
 54    language_server_id: &LanguageServerId,
 55    status: &LanguageServerInstallationStatus,
 56) {
 57    wit::set_language_server_installation_status(&language_server_id.0, status)
 58}
 59
 60/// A Zed extension.
 61pub trait Extension: Send + Sync {
 62    /// Returns a new instance of the extension.
 63    fn new() -> Self
 64    where
 65        Self: Sized;
 66
 67    /// Returns the command used to start the language server for the specified
 68    /// language.
 69    fn language_server_command(
 70        &mut self,
 71        _language_server_id: &LanguageServerId,
 72        _worktree: &Worktree,
 73    ) -> Result<Command> {
 74        Err("`language_server_command` not implemented".to_string())
 75    }
 76
 77    /// Returns the initialization options to pass to the specified language server.
 78    fn language_server_initialization_options(
 79        &mut self,
 80        _language_server_id: &LanguageServerId,
 81        _worktree: &Worktree,
 82    ) -> Result<Option<serde_json::Value>> {
 83        Ok(None)
 84    }
 85
 86    /// Returns the workspace configuration options to pass to the language server.
 87    fn language_server_workspace_configuration(
 88        &mut self,
 89        _language_server_id: &LanguageServerId,
 90        _worktree: &Worktree,
 91    ) -> Result<Option<serde_json::Value>> {
 92        Ok(None)
 93    }
 94
 95    /// Returns the label for the given completion.
 96    fn label_for_completion(
 97        &self,
 98        _language_server_id: &LanguageServerId,
 99        _completion: Completion,
100    ) -> Option<CodeLabel> {
101        None
102    }
103
104    /// Returns the label for the given symbol.
105    fn label_for_symbol(
106        &self,
107        _language_server_id: &LanguageServerId,
108        _symbol: Symbol,
109    ) -> Option<CodeLabel> {
110        None
111    }
112
113    /// Returns the completions that should be shown when completing the provided slash command with the given query.
114    fn complete_slash_command_argument(
115        &self,
116        _command: SlashCommand,
117        _args: Vec<String>,
118    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
119        Ok(Vec::new())
120    }
121
122    /// Returns the output from running the provided slash command.
123    fn run_slash_command(
124        &self,
125        _command: SlashCommand,
126        _args: Vec<String>,
127        _worktree: Option<&Worktree>,
128    ) -> Result<SlashCommandOutput, String> {
129        Err("`run_slash_command` not implemented".to_string())
130    }
131
132    /// Returns the command used to start a context server.
133    fn context_server_command(
134        &mut self,
135        _context_server_id: &ContextServerId,
136        _project: &Project,
137    ) -> Result<Command> {
138        Err("`context_server_command` not implemented".to_string())
139    }
140
141    /// Returns a list of package names as suggestions to be included in the
142    /// search results of the `/docs` slash command.
143    ///
144    /// This can be used to provide completions for known packages (e.g., from the
145    /// local project or a registry) before a package has been indexed.
146    fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
147        Ok(Vec::new())
148    }
149
150    /// Indexes the docs for the specified package.
151    fn index_docs(
152        &self,
153        _provider: String,
154        _package: String,
155        _database: &KeyValueStore,
156    ) -> Result<(), String> {
157        Err("`index_docs` not implemented".to_string())
158    }
159}
160
161/// Registers the provided type as a Zed extension.
162///
163/// The type must implement the [`Extension`] trait.
164#[macro_export]
165macro_rules! register_extension {
166    ($extension_type:ty) => {
167        #[export_name = "init-extension"]
168        pub extern "C" fn __init_extension() {
169            std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
170            zed_extension_api::register_extension(|| {
171                Box::new(<$extension_type as zed_extension_api::Extension>::new())
172            });
173        }
174    };
175}
176
177#[doc(hidden)]
178pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
179    unsafe { EXTENSION = Some((build_extension)()) }
180}
181
182fn extension() -> &'static mut dyn Extension {
183    unsafe { EXTENSION.as_deref_mut().unwrap() }
184}
185
186static mut EXTENSION: Option<Box<dyn Extension>> = None;
187
188#[cfg(target_arch = "wasm32")]
189#[link_section = "zed:api-version"]
190#[doc(hidden)]
191pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
192
193mod wit {
194    #![allow(clippy::too_many_arguments, clippy::missing_safety_doc)]
195
196    wit_bindgen::generate!({
197        skip: ["init-extension"],
198        path: "./wit/since_v0.3.0",
199    });
200}
201
202wit::export!(Component);
203
204struct Component;
205
206impl wit::Guest for Component {
207    fn language_server_command(
208        language_server_id: String,
209        worktree: &wit::Worktree,
210    ) -> Result<wit::Command> {
211        let language_server_id = LanguageServerId(language_server_id);
212        extension().language_server_command(&language_server_id, worktree)
213    }
214
215    fn language_server_initialization_options(
216        language_server_id: String,
217        worktree: &Worktree,
218    ) -> Result<Option<String>, String> {
219        let language_server_id = LanguageServerId(language_server_id);
220        Ok(extension()
221            .language_server_initialization_options(&language_server_id, worktree)?
222            .and_then(|value| serde_json::to_string(&value).ok()))
223    }
224
225    fn language_server_workspace_configuration(
226        language_server_id: String,
227        worktree: &Worktree,
228    ) -> Result<Option<String>, String> {
229        let language_server_id = LanguageServerId(language_server_id);
230        Ok(extension()
231            .language_server_workspace_configuration(&language_server_id, worktree)?
232            .and_then(|value| serde_json::to_string(&value).ok()))
233    }
234
235    fn labels_for_completions(
236        language_server_id: String,
237        completions: Vec<Completion>,
238    ) -> Result<Vec<Option<CodeLabel>>, String> {
239        let language_server_id = LanguageServerId(language_server_id);
240        let mut labels = Vec::new();
241        for (ix, completion) in completions.into_iter().enumerate() {
242            let label = extension().label_for_completion(&language_server_id, completion);
243            if let Some(label) = label {
244                labels.resize(ix + 1, None);
245                *labels.last_mut().unwrap() = Some(label);
246            }
247        }
248        Ok(labels)
249    }
250
251    fn labels_for_symbols(
252        language_server_id: String,
253        symbols: Vec<Symbol>,
254    ) -> Result<Vec<Option<CodeLabel>>, String> {
255        let language_server_id = LanguageServerId(language_server_id);
256        let mut labels = Vec::new();
257        for (ix, symbol) in symbols.into_iter().enumerate() {
258            let label = extension().label_for_symbol(&language_server_id, symbol);
259            if let Some(label) = label {
260                labels.resize(ix + 1, None);
261                *labels.last_mut().unwrap() = Some(label);
262            }
263        }
264        Ok(labels)
265    }
266
267    fn complete_slash_command_argument(
268        command: SlashCommand,
269        args: Vec<String>,
270    ) -> Result<Vec<SlashCommandArgumentCompletion>, String> {
271        extension().complete_slash_command_argument(command, args)
272    }
273
274    fn run_slash_command(
275        command: SlashCommand,
276        args: Vec<String>,
277        worktree: Option<&Worktree>,
278    ) -> Result<SlashCommandOutput, String> {
279        extension().run_slash_command(command, args, worktree)
280    }
281
282    fn context_server_command(
283        context_server_id: String,
284        project: &Project,
285    ) -> Result<wit::Command> {
286        let context_server_id = ContextServerId(context_server_id);
287        extension().context_server_command(&context_server_id, project)
288    }
289
290    fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
291        extension().suggest_docs_packages(provider)
292    }
293
294    fn index_docs(
295        provider: String,
296        package: String,
297        database: &KeyValueStore,
298    ) -> Result<(), String> {
299        extension().index_docs(provider, package, database)
300    }
301}
302
303/// The ID of a language server.
304#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
305pub struct LanguageServerId(String);
306
307impl AsRef<str> for LanguageServerId {
308    fn as_ref(&self) -> &str {
309        &self.0
310    }
311}
312
313impl fmt::Display for LanguageServerId {
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        write!(f, "{}", self.0)
316    }
317}
318
319/// The ID of a context server.
320#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
321pub struct ContextServerId(String);
322
323impl AsRef<str> for ContextServerId {
324    fn as_ref(&self) -> &str {
325        &self.0
326    }
327}
328
329impl fmt::Display for ContextServerId {
330    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331        write!(f, "{}", self.0)
332    }
333}
334
335impl CodeLabelSpan {
336    /// Returns a [`CodeLabelSpan::CodeRange`].
337    pub fn code_range(range: impl Into<wit::Range>) -> Self {
338        Self::CodeRange(range.into())
339    }
340
341    /// Returns a [`CodeLabelSpan::Literal`].
342    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
343        Self::Literal(CodeLabelSpanLiteral {
344            text: text.into(),
345            highlight_name,
346        })
347    }
348}
349
350impl From<std::ops::Range<u32>> for wit::Range {
351    fn from(value: std::ops::Range<u32>) -> Self {
352        Self {
353            start: value.start,
354            end: value.end,
355        }
356    }
357}
358
359impl From<std::ops::Range<usize>> for wit::Range {
360    fn from(value: std::ops::Range<usize>) -> Self {
361        Self {
362            start: value.start as u32,
363            end: value.end as u32,
364        }
365    }
366}