From 91956d1f035b79d1e84318b62cce24659bb3d14d Mon Sep 17 00:00:00 2001 From: Ian Wrzesinski <133046678+wrzian@users.noreply.github.com> Date: Mon, 24 Mar 2025 14:07:19 -0400 Subject: [PATCH] Use `std::ops::ControlFlow` in `Content::traverse` (#6053) Co-authored-by: Max Mynter --- .../typst-library/src/foundations/content.rs | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/crates/typst-library/src/foundations/content.rs b/crates/typst-library/src/foundations/content.rs index 76cd6a222..daf6c2dd9 100644 --- a/crates/typst-library/src/foundations/content.rs +++ b/crates/typst-library/src/foundations/content.rs @@ -3,7 +3,7 @@ use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::iter::{self, Sum}; use std::marker::PhantomData; -use std::ops::{Add, AddAssign, Deref, DerefMut}; +use std::ops::{Add, AddAssign, ControlFlow, Deref, DerefMut}; use std::sync::Arc; use comemo::Tracked; @@ -414,10 +414,11 @@ impl Content { /// Elements produced in `show` rules will not be included in the results. pub fn query(&self, selector: Selector) -> Vec { let mut results = Vec::new(); - self.traverse(&mut |element| { + self.traverse(&mut |element| -> ControlFlow<()> { if selector.matches(&element, None) { results.push(element); } + ControlFlow::Continue(()) }); results } @@ -427,54 +428,58 @@ impl Content { /// /// Elements produced in `show` rules will not be included in the results. pub fn query_first(&self, selector: &Selector) -> Option { - let mut result = None; - self.traverse(&mut |element| { - if result.is_none() && selector.matches(&element, None) { - result = Some(element); + self.traverse(&mut |element| -> ControlFlow { + if selector.matches(&element, None) { + ControlFlow::Break(element) + } else { + ControlFlow::Continue(()) } - }); - result + }) + .break_value() } /// Extracts the plain text of this content. pub fn plain_text(&self) -> EcoString { let mut text = EcoString::new(); - self.traverse(&mut |element| { + self.traverse(&mut |element| -> ControlFlow<()> { if let Some(textable) = element.with::() { textable.plain_text(&mut text); } + ControlFlow::Continue(()) }); text } /// Traverse this content. - fn traverse(&self, f: &mut F) + fn traverse(&self, f: &mut F) -> ControlFlow where - F: FnMut(Content), + F: FnMut(Content) -> ControlFlow, { - f(self.clone()); - - self.inner - .elem - .fields() - .into_iter() - .for_each(|(_, value)| walk_value(value, f)); - /// Walks a given value to find any content that matches the selector. - fn walk_value(value: Value, f: &mut F) + /// + /// Returns early if the function gives `ControlFlow::Break`. + fn walk_value(value: Value, f: &mut F) -> ControlFlow where - F: FnMut(Content), + F: FnMut(Content) -> ControlFlow, { match value { Value::Content(content) => content.traverse(f), Value::Array(array) => { for value in array { - walk_value(value, f); + walk_value(value, f)?; } + ControlFlow::Continue(()) } - _ => {} + _ => ControlFlow::Continue(()), } } + + // Call f on the element itself before recursively iterating its fields. + f(self.clone())?; + for (_, value) in self.inner.elem.fields() { + walk_value(value, f)?; + } + ControlFlow::Continue(()) } }