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