Skip to main content

mathtex_editor_core/
path.rs

1//! Stable caret addresses: a chain of child indexes and named slots from the root plus a gap index.
2
3use std::fmt;
4
5use serde::{Deserialize, Serialize};
6
7use crate::model::{Cursor, Kind, NodeId, SeqId, Tree};
8
9/// A named slot of a structure.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11#[serde(rename_all = "snake_case")]
12pub enum Slot {
13    /// Fraction numerator.
14    Numerator,
15    /// Fraction denominator.
16    Denominator,
17    /// Base of a script, accent, or under over construct.
18    Base,
19    /// Subscript.
20    Sub,
21    /// Superscript.
22    Sup,
23    /// Lower limit of a big operator.
24    Lower,
25    /// Upper limit of a big operator.
26    Upper,
27    /// Degree of a radical.
28    Index,
29    /// Radicand of a radical.
30    Radicand,
31    /// Body of delimiters or content of a styled run.
32    Body,
33    /// Label above an under over construct.
34    Over,
35    /// Label below an under over construct.
36    Under,
37    /// Matrix cell by zero based row and column.
38    Cell {
39        /// Row index.
40        row: usize,
41        /// Column index.
42        col: usize,
43    },
44}
45
46/// One step down the tree: the child at `node` in the current sequence, then its `slot`.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48pub struct Step {
49    /// Index of the child node in its sequence.
50    pub node: usize,
51    /// The slot of that node to descend into.
52    pub slot: Slot,
53}
54
55/// A caret position that survives serialization and editor rebuilds.
56#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
57pub struct CaretPath {
58    /// Steps from the root sequence down to the caret's sequence, empty for the root.
59    pub steps: Vec<Step>,
60    /// Gap index in that sequence, `0` is before the first item.
61    pub index: usize,
62}
63
64impl CaretPath {
65    /// A caret at gap `index` of the root sequence.
66    pub fn root(index: usize) -> Self {
67        Self { steps: Vec::new(), index }
68    }
69}
70
71/// A selection between two carets in the same sequence.
72#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
73pub struct Selection {
74    /// The fixed end.
75    pub anchor: CaretPath,
76    /// The moving end, where the caret is drawn.
77    pub focus: CaretPath,
78}
79
80/// Why a path does not address a caret position in the current document.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum PathError {
83    /// Step `depth` names a child index past the end of its sequence.
84    NoNode {
85        /// Zero based step index.
86        depth: usize,
87    },
88    /// Step `depth` names a slot its node does not have.
89    NoSlot {
90        /// Zero based step index.
91        depth: usize,
92    },
93    /// The gap index is past the end of the addressed sequence.
94    GapOutOfRange {
95        /// Length of the addressed sequence.
96        len: usize,
97        /// The requested gap.
98        index: usize,
99    },
100    /// Anchor and focus address different sequences.
101    SplitSelection,
102}
103
104impl fmt::Display for PathError {
105    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106        match self {
107            PathError::NoNode { depth } => write!(f, "step {depth} names a missing node"),
108            PathError::NoSlot { depth } => write!(f, "step {depth} names a missing slot"),
109            PathError::GapOutOfRange { len, index } => {
110                write!(f, "gap {index} is outside a sequence of length {len}")
111            }
112            PathError::SplitSelection => write!(f, "selection ends lie in different sequences"),
113        }
114    }
115}
116
117impl std::error::Error for PathError {}
118
119impl Tree {
120    /// The named slot `seq` occupies in `node`.
121    pub(crate) fn slot_of(&self, node: NodeId, seq: SeqId) -> Option<Slot> {
122        let hit = |s: SeqId| s == seq;
123        Some(match self.kind(node)? {
124            Kind::Atom(_) | Kind::HostBox { .. } => return None,
125            Kind::Frac { num, den, .. } => {
126                if hit(*num) {
127                    Slot::Numerator
128                } else if hit(*den) {
129                    Slot::Denominator
130                } else {
131                    return None;
132                }
133            }
134            Kind::Script { base, sub, sup } => {
135                if hit(*base) {
136                    Slot::Base
137                } else if sub.is_some_and(hit) {
138                    Slot::Sub
139                } else if sup.is_some_and(hit) {
140                    Slot::Sup
141                } else {
142                    return None;
143                }
144            }
145            Kind::BigOp { lower, upper, .. } => {
146                if hit(*lower) {
147                    Slot::Lower
148                } else if hit(*upper) {
149                    Slot::Upper
150                } else {
151                    return None;
152                }
153            }
154            Kind::Sqrt { index, radicand } => {
155                if hit(*index) {
156                    Slot::Index
157                } else if hit(*radicand) {
158                    Slot::Radicand
159                } else {
160                    return None;
161                }
162            }
163            Kind::Delim { body, .. } | Kind::Styled { content: body, .. } => {
164                if hit(*body) {
165                    Slot::Body
166                } else {
167                    return None;
168                }
169            }
170            Kind::Accent { base, .. } => {
171                if hit(*base) {
172                    Slot::Base
173                } else {
174                    return None;
175                }
176            }
177            Kind::UnderOver { base, over, under, .. } => {
178                if hit(*base) {
179                    Slot::Base
180                } else if over.is_some_and(hit) {
181                    Slot::Over
182                } else if under.is_some_and(hit) {
183                    Slot::Under
184                } else {
185                    return None;
186                }
187            }
188            Kind::Matrix { rows, .. } => {
189                let (row, col) = rows
190                    .iter()
191                    .enumerate()
192                    .find_map(|(r, cells)| cells.iter().position(|&c| c == seq).map(|c| (r, c)))?;
193                Slot::Cell { row, col }
194            }
195        })
196    }
197
198    /// The sequence behind a named slot of `node`.
199    pub(crate) fn slot_seq(&self, node: NodeId, slot: Slot) -> Option<SeqId> {
200        match (self.kind(node)?, slot) {
201            (Kind::Frac { num, .. }, Slot::Numerator) => Some(*num),
202            (Kind::Frac { den, .. }, Slot::Denominator) => Some(*den),
203            (Kind::Script { base, .. }, Slot::Base) => Some(*base),
204            (Kind::Script { sub, .. }, Slot::Sub) => *sub,
205            (Kind::Script { sup, .. }, Slot::Sup) => *sup,
206            (Kind::BigOp { lower, .. }, Slot::Lower) => Some(*lower),
207            (Kind::BigOp { upper, .. }, Slot::Upper) => Some(*upper),
208            (Kind::Sqrt { index, .. }, Slot::Index) => Some(*index),
209            (Kind::Sqrt { radicand, .. }, Slot::Radicand) => Some(*radicand),
210            (Kind::Delim { body, .. }, Slot::Body) => Some(*body),
211            (Kind::Styled { content, .. }, Slot::Body) => Some(*content),
212            (Kind::Accent { base, .. }, Slot::Base) => Some(*base),
213            (Kind::UnderOver { base, .. }, Slot::Base) => Some(*base),
214            (Kind::UnderOver { over, .. }, Slot::Over) => *over,
215            (Kind::UnderOver { under, .. }, Slot::Under) => *under,
216            (Kind::Matrix { rows, .. }, Slot::Cell { row, col }) => rows.get(row)?.get(col).copied(),
217            _ => None,
218        }
219    }
220
221    /// The steps from the root down to `seq`.
222    pub(crate) fn seq_steps(&self, seq: SeqId) -> Vec<Step> {
223        let mut steps = Vec::new();
224        let mut cur = seq;
225        while let Some(node) = self.seq_parent(cur) {
226            let (Some((pseq, idx)), Some(slot)) = (self.index_in_parent(node), self.slot_of(node, cur)) else {
227                break;
228            };
229            steps.push(Step { node: idx, slot });
230            cur = pseq;
231        }
232        steps.reverse();
233        steps
234    }
235
236    pub(crate) fn path_of(&self, at: Cursor) -> CaretPath {
237        CaretPath { steps: self.seq_steps(at.seq), index: at.index }
238    }
239
240    pub(crate) fn resolve_steps(&self, steps: &[Step]) -> Result<SeqId, PathError> {
241        let mut seq = self.root();
242        for (depth, step) in steps.iter().enumerate() {
243            let node = *self.items(seq).get(step.node).ok_or(PathError::NoNode { depth })?;
244            seq = self.slot_seq(node, step.slot).ok_or(PathError::NoSlot { depth })?;
245        }
246        Ok(seq)
247    }
248
249    pub(crate) fn resolve(&self, path: &CaretPath) -> Result<Cursor, PathError> {
250        let seq = self.resolve_steps(&path.steps)?;
251        let len = self.len(seq);
252        if path.index > len {
253            return Err(PathError::GapOutOfRange { len, index: path.index });
254        }
255        Ok(Cursor { seq, index: path.index })
256    }
257}