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