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