This commit is contained in:
Laurenz 2023-03-17 16:04:14 +01:00
parent c47e4cb496
commit 6d64d3e8e9
6 changed files with 118 additions and 0 deletions

View File

@ -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());

View File

@ -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::*;

69
library/src/meta/query.rs Normal file
View File

@ -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())
}

View File

@ -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

BIN
tests/ref/meta/query.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

30
tests/typ/meta/query.typ Normal file
View File

@ -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)