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