Skip to main content

switchback_traits/
intra_links.rs

1//! Intra-link splice helpers.
2
3use crate::LinkFormatter;
4use crate::link_context::LinkContext;
5use crate::{Anchor, IntraLink, LinkTarget};
6
7/// Applies resolved intra-links to a prose field, replacing anchored spans with
8/// formatted link strings.
9pub fn apply_intra_links(
10    field: &str,
11    content: &str,
12    links: &[IntraLink],
13    formatter: &dyn LinkFormatter,
14    ctx: &LinkContext,
15) -> String {
16    let mut field_links: Vec<_> = links
17        .iter()
18        .filter(|l| l.anchor.field == field && !matches!(l.target, LinkTarget::Unresolved))
19        .collect();
20    if field_links.is_empty() {
21        return content.to_string();
22    }
23    field_links.sort_by_key(|l| l.anchor.byte_start);
24    let mut out = String::new();
25    let mut cursor = 0usize;
26    for link in field_links {
27        let start = link.anchor.byte_start as usize;
28        let end = link.anchor.byte_end as usize;
29        if start < cursor || end > content.len() || start > end {
30            continue;
31        }
32        out.push_str(&content[cursor..start]);
33        out.push_str(&formatter.format(&link.target, ctx));
34        cursor = end;
35    }
36    out.push_str(&content[cursor..]);
37    out
38}
39
40/// Returns intra-links whose anchor targets `field`.
41pub fn links_for_field<'a>(links: &'a [IntraLink], field: &str) -> Vec<&'a IntraLink> {
42    links.iter().filter(|l| l.anchor.field == field).collect()
43}
44
45/// Builds an anchor for a byte span within a named field.
46pub fn anchor(field: impl Into<String>, byte_start: u32, byte_end: u32) -> Anchor {
47    Anchor {
48        field: field.into(),
49        byte_start,
50        byte_end,
51    }
52}
53
54#[cfg(test)]
55mod tests;