logiguard fork v3: full patch set on verified 8c74db0 tree
Includes prior-session patches (carry forward so the app compiles): - crates/gpui/build.rs: cross-compile manifest fix - crates/gpui/src/platform.rs: PlatformWindow::activate_with_token trait method - crates/gpui/src/window.rs: Window::activate_with_token public API - crates/gpui_linux/src/linux/wayland/window.rs: WaylandWindow::activate_with_token + activate() keyboard-serial fix Plus the focus-serial fix: - serial.rs: SerialKind::KeyboardEnter - client.rs: store wl_keyboard.enter serial; latest_serial_of() Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
17
crates/refineable/Cargo.toml
Normal file
17
crates/refineable/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "refineable"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
description = "A macro for creating 'refinement' types that can be used to partially initialize or mutate a complex struct"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/refineable.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
derive_refineable.workspace = true
|
||||
1
crates/refineable/LICENSE-APACHE
Symbolic link
1
crates/refineable/LICENSE-APACHE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../LICENSE-APACHE
|
||||
21
crates/refineable/derive_refineable/Cargo.toml
Normal file
21
crates/refineable/derive_refineable/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "derive_refineable"
|
||||
version = "0.1.0"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
license = "Apache-2.0"
|
||||
description = "A derive macro for creating refinement types in Rust"
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/derive_refineable.rs"
|
||||
proc-macro = true
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
proc-macro2.workspace = true
|
||||
quote.workspace = true
|
||||
syn.workspace = true
|
||||
1
crates/refineable/derive_refineable/LICENSE-APACHE
Symbolic link
1
crates/refineable/derive_refineable/LICENSE-APACHE
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../LICENSE-APACHE
|
||||
548
crates/refineable/derive_refineable/src/derive_refineable.rs
Normal file
548
crates/refineable/derive_refineable/src/derive_refineable.rs
Normal file
@@ -0,0 +1,548 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
DeriveInput, Field, FieldsNamed, PredicateType, TraitBound, Type, TypeParamBound, WhereClause,
|
||||
WherePredicate, parse_macro_input, parse_quote,
|
||||
};
|
||||
|
||||
#[proc_macro_derive(Refineable, attributes(refineable))]
|
||||
pub fn derive_refineable(input: TokenStream) -> TokenStream {
|
||||
let DeriveInput {
|
||||
ident,
|
||||
data,
|
||||
generics,
|
||||
attrs,
|
||||
..
|
||||
} = parse_macro_input!(input);
|
||||
|
||||
let refineable_attr = attrs.iter().find(|attr| attr.path().is_ident("refineable"));
|
||||
|
||||
let mut impl_debug_on_refinement = false;
|
||||
let mut derives_serialize = false;
|
||||
let mut refinement_traits_to_derive = vec![];
|
||||
|
||||
if let Some(refineable_attr) = refineable_attr {
|
||||
let _ = refineable_attr.parse_nested_meta(|meta| {
|
||||
if meta.path.is_ident("Debug") {
|
||||
impl_debug_on_refinement = true;
|
||||
} else {
|
||||
if meta.path.is_ident("Serialize") {
|
||||
derives_serialize = true;
|
||||
}
|
||||
refinement_traits_to_derive.push(meta.path);
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
let refinement_ident = format_ident!("{}Refinement", ident);
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
|
||||
let fields = match data {
|
||||
syn::Data::Struct(syn::DataStruct {
|
||||
fields: syn::Fields::Named(FieldsNamed { named, .. }),
|
||||
..
|
||||
}) => named.into_iter().collect::<Vec<Field>>(),
|
||||
_ => panic!("This derive macro only supports structs with named fields"),
|
||||
};
|
||||
|
||||
let field_names: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
|
||||
let field_visibilities: Vec<_> = fields.iter().map(|f| &f.vis).collect();
|
||||
let wrapped_types: Vec<_> = fields.iter().map(|f| get_wrapper_type(f, &f.ty)).collect();
|
||||
|
||||
let field_attributes: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|f| {
|
||||
if derives_serialize {
|
||||
if is_refineable_field(f) {
|
||||
quote! { #[serde(default, skip_serializing_if = "::refineable::IsEmpty::is_empty")] }
|
||||
} else {
|
||||
quote! { #[serde(skip_serializing_if = "::std::option::Option::is_none")] }
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Create trait bound that each wrapped type must implement Clone
|
||||
let type_param_bounds: Vec<_> = wrapped_types
|
||||
.iter()
|
||||
.map(|ty| {
|
||||
WherePredicate::Type(PredicateType {
|
||||
lifetimes: None,
|
||||
bounded_ty: ty.clone(),
|
||||
colon_token: Default::default(),
|
||||
bounds: {
|
||||
let mut punctuated = syn::punctuated::Punctuated::new();
|
||||
punctuated.push_value(TypeParamBound::Trait(TraitBound {
|
||||
paren_token: None,
|
||||
modifier: syn::TraitBoundModifier::None,
|
||||
lifetimes: None,
|
||||
path: parse_quote!(Clone),
|
||||
}));
|
||||
|
||||
punctuated
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Append to where_clause or create a new one if it doesn't exist
|
||||
let where_clause = match where_clause.cloned() {
|
||||
Some(mut where_clause) => {
|
||||
where_clause.predicates.extend(type_param_bounds);
|
||||
where_clause.clone()
|
||||
}
|
||||
None => WhereClause {
|
||||
where_token: Default::default(),
|
||||
predicates: type_param_bounds.into_iter().collect(),
|
||||
},
|
||||
};
|
||||
|
||||
let refineable_refine_assignments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
let is_optional = is_optional_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
self.#name.refine(&refinement.#name);
|
||||
}
|
||||
} else if is_optional {
|
||||
quote! {
|
||||
if let Some(value) = &refinement.#name {
|
||||
self.#name = Some(value.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if let Some(value) = &refinement.#name {
|
||||
self.#name = value.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refineable_refined_assignments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
let is_optional = is_optional_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
self.#name = self.#name.refined(refinement.#name);
|
||||
}
|
||||
} else if is_optional {
|
||||
quote! {
|
||||
if let Some(value) = refinement.#name {
|
||||
self.#name = Some(value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if let Some(value) = refinement.#name {
|
||||
self.#name = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refinement_refine_assignments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
self.#name.refine(&refinement.#name);
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if let Some(value) = &refinement.#name {
|
||||
self.#name = Some(value.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refinement_refined_assignments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
self.#name = self.#name.refined(refinement.#name);
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if let Some(value) = refinement.#name {
|
||||
self.#name = Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let from_refinement_assignments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
let is_optional = is_optional_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
#name: value.#name.into(),
|
||||
}
|
||||
} else if is_optional {
|
||||
quote! {
|
||||
#name: value.#name.map(|v| v.into()),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name: value.#name.map(|v| v.into()).unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let debug_impl = if impl_debug_on_refinement {
|
||||
let refinement_field_debugs: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
quote! {
|
||||
if self.#name.is_some() {
|
||||
debug_struct.field(stringify!(#name), &self.#name);
|
||||
} else {
|
||||
all_some = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! {
|
||||
impl #impl_generics std::fmt::Debug for #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut debug_struct = f.debug_struct(stringify!(#refinement_ident));
|
||||
let mut all_some = true;
|
||||
#( #refinement_field_debugs )*
|
||||
if all_some {
|
||||
debug_struct.finish()
|
||||
} else {
|
||||
debug_struct.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let refinement_is_empty_conditions: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, field)| {
|
||||
let name = &field.ident;
|
||||
|
||||
let condition = if is_refineable_field(field) {
|
||||
quote! { self.#name.is_empty() }
|
||||
} else {
|
||||
quote! { self.#name.is_none() }
|
||||
};
|
||||
|
||||
if i < fields.len() - 1 {
|
||||
quote! { #condition && }
|
||||
} else {
|
||||
condition
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refineable_is_superset_conditions: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
let is_optional = is_optional_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
if !self.#name.is_superset_of(&refinement.#name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if is_optional {
|
||||
quote! {
|
||||
if refinement.#name.is_some() && &self.#name != &refinement.#name {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if let Some(refinement_value) = &refinement.#name {
|
||||
if &self.#name != refinement_value {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refinement_is_superset_conditions: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
if !self.#name.is_superset_of(&refinement.#name) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
if refinement.#name.is_some() && &self.#name != &refinement.#name {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refineable_subtract_assignments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
let is_optional = is_optional_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
#name: self.#name.subtract(&refinement.#name),
|
||||
}
|
||||
} else if is_optional {
|
||||
quote! {
|
||||
#name: if &self.#name == &refinement.#name {
|
||||
None
|
||||
} else {
|
||||
self.#name.clone()
|
||||
},
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name: if let Some(refinement_value) = &refinement.#name {
|
||||
if &self.#name == refinement_value {
|
||||
None
|
||||
} else {
|
||||
Some(self.#name.clone())
|
||||
}
|
||||
} else {
|
||||
Some(self.#name.clone())
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let refinement_subtract_assignments: Vec<TokenStream2> = fields
|
||||
.iter()
|
||||
.map(|field| {
|
||||
let name = &field.ident;
|
||||
let is_refineable = is_refineable_field(field);
|
||||
|
||||
if is_refineable {
|
||||
quote! {
|
||||
#name: self.#name.subtract(&refinement.#name),
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
#name: if &self.#name == &refinement.#name {
|
||||
None
|
||||
} else {
|
||||
self.#name.clone()
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut derive_stream = quote! {};
|
||||
for trait_to_derive in refinement_traits_to_derive {
|
||||
derive_stream.extend(quote! { #[derive(#trait_to_derive)] })
|
||||
}
|
||||
|
||||
let r#gen = quote! {
|
||||
/// A refinable version of [`#ident`], see that documentation for details.
|
||||
#[derive(Clone)]
|
||||
#derive_stream
|
||||
pub struct #refinement_ident #impl_generics {
|
||||
#(
|
||||
#[allow(missing_docs)]
|
||||
#field_attributes
|
||||
#field_visibilities #field_names: #wrapped_types
|
||||
),*
|
||||
}
|
||||
|
||||
impl #impl_generics Refineable for #ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
type Refinement = #refinement_ident #ty_generics;
|
||||
|
||||
fn refine(&mut self, refinement: &Self::Refinement) {
|
||||
#( #refineable_refine_assignments )*
|
||||
}
|
||||
|
||||
fn refined(mut self, refinement: Self::Refinement) -> Self {
|
||||
#( #refineable_refined_assignments )*
|
||||
self
|
||||
}
|
||||
|
||||
fn is_superset_of(&self, refinement: &Self::Refinement) -> bool
|
||||
{
|
||||
#( #refineable_is_superset_conditions )*
|
||||
true
|
||||
}
|
||||
|
||||
fn subtract(&self, refinement: &Self::Refinement) -> Self::Refinement
|
||||
{
|
||||
#refinement_ident {
|
||||
#( #refineable_subtract_assignments )*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics Refineable for #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
type Refinement = #refinement_ident #ty_generics;
|
||||
|
||||
fn refine(&mut self, refinement: &Self::Refinement) {
|
||||
#( #refinement_refine_assignments )*
|
||||
}
|
||||
|
||||
fn refined(mut self, refinement: Self::Refinement) -> Self {
|
||||
#( #refinement_refined_assignments )*
|
||||
self
|
||||
}
|
||||
|
||||
fn is_superset_of(&self, refinement: &Self::Refinement) -> bool
|
||||
{
|
||||
#( #refinement_is_superset_conditions )*
|
||||
true
|
||||
}
|
||||
|
||||
fn subtract(&self, refinement: &Self::Refinement) -> Self::Refinement
|
||||
{
|
||||
#refinement_ident {
|
||||
#( #refinement_subtract_assignments )*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics ::refineable::IsEmpty for #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn is_empty(&self) -> bool {
|
||||
#( #refinement_is_empty_conditions )*
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics From<#refinement_ident #ty_generics> for #ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn from(value: #refinement_ident #ty_generics) -> Self {
|
||||
Self {
|
||||
#( #from_refinement_assignments )*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics ::core::default::Default for #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
fn default() -> Self {
|
||||
#refinement_ident {
|
||||
#( #field_names: Default::default() ),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl #impl_generics #refinement_ident #ty_generics
|
||||
#where_clause
|
||||
{
|
||||
/// Returns `true` if all fields are `Some`
|
||||
pub fn is_some(&self) -> bool {
|
||||
#(
|
||||
if self.#field_names.is_some() {
|
||||
return true;
|
||||
}
|
||||
)*
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#debug_impl
|
||||
};
|
||||
r#gen.into()
|
||||
}
|
||||
|
||||
fn is_refineable_field(f: &Field) -> bool {
|
||||
f.attrs
|
||||
.iter()
|
||||
.any(|attr| attr.path().is_ident("refineable"))
|
||||
}
|
||||
|
||||
fn is_optional_field(f: &Field) -> bool {
|
||||
if let Type::Path(typepath) = &f.ty
|
||||
&& typepath.qself.is_none()
|
||||
{
|
||||
let segments = &typepath.path.segments;
|
||||
if segments.len() == 1 && segments.iter().any(|s| s.ident == "Option") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn get_wrapper_type(field: &Field, ty: &Type) -> syn::Type {
|
||||
if is_refineable_field(field) {
|
||||
let struct_name = if let Type::Path(tp) = ty {
|
||||
tp.path.segments.last().unwrap().ident.clone()
|
||||
} else {
|
||||
panic!("Expected struct type for a refineable field");
|
||||
};
|
||||
|
||||
let refinement_struct_name = if struct_name.to_string().ends_with("Refinement") {
|
||||
format_ident!("{}", struct_name)
|
||||
} else {
|
||||
format_ident!("{}Refinement", struct_name)
|
||||
};
|
||||
let generics = if let Type::Path(tp) = ty {
|
||||
&tp.path.segments.last().unwrap().arguments
|
||||
} else {
|
||||
&syn::PathArguments::None
|
||||
};
|
||||
parse_quote!(#refinement_struct_name #generics)
|
||||
} else if is_optional_field(field) {
|
||||
ty.clone()
|
||||
} else {
|
||||
parse_quote!(Option<#ty>)
|
||||
}
|
||||
}
|
||||
132
crates/refineable/src/refineable.rs
Normal file
132
crates/refineable/src/refineable.rs
Normal file
@@ -0,0 +1,132 @@
|
||||
pub use derive_refineable::Refineable;
|
||||
|
||||
/// A trait for types that can be refined with partial updates.
|
||||
///
|
||||
/// The `Refineable` trait enables hierarchical configuration patterns where a base configuration
|
||||
/// can be selectively overridden by refinements. This is particularly useful for styling and
|
||||
/// settings, and theme hierarchies.
|
||||
///
|
||||
/// # Derive Macro
|
||||
///
|
||||
/// The `#[derive(Refineable)]` macro automatically generates a companion refinement type and
|
||||
/// implements this trait. For a struct `Style`, it creates `StyleRefinement` where each field is
|
||||
/// wrapped appropriately:
|
||||
///
|
||||
/// - **Refineable fields** (marked with `#[refineable]`): Become the corresponding refinement type
|
||||
/// (e.g., `Bar` becomes `BarRefinement`, or `BarRefinement` remains `BarRefinement`)
|
||||
/// - **Optional fields** (`Option<T>`): Remain as `Option<T>`
|
||||
/// - **Regular fields**: Become `Option<T>`
|
||||
///
|
||||
/// ## Attributes
|
||||
///
|
||||
/// The derive macro supports these attributes on the struct:
|
||||
/// - `#[refineable(Debug)]`: Implements `Debug` for the refinement type
|
||||
/// - `#[refineable(Serialize)]`: Derives `Serialize` which skips serializing `None`
|
||||
/// - `#[refineable(OtherTrait)]`: Derives additional traits on the refinement type
|
||||
///
|
||||
/// Fields can be marked with:
|
||||
/// - `#[refineable]`: Field is itself refineable (uses nested refinement type)
|
||||
pub trait Refineable: Clone {
|
||||
type Refinement: Refineable<Refinement = Self::Refinement> + IsEmpty + Default;
|
||||
|
||||
/// Applies the given refinement to this instance, modifying it in place.
|
||||
///
|
||||
/// Only non-empty values in the refinement are applied.
|
||||
///
|
||||
/// * For refineable fields, this recursively calls `refine`.
|
||||
/// * For other fields, the value is replaced if present in the refinement.
|
||||
fn refine(&mut self, refinement: &Self::Refinement);
|
||||
|
||||
/// Returns a new instance with the refinement applied, equivalent to cloning `self` and calling
|
||||
/// `refine` on it.
|
||||
fn refined(self, refinement: Self::Refinement) -> Self;
|
||||
|
||||
/// Creates an instance from a cascade by merging all refinements atop the default value.
|
||||
fn from_cascade(cascade: &Cascade<Self>) -> Self
|
||||
where
|
||||
Self: Default + Sized,
|
||||
{
|
||||
Self::default().refined(cascade.merged())
|
||||
}
|
||||
|
||||
/// Returns `true` if this instance would contain all values from the refinement.
|
||||
///
|
||||
/// For refineable fields, this recursively checks `is_superset_of`. For other fields, this
|
||||
/// checks if the refinement's `Some` values match this instance's values.
|
||||
fn is_superset_of(&self, refinement: &Self::Refinement) -> bool;
|
||||
|
||||
/// Returns a refinement that represents the difference between this instance and the given
|
||||
/// refinement.
|
||||
///
|
||||
/// For refineable fields, this recursively calls `subtract`. For other fields, the field is
|
||||
/// `None` if the field's value is equal to the refinement.
|
||||
fn subtract(&self, refinement: &Self::Refinement) -> Self::Refinement;
|
||||
}
|
||||
|
||||
pub trait IsEmpty {
|
||||
/// Returns `true` if applying this refinement would have no effect.
|
||||
fn is_empty(&self) -> bool;
|
||||
}
|
||||
|
||||
/// A cascade of refinements that can be merged in priority order.
|
||||
///
|
||||
/// A cascade maintains a sequence of optional refinements where later entries
|
||||
/// take precedence over earlier ones. The first slot (index 0) is always the
|
||||
/// base refinement and is guaranteed to be present.
|
||||
///
|
||||
/// This is useful for implementing configuration hierarchies like CSS cascading,
|
||||
/// where styles from different sources (user agent, user, author) are combined
|
||||
/// with specific precedence rules.
|
||||
pub struct Cascade<S: Refineable>(Vec<Option<S::Refinement>>);
|
||||
|
||||
impl<S: Refineable + Default> Default for Cascade<S> {
|
||||
fn default() -> Self {
|
||||
Self(vec![Some(Default::default())])
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a specific slot in a cascade.
|
||||
///
|
||||
/// Slots are used to identify specific positions in the cascade where
|
||||
/// refinements can be set or updated.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct CascadeSlot(usize);
|
||||
|
||||
impl<S: Refineable + Default> Cascade<S> {
|
||||
/// Reserves a new slot in the cascade and returns a handle to it.
|
||||
///
|
||||
/// The new slot is initially empty (`None`) and can be populated later
|
||||
/// using `set()`.
|
||||
pub fn reserve(&mut self) -> CascadeSlot {
|
||||
self.0.push(None);
|
||||
CascadeSlot(self.0.len() - 1)
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the base refinement (slot 0).
|
||||
///
|
||||
/// The base refinement is always present and serves as the foundation
|
||||
/// for the cascade.
|
||||
pub fn base(&mut self) -> &mut S::Refinement {
|
||||
self.0[0].as_mut().unwrap()
|
||||
}
|
||||
|
||||
/// Sets the refinement for a specific slot in the cascade.
|
||||
///
|
||||
/// Setting a slot to `None` effectively removes it from consideration
|
||||
/// during merging.
|
||||
pub fn set(&mut self, slot: CascadeSlot, refinement: Option<S::Refinement>) {
|
||||
self.0[slot.0] = refinement
|
||||
}
|
||||
|
||||
/// Merges all refinements in the cascade into a single refinement.
|
||||
///
|
||||
/// Refinements are applied in order, with later slots taking precedence.
|
||||
/// Empty slots (`None`) are skipped during merging.
|
||||
pub fn merged(&self) -> S::Refinement {
|
||||
let mut merged = self.0[0].clone().unwrap();
|
||||
for refinement in self.0.iter().skip(1).flatten() {
|
||||
merged.refine(refinement);
|
||||
}
|
||||
merged
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user