lex_core/lex/ast/anchoring.rs
1//! Whole-element anchoring: reference lines and their resolved anchors.
2//!
3//! A *reference line* is a line whose only content (after indentation) is a
4//! single bracketed reference, e.g. a line that is exactly `[./readme.txt]`.
5//! Per `specs/.../references-general.lex` §2.3.2–§2.3.4 such a line anchors the
6//! *entire head line of the element directly above it* (a session title, a list
7//! item's own line, a definition's subject term, a verbatim subject, or a
8//! paragraph line). It never attaches downward, and when there is no content
9//! line directly above it (first line of its container, or preceded by a blank
10//! line) it *self-links* — it stands alone and links its own text, exactly like
11//! a lone inline reference.
12//!
13//! Reference lines are removed from the line stream *before* structural parsing
14//! (see [`crate::lex::anchoring`]), so they are transparent to the
15//! definition-vs-session decision. The removal pass also resolves each line's
16//! anchor here, against the original (pre-removal) source, so every range below
17//! is in original-source coordinates — which is what editors and serializers
18//! need, since the document the user sees still contains the reference lines.
19
20use super::range::Range;
21use crate::lex::inlines::ReferenceInline;
22
23/// A reference line and its resolved anchor.
24///
25/// Collected on [`crate::lex::ast::Document`] as a queryable, document-level
26/// list (`Document::reference_lines()`), so downstream consumers — the babel
27/// serializers and the LSP `documentLink` provider — can read the anchor
28/// without re-deriving the line-adjacency rules.
29#[derive(Debug, Clone, PartialEq)]
30pub struct ReferenceLine {
31 /// The bracketed reference itself (raw text + classified type).
32 pub reference: ReferenceInline,
33 /// Source range covering the `[bracketed]` reference, in original-source
34 /// coordinates (brackets inclusive).
35 pub reference_range: Range,
36 /// The resolved anchor for this reference line.
37 pub anchor: ReferenceAnchor,
38}
39
40/// The resolved anchor of a reference line.
41#[derive(Debug, Clone, PartialEq)]
42pub enum ReferenceAnchor {
43 /// Anchors the whole head line of the element directly above.
44 WholeElement {
45 /// The element's head-line text the link wraps (list marker and the
46 /// definition's trailing `:` excluded — the head line *only*).
47 anchor_text: String,
48 /// Source range of `anchor_text`, in original-source coordinates.
49 anchor_range: Range,
50 /// What kind of element head line was anchored.
51 element: AnchoredElement,
52 },
53 /// No content line directly above: the reference line links its own text,
54 /// exactly like a lone inline reference (§2.3.2).
55 SelfLink,
56}
57
58impl ReferenceAnchor {
59 /// True when this reference line took a whole-element anchor.
60 pub fn is_whole_element(&self) -> bool {
61 matches!(self, ReferenceAnchor::WholeElement { .. })
62 }
63}
64
65/// The head-line shape a reference line anchored.
66///
67/// The anchor is resolved against the original source line directly above the
68/// reference line, so the classification reflects what that line *looks like*,
69/// which is also exactly what determines how much of it the anchor covers:
70///
71/// - [`AnchoredElement::ListItem`] — the line opens with a list marker (`- `,
72/// `1. `, `a) `, …); the marker is excluded from the anchor.
73/// - [`AnchoredElement::Subject`] — the line ends with a `:` subject marker (a
74/// definition term, a colon-style session title, or a verbatim subject); the
75/// trailing colon is excluded from the anchor.
76/// - [`AnchoredElement::WholeLine`] — any other head line (a plain paragraph
77/// line, or a session title written without a colon); the whole line is the
78/// anchor.
79///
80/// Session title vs. paragraph vs. verbatim subject are *not* distinguished
81/// here because they are indistinguishable from the head line alone and, more
82/// importantly, anchor identically (the whole line, modulo the colon rule). The
83/// behaviorally-meaningful distinctions — marker stripping and colon stripping
84/// — are the ones captured.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86pub enum AnchoredElement {
87 /// The head line opens with a list marker, excluded from the anchor.
88 ListItem,
89 /// The head line ends with a `:` subject marker, excluded from the anchor.
90 Subject,
91 /// A plain head line; the whole line is the anchor.
92 WholeLine,
93}