use crate::model::{Cursor, Kind, NodeId, SeqId, Tree, Variant};
pub fn slots_in_order(tree: &Tree, node: NodeId) -> Vec<SeqId> {
tree.child_seqs(node)
}
pub fn adjacent_structure_backward(tree: &Tree, at: Cursor) -> Option<NodeId> {
if at.index == 0 {
return None;
}
let prev = tree.items(at.seq)[at.index - 1];
if tree.child_seqs(prev).is_empty() {
return None; }
if matches!(tree.kind(prev), Some(Kind::Styled { variant: Variant::Text, .. })) {
return None; }
Some(prev)
}
pub fn adjacent_structure_forward(tree: &Tree, at: Cursor) -> Option<NodeId> {
if at.index >= tree.len(at.seq) {
return None;
}
let next = tree.items(at.seq)[at.index];
if tree.child_seqs(next).is_empty() {
return None;
}
if matches!(tree.kind(next), Some(Kind::Styled { variant: Variant::Text, .. })) {
return None;
}
Some(next)
}
pub fn normalize(tree: &Tree, at: Cursor) -> Cursor {
if at.index == 0 && !tree.is_empty(at.seq) {
if let Some(script) = tree.script_base_node(at.seq) {
if let Some((pseq, pi)) = tree.index_in_parent(script) {
return Cursor { seq: pseq, index: pi };
}
}
}
at
}
pub fn move_right(tree: &Tree, at: Cursor) -> Option<Cursor> {
if at.index < tree.len(at.seq) {
let node = tree.items(at.seq)[at.index];
match tree.child_seqs(node).first() {
None => Some(Cursor {
seq: at.seq,
index: at.index + 1,
}), Some(&first) => {
if tree.script_base_node(first).is_some() && !tree.is_empty(first) {
move_right(tree, Cursor { seq: first, index: 0 })
} else {
Some(Cursor { seq: first, index: 0 })
}
} }
} else {
let parent = tree.seq_parent(at.seq)?;
let slots = tree.child_seqs(parent);
let pos = slots.iter().position(|&s| s == at.seq)?;
if let Some(&next) = slots.get(pos + 1) {
Some(Cursor { seq: next, index: 0 })
} else {
let (pseq, pi) = tree.index_in_parent(parent)?;
Some(Cursor {
seq: pseq,
index: pi + 1,
})
}
}
}
pub fn move_left(tree: &Tree, at: Cursor) -> Option<Cursor> {
let candidate = if at.index > 0 {
let node = tree.items(at.seq)[at.index - 1];
match tree.child_seqs(node).last() {
None => Cursor {
seq: at.seq,
index: at.index - 1,
},
Some(&last) => Cursor {
seq: last,
index: tree.len(last),
},
}
} else {
let parent = tree.seq_parent(at.seq)?;
let slots = tree.child_seqs(parent);
let pos = slots.iter().position(|&s| s == at.seq)?;
if pos > 0 {
let prev = slots[pos - 1];
Cursor {
seq: prev,
index: tree.len(prev),
}
} else {
let (pseq, pi) = tree.index_in_parent(parent)?;
Cursor {
seq: pseq,
index: pi,
}
}
};
Some(normalize(tree, candidate))
}
pub fn enter(tree: &Tree, at: Cursor) -> Option<Cursor> {
if at.index < tree.len(at.seq) {
let node = tree.items(at.seq)[at.index];
tree.child_seqs(node)
.first()
.map(|&s| Cursor { seq: s, index: 0 })
} else {
None
}
}
pub fn exit(tree: &Tree, at: Cursor) -> Option<Cursor> {
let parent = tree.seq_parent(at.seq)?;
let (pseq, pi) = tree.index_in_parent(parent)?;
Some(Cursor {
seq: pseq,
index: pi,
})
}
pub fn vertical(tree: &Tree, at: Cursor, up: bool) -> Option<Cursor> {
let parent = tree.seq_parent(at.seq)?;
let target = match tree.kind(parent)? {
Kind::Frac { num, den, .. } => {
if at.seq == *num && !up {
Some(*den)
} else if at.seq == *den && up {
Some(*num)
} else {
None
}
}
Kind::Script { base, sub, sup } => {
let mut order = Vec::new();
order.extend(sup.iter().copied());
order.push(*base);
order.extend(sub.iter().copied());
let pos = order.iter().position(|&s| s == at.seq)?;
let next = if up { pos.checked_sub(1)? } else { pos + 1 };
order.get(next).copied()
}
Kind::BigOp { lower, upper, .. } => {
if at.seq == *lower && up {
Some(*upper)
} else if at.seq == *upper && !up {
Some(*lower)
} else {
None
}
}
Kind::Matrix { rows, .. } => {
let (r, c) = rows.iter().enumerate().find_map(|(ri, row)| {
row.iter().position(|&s| s == at.seq).map(|ci| (ri, ci))
})?;
let nr = if up { r.checked_sub(1)? } else { r + 1 };
rows.get(nr).and_then(|row| row.get(c)).copied()
}
_ => None,
}?;
let index = if tree.script_base_node(target).is_some() && !tree.is_empty(target) {
tree.len(target)
} else {
at.index.min(tree.len(target))
};
Some(Cursor { seq: target, index })
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::{Cursor, MathClass, ScriptSlot, Symbol};
fn atom(c: &str) -> Symbol {
Symbol { latex: c.into(), class: MathClass::Ord }
}
fn op(c: &str) -> Symbol {
Symbol { latex: c.into(), class: MathClass::Op }
}
fn x_squared(t: &mut Tree) -> (SeqId, NodeId, SeqId, SeqId) {
let root = t.root();
t.insert_atom(Cursor { seq: root, index: 0 }, atom("x"));
let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
t.insert_atom(c, atom("2"));
let script = t.items(root)[0];
let (base, sup) = match t.kind(script) {
Some(Kind::Script { base, sup, .. }) => (*base, sup.unwrap()),
_ => panic!("expected script"),
};
(root, script, base, sup)
}
#[test]
fn script_base_single_node_navigation() {
let mut t = Tree::new();
let (root, _script, base, sup) = x_squared(&mut t);
let r = move_right(&t, Cursor { seq: root, index: 0 }).unwrap();
assert_eq!((r.seq, r.index), (base, 1)); let r = move_right(&t, r).unwrap();
assert_eq!((r.seq, r.index), (sup, 0));
let l = move_left(&t, Cursor { seq: base, index: 1 }).unwrap();
assert_eq!((l.seq, l.index), (root, 0)); let n = normalize(&t, Cursor { seq: base, index: 0 });
assert_eq!((n.seq, n.index), (root, 0));
}
#[test]
fn script_base_composite_node_is_enterable() {
let mut t = Tree::new();
let root = t.root();
t.insert_fraction(Cursor { seq: root, index: 0 }, crate::model::FracStyle::Bar, None);
let frac = t.items(root)[0];
t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup); let script = t.items(root)[0];
let base = match t.kind(script) {
Some(Kind::Script { base, .. }) => *base,
_ => panic!(),
};
let (num, den) = (t.child_seqs(frac)[0], t.child_seqs(frac)[1]);
let r = move_right(&t, Cursor { seq: root, index: 0 }).unwrap();
assert_eq!((r.seq, r.index), (num, 0));
let l = move_left(&t, Cursor { seq: base, index: 1 }).unwrap();
assert_eq!(l.seq, den);
}
#[test]
fn bigop_left_navigation_spec() {
let mut t = Tree::new();
let root = t.root();
t.insert_big_op(Cursor { seq: root, index: 0 }, op("\\sum"));
let bigop = t.items(root)[0];
let cs = t.child_seqs(bigop); let (upper, lower) = (cs[0], cs[1]);
t.insert_atom(Cursor { seq: lower, index: 0 }, atom("2"));
t.insert_atom(Cursor { seq: upper, index: 0 }, atom("3"));
let mut c = Cursor { seq: root, index: 1 }; let expected = [
(lower, 1), (lower, 0), (upper, 1), (upper, 0), (root, 0), ];
for (i, (eseq, eidx)) in expected.into_iter().enumerate() {
c = move_left(&t, c).unwrap_or_else(|| panic!("step {i}: no move"));
assert_eq!((c.seq, c.index), (eseq, eidx), "left step {i}");
}
}
}