extension.rs

  1pub mod extension_builder;
  2mod extension_manifest;
  3mod types;
  4
  5use std::path::{Path, PathBuf};
  6use std::sync::Arc;
  7
  8use ::lsp::LanguageServerName;
  9use anyhow::{anyhow, bail, Context as _, Result};
 10use async_trait::async_trait;
 11use fs::normalize_path;
 12use gpui::Task;
 13use language::LanguageName;
 14use semantic_version::SemanticVersion;
 15
 16pub use crate::extension_manifest::*;
 17pub use crate::types::*;
 18
 19#[async_trait]
 20pub trait WorktreeDelegate: Send + Sync + 'static {
 21    fn id(&self) -> u64;
 22    fn root_path(&self) -> String;
 23    async fn read_text_file(&self, path: PathBuf) -> Result<String>;
 24    async fn which(&self, binary_name: String) -> Option<String>;
 25    async fn shell_env(&self) -> Vec<(String, String)>;
 26}
 27
 28pub trait KeyValueStoreDelegate: Send + Sync + 'static {
 29    fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
 30}
 31
 32#[async_trait]
 33pub trait Extension: Send + Sync + 'static {
 34    /// Returns the [`ExtensionManifest`] for this extension.
 35    fn manifest(&self) -> Arc<ExtensionManifest>;
 36
 37    /// Returns the path to this extension's working directory.
 38    fn work_dir(&self) -> Arc<Path>;
 39
 40    /// Returns a path relative to this extension's working directory.
 41    fn path_from_extension(&self, path: &Path) -> PathBuf {
 42        normalize_path(&self.work_dir().join(path))
 43    }
 44
 45    async fn language_server_command(
 46        &self,
 47        language_server_id: LanguageServerName,
 48        language_name: LanguageName,
 49        worktree: Arc<dyn WorktreeDelegate>,
 50    ) -> Result<Command>;
 51
 52    async fn language_server_initialization_options(
 53        &self,
 54        language_server_id: LanguageServerName,
 55        language_name: LanguageName,
 56        worktree: Arc<dyn WorktreeDelegate>,
 57    ) -> Result<Option<String>>;
 58
 59    async fn language_server_workspace_configuration(
 60        &self,
 61        language_server_id: LanguageServerName,
 62        worktree: Arc<dyn WorktreeDelegate>,
 63    ) -> Result<Option<String>>;
 64
 65    async fn labels_for_completions(
 66        &self,
 67        language_server_id: LanguageServerName,
 68        completions: Vec<Completion>,
 69    ) -> Result<Vec<Option<CodeLabel>>>;
 70
 71    async fn labels_for_symbols(
 72        &self,
 73        language_server_id: LanguageServerName,
 74        symbols: Vec<Symbol>,
 75    ) -> Result<Vec<Option<CodeLabel>>>;
 76
 77    async fn complete_slash_command_argument(
 78        &self,
 79        command: SlashCommand,
 80        arguments: Vec<String>,
 81    ) -> Result<Vec<SlashCommandArgumentCompletion>>;
 82
 83    async fn run_slash_command(
 84        &self,
 85        command: SlashCommand,
 86        arguments: Vec<String>,
 87        worktree: Option<Arc<dyn WorktreeDelegate>>,
 88    ) -> Result<SlashCommandOutput>;
 89
 90    async fn suggest_docs_packages(&self, provider: Arc<str>) -> Result<Vec<String>>;
 91
 92    async fn index_docs(
 93        &self,
 94        provider: Arc<str>,
 95        package_name: Arc<str>,
 96        kv_store: Arc<dyn KeyValueStoreDelegate>,
 97    ) -> Result<()>;
 98}
 99
100pub fn parse_wasm_extension_version(
101    extension_id: &str,
102    wasm_bytes: &[u8],
103) -> Result<SemanticVersion> {
104    let mut version = None;
105
106    for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
107        if let wasmparser::Payload::CustomSection(s) =
108            part.context("error parsing wasm extension")?
109        {
110            if s.name() == "zed:api-version" {
111                version = parse_wasm_extension_version_custom_section(s.data());
112                if version.is_none() {
113                    bail!(
114                        "extension {} has invalid zed:api-version section: {:?}",
115                        extension_id,
116                        s.data()
117                    );
118                }
119            }
120        }
121    }
122
123    // The reason we wait until we're done parsing all of the Wasm bytes to return the version
124    // is to work around a panic that can happen inside of Wasmtime when the bytes are invalid.
125    //
126    // By parsing the entirety of the Wasm bytes before we return, we're able to detect this problem
127    // earlier as an `Err` rather than as a panic.
128    version.ok_or_else(|| anyhow!("extension {} has no zed:api-version section", extension_id))
129}
130
131fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
132    if data.len() == 6 {
133        Some(SemanticVersion::new(
134            u16::from_be_bytes([data[0], data[1]]) as _,
135            u16::from_be_bytes([data[2], data[3]]) as _,
136            u16::from_be_bytes([data[4], data[5]]) as _,
137        ))
138    } else {
139        None
140    }
141}