use std::fmt;
use serde::{Deserialize, Serialize};
use crate::model::{Cursor, Kind, NodeId, SeqId, Tree};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Slot {
Numerator,
Denominator,
Base,
Sub,
Sup,
Lower,
Upper,
Index,
Radicand,
Body,
Over,
Under,
Cell {
row: usize,
col: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Step {
pub node: usize,
pub slot: Slot,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CaretPath {
pub steps: Vec<Step>,
pub index: usize,
}
impl CaretPath {
pub fn root(index: usize) -> Self {
Self { steps: Vec::new(), index }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Selection {
pub anchor: CaretPath,
pub focus: CaretPath,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathError {
NoNode {
depth: usize,
},
NoSlot {
depth: usize,
},
GapOutOfRange {
len: usize,
index: usize,
},
SplitSelection,
}
impl fmt::Display for PathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PathError::NoNode { depth } => write!(f, "step {depth} names a missing node"),
PathError::NoSlot { depth } => write!(f, "step {depth} names a missing slot"),
PathError::GapOutOfRange { len, index } => {
write!(f, "gap {index} is outside a sequence of length {len}")
}
PathError::SplitSelection => write!(f, "selection ends lie in different sequences"),
}
}
}
impl std::error::Error for PathError {}
impl Tree {
pub(crate) fn slot_of(&self, node: NodeId, seq: SeqId) -> Option<Slot> {
let hit = |s: SeqId| s == seq;
Some(match self.kind(node)? {
Kind::Atom(_) | Kind::HostBox { .. } => return None,
Kind::Frac { num, den, .. } => {
if hit(*num) {
Slot::Numerator
} else if hit(*den) {
Slot::Denominator
} else {
return None;
}
}
Kind::Script { base, sub, sup } => {
if hit(*base) {
Slot::Base
} else if sub.is_some_and(hit) {
Slot::Sub
} else if sup.is_some_and(hit) {
Slot::Sup
} else {
return None;
}
}
Kind::BigOp { lower, upper, .. } => {
if hit(*lower) {
Slot::Lower
} else if hit(*upper) {
Slot::Upper
} else {
return None;
}
}
Kind::Sqrt { index, radicand } => {
if hit(*index) {
Slot::Index
} else if hit(*radicand) {
Slot::Radicand
} else {
return None;
}
}
Kind::Delim { body, .. } | Kind::Styled { content: body, .. } => {
if hit(*body) {
Slot::Body
} else {
return None;
}
}
Kind::Accent { base, .. } => {
if hit(*base) {
Slot::Base
} else {
return None;
}
}
Kind::UnderOver { base, over, under, .. } => {
if hit(*base) {
Slot::Base
} else if over.is_some_and(hit) {
Slot::Over
} else if under.is_some_and(hit) {
Slot::Under
} else {
return None;
}
}
Kind::Matrix { rows, .. } => {
let (row, col) = rows
.iter()
.enumerate()
.find_map(|(r, cells)| cells.iter().position(|&c| c == seq).map(|c| (r, c)))?;
Slot::Cell { row, col }
}
})
}
pub(crate) fn slot_seq(&self, node: NodeId, slot: Slot) -> Option<SeqId> {
match (self.kind(node)?, slot) {
(Kind::Frac { num, .. }, Slot::Numerator) => Some(*num),
(Kind::Frac { den, .. }, Slot::Denominator) => Some(*den),
(Kind::Script { base, .. }, Slot::Base) => Some(*base),
(Kind::Script { sub, .. }, Slot::Sub) => *sub,
(Kind::Script { sup, .. }, Slot::Sup) => *sup,
(Kind::BigOp { lower, .. }, Slot::Lower) => Some(*lower),
(Kind::BigOp { upper, .. }, Slot::Upper) => Some(*upper),
(Kind::Sqrt { index, .. }, Slot::Index) => Some(*index),
(Kind::Sqrt { radicand, .. }, Slot::Radicand) => Some(*radicand),
(Kind::Delim { body, .. }, Slot::Body) => Some(*body),
(Kind::Styled { content, .. }, Slot::Body) => Some(*content),
(Kind::Accent { base, .. }, Slot::Base) => Some(*base),
(Kind::UnderOver { base, .. }, Slot::Base) => Some(*base),
(Kind::UnderOver { over, .. }, Slot::Over) => *over,
(Kind::UnderOver { under, .. }, Slot::Under) => *under,
(Kind::Matrix { rows, .. }, Slot::Cell { row, col }) => rows.get(row)?.get(col).copied(),
_ => None,
}
}
pub(crate) fn seq_steps(&self, seq: SeqId) -> Vec<Step> {
let mut steps = Vec::new();
let mut cur = seq;
while let Some(node) = self.seq_parent(cur) {
let (Some((pseq, idx)), Some(slot)) = (self.index_in_parent(node), self.slot_of(node, cur)) else {
break;
};
steps.push(Step { node: idx, slot });
cur = pseq;
}
steps.reverse();
steps
}
pub(crate) fn path_of(&self, at: Cursor) -> CaretPath {
CaretPath { steps: self.seq_steps(at.seq), index: at.index }
}
pub(crate) fn resolve_steps(&self, steps: &[Step]) -> Result<SeqId, PathError> {
let mut seq = self.root();
for (depth, step) in steps.iter().enumerate() {
let node = *self.items(seq).get(step.node).ok_or(PathError::NoNode { depth })?;
seq = self.slot_seq(node, step.slot).ok_or(PathError::NoSlot { depth })?;
}
Ok(seq)
}
pub(crate) fn resolve(&self, path: &CaretPath) -> Result<Cursor, PathError> {
let seq = self.resolve_steps(&path.steps)?;
let len = self.len(seq);
if path.index > len {
return Err(PathError::GapOutOfRange { len, index: path.index });
}
Ok(Cursor { seq, index: path.index })
}
}