From 0d7f5f49e6514a9b778e671d8c99637f2a7766f6 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 28 Mar 2024 16:49:26 -0400 Subject: [PATCH] Disable incompatible extension versions in extension view (#9938) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR makes it so extension versions that are incompatible with what the current Zed instance supports are disabled in the UI, to prevent attempting to install them. Here's what it looks like in the extension version picker: Screenshot 2024-03-28 at 4 21 15 PM Release Notes: - N/A --- crates/extension/src/extension_manifest.rs | 8 ++++ crates/extension/src/extension_store.rs | 27 +++++++++++- crates/extension/src/wasm_host/wit.rs | 5 +++ crates/extension/src/wasm_host/wit/v0_0_1.rs | 7 ++++ .../src/extension_version_selector.rs | 41 ++++++++++++++----- crates/extensions_ui/src/extensions_ui.rs | 19 +++++---- 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/crates/extension/src/extension_manifest.rs b/crates/extension/src/extension_manifest.rs index c52a85c3ac..63d3da51c1 100644 --- a/crates/extension/src/extension_manifest.rs +++ b/crates/extension/src/extension_manifest.rs @@ -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, 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); diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index 64f09e6f10..2658f7f03b 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -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, diff --git a/crates/extension/src/wasm_host/wit.rs b/crates/extension/src/wasm_host/wit.rs index 2c752a7a8f..321aa40ad0 100644 --- a/crates/extension/src/wasm_host/wit.rs +++ b/crates/extension/src/wasm_host/wit.rs @@ -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), diff --git a/crates/extension/src/wasm_host/wit/v0_0_1.rs b/crates/extension/src/wasm_host/wit/v0_0_1.rs index a48569b4e2..212231a1d9 100644 --- a/crates/extension/src/wasm_host/wit/v0_0_1.rs +++ b/crates/extension/src/wasm_host/wit/v0_0_1.rs @@ -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", diff --git a/crates/extensions_ui/src/extension_version_selector.rs b/crates/extensions_ui/src/extension_version_selector.rs index 8eeac35cc2..a893b7bbb9 100644 --- a/crates/extensions_ui/src/extension_version_selector.rs +++ b/crates/extensions_ui/src/extension_version_selector.rs @@ -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)), + ), + ), ) } } diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs index 98db928234..1649230f5f 100644 --- a/crates/extensions_ui/src/extensions_ui.rs +++ b/crates/extensions_ui/src/extensions_ui.rs @@ -578,10 +578,14 @@ impl ExtensionsPage { status: &ExtensionStatus, cx: &mut ViewContext, ) -> (Button, Option