extension_api.rs

  1use core::fmt;
  2
  3use wit::*;
  4
  5// WIT re-exports.
  6//
  7// We explicitly enumerate the symbols we want to re-export, as there are some
  8// that we may want to shadow to provide a cleaner Rust API.
  9pub use wit::{
 10    current_platform, download_file, latest_github_release, make_file_executable, node_binary_path,
 11    npm_install_package, npm_package_installed_version, npm_package_latest_version,
 12    zed::extension::lsp, Architecture, CodeLabel, CodeLabelSpan, CodeLabelSpanLiteral, Command,
 13    DownloadedFileType, EnvVars, GithubRelease, GithubReleaseAsset, GithubReleaseOptions,
 14    LanguageServerInstallationStatus, Os, Range, Worktree,
 15};
 16
 17// Undocumented WIT re-exports.
 18//
 19// These are symbols that need to be public for the purposes of implementing
 20// the extension host, but aren't relevant to extension authors.
 21#[doc(hidden)]
 22pub use wit::Guest;
 23
 24/// A result returned from a Zed extension.
 25pub type Result<T, E = String> = core::result::Result<T, E>;
 26
 27/// Updates the installation status for the given language server.
 28pub fn set_language_server_installation_status(
 29    language_server_id: &LanguageServerId,
 30    status: &LanguageServerInstallationStatus,
 31) {
 32    wit::set_language_server_installation_status(&language_server_id.0, status)
 33}
 34
 35/// A Zed extension.
 36pub trait Extension: Send + Sync {
 37    /// Returns a new instance of the extension.
 38    fn new() -> Self
 39    where
 40        Self: Sized;
 41
 42    /// Returns the command used to start the language server for the specified
 43    /// language.
 44    fn language_server_command(
 45        &mut self,
 46        language_server_id: &LanguageServerId,
 47        worktree: &Worktree,
 48    ) -> Result<Command>;
 49
 50    /// Returns the initialization options to pass to the specified language server.
 51    fn language_server_initialization_options(
 52        &mut self,
 53        _language_server_id: &LanguageServerId,
 54        _worktree: &Worktree,
 55    ) -> Result<Option<String>> {
 56        Ok(None)
 57    }
 58
 59    /// Returns the label for the given completion.
 60    fn label_for_completion(
 61        &self,
 62        _language_server_id: &LanguageServerId,
 63        _completion: Completion,
 64    ) -> Option<CodeLabel> {
 65        None
 66    }
 67
 68    /// Returns the label for the given symbol.
 69    fn label_for_symbol(
 70        &self,
 71        _language_server_id: &LanguageServerId,
 72        _symbol: Symbol,
 73    ) -> Option<CodeLabel> {
 74        None
 75    }
 76}
 77
 78#[macro_export]
 79macro_rules! register_extension {
 80    ($extension_type:ty) => {
 81        #[export_name = "init-extension"]
 82        pub extern "C" fn __init_extension() {
 83            std::env::set_current_dir(std::env::var("PWD").unwrap()).unwrap();
 84            zed_extension_api::register_extension(|| {
 85                Box::new(<$extension_type as zed_extension_api::Extension>::new())
 86            });
 87        }
 88    };
 89}
 90
 91#[doc(hidden)]
 92pub fn register_extension(build_extension: fn() -> Box<dyn Extension>) {
 93    unsafe { EXTENSION = Some((build_extension)()) }
 94}
 95
 96fn extension() -> &'static mut dyn Extension {
 97    unsafe { EXTENSION.as_deref_mut().unwrap() }
 98}
 99
100static mut EXTENSION: Option<Box<dyn Extension>> = None;
101
102#[cfg(target_arch = "wasm32")]
103#[link_section = "zed:api-version"]
104#[doc(hidden)]
105pub static ZED_API_VERSION: [u8; 6] = *include_bytes!(concat!(env!("OUT_DIR"), "/version_bytes"));
106
107mod wit {
108    wit_bindgen::generate!({
109        skip: ["init-extension"],
110        path: "./wit/since_v0.0.6",
111    });
112}
113
114wit::export!(Component);
115
116struct Component;
117
118impl wit::Guest for Component {
119    fn language_server_command(
120        language_server_id: String,
121        worktree: &wit::Worktree,
122    ) -> Result<wit::Command> {
123        let language_server_id = LanguageServerId(language_server_id);
124        extension().language_server_command(&language_server_id, worktree)
125    }
126
127    fn language_server_initialization_options(
128        language_server_id: String,
129        worktree: &Worktree,
130    ) -> Result<Option<String>, String> {
131        let language_server_id = LanguageServerId(language_server_id);
132        extension().language_server_initialization_options(&language_server_id, worktree)
133    }
134
135    fn labels_for_completions(
136        language_server_id: String,
137        completions: Vec<Completion>,
138    ) -> Result<Vec<Option<CodeLabel>>, String> {
139        let language_server_id = LanguageServerId(language_server_id);
140        let mut labels = Vec::new();
141        for (ix, completion) in completions.into_iter().enumerate() {
142            let label = extension().label_for_completion(&language_server_id, completion);
143            if let Some(label) = label {
144                labels.resize(ix + 1, None);
145                *labels.last_mut().unwrap() = Some(label);
146            }
147        }
148        Ok(labels)
149    }
150
151    fn labels_for_symbols(
152        language_server_id: String,
153        symbols: Vec<Symbol>,
154    ) -> Result<Vec<Option<CodeLabel>>, String> {
155        let language_server_id = LanguageServerId(language_server_id);
156        let mut labels = Vec::new();
157        for (ix, symbol) in symbols.into_iter().enumerate() {
158            let label = extension().label_for_symbol(&language_server_id, symbol);
159            if let Some(label) = label {
160                labels.resize(ix + 1, None);
161                *labels.last_mut().unwrap() = Some(label);
162            }
163        }
164        Ok(labels)
165    }
166}
167
168#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
169pub struct LanguageServerId(String);
170
171impl AsRef<str> for LanguageServerId {
172    fn as_ref(&self) -> &str {
173        &self.0
174    }
175}
176
177impl fmt::Display for LanguageServerId {
178    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179        write!(f, "{}", self.0)
180    }
181}
182
183impl CodeLabelSpan {
184    /// Returns a [`CodeLabelSpan::CodeRange`].
185    pub fn code_range(range: impl Into<wit::Range>) -> Self {
186        Self::CodeRange(range.into())
187    }
188
189    /// Returns a [`CodeLabelSpan::Literal`].
190    pub fn literal(text: impl Into<String>, highlight_name: Option<String>) -> Self {
191        Self::Literal(CodeLabelSpanLiteral {
192            text: text.into(),
193            highlight_name,
194        })
195    }
196}
197
198impl From<std::ops::Range<u32>> for wit::Range {
199    fn from(value: std::ops::Range<u32>) -> Self {
200        Self {
201            start: value.start,
202            end: value.end,
203        }
204    }
205}
206
207impl From<std::ops::Range<usize>> for wit::Range {
208    fn from(value: std::ops::Range<usize>) -> Self {
209        Self {
210            start: value.start as u32,
211            end: value.end as u32,
212        }
213    }
214}