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::{anyhow, bail, Context as _, Result};
 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 labels_for_completions(
 80        &self,
 81        language_server_id: LanguageServerName,
 82        completions: Vec<Completion>,
 83    ) -> Result<Vec<Option<CodeLabel>>>;
 84
 85    async fn labels_for_symbols(
 86        &self,
 87        language_server_id: LanguageServerName,
 88        symbols: Vec<Symbol>,
 89    ) -> Result<Vec<Option<CodeLabel>>>;
 90
 91    async fn complete_slash_command_argument(
 92        &self,
 93        command: SlashCommand,
 94        arguments: Vec<String>,
 95    ) -> Result<Vec<SlashCommandArgumentCompletion>>;
 96
 97    async fn run_slash_command(
 98        &self,
 99        command: SlashCommand,
100        arguments: Vec<String>,
101        worktree: Option<Arc<dyn WorktreeDelegate>>,
102    ) -> Result<SlashCommandOutput>;
103
104    async fn context_server_command(
105        &self,
106        context_server_id: Arc<str>,
107        project: Arc<dyn ProjectDelegate>,
108    ) -> Result<Command>;
109
110    async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
111
112    async fn index_docs(
113        &self,
114        provider: Arc<str>,
115        package_name: Arc<str>,
116        kv_store: Arc<dyn KeyValueStoreDelegate>,
117    ) -> Result<()>;
118}
119
120pub fn parse_wasm_extension_version(
121    extension_id: &str,
122    wasm_bytes: &[u8],
123) -> Result<SemanticVersion> {
124    let mut version = None;
125
126    for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
127        if let wasmparser::Payload::CustomSection(s) =
128            part.context("error parsing wasm extension")?
129        {
130            if s.name() == "zed:api-version" {
131                version = parse_wasm_extension_version_custom_section(s.data());
132                if version.is_none() {
133                    bail!(
134                        "extension {} has invalid zed:api-version section: {:?}",
135                        extension_id,
136                        s.data()
137                    );
138                }
139            }
140        }
141    }
142
143    // The reason we wait until we're done parsing all of the Wasm bytes to return the version
144    // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
145    //
146    // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
147    // earlier as an `Err` rather than as a panic.
148    version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
149}
150
151fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
152    if data.len() == 6 {
153        Some(SemanticVersion::new(
154            u16::from_be_bytes([data[0], data[1]]) as _,
155            u16::from_be_bytes([data[2], data[3]]) as _,
156            u16::from_be_bytes([data[4], data[5]]) as _,
157        ))
158    } else {
159        None
160    }
161}