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