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::{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}
83
84impl Default for Editor {
85 fn default() -> Self {
86 Self::new()
87 }
88}
89
90impl Editor {
91 pub fn new() -> Self {
93 Self::with_tree(Tree::new())
94 }
95
96 pub fn from_document(doc: &Document) -> Result<Self, DocumentError> {
98 doc.validate()?;
99 Ok(Self::with_tree(Tree::from_doc(doc)))
100 }
101
102 fn with_tree(tree: Tree) -> Self {
103 let cursor = Cursor { seq: tree.root(), index: 0 };
104 Self {
105 tree,
106 cursor,
107 anchor: None,
108 menu: None,
109 revision: 0,
110 serial: 0,
111 id: NEXT_EDITOR.fetch_add(1, Ordering::Relaxed),
112 policy: HostBoxPolicy::Skip,
113 }
114 }
115
116 pub fn document(&self) -> Document {
118 self.tree.to_doc()
119 }
120
121 pub fn revision(&self) -> u64 {
123 self.revision
124 }
125
126 pub fn cursor(&self) -> CaretPath {
128 self.tree.path_of(self.cursor)
129 }
130
131 pub fn set_cursor(&mut self, at: &CaretPath) -> Result<(), PathError> {
133 let c = self.tree.resolve(at)?;
134 self.cursor = c;
135 self.anchor = None;
136 self.menu = None;
137 self.serial += 1;
138 self.normalize();
139 Ok(())
140 }
141
142 pub fn selection(&self) -> Option<Selection> {
144 let s = self.sel()?;
145 let steps = self.tree.seq_steps(s.seq);
146 Some(Selection {
147 anchor: CaretPath { steps: steps.clone(), index: s.anchor },
148 focus: CaretPath { steps, index: s.focus },
149 })
150 }
151
152 pub fn set_selection(&mut self, anchor: &CaretPath, focus: &CaretPath) -> Result<(), PathError> {
154 let a = self.tree.resolve(anchor)?;
155 let f = self.tree.resolve(focus)?;
156 if a.seq != f.seq {
157 return Err(PathError::SplitSelection);
158 }
159 self.cursor = f;
160 self.anchor = Some(a.index);
161 self.menu = None;
162 self.serial += 1;
163 self.normalize();
164 Ok(())
165 }
166
167 pub fn at_start(&self) -> bool {
169 self.cursor == Cursor { seq: self.tree.root(), index: 0 }
170 }
171
172 pub fn at_end(&self) -> bool {
174 let root = self.tree.root();
175 self.cursor == Cursor { seq: root, index: self.tree.len(root) }
176 }
177
178 pub fn at_slot_start(&self) -> bool {
180 self.cursor.index == 0
181 }
182
183 pub fn at_slot_end(&self) -> bool {
185 self.cursor.index == self.tree.len(self.cursor.seq)
186 }
187
188 pub fn in_text_slot(&self) -> bool {
190 self.tree.is_text_slot(self.cursor.seq)
191 }
192
193 pub fn menu(&self) -> Option<MenuView> {
195 self.menu.as_ref().map(Menu::view)
196 }
197
198 pub fn matrix_shape(&self) -> Option<(usize, usize)> {
200 self.tree.matrix_shape_at(self.cursor)
201 }
202
203 pub fn input_context(&self) -> InputContext {
205 InputContext {
206 in_text_slot: self.in_text_slot(),
207 menu_open: self.menu.is_some(),
208 closing_delimiter: self.innermost_delim().and_then(|(_, close, body)| self.at_end_of(body).then_some(close)),
209 serial: self.serial,
210 }
211 }
212
213 pub fn set_host_box_policy(&mut self, policy: HostBoxPolicy) {
215 self.policy = policy;
216 }
217
218 pub fn place_at(&mut self, edge: Edge) {
220 let root = self.tree.root();
221 let index = match edge {
222 Edge::Start => 0,
223 Edge::End => self.tree.len(root),
224 };
225 self.cursor = Cursor { seq: root, index };
226 self.anchor = None;
227 self.menu = None;
228 self.serial += 1;
229 }
230
231 pub fn source(&self) -> Source {
233 export::source(&self.tree, self.id, self.revision, true)
234 }
235
236 pub fn display_source(&self) -> Source {
238 export::source(&self.tree, self.id, self.revision, false)
239 }
240
241 fn check(&self, source: &Source) -> Result<(), StaleSource> {
242 if source.revision == self.revision && source.spans.owner == self.id {
243 Ok(())
244 } else {
245 Err(StaleSource { source: source.revision, editor: self.revision })
246 }
247 }
248
249 pub fn render(&self, source: &Source, fragment: &Fragment) -> Result<RenderOutput, StaleSource> {
251 self.check(source)?;
252 let menu = self.menu.as_ref().map(|m| m.anchor);
253 Ok(matcher::render(&self.tree, self.cursor, self.sel(), &source.spans, fragment, menu))
254 }
255
256 pub fn hit_test(&self, source: &Source, fragment: &Fragment, at: Point) -> Result<Option<CaretPath>, StaleSource> {
258 self.check(source)?;
259 let hit = matcher::hit_test(&self.tree, &source.spans, fragment, at);
260 Ok(hit.map(|c| self.tree.path_of(nav::normalize(&self.tree, c))))
261 }
262
263 pub fn snapshot(&self) -> Snapshot {
265 Snapshot { document: self.document(), cursor: self.cursor(), selection: self.selection() }
266 }
267
268 pub fn restore(&mut self, s: &Snapshot) -> Result<(), RestoreError> {
270 s.document.validate().map_err(RestoreError::Document)?;
271 let tree = Tree::from_doc(&s.document);
272 let (cursor, anchor) = match &s.selection {
273 Some(sel) => {
274 let a = tree.resolve(&sel.anchor)?;
275 let f = tree.resolve(&sel.focus)?;
276 if a.seq != f.seq {
277 return Err(PathError::SplitSelection.into());
278 }
279 (f, Some(a.index))
280 }
281 None => (tree.resolve(&s.cursor)?, None),
282 };
283 self.tree = tree;
284 self.cursor = cursor;
285 self.anchor = anchor;
286 self.menu = None;
287 self.revision += 1;
288 self.serial += 1;
289 self.normalize();
290 Ok(())
291 }
292
293 pub fn selection_document(&self) -> Option<Document> {
295 let s = self.sel()?;
296 let items = self.tree.items(s.seq);
297 let hi = s.hi().min(items.len());
298 Some(Document::new(items[s.lo()..hi].iter().filter_map(|&n| self.tree.node_to_doc(n)).collect()))
299 }
300
301 pub fn selection_tex(&self) -> Option<String> {
303 self.sel().map(|s| export::range_tex(&self.tree, s))
304 }
305
306 pub fn exec(&mut self, cmd: Command) -> Outcome {
308 let edits = self.tree.edits;
309 let before = self.position();
310 let mut out = Outcome::default();
311 self.serial += 1;
312 self.run(cmd, &mut out);
313 out.changed = self.tree.edits != edits;
314 if out.changed {
315 self.revision += 1;
316 }
317 out.moved = self.position() != before;
318 out.revision = self.revision;
319 out
320 }
321
322 fn position(&self) -> (CaretPath, Option<usize>) {
323 (self.cursor(), self.sel().map(|s| s.anchor))
324 }
325
326 fn sel(&self) -> Option<SeqRange> {
327 let a = self.anchor.filter(|&a| a != self.cursor.index)?;
328 Some(SeqRange { seq: self.cursor.seq, anchor: a, focus: self.cursor.index })
329 }
330
331 fn set(&mut self, c: Cursor) {
332 self.cursor = c;
333 self.anchor = None;
334 }
335
336 fn edit(&mut self, result: Option<Cursor>) {
338 if let Some(c) = result {
339 self.set(c);
340 }
341 }
342
343 fn run(&mut self, cmd: Command, out: &mut Outcome) {
344 if self.menu.is_some() {
345 if self.run_menu(&cmd) {
346 self.normalize();
347 return;
348 }
349 self.menu = None;
351 }
352 self.dispatch(cmd, out);
353 self.normalize();
354 }
355
356 fn dispatch(&mut self, cmd: Command, out: &mut Outcome) {
357 let at = self.cursor;
358 let sel = self.sel();
359 match cmd {
360 Command::Move(dir @ (Dir::Left | Dir::Right)) => {
361 let right = dir == Dir::Right;
362 if self.policy == HostBoxPolicy::Enter {
363 if let Some(token) = self.host_box_neighbor(right) {
364 let side = if right { Side::Before } else { Side::After };
365 out.entered_host_box = Some(HostBoxEntry { token, side });
366 return;
367 }
368 }
369 let next = if right { nav::move_right(&self.tree, at) } else { nav::move_left(&self.tree, at) };
370 match next {
371 Some(c) => self.set(c),
372 None => self.boundary(if right { ExitDir::Right } else { ExitDir::Left }, out),
373 }
374 }
375 Command::Move(dir) => {
376 let up = dir == Dir::Up;
377 match nav::vertical(&self.tree, at, up) {
378 Some(c) => self.set(c),
379 None => self.boundary(if up { ExitDir::Up } else { ExitDir::Down }, out),
380 }
381 }
382 Command::MoveLineStart => self.set(Cursor { seq: at.seq, index: 0 }),
383 Command::MoveLineEnd => self.set(Cursor { seq: at.seq, index: self.tree.len(at.seq) }),
384 Command::Tab => match self.next_fill_target(true) {
385 Some(c) => self.set(c),
386 None => out.exit = Some(ExitDir::Right),
387 },
388 Command::ShiftTab => match self.next_fill_target(false) {
389 Some(c) => self.set(c),
390 None => out.exit = Some(ExitDir::Left),
391 },
392 Command::MoveTo(path) => {
393 if let Ok(c) = self.tree.resolve(&path) {
394 self.set(c);
395 }
396 }
397 Command::ExtendTo(path) => {
398 if let Ok(target) = self.tree.resolve(&path) {
399 let anchor = Cursor { seq: at.seq, index: self.anchor.unwrap_or(at.index) };
400 self.select(selection::extend_to(&self.tree, anchor, target));
401 }
402 }
403 Command::Extend(dir @ (Dir::Left | Dir::Right)) => {
404 let s = sel.unwrap_or(SeqRange { seq: at.seq, anchor: at.index, focus: at.index });
405 self.select(selection::extend(&self.tree, s, dir == Dir::Right));
406 }
407 Command::Extend(_) => {}
408 Command::SelectAll => self.select(selection::select_all(&self.tree)),
409 Command::Collapse | Command::Confirm => self.anchor = None,
410 Command::MenuSelect(_) => {}
411 Command::InsertAtom(sym) => {
412 let r = self.tree.insert_atom(at, sel, sym);
413 self.edit(r);
414 }
415 Command::InsertHostBox(token) => {
416 let r = self.tree.insert_host_box(at, sel, token);
417 self.edit(r);
418 }
419 Command::InsertText(text) => self.insert_text(&text),
420 Command::InsertFraction(style) => {
421 let r = self.tree.insert_fraction(at, style, sel);
422 self.edit(r);
423 }
424 Command::InsertScript(slot) => {
425 if let (None, Some(c)) = (sel, self.tree.bigop_limit_target(at, slot)) {
427 self.set(c);
428 } else {
429 let r = self.tree.attach_script(at, slot, sel);
430 self.edit(r);
431 }
432 }
433 Command::InsertBigOp(op) => {
434 let r = self.tree.insert_big_op(at, sel, op);
435 self.edit(r);
436 }
437 Command::InsertSqrt => {
438 let r = self.tree.insert_sqrt(at, sel);
439 self.edit(r);
440 }
441 Command::InsertDelimiters { open, close } => {
442 let r = self.tree.insert_delimiters(at, open, close, sel);
443 self.edit(r);
444 }
445 Command::InsertAccent(mark) => {
446 let r = self.tree.insert_accent(at, mark, sel);
447 self.edit(r);
448 }
449 Command::InsertUnderOver(spec) => {
450 let r = self.tree.insert_under_over(at, spec, sel);
451 self.edit(r);
452 }
453 Command::InsertStyled(variant) => {
454 let r = self.tree.insert_styled(at, variant, sel);
455 self.edit(r);
456 }
457 Command::InsertMatrix { env, rows, cols } => {
458 let r = self.tree.insert_matrix(at, sel, env, rows, cols);
459 self.edit(r);
460 }
461 Command::InsertDocument(doc) => {
462 if doc.validate().is_err() {
463 return;
464 }
465 let r = self.tree.insert_doc(at, sel, &doc);
466 self.edit(r);
467 }
468 Command::DeleteBackward | Command::DeleteForward => {
469 let back = cmd == Command::DeleteBackward;
470 let neighbor = if back {
471 nav::adjacent_structure_backward(&self.tree, at)
472 } else {
473 nav::adjacent_structure_forward(&self.tree, at)
474 };
475 if let Some(s) = sel {
476 let c = self.tree.delete_range(s);
477 self.set(c);
478 } else if let Some(node) = neighbor {
479 self.select_or_open_menu(node);
480 } else if at.seq == self.tree.root() && self.tree.is_empty(at.seq) {
481 out.close = true;
482 } else {
483 let c = if back { self.tree.delete_backward(at) } else { self.tree.delete_forward(at) };
484 self.set(c);
485 }
486 }
487 Command::MatrixInsertRow(side) => {
488 let r = self.tree.matrix_insert_row(at, side);
489 self.edit(r);
490 }
491 Command::MatrixDeleteRow => {
492 let r = self.tree.matrix_delete_row(at);
493 self.edit(r);
494 }
495 Command::MatrixInsertCol(side) => {
496 let r = self.tree.matrix_insert_col(at, side);
497 self.edit(r);
498 }
499 Command::MatrixDeleteCol => {
500 let r = self.tree.matrix_delete_col(at);
501 self.edit(r);
502 }
503 Command::ReplaceTyped { typed, with } => {
504 if sel.is_none() && self.typed_matches(&typed) {
505 let n = typed.chars().count();
506 let c = self.tree.delete_range(SeqRange { seq: at.seq, anchor: at.index - n, focus: at.index });
507 self.set(c);
508 self.normalize();
509 for cmd in with {
510 self.run(cmd, out);
511 }
512 }
513 }
514 Command::CloseDelimiter(close) => {
515 if sel.is_none() {
516 if let Some(after) = self.closing_target(close) {
517 self.set(after);
518 return;
519 }
520 }
521 if let Some(sym) = Symbol::from_char(close) {
522 let r = self.tree.insert_atom(at, sel, sym);
523 self.edit(r);
524 }
525 }
526 }
527 }
528
529 fn run_menu(&mut self, cmd: &Command) -> bool {
531 let Some(menu) = self.menu.as_mut() else {
532 return false;
533 };
534 match cmd {
535 Command::InsertAtom(sym) if sym.latex.chars().count() == 1 => {
536 menu.query.push_str(&sym.latex);
537 menu.selected = 0;
538 }
539 Command::InsertText(text) => {
540 menu.query.push_str(text);
541 menu.selected = 0;
542 }
543 Command::DeleteBackward | Command::DeleteForward => {
545 if menu.query.pop().is_none() {
546 self.commit_menu_row(0);
547 } else {
548 menu.selected = 0;
549 }
550 }
551 Command::Move(Dir::Up) => menu.selected = menu.selected.saturating_sub(1),
552 Command::Move(Dir::Down) => {
553 let max = menu.visible().len().saturating_sub(1);
554 menu.selected = (menu.selected + 1).min(max);
555 }
556 Command::Confirm => {
557 let selected = menu.selected;
558 self.commit_menu_row(selected);
559 }
560 Command::MenuSelect(i) => self.commit_menu_row(*i),
561 Command::Collapse => self.menu = None,
562 _ => return false,
563 }
564 true
565 }
566
567 fn commit_menu_row(&mut self, visible_idx: usize) {
569 let Some(menu) = self.menu.take() else {
570 return;
571 };
572 match menu.visible().get(visible_idx) {
573 Some(RowEffect::Delete) => {
574 if let Some((seq, idx)) = self.tree.index_in_parent(menu.anchor) {
575 let c = self.tree.delete_range(SeqRange { seq, anchor: idx, focus: idx + 1 });
576 self.set(c);
577 }
578 }
579 Some(RowEffect::Swap(kind)) => self.tree.apply_swap(menu.anchor, kind),
580 None => {}
581 }
582 }
583
584 fn select_or_open_menu(&mut self, node: NodeId) {
586 match self.tree.swap_variants(node) {
587 Some(variants) => self.menu = Some(Menu::for_node(node, variants)),
588 None => {
589 if let Some((seq, idx)) = self.tree.index_in_parent(node) {
590 self.select(SeqRange { seq, anchor: idx, focus: idx + 1 });
591 }
592 }
593 }
594 }
595
596 fn select(&mut self, s: SeqRange) {
597 self.cursor = Cursor { seq: s.seq, index: s.focus };
598 self.anchor = Some(s.anchor);
599 }
600
601 fn insert_text(&mut self, text: &str) {
602 let text_slot = self.tree.is_text_slot(self.cursor.seq);
603 let mut sel = self.sel();
604 let mut at = self.cursor;
605 let mut any = false;
606 for ch in text.chars() {
607 if ch == ' ' && !text_slot {
608 continue;
609 }
610 let Some(sym) = Symbol::from_char(ch) else { continue };
611 if let Some(c) = self.tree.insert_atom(at, sel.take(), sym) {
612 at = c;
613 any = true;
614 }
615 }
616 if any {
617 self.set(at);
618 }
619 }
620
621 fn boundary(&mut self, dir: ExitDir, out: &mut Outcome) {
623 if self.sel().is_some() {
624 self.anchor = None;
625 } else {
626 out.exit = Some(dir);
627 }
628 }
629
630 fn host_box_neighbor(&self, right: bool) -> Option<u32> {
632 let items = self.tree.items(self.cursor.seq);
633 let node = if right { items.get(self.cursor.index) } else { items.get(self.cursor.index.checked_sub(1)?) };
634 match self.tree.kind(*node?) {
635 Some(Kind::HostBox { token }) => Some(*token),
636 _ => None,
637 }
638 }
639
640 fn typed_matches(&self, typed: &str) -> bool {
642 let items = self.tree.items(self.cursor.seq);
643 let n = typed.chars().count();
644 let Some(start) = self.cursor.index.checked_sub(n) else {
645 return false;
646 };
647 typed.chars().zip(&items[start..self.cursor.index]).all(|(ch, &node)| {
648 matches!((self.tree.kind(node), Symbol::from_char(ch)), (Some(Kind::Atom(s)), Some(e)) if s.latex == e.latex)
649 })
650 }
651
652 fn innermost_delim(&self) -> Option<(NodeId, char, SeqId)> {
654 let mut seq = self.cursor.seq;
655 loop {
656 let node = self.tree.seq_parent(seq)?;
657 if let Some(Kind::Delim { close, body, .. }) = self.tree.kind(node) {
658 return Some((node, *close, *body));
659 }
660 seq = self.tree.before_parent(seq)?.seq;
661 }
662 }
663
664 fn at_end_of(&self, body: SeqId) -> bool {
666 let mut cur = self.cursor;
667 loop {
668 if cur.index != self.tree.len(cur.seq) {
669 return false;
670 }
671 if cur.seq == body {
672 return true;
673 }
674 let Some(node) = self.tree.seq_parent(cur.seq) else {
675 return false;
676 };
677 if self.tree.child_seqs(node).last() != Some(&cur.seq) {
678 return false;
679 }
680 let Some((seq, idx)) = self.tree.index_in_parent(node) else {
681 return false;
682 };
683 cur = Cursor { seq, index: idx + 1 };
684 }
685 }
686
687 fn closing_target(&self, close: char) -> Option<Cursor> {
689 let mut seq = self.cursor.seq;
690 loop {
691 let node = self.tree.seq_parent(seq)?;
692 if let Some(Kind::Delim { close: c, body, .. }) = self.tree.kind(node) {
693 if *c == close && self.at_end_of(*body) {
694 let (pseq, idx) = self.tree.index_in_parent(node)?;
695 return Some(Cursor { seq: pseq, index: idx + 1 });
696 }
697 }
698 seq = self.tree.before_parent(seq)?.seq;
699 }
700 }
701
702 fn next_fill_target(&self, forward: bool) -> Option<Cursor> {
703 let at = self.cursor;
704 let tree = &self.tree;
705 let empty_start = |s: SeqId| tree.is_empty(s).then_some(Cursor { seq: s, index: 0 });
706 if forward {
707 if let Some(Kind::BigOp { lower, upper, .. }) = tree.seq_parent(at.seq).and_then(|p| tree.kind(p)) {
709 if at.seq == *lower {
710 if let Some(c) = empty_start(*upper) {
711 return Some(c);
712 }
713 }
714 }
715 let prev = at.index.checked_sub(1).and_then(|i| tree.items(at.seq).get(i));
716 if let Some(Kind::BigOp { lower, upper, .. }) = prev.and_then(|&p| tree.kind(p)) {
717 if let Some(c) = empty_start(*lower).or_else(|| empty_start(*upper)) {
718 return Some(c);
719 }
720 }
721 if let Some(Kind::Sqrt { index, radicand }) = tree.seq_parent(at.seq).and_then(|p| tree.kind(p)) {
723 if at.seq == *radicand {
724 if let Some(c) = empty_start(*index) {
725 return Some(c);
726 }
727 }
728 }
729 }
730 nav::next_empty_slot(tree, at, forward).map(|seq| Cursor { seq, index: 0 })
731 }
732
733 fn normalize(&mut self) {
735 let root = self.tree.root();
736 if !self.tree.seqs.contains_key(self.cursor.seq) {
737 self.cursor = Cursor { seq: root, index: self.tree.len(root) };
738 self.anchor = None;
739 }
740 let len = self.tree.len(self.cursor.seq);
741 self.cursor.index = self.cursor.index.min(len);
742 self.anchor = self.anchor.map(|a| a.min(len));
743 loop {
744 match self.anchor {
745 Some(a) if a != self.cursor.index => {
746 if !nav::is_illegal(&self.tree, self.cursor) {
748 break;
749 }
750 let Some(p) = self.tree.before_parent(self.cursor.seq) else { break };
751 self.anchor = Some(p.index + 1);
752 self.cursor = p;
753 }
754 _ => {
755 self.anchor = None;
756 self.cursor = nav::normalize(&self.tree, self.cursor);
757 break;
758 }
759 }
760 }
761 if self.menu.as_ref().is_some_and(|m| !self.tree.nodes.contains_key(m.anchor)) {
762 self.menu = None;
763 }
764 }
765
766 #[cfg(test)]
767 pub(crate) fn tree(&self) -> &Tree {
768 &self.tree
769 }
770
771 #[cfg(test)]
772 pub(crate) fn raw_cursor(&self) -> Cursor {
773 self.cursor
774 }
775
776 #[cfg(test)]
777 pub(crate) fn raw_anchor(&self) -> Option<usize> {
778 self.anchor
779 }
780
781 #[cfg(test)]
782 pub(crate) fn menu_anchor(&self) -> Option<NodeId> {
783 self.menu.as_ref().map(|m| m.anchor)
784 }
785}