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 c52a85c3acb45ea2551562bee7fcb5b5564b7dac..63d3da51c185766ef3e820ad6f8862a49733e385 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 64f09e6f1060a95487af8d2841eb3a9e2078624f..2658f7f03b08df291116061a61b3591d278e1597 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 2c752a7a8f55d43cd929c735002cab901bfd1682..321aa40ad0792b245616aba5f3804037b179636a 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 a48569b4e24b097be3613d4b32f1e060340c93ac..212231a1d9c5aa2d70198c84d52905b00c258b24 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 8eeac35cc247be1fdf65b365d4b33700b6d85cfd..a893b7bbb9c2e2424631c6271cead59ba33847b3 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 98db928234b7cce7e2748095c4883ed96eb7073e..1649230f5f28fbacd298cf6c771cb94a567bce34 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