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