1use std::fmt;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use mathtex_ir::Fragment;
7use serde::{Deserialize, Serialize};
8
9use crate::command::{Command, Dir, Edge, ExitDir, HostBoxEntry, HostBoxPolicy, Outcome, Side};
10use crate::doc::{Document, DocumentError};
11use crate::export::{self, Source};
12use crate::geometry::{EmptySlotCaret, Point, RenderOutput, StaleSource};
13use crate::menu::{Menu, MenuView, RowEffect};
14use crate::model::{Cursor, Kind, NodeId, SeqId, SeqRange, Symbol, Tree};
15use crate::path::{CaretPath, PathError, Selection};
16use crate::{matcher, nav, selection};
17
18static NEXT_EDITOR: AtomicU64 = AtomicU64::new(1);
20
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct Snapshot {
24 pub document: Document,
26 pub cursor: CaretPath,
28 pub selection: Option<Selection>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum RestoreError {
35 Document(DocumentError),
37 Path(PathError),
39}
40
41impl From<PathError> for RestoreError {
42 fn from(e: PathError) -> Self {
43 RestoreError::Path(e)
44 }
45}
46
47impl fmt::Display for RestoreError {
48 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49 match self {
50 RestoreError::Document(e) => write!(f, "invalid snapshot document: {e}"),
51 RestoreError::Path(e) => write!(f, "invalid snapshot caret: {e}"),
52 }
53 }
54}
55
56impl std::error::Error for RestoreError {}
57
58#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
60pub struct InputContext {
61 pub in_text_slot: bool,
63 pub menu_open: bool,
65 pub closing_delimiter: Option<char>,
67 pub serial: u64,
69}
70
71pub struct Editor {
73 tree: Tree,
74 cursor: Cursor,
75 anchor: Option<usize>,
77 menu: Option<Menu>,
78 revision: u64,
79 serial: u64,
80 id: u64,
81 policy: HostBoxPolicy,
82 empty_caret: EmptySlotCaret,
83}
84
85impl Default for Editor {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91impl Editor {
92 pub fn new() -> Self {
94 Self::with_tree(Tree::new())
95 }
96
97 pub fn from_document(doc: &Document) -> Result<Self, DocumentError> {
99 doc.validate()?;
100 Ok(Self::with_tree(Tree::from_doc(doc)))
101 }
102
103 fn with_tree(tree: Tree) -> Self {
104 let cursor = Cursor { seq: tree.root(), index: 0 };
105 Self {
106 tree,
107 cursor,
108 anchor: None,
109 menu: None,
110 revision: 0,
111 serial: 0,
112 id: NEXT_EDITOR.fetch_add(1, Ordering::Relaxed),
113 policy: HostBoxPolicy::Skip,
114 empty_caret: EmptySlotCaret::Start,
115 }
116 }
117
118 pub fn document(&self) -> Document {
120 self.tree.to_doc()
121 }
122
123 pub fn revision(&self) -> u64 {
125 self.revision
126 }
127
128 pub fn cursor(&self) -> CaretPath {
130 self.tree.path_of(self.cursor)
131 }
132
133 pub fn set_cursor(&mut self, at: &CaretPath) -> Result<(), PathError> {
135 let c = self.tree.resolve(at)?;
136 self.cursor = c;
137 self.anchor = None;
138 self.menu = None;
139 self.serial += 1;
140 self.normalize();
141 Ok(())
142 }
143
144 pub fn selection(&self) -> Option<Selection> {
146 let s = self.sel()?;
147 let steps = self.tree.seq_steps(s.seq);
148 Some(Selection {
149 anchor: CaretPath { steps: steps.clone(), index: s.anchor },
150 focus: CaretPath { steps, index: s.focus },
151 })
152 }
153
154 pub fn set_selection(&mut self, anchor: &CaretPath, focus: &CaretPath) -> Result<(), PathError> {
156 let a = self.tree.resolve(anchor)?;
157 let f = self.tree.resolve(focus)?;
158 if a.seq != f.seq {
159 return Err(PathError::SplitSelection);
160 }
161 self.cursor = f;
162 self.anchor = Some(a.index);
163 self.menu = None;
164 self.serial += 1;
165 self.normalize();
166 Ok(())
167 }
168
169 pub fn at_start(&self) -> bool {
171 self.cursor == Cursor { seq: self.tree.root(), index: 0 }
172 }
173
174 pub fn at_end(&self) -> bool {
176 let root = self.tree.root();
177 self.cursor == Cursor { seq: root, index: self.tree.len(root) }
178 }
179
180 pub fn at_slot_start(&self) -> bool {
182 self.cursor.index == 0
183 }
184
185 pub fn at_slot_end(&self) -> bool {
187 self.cursor.index == self.tree.len(self.cursor.seq)
188 }
189
190 pub fn letters_before_caret(&self) -> String {
192 if self.sel().is_some() || self.menu.is_some() {
193 return String::new();
194 }
195 let items = &self.tree.items(self.cursor.seq)[..self.cursor.index];
196 let letter = |&node: &NodeId| match self.tree.kind(node) {
197 Some(Kind::Atom(s)) => match s.latex.as_bytes() {
198 [b] if b.is_ascii_alphabetic() => Some(char::from(*b)),
199 _ => None,
200 },
201 _ => None,
202 };
203 let mut letters: Vec<char> = items.iter().rev().map_while(letter).collect();
204 letters.reverse();
205 letters.into_iter().collect()
206 }
207
208 pub fn in_text_slot(&self) -> bool {
210 self.tree.is_text_slot(self.cursor.seq)
211 }
212
213 pub fn menu(&self) -> Option<MenuView> {
215 self.menu.as_ref().map(Menu::view)
216 }
217
218 pub fn menu_previews(&self) -> Vec<Option<Document>> {
220 self.menu.as_ref().map(|m| m.previews(&self.tree)).unwrap_or_default()
221 }
222
223 pub fn matrix_shape(&self) -> Option<(usize, usize)> {
225 self.tree.matrix_shape_at(self.cursor)
226 }
227
228 pub fn input_context(&self) -> InputContext {
230 InputContext {
231 in_text_slot: self.in_text_slot(),
232 menu_open: self.menu.is_some(),
233 closing_delimiter: self.innermost_delim().and_then(|(_, close, body)| self.at_end_of(body).then_some(close)),
234 serial: self.serial,
235 }
236 }
237
238 pub fn set_host_box_policy(&mut self, policy: HostBoxPolicy) {
240 self.policy = policy;
241 }
242
243 pub fn set_empty_slot_caret(&mut self, place: EmptySlotCaret) {
245 self.empty_caret = place;
246 }
247
248 pub fn place_at(&mut self, edge: Edge) {
250 let root = self.tree.root();
251 let index = match edge {
252 Edge::Start => 0,
253 Edge::End => self.tree.len(root),
254 };
255 self.cursor = Cursor { seq: root, index };
256 self.anchor = None;
257 self.menu = None;
258 self.serial += 1;
259 }
260
261 pub fn source(&self) -> Source {
263 export::source(&self.tree, self.id, self.revision, true)
264 }
265
266 pub fn display_source(&self) -> Source {
268 export::source(&self.tree, self.id, self.revision, false)
269 }
270
271 fn check(&self, source: &Source) -> Result<(), StaleSource> {
272 if source.revision == self.revision && source.spans.owner == self.id {
273 Ok(())
274 } else {
275 Err(StaleSource { source: source.revision, editor: self.revision })
276 }
277 }
278
279 pub fn render(&self, source: &Source, fragment: &Fragment) -> Result<RenderOutput, StaleSource> {
281 self.check(source)?;
282 let menu = self.menu.as_ref().map(|m| m.anchor);
283 Ok(matcher::render(&self.tree, self.cursor, self.sel(), &source.spans, fragment, menu, self.empty_caret))
284 }
285
286 pub fn hit_test(&self, source: &Source, fragment: &Fragment, at: Point) -> Result<Option<CaretPath>, StaleSource> {
288 self.check(source)?;
289 let hit = matcher::hit_test(&self.tree, &source.spans, fragment, at);
290 Ok(hit.map(|c| self.tree.path_of(nav::normalize(&self.tree, c))))
291 }
292
293 pub fn snapshot(&self) -> Snapshot {
295 Snapshot { document: self.document(), cursor: self.cursor(), selection: self.selection() }
296 }
297
298 pub fn restore(&mut self, s: &Snapshot) -> Result<(), RestoreError> {
300 s.document.validate().map_err(RestoreError::Document)?;
301 let tree = Tree::from_doc(&s.document);
302 let (cursor, anchor) = match &s.selection {
303 Some(sel) => {
304 let a = tree.resolve(&sel.anchor)?;
305 let f = tree.resolve(&sel.focus)?;
306 if a.seq != f.seq {
307 return Err(PathError::SplitSelection.into());
308 }
309 (f, Some(a.index))
310 }
311 None => (tree.resolve(&s.cursor)?, None),
312 };
313 self.tree = tree;
314 self.cursor = cursor;
315 self.anchor = anchor;
316 self.menu = None;
317 self.revision += 1;
318 self.serial += 1;
319 self.normalize();
320 Ok(())
321 }
322
323 pub fn selection_document(&self) -> Option<Document> {
325 let s = self.sel()?;
326 let items = self.tree.items(s.seq);
327 let hi = s.hi().min(items.len());
328 Some(Document::new(items[s.lo()..hi].iter().filter_map(|&n| self.tree.node_to_doc(n)).collect()))
329 }
330
331 pub fn selection_tex(&self) -> Option<String> {
333 self.sel().map(|s| export::range_tex(&self.tree, s))
334 }
335
336 pub fn exec(&mut self, cmd: Command) -> Outcome {
338 let edits = self.tree.edits;
339 let before = self.position();
340 let mut out = Outcome::default();
341 self.serial += 1;
342 self.run(cmd, &mut out);
343 out.changed = self.tree.edits != edits;
344 if out.changed {
345 self.revision += 1;
346 }
347 out.moved = self.position() != before;
348 out.revision = self.revision;
349 out
350 }
351
352 fn position(&self) -> (CaretPath, Option<usize>) {
353 (self.cursor(), self.sel().map(|s| s.anchor))
354 }
355
356 fn sel(&self) -> Option<SeqRange> {
357 let a = self.anchor.filter(|&a| a != self.cursor.index)?;
358 Some(SeqRange { seq: self.cursor.seq, anchor: a, focus: self.cursor.index })
359 }
360
361 fn set(&mut self, c: Cursor) {
362 self.cursor = c;
363 self.anchor = None;
364 }
365
366 fn edit(&mut self, result: Option<Cursor>) {
368 if let Some(c) = result {
369 self.set(c);
370 }
371 }
372
373 fn run(&mut self, cmd: Command, out: &mut Outcome) {
374 if self.menu.is_some() {
375 if self.run_menu(&cmd) {
376 self.normalize();
377 return;
378 }
379 self.menu = None;
381 }
382 self.dispatch(cmd, out);
383 self.normalize();
384 }
385
386 fn dispatch(&mut self, cmd: Command, out: &mut Outcome) {
387 let at = self.cursor;
388 let sel = self.sel();
389 match cmd {
390 Command::Move(dir @ (Dir::Left | Dir::Right)) => {
391 let right = dir == Dir::Right;
392 if self.policy == HostBoxPolicy::Enter {
393 if let Some(token) = self.host_box_neighbor(right) {
394 let side = if right { Side::Before } else { Side::After };
395 out.entered_host_box = Some(HostBoxEntry { token, side });
396 return;
397 }
398 }
399 let next = if right { nav::move_right(&self.tree, at) } else { nav::move_left(&self.tree, at) };
400 match next {
401 Some(c) => self.set(c),
402 None => self.boundary(if right { ExitDir::Right } else { ExitDir::Left }, out),
403 }
404 }
405 Command::Move(dir) => {
406 let up = dir == Dir::Up;
407 match nav::vertical(&self.tree, at, up) {
408 Some(c) => self.set(c),
409 None => self.boundary(if up { ExitDir::Up } else { ExitDir::Down }, out),
410 }
411 }
412 Command::MoveLineStart => self.set(Cursor { seq: at.seq, index: 0 }),
413 Command::MoveLineEnd => self.set(Cursor { seq: at.seq, index: self.tree.len(at.seq) }),
414 Command::Tab => match self.next_fill_target(true) {
415 Some(c) => self.set(c),
416 None => out.exit = Some(ExitDir::Right),
417 },
418 Command::ShiftTab => match self.next_fill_target(false) {
419 Some(c) => self.set(c),
420 None => out.exit = Some(ExitDir::Left),
421 },
422 Command::MoveTo(path) => {
423 if let Ok(c) = self.tree.resolve(&path) {
424 self.set(c);
425 }
426 }
427 Command::ExtendTo(path) => {
428 if let Ok(target) = self.tree.resolve(&path) {
429 let anchor = Cursor { seq: at.seq, index: self.anchor.unwrap_or(at.index) };
430 self.select(selection::extend_to(&self.tree, anchor, target));
431 }
432 }
433 Command::Extend(dir @ (Dir::Left | Dir::Right)) => {
434 let s = sel.unwrap_or(SeqRange { seq: at.seq, anchor: at.index, focus: at.index });
435 self.select(selection::extend(&self.tree, s, dir == Dir::Right));
436 }
437 Command::Extend(_) => {}
438 Command::SelectAll => self.select(selection::select_all(&self.tree)),
439 Command::Collapse | Command::Confirm => self.anchor = None,
440 Command::MenuSelect(_) => {}
441 Command::InsertAtom(sym) => {
442 let r = self.tree.insert_atom(at, sel, sym);
443 self.edit(r);
444 }
445 Command::InsertHostBox(token) => {
446 let r = self.tree.insert_host_box(at, sel, token);
447 self.edit(r);
448 }
449 Command::InsertText(text) => self.insert_text(&text),
450 Command::InsertFraction(style) => {
451 let r = self.tree.insert_fraction(at, style, sel);
452 self.edit(r);
453 }
454 Command::InsertScript(slot) => {
455 if let (None, Some(c)) = (sel, self.tree.limit_target(at, slot)) {
457 self.set(c);
458 } else {
459 let r = self.tree.attach_script(at, slot, sel);
460 self.edit(r);
461 }
462 }
463 Command::InsertBigOp(op) => {
464 let r = self.tree.insert_big_op(at, sel, op);
465 self.edit(r);
466 }
467 Command::InsertSqrt => {
468 let r = self.tree.insert_sqrt(at, sel);
469 self.edit(r);
470 }
471 Command::InsertDelimiters { open, close } => {
472 let r = self.tree.insert_delimiters(at, open, close, sel);
473 self.edit(r);
474 }
475 Command::InsertAccent(mark) => {
476 let r = self.tree.insert_accent(at, mark, sel);
477 self.edit(r);
478 }
479 Command::InsertUnderOver(spec) => {
480 let r = self.tree.insert_under_over(at, spec, sel);
481 self.edit(r);
482 }
483 Command::InsertStyled(variant) => {
484 let r = self.tree.insert_styled(at, variant, sel);
485 self.edit(r);
486 }
487 Command::InsertMatrix { env, rows, cols } => {
488 let r = self.tree.insert_matrix(at, sel, env, rows, cols);
489 self.edit(r);
490 }
491 Command::InsertRatio => {
492 let r = self.tree.insert_ratio(at, sel);
493 self.edit(r);
494 }
495 Command::InsertEvalBar => {
496 let r = self.tree.insert_eval_bar(at, sel);
497 self.edit(r);
498 }
499 Command::InsertLabeledArrow(arrow) => {
500 let r = self.tree.insert_labeled_arrow(at, arrow, sel);
501 self.edit(r);
502 }
503 Command::InsertDocument(doc) => {
504 if doc.validate().is_err() {
505 return;
506 }
507 let r = self.tree.insert_doc(at, sel, &doc);
508 self.edit(r);
509 }
510 Command::DeleteBackward | Command::DeleteForward => {
511 let back = cmd == Command::DeleteBackward;
512 let neighbor = if back {
513 nav::adjacent_structure_backward(&self.tree, at)
514 } else {
515 nav::adjacent_structure_forward(&self.tree, at)
516 };
517 if let Some(s) = sel {
518 let c = self.tree.delete_range(s);
519 self.set(c);
520 } else if let Some(node) = neighbor {
521 self.select_or_open_menu(node);
522 } else if at.seq == self.tree.root() && self.tree.is_empty(at.seq) {
523 out.close = true;
524 } else {
525 let c = if back { self.tree.delete_backward(at) } else { self.tree.delete_forward(at) };
526 self.set(c);
527 }
528 }
529 Command::MatrixInsertRow(side) => {
530 let r = self.tree.matrix_insert_row(at, side);
531 self.edit(r);
532 }
533 Command::MatrixDeleteRow => {
534 let r = self.tree.matrix_delete_row(at);
535 self.edit(r);
536 }
537 Command::MatrixInsertCol(side) => {
538 let r = self.tree.matrix_insert_col(at, side);
539 self.edit(r);
540 }
541 Command::MatrixDeleteCol => {
542 let r = self.tree.matrix_delete_col(at);
543 self.edit(r);
544 }
545 Command::ReplaceTyped { typed, with } => {
546 if sel.is_none() && self.typed_matches(&typed) {
547 let n = typed.chars().count();
548 let c = self.tree.delete_range(SeqRange { seq: at.seq, anchor: at.index - n, focus: at.index });
549 self.set(c);
550 self.normalize();
551 for cmd in with {
552 self.run(cmd, out);
553 }
554 }
555 }
556 Command::CloseDelimiter(close) => {
557 if sel.is_none() {
558 if let Some(after) = self.closing_target(close) {
559 self.set(after);
560 return;
561 }
562 }
563 if let Some(sym) = Symbol::from_char(close) {
564 let r = self.tree.insert_atom(at, sel, sym);
565 self.edit(r);
566 }
567 }
568 }
569 }
570
571 fn run_menu(&mut self, cmd: &Command) -> bool {
573 let Some(menu) = self.menu.as_mut() else {
574 return false;
575 };
576 match cmd {
577 Command::InsertAtom(sym) if sym.latex.chars().count() == 1 => {
578 menu.query.push_str(&sym.latex);
579 menu.selected = 0;
580 }
581 Command::InsertText(text) => {
582 menu.query.push_str(text);
583 menu.selected = 0;
584 }
585 Command::DeleteBackward | Command::DeleteForward => {
587 if menu.query.pop().is_none() {
588 self.commit_menu_row(0);
589 } else {
590 menu.selected = 0;
591 }
592 }
593 Command::Move(Dir::Up) => menu.selected = menu.selected.saturating_sub(1),
594 Command::Move(Dir::Down) => {
595 let max = menu.visible().len().saturating_sub(1);
596 menu.selected = (menu.selected + 1).min(max);
597 }
598 Command::Confirm => {
599 let selected = menu.selected;
600 self.commit_menu_row(selected);
601 }
602 Command::MenuSelect(i) => self.commit_menu_row(*i),
603 Command::Collapse => self.menu = None,
604 _ => return false,
605 }
606 true
607 }
608
609 fn commit_menu_row(&mut self, visible_idx: usize) {
611 let Some(menu) = self.menu.take() else {
612 return;
613 };
614 match menu.visible().get(visible_idx) {
615 Some(RowEffect::Delete) => {
616 let Some((seq, idx)) = self.tree.index_in_parent(menu.anchor) else {
617 return;
618 };
619 let after = self.cursor == Cursor { seq, index: idx + 1 };
621 let c = match self.tree.dissolve(menu.anchor) {
622 Some(d) => {
623 let end = d.runs.last().map_or(idx, |(_, run)| run.end);
624 Cursor { seq: d.seq, index: if after { end } else { idx } }
625 }
626 None => self.tree.delete_range(SeqRange { seq, anchor: idx, focus: idx + 1 }),
627 };
628 self.set(c);
629 }
630 Some(RowEffect::Swap(kind)) => self.tree.apply_swap(menu.anchor, kind),
631 None => {}
632 }
633 }
634
635 fn select_or_open_menu(&mut self, node: NodeId) {
637 match self.tree.swap_variants(node) {
638 Some(variants) => self.menu = Some(Menu::for_node(node, variants)),
639 None => {
640 if let Some((seq, idx)) = self.tree.index_in_parent(node) {
641 self.select(SeqRange { seq, anchor: idx, focus: idx + 1 });
642 }
643 }
644 }
645 }
646
647 fn select(&mut self, s: SeqRange) {
648 self.cursor = Cursor { seq: s.seq, index: s.focus };
649 self.anchor = Some(s.anchor);
650 }
651
652 fn insert_text(&mut self, text: &str) {
653 let text_slot = self.tree.is_text_slot(self.cursor.seq);
654 let mut sel = self.sel();
655 let mut at = self.cursor;
656 let mut any = false;
657 for ch in text.chars() {
658 if ch == ' ' && !text_slot {
659 continue;
660 }
661 let Some(sym) = Symbol::from_char(ch) else { continue };
662 if let Some(c) = self.tree.insert_atom(at, sel.take(), sym) {
663 at = c;
664 any = true;
665 }
666 }
667 if any {
668 self.set(at);
669 }
670 }
671
672 fn boundary(&mut self, dir: ExitDir, out: &mut Outcome) {
674 if self.sel().is_some() {
675 self.anchor = None;
676 } else {
677 out.exit = Some(dir);
678 }
679 }
680
681 fn host_box_neighbor(&self, right: bool) -> Option<u32> {
683 let items = self.tree.items(self.cursor.seq);
684 let node = if right { items.get(self.cursor.index) } else { items.get(self.cursor.index.checked_sub(1)?) };
685 match self.tree.kind(*node?) {
686 Some(Kind::HostBox { token }) => Some(*token),
687 _ => None,
688 }
689 }
690
691 fn typed_matches(&self, typed: &str) -> bool {
693 let items = self.tree.items(self.cursor.seq);
694 let n = typed.chars().count();
695 let Some(start) = self.cursor.index.checked_sub(n) else {
696 return false;
697 };
698 typed.chars().zip(&items[start..self.cursor.index]).all(|(ch, &node)| {
699 matches!((self.tree.kind(node), Symbol::from_char(ch)), (Some(Kind::Atom(s)), Some(e)) if s.latex == e.latex)
700 })
701 }
702
703 fn innermost_delim(&self) -> Option<(NodeId, char, SeqId)> {
705 let mut seq = self.cursor.seq;
706 loop {
707 let node = self.tree.seq_parent(seq)?;
708 if let Some(Kind::Delim { close, body, .. }) = self.tree.kind(node) {
709 return Some((node, *close, *body));
710 }
711 seq = self.tree.before_parent(seq)?.seq;
712 }
713 }
714
715 fn at_end_of(&self, body: SeqId) -> bool {
717 let mut cur = self.cursor;
718 loop {
719 if cur.index != self.tree.len(cur.seq) {
720 return false;
721 }
722 if cur.seq == body {
723 return true;
724 }
725 let Some(node) = self.tree.seq_parent(cur.seq) else {
726 return false;
727 };
728 if self.tree.child_seqs(node).last() != Some(&cur.seq) {
729 return false;
730 }
731 let Some((seq, idx)) = self.tree.index_in_parent(node) else {
732 return false;
733 };
734 cur = Cursor { seq, index: idx + 1 };
735 }
736 }
737
738 fn closing_target(&self, close: char) -> Option<Cursor> {
740 let mut seq = self.cursor.seq;
741 loop {
742 let node = self.tree.seq_parent(seq)?;
743 if let Some(Kind::Delim { close: c, body, .. }) = self.tree.kind(node) {
744 if *c == close && self.at_end_of(*body) {
745 let (pseq, idx) = self.tree.index_in_parent(node)?;
746 return Some(Cursor { seq: pseq, index: idx + 1 });
747 }
748 }
749 seq = self.tree.before_parent(seq)?.seq;
750 }
751 }
752
753 fn next_fill_target(&self, forward: bool) -> Option<Cursor> {
754 let at = self.cursor;
755 let tree = &self.tree;
756 let empty_start = |s: SeqId| tree.is_empty(s).then_some(Cursor { seq: s, index: 0 });
757 if forward {
758 if let Some(Kind::BigOp { lower, upper, .. }) = tree.seq_parent(at.seq).and_then(|p| tree.kind(p)) {
760 if at.seq == *lower {
761 if let Some(c) = empty_start(*upper) {
762 return Some(c);
763 }
764 }
765 }
766 let prev = at.index.checked_sub(1).and_then(|i| tree.items(at.seq).get(i));
767 if let Some(Kind::BigOp { lower, upper, .. }) = prev.and_then(|&p| tree.kind(p)) {
768 if let Some(c) = empty_start(*lower).or_else(|| empty_start(*upper)) {
769 return Some(c);
770 }
771 }
772 if let Some(Kind::Sqrt { index, radicand }) = tree.seq_parent(at.seq).and_then(|p| tree.kind(p)) {
774 if at.seq == *radicand {
775 if let Some(c) = empty_start(*index) {
776 return Some(c);
777 }
778 }
779 }
780 }
781 nav::next_empty_slot(tree, at, forward).map(|seq| Cursor { seq, index: 0 })
782 }
783
784 fn normalize(&mut self) {
786 let root = self.tree.root();
787 if !self.tree.seqs.contains_key(self.cursor.seq) {
788 self.cursor = Cursor { seq: root, index: self.tree.len(root) };
789 self.anchor = None;
790 }
791 let len = self.tree.len(self.cursor.seq);
792 self.cursor.index = self.cursor.index.min(len);
793 self.anchor = self.anchor.map(|a| a.min(len));
794 loop {
795 match self.anchor {
796 Some(a) if a != self.cursor.index => {
797 if !nav::is_illegal(&self.tree, self.cursor) {
799 break;
800 }
801 let Some(p) = self.tree.before_parent(self.cursor.seq) else { break };
802 self.anchor = Some(p.index + 1);
803 self.cursor = p;
804 }
805 _ => {
806 self.anchor = None;
807 self.cursor = nav::normalize(&self.tree, self.cursor);
808 break;
809 }
810 }
811 }
812 if self.menu.as_ref().is_some_and(|m| !self.tree.nodes.contains_key(m.anchor)) {
813 self.menu = None;
814 }
815 }
816
817 #[cfg(test)]
818 pub(crate) fn tree(&self) -> &Tree {
819 &self.tree
820 }
821
822 #[cfg(test)]
823 pub(crate) fn raw_cursor(&self) -> Cursor {
824 self.cursor
825 }
826
827 #[cfg(test)]
828 pub(crate) fn raw_anchor(&self) -> Option<usize> {
829 self.anchor
830 }
831
832 #[cfg(test)]
833 pub(crate) fn menu_anchor(&self) -> Option<NodeId> {
834 self.menu.as_ref().map(|m| m.anchor)
835 }
836}