extension.rs

  1mod capabilities;
  2pub mod extension_builder;
  3mod extension_events;
  4mod extension_host_proxy;
  5mod extension_manifest;
  6mod types;
  7
  8use std::path::{Path, PathBuf};
  9use std::sync::Arc;
 10
 11use ::lsp::LanguageServerName;
 12use anyhow::{Context as _, Result, bail};
 13use async_trait::async_trait;
 14use gpui::{App, Task};
 15use language::LanguageName;
 16use semver::Version;
 17use task::{SpawnInTerminal, ZedDebugConfig};
 18use util::rel_path::RelPath;
 19
 20pub use crate::capabilities::*;
 21pub use crate::extension_events::*;
 22pub use crate::extension_host_proxy::*;
 23pub use crate::extension_manifest::*;
 24pub use crate::types::*;
 25
 26/// Initializes the `extension` crate.
 27pub fn init(cx: &mut App) {
 28    extension_events::init(cx);
 29    ExtensionHostProxy::default_global(cx);
 30}
 31
 32#[async_trait]
 33pub trait WorktreeDelegate: Send + Sync + 'static {
 34    fn id(&self) -> u64;
 35    fn root_path(&self) -> String;
 36    async fn read_text_file(&self, path: &RelPath) -> Result<String>;
 37    async fn which(&self, binary_name: String) -> Option<String>;
 38    async fn shell_env(&self) -> Vec<(String, String)>;
 39}
 40
 41pub trait ProjectDelegate: Send + Sync + 'static {
 42    fn worktree_ids(&self) -> Vec<u64>;
 43}
 44
 45pub trait KeyValueStoreDelegate: Send + Sync + 'static {
 46    fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
 47}
 48
 49#[async_trait]
 50pub trait Extension: Send + Sync + 'static {
 51    /// Returns the [`ExtensionManifest`] for this extension.
 52    fn manifest(&self) -> Arc<ExtensionManifest>;
 53
 54    /// Returns the path to this extension's working directory.
 55    fn work_dir(&self) -> Arc<Path>;
 56
 57    /// Returns a path relative to this extension's working directory.
 58    fn path_from_extension(&self, path: &Path) -> PathBuf {
 59        util::normalize_path(&self.work_dir().join(path))
 60    }
 61
 62    async fn language_server_command(
 63        &self,
 64        language_server_id: LanguageServerName,
 65        language_name: LanguageName,
 66        worktree: Arc<dyn WorktreeDelegate>,
 67    ) -> Result<Command>;
 68
 69    async fn language_server_initialization_options(
 70        &self,
 71        language_server_id: LanguageServerName,
 72        language_name: LanguageName,
 73        worktree: Arc<dyn WorktreeDelegate>,
 74    ) -> Result<Option<String>>;
 75
 76    async fn language_server_workspace_configuration(
 77        &self,
 78        language_server_id: LanguageServerName,
 79        worktree: Arc<dyn WorktreeDelegate>,
 80    ) -> Result<Option<String>>;
 81
 82    async fn language_server_initialization_options_schema(
 83        &self,
 84        language_server_id: LanguageServerName,
 85        worktree: Arc<dyn WorktreeDelegate>,
 86    ) -> Result<Option<String>>;
 87
 88    async fn language_server_workspace_configuration_schema(
 89        &self,
 90        language_server_id: LanguageServerName,
 91        worktree: Arc<dyn WorktreeDelegate>,
 92    ) -> Result<Option<String>>;
 93
 94    async fn language_server_additional_initialization_options(
 95        &self,
 96        language_server_id: LanguageServerName,
 97        target_language_server_id: LanguageServerName,
 98        worktree: Arc<dyn WorktreeDelegate>,
 99    ) -> Result<Option<String>>;
