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