Detailed changes
@@ -5,6 +5,7 @@ use language::LanguageServerName;
use serde::{Deserialize, Serialize};
use std::{
ffi::OsStr,
+ fmt,
path::{Path, PathBuf},
sync::Arc,
};
@@ -31,9 +32,16 @@ pub struct OldExtensionManifest {
pub grammars: BTreeMap<Arc<str>, PathBuf>,
}
+/// The schema version of the [`ExtensionManifest`].
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
pub struct SchemaVersion(pub i32);
+impl fmt::Display for SchemaVersion {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
impl SchemaVersion {
pub const ZERO: Self = Self(0);
@@ -7,6 +7,7 @@ mod wasm_host;
#[cfg(test)]
mod extension_store_test;
+use crate::extension_manifest::SchemaVersion;
use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
use anyhow::{anyhow, bail, Context as _, Result};
use async_compression::futures::bufread::GzipDecoder;
@@ -50,7 +51,7 @@ use util::{
paths::EXTENSIONS_DIR,
ResultExt,
};
-use wasm_host::{WasmExtension, WasmHost};
+use wasm_host::{wit::is_supported_wasm_api_version, WasmExtension, WasmHost};
pub use extension_manifest::{
ExtensionLibraryKind, ExtensionManifest, GrammarManifestEntry, OldExtensionManifest,
@@ -60,7 +61,29 @@ pub use extension_settings::ExtensionSettings;
const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
-const CURRENT_SCHEMA_VERSION: i64 = 1;
+/// The current extension [`SchemaVersion`] supported by Zed.
+const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(1);
+
+/// Returns whether the given extension version is compatible with this version of Zed.
+pub fn is_version_compatible(extension_version: &ExtensionMetadata) -> bool {
+ let schema_version = extension_version.manifest.schema_version.unwrap_or(0);
+ if CURRENT_SCHEMA_VERSION.0 < schema_version {
+ return false;
+ }
+
+ if let Some(wasm_api_version) = extension_version
+ .manifest
+ .wasm_api_version
+ .as_ref()
+ .and_then(|wasm_api_version| SemanticVersion::from_str(wasm_api_version).ok())
+ {
+ if !is_supported_wasm_api_version(wasm_api_version) {
+ return false;
+ }
+ }
+
+ true
+}
pub struct ExtensionStore {
builder: Arc<ExtensionBuilder>,
@@ -28,6 +28,11 @@ fn wasi_view(state: &mut WasmState) -> &mut WasmState {
state
}
+/// Returns whether the given Wasm API version is supported by the Wasm host.
+pub fn is_supported_wasm_api_version(version: SemanticVersion) -> bool {
+ v0_0_1::VERSION <= version && version <= v0_0_4::VERSION
+}
+
pub enum Extension {
V004(v0_0_4::Extension),
V001(v0_0_1::Extension),
@@ -4,8 +4,15 @@ use anyhow::Result;
use async_trait::async_trait;
use language::{LanguageServerBinaryStatus, LspAdapterDelegate};
use std::sync::{Arc, OnceLock};
+use util::SemanticVersion;
use wasmtime::component::{Linker, Resource};
+pub const VERSION: SemanticVersion = SemanticVersion {
+ major: 0,
+ minor: 0,
+ patch: 1,
+};
+
wasmtime::component::bindgen!({
async: true,
path: "../extension_api/wit/0.0.1",
@@ -165,6 +165,10 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate {
let candidate_id = self.matches[self.selected_index].candidate_id;
let extension_version = &self.extension_versions[candidate_id];
+ if !extension::is_version_compatible(extension_version) {
+ return;
+ }
+
let extension_store = ExtensionStore::global(cx);
extension_store.update(cx, |store, cx| {
let extension_id = extension_version.id.clone();
@@ -196,21 +200,38 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate {
let version_match = &self.matches[ix];
let extension_version = &self.extension_versions[version_match.candidate_id];
+ let is_version_compatible = extension::is_version_compatible(extension_version);
+ let disabled = !is_version_compatible;
+
Some(
ListItem::new(ix)
.inset(true)
.spacing(ListItemSpacing::Sparse)
.selected(selected)
- .child(HighlightedLabel::new(
- version_match.string.clone(),
- version_match.positions.clone(),
- ))
- .end_slot(Label::new(
- extension_version
- .published_at
- .format("%Y-%m-%d")
- .to_string(),
- )),
+ .disabled(disabled)
+ .child(
+ HighlightedLabel::new(
+ version_match.string.clone(),
+ version_match.positions.clone(),
+ )
+ .when(disabled, |label| label.color(Color::Muted)),
+ )
+ .end_slot(
+ h_flex()
+ .gap_2()
+ .when(!is_version_compatible, |this| {
+ this.child(Label::new("Incompatible").color(Color::Muted))
+ })
+ .child(
+ Label::new(
+ extension_version
+ .published_at
+ .format("%Y-%m-%d")
+ .to_string(),
+ )
+ .when(disabled, |label| label.color(Color::Muted)),
+ ),
+ ),
)
}
}
@@ -578,10 +578,14 @@ impl ExtensionsPage {
status: &ExtensionStatus,
cx: &mut ViewContext<Self>,
) -> (Button, Option<Button>) {
+ let is_compatible = extension::is_version_compatible(&extension);
+ let disabled = !is_compatible;
+
match status.clone() {
ExtensionStatus::NotInstalled => (
- Button::new(SharedString::from(extension.id.clone()), "Install").on_click(
- cx.listener({
+ Button::new(SharedString::from(extension.id.clone()), "Install")
+ .disabled(disabled)
+ .on_click(cx.listener({
let extension_id = extension.id.clone();
let version = extension.manifest.version.clone();
move |this, _, cx| {
@@ -591,8 +595,7 @@ impl ExtensionsPage {
store.install_extension(extension_id.clone(), version.clone(), cx)
});
}
- }),
- ),
+ })),
None,
),
ExtensionStatus::Installing => (
@@ -622,8 +625,9 @@ impl ExtensionsPage {
None
} else {
Some(
- Button::new(SharedString::from(extension.id.clone()), "Upgrade").on_click(
- cx.listener({
+ Button::new(SharedString::from(extension.id.clone()), "Upgrade")
+ .disabled(disabled)
+ .on_click(cx.listener({
let extension_id = extension.id.clone();
let version = extension.manifest.version.clone();
move |this, _, cx| {
@@ -640,8 +644,7 @@ impl ExtensionsPage {
.detach_and_log_err(cx)
});
}
- }),
- ),
+ })),
)
},
),