100
101    async fn language_server_additional_workspace_configuration(
102        &self,
103        language_server_id: LanguageServerName,
104        target_language_server_id: LanguageServerName,
105        worktree: Arc<dyn WorktreeDelegate>,
106    ) -> Result<Option<String>>;
107
108    async fn labels_for_completions(
109        &self,
110        language_server_id: LanguageServerName,
111        completions: Vec<Completion>,
112    ) -> Result<Vec<Option<CodeLabel>>>;
113
114    async fn labels_for_symbols(
115        &self,
116        language_server_id: LanguageServerName,
117        symbols: Vec<Symbol>,
118    ) -> Result<Vec<Option<CodeLabel>>>;
119
120    async fn complete_slash_command_argument(
121        &self,
122        command: SlashCommand,
123        arguments: Vec<String>,
124    ) -> Result<Vec<SlashCommandArgumentCompletion>>;
125
126    async fn run_slash_command(
127        &self,
128        command: SlashCommand,
129        arguments: Vec<String>,
130        worktree: Option<Arc<dyn WorktreeDelegate>>,
131    ) -> Result<SlashCommandOutput>;
132
133    async fn context_server_command(
134        &self,
135        context_server_id: Arc<str>,
136        project: Arc<dyn ProjectDelegate>,
137    ) -> Result<Command>;
138
139    async fn context_server_configuration(
140        &self,
141        context_server_id: Arc<str>,
142        project: Arc<dyn ProjectDelegate>,
143    ) -> Result<Option<ContextServerConfiguration>>;
144
145    async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
146
147    async fn index_docs(
148        &self,
149        provider: Arc<str>,
150        package_name: Arc<str>,
151        kv_store: Arc<dyn KeyValueStoreDelegate>,
152    ) -> Result<()>;
153
154    async fn get_dap_binary(
155        &self,
156        dap_name: Arc<str>,
157        config: DebugTaskDefinition,
158        user_installed_path: Option<PathBuf>,
159        worktree: Arc<dyn WorktreeDelegate>,
160    ) -> Result<DebugAdapterBinary>;
161
162    async fn dap_request_kind(
163        &self,
164        dap_name: Arc<str>,
165        config: serde_json::Value,
166    ) -> Result<StartDebuggingRequestArgumentsRequest>;
167
168    async fn dap_config_to_scenario(&self, config: ZedDebugConfig) -> Result<DebugScenario>;
169
170    async fn dap_locator_create_scenario(
171        &self,
172        locator_name: String,
173        build_config_template: BuildTaskTemplate,
174        resolved_label: String,
175        debug_adapter_name: String,
176    ) -> Result<Option<DebugScenario>>;
177    async fn run_dap_locator(
178        &self,
179        locator_name: String,
180        config: SpawnInTerminal,
181    ) -> Result<DebugRequest>;
182}
183
184pub fn parse_wasm_extension_version(extension_id: &str, wasm_bytes: &[u8]) -> Result<Version> {
185    let mut version = None;
186
187    for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
188        if let wasmparser::Payload::CustomSection(s) =
189            part.context("error parsing wasm extension")?
190            && s.name() == "zed:api-version"
191        {
192            version = parse_wasm_extension_version_custom_section(s.data());
193            if version.is_none() {
194                bail!(
195                    "extension {} has invalid zed:api-version section: {:?}",
196                    extension_id,
197                    s.data()
198                );
199            }
200        }
201    }
202
203    // The reason we wait until we're done parsing all of the Wasm bytes to return the version
204    // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
205    //
206    // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
207    // earlier as an `Err` rather than as a panic.
208    version.with_context(|| format!("extension {extension_id} has no zed:api-version section"))
209}
210
211fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<Version> {
212    if data.len() == 6 {
213        Some(Version::new(
214            u16::from_be_bytes([data[0], data[1]]) as _,
215            u16::from_be_bytes([data[2], data[3]]) as _,
216            u16::from_be_bytes([data[4], data[5]]) as _,
217        ))
218    } else {
219        None
220    }
221}