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 fs::normalize_path;
 15use gpui::{App, Task};
 16use language::LanguageName;
 17use semantic_version::SemanticVersion;
 18use task::{SpawnInTerminal, ZedDebugConfig};
 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: PathBuf) -> 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        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_additional_initialization_options(
 83        &self,
 84        language_server_id: LanguageServerName,
 85        target_language_server_id: LanguageServerName,
 86        worktree: Arc<dyn WorktreeDelegate>,
 87    ) -> Result<Option<String>>;
 88
 89    async fn language_server_additional_workspace_configuration(
 90        &self,
 91        language_server_id: LanguageServerName,
 92        target_language_server_id: LanguageServerName,
 93        worktree: Arc<dyn WorktreeDelegate>,
 94    ) -> Result<Option<String>>;
 95
 96    async fn labels_for_completions(
 97        &self,
 98        language_server_id: LanguageServerName,
 99        completions: Vec<Completion>,
100    ) -> Result<Vec<Option<CodeLabel>>>;
101
102    async fn labels_for_symbols(
103        &self,
104        language_server_id: LanguageServerName,
105        symbols: Vec<Symbol>,
106    ) -> Result<Vec<Option<CodeLabel>>>;
107
108    async fn complete_slash_command_argument(
109        &self,
110        command: SlashCommand,
111        arguments: Vec<String>,
112    ) -> Result<Vec<SlashCommandArgumentCompletion>>;
113
114    async fn run_slash_command(
115        &self,
116        command: SlashCommand,
117        arguments: Vec<String>,
118        worktree: Option<Arc<dyn WorktreeDelegate>>,
119    ) -> Result<SlashCommandOutput>;
120
121    async fn context_server_command(
122        &self,
123        context_server_id: Arc<str>,
124        project: Arc<dyn ProjectDelegate>,
125    ) -> Result<Command>;
126
127    async fn context_server_configuration(
128        &self,
129        context_server_id: Arc<str>,
130        project: Arc<dyn ProjectDelegate>,
131    ) -> Result<Option<ContextServerConfiguration>>;
132
133    async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
134
135    async fn index_docs(
136        &self,
137        provider: Arc<str>,
138        package_name: Arc<str>,
139        kv_store: Arc<dyn KeyValueStoreDelegate>,
140    ) -> Result<()>;
141
142    async fn get_dap_binary(
143        &self,
144        dap_name: Arc<str>,
145        config: DebugTaskDefinition,
146        user_installed_path: Option<PathBuf>,
147        worktree: Arc<dyn WorktreeDelegate>,
148    ) -> Result<DebugAdapterBinary>;
149
150    async fn dap_request_kind(
151        &self,
152        dap_name: Arc<str>,
153        config: serde_json::Value,
154    ) -> Result<StartDebuggingRequestArgumentsRequest>;
155
156    async fn dap_config_to_scenario(&self, config: ZedDebugConfig) -> Result<DebugScenario>;
157
158    async fn dap_locator_create_scenario(
159        &self,
160        locator_name: String,
161        build_config_template: BuildTaskTemplate,
162        resolved_label: String,
163        debug_adapter_name: String,
164    ) -> Result<Option<DebugScenario>>;
165    async fn run_dap_locator(
166        &self,
167        locator_name: String,
168        config: SpawnInTerminal,
169    ) -> Result<DebugRequest>;
170}
171
172pub fn parse_wasm_extension_version(
173    extension_id: &str,
174    wasm_bytes: &[u8],
175) -> Result<SemanticVersion> {
176    let mut version = None;
177
178    for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
179        if let wasmparser::Payload::CustomSection(s) =
180            part.context("error parsing wasm extension")?
181            && s.name() == "zed:api-version"
182        {
183            version = parse_wasm_extension_version_custom_section(s.data());
184            if version.is_none() {
185                bail!(
186                    "extension {} has invalid zed:api-version section: {:?}",
187                    extension_id,
188                    s.data()
189                );
190            }
191        }
192    }
193
194    // The reason we wait until we're done parsing all of the Wasm bytes to return the version
195    // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
196    //
197    // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
198    // earlier as an `Err` rather than as a panic.
199    version.with_context(|| format!("extension {extension_id} has no zed:api-version section"))
200}
201
202fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
203    if data.len() == 6 {
204        Some(SemanticVersion::new(
205            u16::from_be_bytes([data[0], data[1]]) as _,
206            u16::from_be_bytes([data[2], data[3]]) as _,
207            u16::from_be_bytes([data[4], data[5]]) as _,
208        ))
209    } else {
210        None
211    }
212}