extension.rs

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