extension.rs

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