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