summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2023-03-17 16:04:14 +0100
committerLaurenz <laurmaedje@gmail.com>2023-03-17 16:04:14 +0100
commit6d64d3e8e9123f3fa8166c8b710e2b2c61ed5898 (patch)
tree086961c2dfb8e63a437379898e9fc70c21cf8949
parentc47e4cb4969836e7fb8955361728105555b6d722 (diff)
Query
-rw-r--r--library/src/lib.rs1
-rw-r--r--library/src/meta/mod.rs2
-rw-r--r--library/src/meta/query.rs69
-rw-r--r--src/model/typeset.rs16
-rw-r--r--tests/ref/meta/query.pngbin0 -> 121102 bytes
-rw-r--r--tests/typ/meta/query.typ30
6 files changed, 118 insertions, 0 deletions
diff --git a/library/src/lib.rs b/library/src/lib.rs
index 83dbe17a..0850faf4 100644
--- a/library/src/lib.rs
+++ b/library/src/lib.rs
@@ -95,6 +95,7 @@ fn global(math: Module, calc: Module) -> Module {
global.define("counter", meta::counter);
global.define("numbering", meta::numbering);
global.define("state", meta::state);
+ global.define("query", meta::query);
// Symbols.
global.define("sym", symbols::sym());
diff --git a/library/src/meta/mod.rs b/library/src/meta/mod.rs
index 1d774058..50b8627e 100644
--- a/library/src/meta/mod.rs
+++ b/library/src/meta/mod.rs
@@ -8,6 +8,7 @@ mod heading;
mod link;
mod numbering;
mod outline;
+mod query;
mod reference;
mod state;
@@ -19,6 +20,7 @@ pub use self::heading::*;
pub use self::link::*;
pub use self::numbering::*;
pub use self::outline::*;
+pub use self::query::*;
pub use self::reference::*;
pub use self::state::*;
diff --git a/library/src/meta/query.rs b/library/src/meta/query.rs
new file mode 100644
index 00000000..c91f0d1a
--- /dev/null
+++ b/library/src/meta/query.rs
@@ -0,0 +1,69 @@
+use crate::prelude::*;
+
+/// Find elements in the document.
+///
+/// Display: Query
+/// Category: meta
+/// Returns: content
+#[func]
+pub fn query(
+ /// The thing to search for.
+ target: Target,
+ /// A function to format the results with.
+ format: Func,
+) -> Value {
+ QueryNode::new(target.0, format).pack().into()
+}
+
+/// A query target.
+struct Target(Selector);
+
+cast_from_value! {
+ Target,
+ label: Label => Self(Selector::Label(label)),
+ func: Func => {
+ let Some(id) = func.id() else {
+ return Err("this function is not selectable".into());
+ };
+
+ if !Content::new(id).can::<dyn Locatable>() {
+ Err(eco_format!("cannot query for {}s", id.name))?;
+ }
+
+ Self(Selector::Node(id, None))
+ }
+}
+
+/// Executes a query.
+///
+/// Display: Query
+/// Category: special
+#[node(Locatable, Show)]
+pub struct QueryNode {
+ /// The thing to search for.
+ #[required]
+ pub target: Selector,
+
+ /// The function to format the results with.
+ #[required]
+ pub format: Func,
+}
+
+impl Show for QueryNode {
+ fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
+ if !vt.introspector.init() {
+ return Ok(Content::empty());
+ }
+
+ let id = self.0.stable_id().unwrap();
+ let target = self.target();
+ let (before, after) = vt.introspector.query_split(target, id);
+ let func = self.format();
+ let args = Args::new(func.span(), [encode(before), encode(after)]);
+ Ok(func.call_detached(vt.world, args)?.display())
+ }
+}
+
+fn encode(list: Vec<&Content>) -> Value {
+ Value::Array(list.into_iter().cloned().map(Value::Content).collect())
+}
diff --git a/src/model/typeset.rs b/src/model/typeset.rs
index 4c8be135..e9cb3d2c 100644
--- a/src/model/typeset.rs
+++ b/src/model/typeset.rs
@@ -166,6 +166,22 @@ impl Introspector {
self.all().filter(|node| selector.matches(node)).collect()
}
+ /// Query for all metadata matches before the given id.
+ pub fn query_split(
+ &self,
+ selector: Selector,
+ id: StableId,
+ ) -> (Vec<&Content>, Vec<&Content>) {
+ let mut iter = self.all();
+ let before = iter
+ .by_ref()
+ .take_while(|node| node.stable_id() != Some(id))
+ .filter(|node| selector.matches(node))
+ .collect();
+ let after = iter.filter(|node| selector.matches(node)).collect();
+ (before, after)
+ }
+
/// Find the page number for the given stable id.
pub fn page(&self, id: StableId) -> NonZeroUsize {
self.location(id).page
diff --git a/tests/ref/meta/query.png b/tests/ref/meta/query.png
new file mode 100644
index 00000000..80981202
--- /dev/null
+++ b/tests/ref/meta/query.png
Binary files differ
diff --git a/tests/typ/meta/query.typ b/tests/typ/meta/query.typ
new file mode 100644
index 00000000..85608e8e
--- /dev/null
+++ b/tests/typ/meta/query.typ
@@ -0,0 +1,30 @@
+// Test the query function.
+
+---
+#set page(
+ paper: "a7",
+ margin: (y: 1cm, x: 0.5cm),
+ header: {
+ smallcaps[Typst Academy]
+ h(1fr)
+ query(heading, (before, after) => {
+ let elem = if before.len() != 0 {
+ before.last()
+ } else if after.len() != 0 {
+ after.first()
+ }
+ emph(elem.body)
+ })
+ }
+)
+
+#outline()
+
+= Introduction
+#lorem(35)
+
+= Background
+#lorem(35)
+
+= Approach
+#lorem(60)