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}