1use crate::model::{Cursor, Kind, NodeId, SeqId, Tree, Variant};
4
5pub fn slots_in_order(tree: &Tree, node: NodeId) -> Vec<SeqId> {
7 tree.child_seqs(node)
8}
9
10pub fn adjacent_structure_backward(tree: &Tree, at: Cursor) -> Option<NodeId> {
12 if at.index == 0 {
13 return None;
14 }
15 let prev = tree.items(at.seq)[at.index - 1];
16 if tree.child_seqs(prev).is_empty() {
17 return None; }
19 if matches!(tree.kind(prev), Some(Kind::Styled { variant: Variant::Text, .. })) {
20 return None; }
22 Some(prev)
23}
24
25pub fn adjacent_structure_forward(tree: &Tree, at: Cursor) -> Option<NodeId> {
27 if at.index >= tree.len(at.seq) {
28 return None;
29 }
30 let next = tree.items(at.seq)[at.index];
31 if tree.child_seqs(next).is_empty() {
32 return None;
33 }
34 if matches!(tree.kind(next), Some(Kind::Styled { variant: Variant::Text, .. })) {
35 return None;
36 }
37 Some(next)
38}
39
40pub fn normalize(tree: &Tree, at: Cursor) -> Cursor {
42 if at.index == 0 && !tree.is_empty(at.seq) {
43 if let Some(script) = tree.script_base_node(at.seq) {
44 if let Some((pseq, pi)) = tree.index_in_parent(script) {
45 return Cursor { seq: pseq, index: pi };
46 }
47 }
48 }
49 at
50}
51
52pub fn move_right(tree: &Tree, at: Cursor) -> Option<Cursor> {
54 if at.index < tree.len(at.seq) {
55 let node = tree.items(at.seq)[at.index];
56 match tree.child_seqs(node).first() {
57 None => Some(Cursor {
58 seq: at.seq,
59 index: at.index + 1,
60 }), Some(&first) => {
62 if tree.script_base_node(first).is_some() && !tree.is_empty(first) {
64 move_right(tree, Cursor { seq: first, index: 0 })
65 } else {
66 Some(Cursor { seq: first, index: 0 })
67 }
68 } }
70 } else {
71 let parent = tree.seq_parent(at.seq)?;
73 let slots = tree.child_seqs(parent);
74 let pos = slots.iter().position(|&s| s == at.seq)?;
75 if let Some(&next) = slots.get(pos + 1) {
76 Some(Cursor { seq: next, index: 0 })
77 } else {
78 let (pseq, pi) = tree.index_in_parent(parent)?;
79 Some(Cursor {
80 seq: pseq,
81 index: pi + 1,
82 })
83 }
84 }
85}
86
87pub fn move_left(tree: &Tree, at: Cursor) -> Option<Cursor> {
89 let candidate = if at.index > 0 {
90 let node = tree.items(at.seq)[at.index - 1];
91 match tree.child_seqs(node).last() {
92 None => Cursor {
93 seq: at.seq,
94 index: at.index - 1,
95 },
96 Some(&last) => Cursor {
97 seq: last,
98 index: tree.len(last),
99 },
100 }
101 } else {
102 let parent = tree.seq_parent(at.seq)?;
103 let slots = tree.child_seqs(parent);
104 let pos = slots.iter().position(|&s| s == at.seq)?;
105 if pos > 0 {
106 let prev = slots[pos - 1];
107 Cursor {
108 seq: prev,
109 index: tree.len(prev),
110 }
111 } else {
112 let (pseq, pi) = tree.index_in_parent(parent)?;
113 Cursor {
114 seq: pseq,
115 index: pi,
116 }
117 }
118 };
119 Some(normalize(tree, candidate))
121}
122
123pub fn enter(tree: &Tree, at: Cursor) -> Option<Cursor> {
125 if at.index < tree.len(at.seq) {
126 let node = tree.items(at.seq)[at.index];
127 tree.child_seqs(node)
128 .first()
129 .map(|&s| Cursor { seq: s, index: 0 })
130 } else {
131 None
132 }
133}
134
135pub fn exit(tree: &Tree, at: Cursor) -> Option<Cursor> {
137 let parent = tree.seq_parent(at.seq)?;
138 let (pseq, pi) = tree.index_in_parent(parent)?;
139 Some(Cursor {
140 seq: pseq,
141 index: pi,
142 })
143}
144
145pub fn vertical(tree: &Tree, at: Cursor, up: bool) -> Option<Cursor> {
147 let parent = tree.seq_parent(at.seq)?;
148 let target = match tree.kind(parent)? {
149 Kind::Frac { num, den, .. } => {
150 if at.seq == *num && !up {
151 Some(*den)
152 } else if at.seq == *den && up {
153 Some(*num)
154 } else {
155 None
156 }
157 }
158 Kind::Script { base, sub, sup } => {
159 let mut order = Vec::new();
161 order.extend(sup.iter().copied());
162 order.push(*base);
163 order.extend(sub.iter().copied());
164 let pos = order.iter().position(|&s| s == at.seq)?;
165 let next = if up { pos.checked_sub(1)? } else { pos + 1 };
166 order.get(next).copied()
167 }
168 Kind::BigOp { lower, upper, .. } => {
169 if at.seq == *lower && up {
171 Some(*upper)
172 } else if at.seq == *upper && !up {
173 Some(*lower)
174 } else {
175 None
176 }
177 }
178 Kind::Matrix { rows, .. } => {
179 let (r, c) = rows.iter().enumerate().find_map(|(ri, row)| {
180 row.iter().position(|&s| s == at.seq).map(|ci| (ri, ci))
181 })?;
182 let nr = if up { r.checked_sub(1)? } else { r + 1 };
183 rows.get(nr).and_then(|row| row.get(c)).copied()
184 }
185 _ => None,
186 }?;
187 let index = if tree.script_base_node(target).is_some() && !tree.is_empty(target) {
189 tree.len(target)
190 } else {
191 at.index.min(tree.len(target))
192 };
193 Some(Cursor { seq: target, index })
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199 use crate::model::{Cursor, MathClass, ScriptSlot, Symbol};
200
201 fn atom(c: &str) -> Symbol {
202 Symbol { latex: c.into(), class: MathClass::Ord }
203 }
204 fn op(c: &str) -> Symbol {
205 Symbol { latex: c.into(), class: MathClass::Op }
206 }
207
208 fn x_squared(t: &mut Tree) -> (SeqId, NodeId, SeqId, SeqId) {
210 let root = t.root();
211 t.insert_atom(Cursor { seq: root, index: 0 }, atom("x"));
212 let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
213 t.insert_atom(c, atom("2"));
214 let script = t.items(root)[0];
215 let (base, sup) = match t.kind(script) {
216 Some(Kind::Script { base, sup, .. }) => (*base, sup.unwrap()),
217 _ => panic!("expected script"),
218 };
219 (root, script, base, sup)
220 }
221
222 #[test]
224 fn script_base_single_node_navigation() {
225 let mut t = Tree::new();
226 let (root, _script, base, sup) = x_squared(&mut t);
227
228 let r = move_right(&t, Cursor { seq: root, index: 0 }).unwrap();
230 assert_eq!((r.seq, r.index), (base, 1)); let r = move_right(&t, r).unwrap();
233 assert_eq!((r.seq, r.index), (sup, 0)); let l = move_left(&t, Cursor { seq: base, index: 1 }).unwrap();
237 assert_eq!((l.seq, l.index), (root, 0)); let n = normalize(&t, Cursor { seq: base, index: 0 });
240 assert_eq!((n.seq, n.index), (root, 0));
241 }
242
243 #[test]
245 fn script_base_composite_node_is_enterable() {
246 let mut t = Tree::new();
247 let root = t.root();
248 t.insert_fraction(Cursor { seq: root, index: 0 }, crate::model::FracStyle::Bar, None);
249 let frac = t.items(root)[0];
250 t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup); let script = t.items(root)[0];
252 let base = match t.kind(script) {
253 Some(Kind::Script { base, .. }) => *base,
254 _ => panic!(),
255 };
256 let (num, den) = (t.child_seqs(frac)[0], t.child_seqs(frac)[1]);
257
258 let r = move_right(&t, Cursor { seq: root, index: 0 }).unwrap();
260 assert_eq!((r.seq, r.index), (num, 0));
261 let l = move_left(&t, Cursor { seq: base, index: 1 }).unwrap();
263 assert_eq!(l.seq, den);
264 }
265
266 #[test]
268 fn bigop_left_navigation_spec() {
269 let mut t = Tree::new();
270 let root = t.root();
271 t.insert_big_op(Cursor { seq: root, index: 0 }, op("\\sum"));
272 let bigop = t.items(root)[0];
273 let cs = t.child_seqs(bigop); let (upper, lower) = (cs[0], cs[1]);
275 t.insert_atom(Cursor { seq: lower, index: 0 }, atom("2"));
276 t.insert_atom(Cursor { seq: upper, index: 0 }, atom("3"));
277
278 let mut c = Cursor { seq: root, index: 1 }; let expected = [
280 (lower, 1), (lower, 0), (upper, 1), (upper, 0), (root, 0), ];
286 for (i, (eseq, eidx)) in expected.into_iter().enumerate() {
287 c = move_left(&t, c).unwrap_or_else(|| panic!("step {i}: no move"));
288 assert_eq!((c.seq, c.index), (eseq, eidx), "left step {i}");
289 }
290 }
291}