Respect set rules in where selectors (#3290)
This commit is contained in:
parent
a1e8560ca6
commit
b744b87818
@ -93,7 +93,7 @@ impl Elem {
|
||||
|
||||
/// Fields that are visible to the user.
|
||||
fn visible_fields(&self) -> impl Iterator<Item = &Field> + Clone {
|
||||
self.real_fields().filter(|field| !field.internal && !field.ghost)
|
||||
self.real_fields().filter(|field| !field.internal)
|
||||
}
|
||||
}
|
||||
|
||||
@ -509,7 +509,11 @@ fn create_field_method(field: &Field) -> TokenStream {
|
||||
quote! { (&self, styles: #foundations::StyleChain) -> #output }
|
||||
};
|
||||
|
||||
let mut value = create_style_chain_access(field, quote! { self.#ident.as_ref() });
|
||||
let mut value = create_style_chain_access(
|
||||
field,
|
||||
field.borrowed,
|
||||
quote! { self.#ident.as_ref() },
|
||||
);
|
||||
if field.resolve {
|
||||
value = quote! { #foundations::Resolve::resolve(#value, styles) };
|
||||
}
|
||||
@ -530,7 +534,7 @@ fn create_field_in_method(field: &Field) -> TokenStream {
|
||||
|
||||
let ref_ = field.borrowed.then(|| quote! { & });
|
||||
|
||||
let mut value = create_style_chain_access(field, quote! { None });
|
||||
let mut value = create_style_chain_access(field, field.borrowed, quote! { None });
|
||||
if field.resolve {
|
||||
value = quote! { #foundations::Resolve::resolve(#value, styles) };
|
||||
}
|
||||
@ -560,16 +564,20 @@ fn create_set_field_method(field: &Field) -> TokenStream {
|
||||
}
|
||||
|
||||
/// Create a style chain access method for a field.
|
||||
fn create_style_chain_access(field: &Field, inherent: TokenStream) -> TokenStream {
|
||||
fn create_style_chain_access(
|
||||
field: &Field,
|
||||
borrowed: bool,
|
||||
inherent: TokenStream,
|
||||
) -> TokenStream {
|
||||
let Field { ty, default, enum_ident, const_ident, .. } = field;
|
||||
|
||||
let getter = match (field.fold, field.borrowed) {
|
||||
let getter = match (field.fold, borrowed) {
|
||||
(false, false) => quote! { get },
|
||||
(false, true) => quote! { get_ref },
|
||||
(true, _) => quote! { get_folded },
|
||||
};
|
||||
|
||||
let default = if field.borrowed {
|
||||
let default = if borrowed {
|
||||
quote! { || &#const_ident }
|
||||
} else {
|
||||
match default {
|
||||
@ -821,9 +829,10 @@ fn create_capable_impl(element: &Elem) -> TokenStream {
|
||||
/// Creates the element's `Fields` implementation.
|
||||
fn create_fields_impl(element: &Elem) -> TokenStream {
|
||||
let into_value = quote! { #foundations::IntoValue::into_value };
|
||||
let visible_non_ghost = || element.visible_fields().filter(|field| !field.ghost);
|
||||
|
||||
// Fields that can be checked using the `has` method.
|
||||
let has_arms = element.visible_fields().map(|field| {
|
||||
let has_arms = visible_non_ghost().map(|field| {
|
||||
let Field { enum_ident, ident, .. } = field;
|
||||
|
||||
let expr = if field.inherent() {
|
||||
@ -836,7 +845,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
|
||||
});
|
||||
|
||||
// Fields that can be accessed using the `field` method.
|
||||
let field_arms = element.visible_fields().map(|field| {
|
||||
let field_arms = visible_non_ghost().filter(|field| !field.ghost).map(|field| {
|
||||
let Field { enum_ident, ident, .. } = field;
|
||||
|
||||
let expr = if field.inherent() {
|
||||
@ -848,8 +857,29 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
|
||||
quote! { Fields::#enum_ident => #expr }
|
||||
});
|
||||
|
||||
// Fields that can be accessed using the `field_with_styles` method.
|
||||
let field_with_styles_arms = element.visible_fields().map(|field| {
|
||||
let Field { enum_ident, ident, .. } = field;
|
||||
|
||||
let expr = if field.inherent() {
|
||||
quote! { Some(#into_value(self.#ident.clone())) }
|
||||
} else if field.synthesized && field.default.is_none() {
|
||||
quote! { self.#ident.clone().map(#into_value) }
|
||||
} else {
|
||||
let value = create_style_chain_access(
|
||||
field,
|
||||
false,
|
||||
if field.ghost { quote!(None) } else { quote!(self.#ident.as_ref()) },
|
||||
);
|
||||
|
||||
quote! { Some(#into_value(#value)) }
|
||||
};
|
||||
|
||||
quote! { Fields::#enum_ident => #expr }
|
||||
});
|
||||
|
||||
// Creation of the `fields` dictionary for inherent fields.
|
||||
let field_inserts = element.visible_fields().map(|field| {
|
||||
let field_inserts = visible_non_ghost().map(|field| {
|
||||
let Field { ident, name, .. } = field;
|
||||
let string = quote! { #name.into() };
|
||||
|
||||
@ -873,7 +903,7 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
|
||||
type Enum = Fields;
|
||||
|
||||
fn has(&self, id: u8) -> bool {
|
||||
let Ok(id) = <#ident as #foundations::Fields>::Enum::try_from(id) else {
|
||||
let Ok(id) = Fields::try_from(id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@ -884,13 +914,21 @@ fn create_fields_impl(element: &Elem) -> TokenStream {
|
||||
}
|
||||
|
||||
fn field(&self, id: u8) -> Option<#foundations::Value> {
|
||||
let id = <#ident as #foundations::Fields>::Enum::try_from(id).ok()?;
|
||||
let id = Fields::try_from(id).ok()?;
|
||||
match id {
|
||||
#(#field_arms,)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn field_with_styles(&self, id: u8, styles: #foundations::StyleChain) -> Option<#foundations::Value> {
|
||||
let id = Fields::try_from(id).ok()?;
|
||||
match id {
|
||||
#(#field_with_styles_arms,)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn fields(&self) -> #foundations::Dict {
|
||||
let mut fields = #foundations::Dict::new();
|
||||
#(#field_inserts)*
|
||||
|
@ -15,7 +15,8 @@ use crate::diag::{SourceResult, StrResult};
|
||||
use crate::engine::Engine;
|
||||
use crate::foundations::{
|
||||
elem, func, scope, ty, Dict, Element, Fields, Finalize, Guard, IntoValue, Label,
|
||||
NativeElement, Recipe, Repr, Selector, Str, Style, Styles, Synthesize, Value,
|
||||
NativeElement, Recipe, Repr, Selector, Str, Style, StyleChain, Styles, Synthesize,
|
||||
Value,
|
||||
};
|
||||
use crate::introspection::{Locatable, Location, Meta, MetaElem};
|
||||
use crate::layout::{AlignElem, Alignment, Axes, Length, MoveElem, PadElem, Rel, Sides};
|
||||
@ -181,13 +182,16 @@ impl Content {
|
||||
/// This is the preferred way to access fields. However, you can only use it
|
||||
/// if you have set the field IDs yourself or are using the field IDs
|
||||
/// generated by the `#[elem]` macro.
|
||||
pub fn get(&self, id: u8) -> Option<Value> {
|
||||
pub fn get(&self, id: u8, styles: Option<StyleChain>) -> Option<Value> {
|
||||
if id == 255 {
|
||||
if let Some(label) = self.label() {
|
||||
return Some(label.into_value());
|
||||
}
|
||||
}
|
||||
self.inner.elem.field(id)
|
||||
match styles {
|
||||
Some(styles) => self.inner.elem.field_with_styles(id, styles),
|
||||
None => self.inner.elem.field(id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a field by name.
|
||||
@ -201,7 +205,7 @@ impl Content {
|
||||
}
|
||||
}
|
||||
let id = self.elem().field_id(name)?;
|
||||
self.get(id)
|
||||
self.get(id, None)
|
||||
}
|
||||
|
||||
/// Get a field by ID, returning a missing field error if it does not exist.
|
||||
@ -210,7 +214,7 @@ impl Content {
|
||||
/// if you have set the field IDs yourself or are using the field IDs
|
||||
/// generated by the `#[elem]` macro.
|
||||
pub fn field(&self, id: u8) -> StrResult<Value> {
|
||||
self.get(id)
|
||||
self.get(id, None)
|
||||
.ok_or_else(|| missing_field(self.elem().field_name(id).unwrap()))
|
||||
}
|
||||
|
||||
@ -400,7 +404,7 @@ impl Content {
|
||||
pub fn query(&self, selector: Selector) -> Vec<Content> {
|
||||
let mut results = Vec::new();
|
||||
self.traverse(&mut |element| {
|
||||
if selector.matches(&element) {
|
||||
if selector.matches(&element, None) {
|
||||
results.push(element);
|
||||
}
|
||||
});
|
||||
@ -414,7 +418,7 @@ impl Content {
|
||||
pub fn query_first(&self, selector: Selector) -> Option<Content> {
|
||||
let mut result = None;
|
||||
self.traverse(&mut |element| {
|
||||
if result.is_none() && selector.matches(&element) {
|
||||
if result.is_none() && selector.matches(&element, None) {
|
||||
result = Some(element);
|
||||
}
|
||||
});
|
||||
|
@ -220,6 +220,9 @@ pub trait Fields {
|
||||
/// Get the field with the given field ID.
|
||||
fn field(&self, id: u8) -> Option<Value>;
|
||||
|
||||
/// Get the field with the given ID in the presence of styles.
|
||||
fn field_with_styles(&self, id: u8, styles: StyleChain) -> Option<Value>;
|
||||
|
||||
/// Get the fields of the element.
|
||||
fn fields(&self) -> Dict;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ use smallvec::SmallVec;
|
||||
use crate::diag::{bail, StrResult};
|
||||
use crate::foundations::{
|
||||
cast, func, repr, scope, ty, CastInfo, Content, Dict, Element, FromValue, Func,
|
||||
Label, Reflect, Regex, Repr, Str, Type, Value,
|
||||
Label, Reflect, Regex, Repr, Str, StyleChain, Type, Value,
|
||||
};
|
||||
use crate::introspection::{Locatable, Location};
|
||||
use crate::symbols::Symbol;
|
||||
@ -128,23 +128,26 @@ impl Selector {
|
||||
}
|
||||
|
||||
/// Whether the selector matches for the target.
|
||||
pub fn matches(&self, target: &Content) -> bool {
|
||||
// TODO: optimize field access to not clone.
|
||||
pub fn matches(&self, target: &Content, styles: Option<StyleChain>) -> bool {
|
||||
match self {
|
||||
Self::Elem(element, dict) => {
|
||||
// TODO: Optimize field access to not clone.
|
||||
target.func() == *element
|
||||
&& dict
|
||||
.iter()
|
||||
.flat_map(|dict| dict.iter())
|
||||
.all(|(id, value)| target.get(*id).as_ref() == Some(value))
|
||||
&& dict.iter().flat_map(|dict| dict.iter()).all(|(id, value)| {
|
||||
target.get(*id, styles).as_ref() == Some(value)
|
||||
})
|
||||
}
|
||||
Self::Label(label) => target.label() == Some(*label),
|
||||
Self::Regex(regex) => target
|
||||
.to_packed::<TextElem>()
|
||||
.map_or(false, |elem| regex.is_match(elem.text())),
|
||||
Self::Can(cap) => target.func().can_type_id(*cap),
|
||||
Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
|
||||
Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)),
|
||||
Self::Or(selectors) => {
|
||||
selectors.iter().any(move |sel| sel.matches(target, styles))
|
||||
}
|
||||
Self::And(selectors) => {
|
||||
selectors.iter().all(move |sel| sel.matches(target, styles))
|
||||
}
|
||||
Self::Location(location) => target.location() == Some(*location),
|
||||
// Not supported here.
|
||||
Self::Before { .. } | Self::After { .. } => false,
|
||||
|
@ -369,10 +369,10 @@ impl Recipe {
|
||||
}
|
||||
|
||||
/// Whether the recipe is applicable to the target.
|
||||
pub fn applicable(&self, target: &Content) -> bool {
|
||||
pub fn applicable(&self, target: &Content, styles: StyleChain) -> bool {
|
||||
self.selector
|
||||
.as_ref()
|
||||
.map_or(false, |selector| selector.matches(target))
|
||||
.map_or(false, |selector| selector.matches(target, Some(styles)))
|
||||
}
|
||||
|
||||
/// Apply the recipe to the given content.
|
||||
|
@ -127,9 +127,11 @@ impl Introspector {
|
||||
indices.iter().map(|&index| self.elems[index].0.clone()).collect()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
Selector::Elem(..) | Selector::Regex(_) | Selector::Can(_) => {
|
||||
self.all().filter(|elem| selector.matches(elem)).cloned().collect()
|
||||
}
|
||||
Selector::Elem(..) | Selector::Regex(_) | Selector::Can(_) => self
|
||||
.all()
|
||||
.filter(|elem| selector.matches(elem, None))
|
||||
.cloned()
|
||||
.collect(),
|
||||
Selector::Location(location) => {
|
||||
self.get(location).cloned().into_iter().collect()
|
||||
}
|
||||
|
@ -80,7 +80,7 @@ pub fn applicable(target: &Content, styles: StyleChain) -> bool {
|
||||
|
||||
// Find out whether any recipe matches and is unguarded.
|
||||
for recipe in styles.recipes() {
|
||||
if !target.is_guarded(Guard(n)) && recipe.applicable(target) {
|
||||
if !target.is_guarded(Guard(n)) && recipe.applicable(target, styles) {
|
||||
return true;
|
||||
}
|
||||
n -= 1;
|
||||
@ -133,7 +133,7 @@ pub fn realize(
|
||||
// Find an applicable show rule recipe.
|
||||
for recipe in styles.recipes() {
|
||||
let guard = Guard(n);
|
||||
if !target.is_guarded(guard) && recipe.applicable(target) {
|
||||
if !target.is_guarded(guard) && recipe.applicable(target, styles) {
|
||||
if let Some(content) = try_apply(engine, target, recipe, guard)? {
|
||||
return Ok(Some(content));
|
||||
}
|
||||
|
BIN
tests/ref/compiler/select-where-styles.png
Normal file
BIN
tests/ref/compiler/select-where-styles.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
91
tests/typ/compiler/select-where-styles.typ
Normal file
91
tests/typ/compiler/select-where-styles.typ
Normal file
@ -0,0 +1,91 @@
|
||||
// Test that where selectors also work with settable fields.
|
||||
|
||||
---
|
||||
// Test that where selectors also trigger on set rule fields.
|
||||
#show raw.where(block: false): box.with(
|
||||
fill: luma(220),
|
||||
inset: (x: 3pt, y: 0pt),
|
||||
outset: (y: 3pt),
|
||||
radius: 2pt,
|
||||
)
|
||||
|
||||
This is #raw("fn main() {}") some text.
|
||||
|
||||
---
|
||||
// Note: This show rule is horribly inefficient because it triggers for
|
||||
// every individual text element. But it should still work.
|
||||
#show text.where(lang: "de"): set text(red)
|
||||
|
||||
#set text(lang: "es")
|
||||
Hola, mundo!
|
||||
|
||||
#set text(lang: "de")
|
||||
Hallo Welt!
|
||||
|
||||
#set text(lang: "en")
|
||||
Hello World!
|
||||
|
||||
---
|
||||
// Test that folding is taken into account.
|
||||
#set text(5pt)
|
||||
#set text(2em)
|
||||
|
||||
#[
|
||||
#show text.where(size: 2em): set text(blue)
|
||||
2em not blue
|
||||
]
|
||||
|
||||
#[
|
||||
#show text.where(size: 10pt): set text(blue)
|
||||
10pt blue
|
||||
]
|
||||
|
||||
---
|
||||
// Test again that folding is taken into account.
|
||||
#set rect(width: 40pt, height: 10pt)
|
||||
#set rect(stroke: blue)
|
||||
#set rect(stroke: 2pt)
|
||||
|
||||
#{
|
||||
show rect.where(stroke: blue): "Not Triggered"
|
||||
rect()
|
||||
}
|
||||
#{
|
||||
show rect.where(stroke: 2pt): "Not Triggered"
|
||||
rect()
|
||||
}
|
||||
#{
|
||||
show rect.where(stroke: 2pt + blue): "Triggered"
|
||||
rect()
|
||||
}
|
||||
|
||||
---
|
||||
// Test that resolving is *not* taken into account.
|
||||
#set line(start: (1em, 1em + 2pt))
|
||||
|
||||
#{
|
||||
show line.where(start: (1em, 1em + 2pt)): "Triggered"
|
||||
line()
|
||||
}
|
||||
#{
|
||||
show line.where(start: (10pt, 12pt)): "Not Triggered"
|
||||
line()
|
||||
}
|
||||
|
||||
|
||||
---
|
||||
// Test again that resolving is *not* taken into account.
|
||||
#set text(hyphenate: auto)
|
||||
|
||||
#[
|
||||
#show text.where(hyphenate: auto): underline
|
||||
Auto
|
||||
]
|
||||
#[
|
||||
#show text.where(hyphenate: true): underline
|
||||
True
|
||||
]
|
||||
#[
|
||||
#show text.where(hyphenate: false): underline
|
||||
False
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user