1use std::ops::Range;
4
5use crate::command::Side;
6use crate::model::{
7 Cursor, Deco, FracStyle, Kind, Mark, MatrixEnv, Node, NodeId, ScriptSlot, Selection, Seq,
8 SeqId, Symbol, Tree, UnderOverSpec, Variant,
9};
10use crate::nav;
11
12pub(crate) enum NewNode {
14 Atom(Symbol),
15 HostBox(u32),
16 Frac(FracStyle),
17 Script(ScriptSlot),
18 BigOp(Symbol),
19 Sqrt,
20 Delim { open: char, close: char },
21 Accent(Mark),
22 UnderOver(UnderOverSpec),
23 Styled(Variant),
24 Matrix { env: MatrixEnv, rows: usize, cols: usize },
25}
26
27impl Tree {
28 pub(crate) fn alloc_seq(&mut self, parent: Option<NodeId>) -> SeqId {
29 self.seqs.insert(Seq {
30 parent,
31 items: Vec::new(),
32 })
33 }
34
35 fn build_kind(&mut self, spec: NewNode) -> Kind {
37 match spec {
38 NewNode::Atom(sym) => Kind::Atom(sym),
39 NewNode::HostBox(token) => Kind::HostBox { token },
40 NewNode::Frac(style) => Kind::Frac {
41 num: self.alloc_seq(None),
42 den: self.alloc_seq(None),
43 style,
44 },
45 NewNode::Script(slot) => {
46 let base = self.alloc_seq(None);
47 let (sub, sup) = match slot {
48 ScriptSlot::Sub => (Some(self.alloc_seq(None)), None),
49 ScriptSlot::Sup => (None, Some(self.alloc_seq(None))),
50 };
51 Kind::Script { base, sub, sup }
52 }
53 NewNode::BigOp(op) => Kind::BigOp {
54 op,
55 lower: self.alloc_seq(None),
56 upper: self.alloc_seq(None),
57 },
58 NewNode::Sqrt => Kind::Sqrt {
59 index: self.alloc_seq(None),
61 radicand: self.alloc_seq(None),
62 },
63 NewNode::Delim { open, close } => Kind::Delim {
64 open,
65 close,
66 body: self.alloc_seq(None),
67 },
68 NewNode::Accent(mark) => Kind::Accent {
69 mark,
70 base: self.alloc_seq(None),
71 },
72 NewNode::UnderOver(spec) => Kind::UnderOver {
73 base: self.alloc_seq(None),
74 over: spec.over.then(|| self.alloc_seq(None)),
75 under: spec.under.then(|| self.alloc_seq(None)),
76 over_deco: spec.over_deco,
77 under_deco: spec.under_deco,
78 },
79 NewNode::Styled(variant) => Kind::Styled {
80 variant,
81 content: self.alloc_seq(None),
82 },
83 NewNode::Matrix { env, rows, cols } => {
84 let grid = (0..rows)
85 .map(|_| (0..cols).map(|_| self.alloc_seq(None)).collect())
86 .collect();
87 Kind::Matrix { env, rows: grid }
88 }
89 }
90 }
91
92 pub(crate) fn insert_new(&mut self, seq: SeqId, index: usize, spec: NewNode) -> NodeId {
94 let kind = self.build_kind(spec);
95 let node_id = self.nodes.insert(Node { parent: seq, kind });
96 for s in self.child_seqs(node_id) {
97 if let Some(sq) = self.seqs.get_mut(s) {
98 sq.parent = Some(node_id);
99 }
100 }
101 if let Some(sq) = self.seqs.get_mut(seq) {
102 let i = index.min(sq.items.len());
103 sq.items.insert(i, node_id);
104 }
105 node_id
106 }
107
108 pub(crate) fn detach(&mut self, node: NodeId) -> NodeId {
110 if let Some((parent, idx)) = self.index_in_parent(node) {
111 if let Some(sq) = self.seqs.get_mut(parent) {
112 sq.items.remove(idx);
113 }
114 }
115 node
116 }
117
118 pub(crate) fn drop_node(&mut self, node: NodeId) {
120 self.detach(node);
121 self.free_node(node);
122 }
123
124 fn free_node(&mut self, node: NodeId) {
125 for s in self.child_seqs(node) {
126 self.free_seq(s);
127 }
128 self.nodes.remove(node);
129 }
130
131 fn free_seq(&mut self, seq: SeqId) {
132 let items: Vec<NodeId> = self
133 .seqs
134 .get(seq)
135 .map(|s| s.items.clone())
136 .unwrap_or_default();
137 for n in items {
138 self.free_node(n);
139 }
140 self.seqs.remove(seq);
141 }
142
143 pub(crate) fn move_range(&mut self, src: SeqId, range: Range<usize>, dst: SeqId, at: usize) {
145 let moved: Vec<NodeId> = self
146 .seqs
147 .get_mut(src)
148 .map(|s| s.items.drain(range).collect())
149 .unwrap_or_default();
150 for &n in &moved {
151 if let Some(nd) = self.nodes.get_mut(n) {
152 nd.parent = dst;
153 }
154 }
155 if let Some(d) = self.seqs.get_mut(dst) {
156 let at = at.min(d.items.len());
157 d.items.splice(at..at, moved);
158 }
159 }
160}
161
162impl Tree {
163 fn make_room_in_script_base(&mut self, at: Cursor) -> Cursor {
165 if !self.is_empty(at.seq) {
166 if let Some(script) = self.script_base_node(at.seq) {
167 if let Some((pseq, pidx)) = self.index_in_parent(script) {
168 let n = self.len(at.seq);
169 self.move_range(at.seq, 0..n, pseq, pidx);
170 return Cursor {
171 seq: at.seq,
172 index: 0,
173 };
174 }
175 }
176 }
177 at
178 }
179
180 pub fn insert_atom(&mut self, at: Cursor, sym: Symbol) -> Cursor {
182 let at = self.make_room_in_script_base(at);
183 self.insert_new(at.seq, at.index, NewNode::Atom(sym));
184 Cursor {
185 seq: at.seq,
186 index: at.index + 1,
187 }
188 }
189
190 pub fn insert_host_box(&mut self, at: Cursor, token: u32) -> Cursor {
192 let at = self.make_room_in_script_base(at);
193 self.insert_new(at.seq, at.index, NewNode::HostBox(token));
194 Cursor {
195 seq: at.seq,
196 index: at.index + 1,
197 }
198 }
199
200 fn insert_struct(
202 &mut self,
203 at: Cursor,
204 sel: Option<Selection>,
205 spec: NewNode,
206 wrap_slot: usize,
207 ) -> (NodeId, bool) {
208 match sel {
209 None => {
210 let at = self.make_room_in_script_base(at);
211 (self.insert_new(at.seq, at.index, spec), false)
212 }
213 Some(s) => {
214 let lo = s.anchor.min(s.focus);
215 let hi = s.anchor.max(s.focus);
216 let node = self.insert_new(s.seq, lo, spec);
217 let slot = self.child_seqs(node)[wrap_slot];
218 self.move_range(s.seq, lo + 1..hi + 1, slot, 0);
219 (node, true)
220 }
221 }
222 }
223
224 pub fn insert_fraction(&mut self, at: Cursor, style: FracStyle, sel: Option<Selection>) -> Cursor {
226 let (node, wrapped) = self.insert_struct(at, sel, NewNode::Frac(style), 0);
227 let slots = self.child_seqs(node);
228 let (num, den) = (slots[0], slots[1]);
229 if wrapped {
230 Cursor { seq: den, index: 0 }
231 } else {
232 Cursor { seq: num, index: 0 }
233 }
234 }
235
236 pub fn insert_sqrt(&mut self, at: Cursor, sel: Option<Selection>) -> Cursor {
238 let (node, _wrapped) = self.insert_struct(at, sel, NewNode::Sqrt, 1);
240 let radicand = self.child_seqs(node)[1];
241 Cursor {
242 seq: radicand,
243 index: 0,
244 }
245 }
246
247 pub fn insert_delimiters(
249 &mut self,
250 at: Cursor,
251 open: char,
252 close: char,
253 sel: Option<Selection>,
254 ) -> Cursor {
255 let (node, wrapped) = self.insert_struct(at, sel, NewNode::Delim { open, close }, 0);
256 let body = self.child_seqs(node)[0];
257 Cursor {
258 seq: body,
259 index: if wrapped { self.len(body) } else { 0 },
260 }
261 }
262
263 pub fn insert_accent(&mut self, at: Cursor, mark: Mark, sel: Option<Selection>) -> Cursor {
265 let (node, wrapped) = self.insert_struct(at, sel, NewNode::Accent(mark), 0);
266 let base = self.child_seqs(node)[0];
267 Cursor {
268 seq: base,
269 index: if wrapped { self.len(base) } else { 0 },
270 }
271 }
272
273 pub fn insert_styled(&mut self, at: Cursor, variant: Variant, sel: Option<Selection>) -> Cursor {
275 let (node, wrapped) = self.insert_struct(at, sel, NewNode::Styled(variant), 0);
276 let content = self.child_seqs(node)[0];
277 Cursor {
278 seq: content,
279 index: if wrapped { self.len(content) } else { 0 },
280 }
281 }
282
283 pub fn insert_under_over(&mut self, at: Cursor, spec: UnderOverSpec, sel: Option<Selection>) -> Cursor {
285 let wrap_slot = if spec.over { 1 } else { 0 };
287 let (node, _wrapped) = self.insert_struct(at, sel, NewNode::UnderOver(spec), wrap_slot);
288 let base = self.child_seqs(node)[wrap_slot];
289 Cursor { seq: base, index: 0 }
290 }
291
292 pub fn insert_matrix(&mut self, at: Cursor, env: MatrixEnv, rows: usize, cols: usize) -> Cursor {
294 let rows = rows.max(1);
295 let cols = cols.max(1);
296 let node = self.insert_new(at.seq, at.index, NewNode::Matrix { env, rows, cols });
297 let first = self.child_seqs(node)[0];
298 Cursor {
299 seq: first,
300 index: 0,
301 }
302 }
303
304 pub fn insert_big_op(&mut self, at: Cursor, op: Symbol) -> Cursor {
306 self.insert_new(at.seq, at.index, NewNode::BigOp(op));
307 Cursor {
308 seq: at.seq,
309 index: at.index + 1,
310 }
311 }
312
313 pub fn bigop_limit_target(&self, at: Cursor, which: ScriptSlot) -> Option<Cursor> {
315 if at.index == 0 {
316 return None;
317 }
318 let prev = self.items(at.seq)[at.index - 1];
319 if let Some(Kind::BigOp { lower, upper, .. }) = self.kind(prev) {
320 let target = match which {
321 ScriptSlot::Sub => *lower,
322 ScriptSlot::Sup => *upper,
323 };
324 return Some(Cursor {
325 seq: target,
326 index: self.len(target),
327 });
328 }
329 None
330 }
331
332 pub fn attach_script(&mut self, at: Cursor, which: ScriptSlot) -> Cursor {
334 if at.index > 0 {
335 let prev = self.items(at.seq)[at.index - 1];
336 if matches!(self.kind(prev), Some(Kind::Script { .. })) {
337 let slot = self.script_slot(prev, which);
338 return Cursor {
339 seq: slot,
340 index: self.len(slot),
341 };
342 }
343 let script = self.insert_new(at.seq, at.index, NewNode::Script(which));
345 let base = match self.kind(script) {
346 Some(Kind::Script { base, .. }) => *base,
347 _ => unreachable!(),
348 };
349 self.move_range(at.seq, at.index - 1..at.index, base, 0);
350 let slot = self.script_slot(script, which);
351 return Cursor { seq: slot, index: 0 };
352 }
353 let script = self.insert_new(at.seq, at.index, NewNode::Script(which));
355 let slot = self.script_slot(script, which);
356 Cursor { seq: slot, index: 0 }
357 }
358
359 fn script_slot(&mut self, node: NodeId, which: ScriptSlot) -> SeqId {
361 let existing = match self.kind(node) {
362 Some(Kind::Script { sub, sup, .. }) => match which {
363 ScriptSlot::Sub => *sub,
364 ScriptSlot::Sup => *sup,
365 },
366 _ => None,
367 };
368 if let Some(s) = existing {
369 return s;
370 }
371 let s = self.alloc_seq(Some(node));
372 if let Some(Node {
373 kind: Kind::Script { sub, sup, .. },
374 ..
375 }) = self.nodes.get_mut(node)
376 {
377 match which {
378 ScriptSlot::Sub => *sub = Some(s),
379 ScriptSlot::Sup => *sup = Some(s),
380 }
381 }
382 s
383 }
384
385 pub fn delete_backward(&mut self, at: Cursor) -> Cursor {
387 if at.index > 0 {
388 let prev = self.items(at.seq)[at.index - 1];
389 if self.child_seqs(prev).is_empty() {
390 self.drop_node(prev);
391 return Cursor {
392 seq: at.seq,
393 index: at.index - 1,
394 };
395 }
396 return nav::move_left(self, at).unwrap_or(at);
397 }
398 if self.is_empty(at.seq) {
399 if let Some(parent) = self.seq_parent(at.seq) {
400 return self.escalate_delete(parent, at.seq, at);
401 }
402 return at;
403 }
404 nav::move_left(self, at).unwrap_or(at)
405 }
406
407 pub fn delete_forward(&mut self, at: Cursor) -> Cursor {
409 if at.index < self.len(at.seq) {
410 let next = self.items(at.seq)[at.index];
411 if self.child_seqs(next).is_empty() {
412 self.drop_node(next);
413 return Cursor {
414 seq: at.seq,
415 index: at.index,
416 };
417 }
418 return nav::move_right(self, at).unwrap_or(at);
419 }
420 if self.is_empty(at.seq) {
421 if let Some(parent) = self.seq_parent(at.seq) {
422 return self.escalate_delete(parent, at.seq, at);
423 }
424 return at;
425 }
426 nav::move_right(self, at).unwrap_or(at)
427 }
428
429 pub fn delete_selection(&mut self, sel: Selection) -> Cursor {
431 let lo = sel.anchor.min(sel.focus);
432 let hi = sel.anchor.max(sel.focus);
433 let victims: Vec<NodeId> = self.items(sel.seq)[lo..hi.min(self.len(sel.seq))].to_vec();
434 for n in victims {
435 self.drop_node(n);
436 }
437 Cursor {
438 seq: sel.seq,
439 index: lo,
440 }
441 }
442
443 fn drop_and_step_out(&mut self, parent: NodeId, at_parent: Cursor) -> Cursor {
445 self.drop_node(parent);
446 at_parent
447 }
448
449 fn promote_and_drop(&mut self, parent: NodeId, from: SeqId, into: SeqId, at: usize) -> Cursor {
451 let n = self.len(from);
452 self.move_range(from, 0..n, into, at);
453 self.drop_node(parent);
454 Cursor { seq: into, index: at + n }
455 }
456
457 fn delete_empty_script_base(
459 &mut self,
460 parent: NodeId,
461 base: SeqId,
462 sub: Option<SeqId>,
463 sup: Option<SeqId>,
464 pseq: SeqId,
465 pidx: usize,
466 at_parent: Cursor,
467 ) -> Cursor {
468 if pidx > 0 {
469 self.move_range(pseq, pidx - 1..pidx, base, 0);
471 return Cursor { seq: base, index: self.len(base) };
472 }
473 let mut offset = 0;
474 for s in [Some(base), sub, sup].into_iter().flatten() {
475 let n = self.len(s);
476 if n > 0 {
477 self.move_range(s, 0..n, pseq, pidx + offset);
478 offset += n;
479 }
480 }
481 self.drop_and_step_out(parent, at_parent)
482 }
483
484 fn collapse_to_base_or_stay(
486 &mut self,
487 parent: NodeId,
488 base: SeqId,
489 opt_a: Option<SeqId>,
490 opt_b: Option<SeqId>,
491 pseq: SeqId,
492 pidx: usize,
493 ) -> Cursor {
494 if opt_a.is_none() && opt_b.is_none() {
495 self.promote_and_drop(parent, base, pseq, pidx)
496 } else {
497 Cursor { seq: base, index: self.len(base) }
498 }
499 }
500
501 fn escalate_delete(&mut self, parent: NodeId, slot: SeqId, fallback: Cursor) -> Cursor {
503 let Some((pseq, pidx)) = self.index_in_parent(parent) else {
504 return fallback;
505 };
506 let Some(kind) = self.nodes.get(parent).map(|n| n.kind.clone()) else {
507 return fallback;
508 };
509 let at_parent = Cursor {
510 seq: pseq,
511 index: pidx,
512 };
513 match kind {
514 Kind::Frac { num, den, .. } => {
516 let other = if slot == num { den } else { num };
517 if self.is_empty(other) {
518 self.drop_and_step_out(parent, at_parent)
519 } else {
520 self.promote_and_drop(parent, other, pseq, pidx)
521 }
522 }
523
524 Kind::Sqrt { radicand, .. } => {
526 if slot == radicand {
527 self.drop_and_step_out(parent, at_parent)
528 } else {
529 at_parent
530 }
531 }
532
533 Kind::Delim { .. } | Kind::Accent { .. } | Kind::Styled { .. } => {
535 self.drop_and_step_out(parent, at_parent)
536 }
537
538 Kind::Script { base, sub, sup } => {
539 if slot == base {
540 self.delete_empty_script_base(parent, base, sub, sup, pseq, pidx, at_parent)
541 } else {
542 if let Some(Node {
544 kind: Kind::Script { sub, sup, .. },
545 ..
546 }) = self.nodes.get_mut(parent)
547 {
548 if *sub == Some(slot) {
549 *sub = None;
550 } else if *sup == Some(slot) {
551 *sup = None;
552 }
553 }
554 self.free_seq(slot);
555 let (rsub, rsup) = match self.kind(parent) {
556 Some(Kind::Script { sub, sup, .. }) => (*sub, *sup),
557 _ => (None, None),
558 };
559 self.collapse_to_base_or_stay(parent, base, rsub, rsup, pseq, pidx)
560 }
561 }
562
563 Kind::BigOp { lower, upper, .. } => {
565 let other = if slot == lower { upper } else { lower };
566 if self.is_empty(other) {
567 self.drop_and_step_out(parent, at_parent)
568 } else {
569 Cursor {
570 seq: other,
571 index: self.len(other),
572 }
573 }
574 }
575
576 Kind::UnderOver {
577 base, over, under, ..
578 } => {
579 if slot == base {
580 self.drop_and_step_out(parent, at_parent)
581 } else {
582 if let Some(Node {
584 kind: Kind::UnderOver { over, under, .. },
585 ..
586 }) = self.nodes.get_mut(parent)
587 {
588 if *over == Some(slot) {
589 *over = None;
590 } else if *under == Some(slot) {
591 *under = None;
592 }
593 }
594 self.free_seq(slot);
595 let (rover, runder) = match self.kind(parent) {
596 Some(Kind::UnderOver { over, under, .. }) => (*over, *under),
597 _ => (None, None),
598 };
599 self.collapse_to_base_or_stay(parent, base, rover, runder, pseq, pidx)
600 }
601 }
602
603 Kind::Matrix { rows, .. } => {
605 let all_empty = rows.iter().flatten().all(|&c| self.is_empty(c));
606 if all_empty {
607 self.drop_and_step_out(parent, at_parent)
608 } else {
609 nav::move_left(self, Cursor { seq: slot, index: 0 }).unwrap_or(fallback)
610 }
611 }
612
613 Kind::Atom(_) | Kind::HostBox { .. } => fallback,
614 }
615 }
616
617 fn matrix_of(&self, at: Cursor) -> Option<(NodeId, usize, usize)> {
619 let m = self.seq_parent(at.seq)?;
620 if let Some(Kind::Matrix { rows, .. }) = self.kind(m) {
621 for (r, row) in rows.iter().enumerate() {
622 if let Some(c) = row.iter().position(|&s| s == at.seq) {
623 return Some((m, r, c));
624 }
625 }
626 }
627 None
628 }
629
630 fn matrix_cols(&self, m: NodeId) -> usize {
631 match self.kind(m) {
632 Some(Kind::Matrix { rows, .. }) => rows.first().map_or(0, |r| r.len()),
633 _ => 0,
634 }
635 }
636
637 pub fn matrix_shape_at(&self, at: Cursor) -> Option<(usize, usize)> {
639 let (m, _, _) = self.matrix_of(at)?;
640 let Some(Kind::Matrix { rows, .. }) = self.kind(m) else {
641 return None;
642 };
643 Some((rows.len(), self.matrix_cols(m)))
644 }
645
646 pub fn matrix_insert_row(&mut self, at: Cursor, side: Side) -> Cursor {
648 let Some((m, r, _c)) = self.matrix_of(at) else {
649 return at;
650 };
651 let cols = self.matrix_cols(m);
652 let new_row: Vec<SeqId> = (0..cols).map(|_| self.alloc_seq(Some(m))).collect();
653 let first = new_row.first().copied();
654 let idx = match side {
655 Side::Before => r,
656 Side::After => r + 1,
657 };
658 if let Some(Node {
659 kind: Kind::Matrix { rows, .. },
660 ..
661 }) = self.nodes.get_mut(m)
662 {
663 let idx = idx.min(rows.len());
664 rows.insert(idx, new_row);
665 }
666 first.map_or(at, |s| Cursor { seq: s, index: 0 })
667 }
668
669 pub fn matrix_delete_row(&mut self, at: Cursor) -> Cursor {
671 let Some((m, r, _c)) = self.matrix_of(at) else {
672 return at;
673 };
674 let row_cells: Vec<SeqId> = match self.kind(m) {
675 Some(Kind::Matrix { rows, .. }) if rows.len() > 1 => rows[r].clone(),
676 _ => return at,
677 };
678 if let Some(Node {
679 kind: Kind::Matrix { rows, .. },
680 ..
681 }) = self.nodes.get_mut(m)
682 {
683 rows.remove(r);
684 }
685 for c in row_cells {
686 self.free_seq(c);
687 }
688 let target = match self.kind(m) {
689 Some(Kind::Matrix { rows, .. }) => rows.get(r.min(rows.len().saturating_sub(1))).and_then(|row| row.first().copied()),
690 _ => None,
691 };
692 target.map_or(at, |s| Cursor { seq: s, index: 0 })
693 }
694
695 pub fn matrix_insert_col(&mut self, at: Cursor, side: Side) -> Cursor {
697 let Some((m, _r, c)) = self.matrix_of(at) else {
698 return at;
699 };
700 let nrows = match self.kind(m) {
701 Some(Kind::Matrix { rows, .. }) => rows.len(),
702 _ => 0,
703 };
704 let new_cells: Vec<SeqId> = (0..nrows).map(|_| self.alloc_seq(Some(m))).collect();
705 let idx = match side {
706 Side::Before => c,
707 Side::After => c + 1,
708 };
709 let first = new_cells.first().copied();
710 if let Some(Node {
711 kind: Kind::Matrix { rows, .. },
712 ..
713 }) = self.nodes.get_mut(m)
714 {
715 for (row, cell) in rows.iter_mut().zip(new_cells) {
716 let i = idx.min(row.len());
717 row.insert(i, cell);
718 }
719 }
720 first.map_or(at, |s| Cursor { seq: s, index: 0 })
721 }
722
723 pub fn matrix_delete_col(&mut self, at: Cursor) -> Cursor {
725 let Some((m, _r, c)) = self.matrix_of(at) else {
726 return at;
727 };
728 if self.matrix_cols(m) <= 1 {
729 return at;
730 }
731 let mut removed = Vec::new();
732 if let Some(Node {
733 kind: Kind::Matrix { rows, .. },
734 ..
735 }) = self.nodes.get_mut(m)
736 {
737 for row in rows.iter_mut() {
738 if c < row.len() {
739 removed.push(row.remove(c));
740 }
741 }
742 }
743 for cell in removed {
744 self.free_seq(cell);
745 }
746 let target = match self.kind(m) {
747 Some(Kind::Matrix { rows, .. }) => rows.first().and_then(|row| {
748 row.get(c.min(row.len().saturating_sub(1))).copied()
749 }),
750 _ => None,
751 };
752 target.map_or(at, |s| Cursor { seq: s, index: 0 })
753 }
754
755 pub fn swap_variants(&self, node: NodeId) -> Option<Vec<SwapVariant>> {
757 match self.kind(node)? {
758 Kind::Atom(_) | Kind::HostBox { .. } | Kind::Frac { .. } | Kind::Script { .. } | Kind::Sqrt { .. } => None,
759 Kind::Delim { open, close, .. } => {
760 let (open, close) = (*open, *close);
761 Some(
762 DELIM_PAIRS
763 .iter()
764 .filter(|&&(o, c, _)| o != open || c != close)
765 .map(|&(open, close, label)| SwapVariant::Delim { open, close, label })
766 .collect(),
767 )
768 }
769 Kind::BigOp { op, .. } => {
770 let current = op.latex.as_str();
771 Some(
772 BIGOP_ALTS
773 .iter()
774 .filter(|&&(latex, _)| latex != current)
775 .map(|&(latex, label)| SwapVariant::BigOp { latex, label })
776 .collect(),
777 )
778 }
779 Kind::Accent { mark, .. } => {
780 let mark = *mark;
781 Some(
782 ACCENT_ALTS
783 .iter()
784 .filter(|&&(m, _)| m != mark)
785 .map(|&(mark, label)| SwapVariant::Accent { mark, label })
786 .collect(),
787 )
788 }
789 Kind::UnderOver { over, under, over_deco, under_deco, .. } => {
791 let mut alts = Vec::new();
792 if over.is_some() {
793 alts.extend(
794 DECO_ALTS
795 .iter()
796 .filter(|&&(d, _)| d != *over_deco)
797 .map(|&(deco, label)| SwapVariant::UnderOverDeco {
798 over: deco,
799 under: *under_deco,
800 label,
801 }),
802 );
803 }
804 if under.is_some() {
805 alts.extend(
806 DECO_ALTS
807 .iter()
808 .filter(|&&(d, _)| d != *under_deco)
809 .map(|&(deco, label)| SwapVariant::UnderOverDeco {
810 over: *over_deco,
811 under: deco,
812 label,
813 }),
814 );
815 }
816 Some(alts)
817 }
818 Kind::Styled { variant, .. } if *variant == Variant::Text => None,
820 Kind::Styled { variant, .. } => {
821 let current = *variant;
822 Some(
823 STYLED_ALTS
824 .iter()
825 .filter(|&&(v, _)| v != current)
826 .map(|&(variant, label)| SwapVariant::Styled { variant, label })
827 .collect(),
828 )
829 }
830 Kind::Matrix { env, .. } => {
831 let current = *env;
832 Some(
833 MATRIX_ENV_ALTS
834 .iter()
835 .filter(|&&(e, _)| e != current)
836 .map(|&(env, label)| SwapVariant::Matrix { env, label })
837 .collect(),
838 )
839 }
840 }
841 }
842
843 pub fn apply_swap(&mut self, node: NodeId, variant: &SwapVariant) {
845 let Some(n) = self.nodes.get_mut(node) else {
846 return;
847 };
848 match (&mut n.kind, variant) {
849 (Kind::Delim { open, close, .. }, SwapVariant::Delim { open: o, close: c, .. }) => {
850 *open = *o;
851 *close = *c;
852 }
853 (Kind::BigOp { op, .. }, SwapVariant::BigOp { latex, .. }) => {
854 op.latex = (*latex).to_string();
855 }
856 (Kind::Accent { mark, .. }, SwapVariant::Accent { mark: m, .. }) => {
857 *mark = *m;
858 }
859 (
860 Kind::UnderOver { over_deco, under_deco, .. },
861 SwapVariant::UnderOverDeco { over, under, .. },
862 ) => {
863 *over_deco = *over;
864 *under_deco = *under;
865 }
866 (Kind::Styled { variant: v, .. }, SwapVariant::Styled { variant: nv, .. }) => {
867 *v = *nv;
868 }
869 (Kind::Matrix { env, .. }, SwapVariant::Matrix { env: e, .. }) => {
870 *env = *e;
871 }
872 _ => {}
873 }
874 }
875}
876
877#[derive(Debug, Clone, PartialEq)]
879pub enum SwapVariant {
880 Delim {
882 open: char,
884 close: char,
886 label: &'static str,
888 },
889 BigOp {
891 latex: &'static str,
893 label: &'static str,
895 },
896 Accent {
898 mark: Mark,
900 label: &'static str,
902 },
903 UnderOverDeco {
905 over: Deco,
907 under: Deco,
909 label: &'static str,
911 },
912 Styled {
914 variant: Variant,
916 label: &'static str,
918 },
919 Matrix {
921 env: MatrixEnv,
923 label: &'static str,
925 },
926}
927
928impl SwapVariant {
929 pub fn label(&self) -> &'static str {
931 match self {
932 SwapVariant::Delim { label, .. }
933 | SwapVariant::BigOp { label, .. }
934 | SwapVariant::Accent { label, .. }
935 | SwapVariant::UnderOverDeco { label, .. }
936 | SwapVariant::Styled { label, .. }
937 | SwapVariant::Matrix { label, .. } => label,
938 }
939 }
940}
941
942const DELIM_PAIRS: &[(char, char, &str)] = &[
943 ('(', ')', "( ) parentheses"),
944 ('[', ']', "[ ] brackets"),
945 ('{', '}', "{ } braces"),
946 ('|', '|', "| | bars"),
947 ('\u{2308}', '\u{2309}', "⌈ ⌉ ceiling"),
948 ('\u{230A}', '\u{230B}', "⌊ ⌋ floor"),
949 ('\u{27E8}', '\u{27E9}', "⟨ ⟩ angle"),
950];
951
952const BIGOP_ALTS: &[(&str, &str)] = &[
953 ("\\sum", "∑ sum"),
954 ("\\prod", "∏ product"),
955 ("\\coprod", "∐ coproduct"),
956 ("\\int", "∫ integral"),
957 ("\\iint", "∬ double integral"),
958 ("\\iiint", "∭ triple integral"),
959 ("\\oint", "∮ contour integral"),
960 ("\\bigcup", "⋃ union"),
961 ("\\bigcap", "⋂ intersection"),
962 ("\\bigsqcup", "⊔ disjoint union"),
963 ("\\biguplus", "⊎ uplus"),
964 ("\\bigoplus", "⊕ oplus"),
965 ("\\bigotimes", "⊗ otimes"),
966 ("\\bigodot", "⊙ odot"),
967 ("\\bigvee", "⋁ vee"),
968 ("\\bigwedge", "⋀ wedge"),
969];
970
971const ACCENT_ALTS: &[(Mark, &str)] = &[
972 (Mark::Hat, "hat"),
973 (Mark::Vec, "vec"),
974 (Mark::Bar, "bar"),
975 (Mark::Tilde, "tilde"),
976 (Mark::Dot, "dot"),
977 (Mark::Ddot, "double dot"),
978 (Mark::Widehat, "wide hat"),
979 (Mark::Widetilde, "wide tilde"),
980 (Mark::Overline, "overline"),
981 (Mark::Underline, "underline"),
982 (Mark::Check, "check"),
983 (Mark::Breve, "breve"),
984];
985
986const DECO_ALTS: &[(Deco, &str)] = &[
987 (Deco::None, "plain"),
988 (Deco::Brace, "brace"),
989 (Deco::Arrow, "arrow"),
990 (Deco::Line, "line"),
991];
992
993const STYLED_ALTS: &[(Variant, &str)] = &[
994 (Variant::Normal, "normal"),
995 (Variant::Bold, "bold"),
996 (Variant::Blackboard, "blackboard"),
997 (Variant::Calligraphic, "calligraphic"),
998 (Variant::Fraktur, "fraktur"),
999 (Variant::Roman, "roman"),
1000 (Variant::SansSerif, "sans serif"),
1001 (Variant::Typewriter, "typewriter"),
1002 (Variant::OperatorName, "operator name"),
1003];
1004
1005const MATRIX_ENV_ALTS: &[(MatrixEnv, &str)] = &[
1006 (MatrixEnv::Matrix, "matrix"),
1007 (MatrixEnv::Pmatrix, "( matrix )"),
1008 (MatrixEnv::Bmatrix, "[ matrix ]"),
1009 (MatrixEnv::Vmatrix, "| matrix |"),
1010 (MatrixEnv::Cases, "cases"),
1011 (MatrixEnv::Aligned, "aligned"),
1012 (MatrixEnv::Array, "array"),
1013];
1014
1015#[cfg(test)]
1016mod tests {
1017 use super::*;
1018 use crate::model::{Deco, MathClass};
1019
1020 fn atom(c: &str) -> Symbol {
1021 Symbol {
1022 latex: c.into(),
1023 class: MathClass::Ord,
1024 }
1025 }
1026
1027 fn fill(t: &mut Tree, seq: SeqId, s: &str) {
1028 let mut c = Cursor { seq, index: t.len(seq) };
1029 for ch in s.chars() {
1030 c = t.insert_atom(c, atom(&ch.to_string()));
1031 }
1032 }
1033
1034 #[test]
1035 fn insert_wires_parents_and_splices() {
1036 let mut t = Tree::new();
1037 let root = t.root();
1038 t.insert_new(root, 0, NewNode::Atom(atom("a")));
1039 let frac = t.insert_new(root, 1, NewNode::Frac(FracStyle::Bar));
1040 assert_eq!(t.child_seqs(frac).len(), 2);
1041 assert_eq!(t.seq_parent(t.child_seqs(frac)[0]), Some(frac));
1042 assert_eq!(t.index_in_parent(frac), Some((root, 1)));
1043 }
1044
1045 #[test]
1046 fn move_right_walks_source_order_through_a_fraction() {
1047 let mut t = Tree::new();
1048 let root = t.root();
1049 let frac = t.insert_new(root, 0, NewNode::Frac(FracStyle::Bar));
1050 let slots = t.child_seqs(frac);
1051 let (num, den) = (slots[0], slots[1]);
1052 let c = nav::move_right(&t, Cursor { seq: root, index: 0 }).unwrap();
1053 assert_eq!((c.seq, c.index), (num, 0));
1054 let c = nav::move_right(&t, c).unwrap();
1055 assert_eq!((c.seq, c.index), (den, 0));
1056 let c = nav::move_right(&t, c).unwrap();
1057 assert_eq!((c.seq, c.index), (root, 1));
1058 assert_eq!(nav::move_right(&t, c), None);
1059 }
1060
1061 #[test]
1062 fn drop_node_frees_the_whole_subtree() {
1063 let mut t = Tree::new();
1064 let root = t.root();
1065 let frac = t.insert_new(root, 0, NewNode::Frac(FracStyle::Bar));
1066 assert!(t.seqs.len() >= 3);
1067 t.drop_node(frac);
1068 assert_eq!(t.len(root), 0);
1069 assert_eq!(t.seqs.len(), 1);
1070 }
1071
1072 #[test]
1073 fn backspace_deletes_an_atom() {
1074 let mut t = Tree::new();
1075 let root = t.root();
1076 fill(&mut t, root, "ab");
1077 let c = t.delete_backward(Cursor { seq: root, index: 2 });
1078 assert_eq!((c.seq, c.index), (root, 1));
1079 assert_eq!(t.len(root), 1);
1080 }
1081
1082 #[test]
1083 fn backspace_in_empty_fraction_removes_it() {
1084 let mut t = Tree::new();
1085 let root = t.root();
1086 let c = t.insert_fraction(Cursor { seq: root, index: 0 }, FracStyle::Bar, None);
1087 let c = t.delete_backward(c);
1089 assert_eq!((c.seq, c.index), (root, 0));
1090 assert_eq!(t.len(root), 0);
1091 assert_eq!(t.seqs.len(), 1);
1092 }
1093
1094 #[test]
1095 fn backspace_in_partial_fraction_unwraps_and_promotes() {
1096 let mut t = Tree::new();
1097 let root = t.root();
1098 let c = t.insert_fraction(Cursor { seq: root, index: 0 }, FracStyle::Bar, None);
1099 let num = c.seq;
1100 fill(&mut t, num, "xy");
1101 let frac = t.items(root)[0];
1103 let den = t.child_seqs(frac)[1];
1104 let c = t.delete_backward(Cursor { seq: den, index: 0 });
1105 assert_eq!(t.len(root), 2);
1107 assert_eq!(c.seq, root);
1108 }
1109
1110 #[test]
1111 fn backspace_at_nonempty_slot_start_exits_without_deleting() {
1112 let mut t = Tree::new();
1113 let root = t.root();
1114 let c = t.insert_fraction(Cursor { seq: root, index: 0 }, FracStyle::Bar, None);
1115 let num = c.seq;
1116 fill(&mut t, num, "x");
1117 let before = t.len(num);
1118 let c = t.delete_backward(Cursor { seq: num, index: 0 });
1119 assert_eq!(t.len(num), before);
1120 assert_eq!(c.seq, root);
1121 }
1122
1123 #[test]
1124 fn fraction_wraps_a_selection() {
1125 let mut t = Tree::new();
1126 let root = t.root();
1127 fill(&mut t, root, "ab");
1128 let sel = Selection {
1129 seq: root,
1130 anchor: 0,
1131 focus: 2,
1132 };
1133 let c = t.insert_fraction(Cursor { seq: root, index: 0 }, FracStyle::Bar, Some(sel));
1134 let frac = t.items(root)[0];
1135 let num = t.child_seqs(frac)[0];
1136 assert_eq!(t.len(num), 2);
1137 assert_eq!(t.len(root), 1);
1138 assert_eq!(c.seq, t.child_seqs(frac)[1]);
1139 }
1140
1141 #[test]
1142 fn script_attaches_to_preceding_atom() {
1143 let mut t = Tree::new();
1144 let root = t.root();
1145 fill(&mut t, root, "x");
1146 let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
1147 let script = t.items(root)[0];
1148 assert!(matches!(t.kind(script), Some(Kind::Script { .. })));
1149 if let Some(Kind::Script { base, sup, .. }) = t.kind(script) {
1151 assert_eq!(t.len(*base), 1);
1152 assert_eq!(Some(c.seq), *sup);
1153 } else {
1154 panic!();
1155 }
1156 }
1157
1158 #[test]
1159 fn deleting_empty_script_unwraps_with_cursor_after_base() {
1160 let mut t = Tree::new();
1161 let root = t.root();
1162 t.insert_atom(Cursor { seq: root, index: 0 }, atom("x"));
1163 let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
1164 let c = t.insert_atom(c, atom("2"));
1165 let c = t.delete_backward(c);
1166 let c = t.delete_backward(c);
1167 assert_eq!(t.len(root), 1);
1168 assert!(matches!(t.kind(t.items(root)[0]), Some(Kind::Atom(_))));
1169 assert_eq!((c.seq, c.index), (root, 1));
1170 }
1171
1172 #[test]
1173 fn typing_into_script_base_evicts_old_base_left() {
1174 let mut t = Tree::new();
1175 let root = t.root();
1176 t.insert_atom(Cursor { seq: root, index: 0 }, atom("x"));
1177 let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
1178 t.insert_atom(c, atom("2"));
1179 let script = t.items(root)[0];
1180 let base = match t.kind(script) {
1181 Some(Kind::Script { base, .. }) => *base,
1182 _ => panic!(),
1183 };
1184 let c = t.insert_atom(Cursor { seq: base, index: 1 }, atom("y"));
1186 assert_eq!(t.len(base), 1);
1187 assert_eq!((c.seq, c.index), (base, 1));
1188 assert_eq!(t.len(root), 2);
1189 assert!(matches!(t.kind(t.items(root)[0]), Some(Kind::Atom(_))));
1190 assert_eq!(t.items(root)[1], script);
1191 }
1192
1193 #[test]
1194 fn deleting_lone_script_base_dissolves_into_sequence() {
1195 let mut t = Tree::new();
1196 let root = t.root();
1197 t.insert_atom(Cursor { seq: root, index: 0 }, atom("x"));
1198 let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
1199 t.insert_atom(c, atom("2"));
1200 let script = t.items(root)[0];
1201 let base = match t.kind(script) {
1202 Some(Kind::Script { base, .. }) => *base,
1203 _ => panic!(),
1204 };
1205 let c = t.delete_backward(Cursor { seq: base, index: 1 });
1207 assert_eq!((c.seq, c.index), (base, 0));
1208 assert!(t.is_empty(base));
1209 let c = t.delete_backward(c);
1211 assert_eq!(t.len(root), 1);
1212 assert!(matches!(t.kind(t.items(root)[0]), Some(Kind::Atom(_))));
1213 assert_eq!((c.seq, c.index), (root, 0));
1214 }
1215
1216 #[test]
1217 fn deleting_empty_base_pulls_left_sibling_in() {
1218 let mut t = Tree::new();
1219 let root = t.root();
1220 t.insert_atom(Cursor { seq: root, index: 0 }, atom("a"));
1222 let c = t.attach_script(Cursor { seq: root, index: 1 }, ScriptSlot::Sup);
1223 let script = t.items(root)[0];
1225 let base = match t.kind(script) {
1226 Some(Kind::Script { base, .. }) => *base,
1227 _ => panic!(),
1228 };
1229 t.insert_atom(c, atom("2"));
1230 t.move_range(base, 0..1, root, 0);
1231 let c = t.delete_backward(Cursor { seq: base, index: 0 });
1232 assert_eq!(t.len(root), 1);
1233 assert_eq!(t.len(base), 1);
1234 assert_eq!((c.seq, c.index), (base, 1));
1235 }
1236
1237 #[test]
1238 fn backspace_in_empty_sqrt_radicand_drops_radical() {
1239 let mut t = Tree::new();
1240 let root = t.root();
1241 let c = t.insert_sqrt(Cursor { seq: root, index: 0 }, None);
1242 let c = t.delete_backward(c);
1243 assert_eq!(t.len(root), 0);
1244 assert_eq!((c.seq, c.index), (root, 0));
1245 }
1246
1247 #[test]
1248 fn backspace_in_empty_delim_drops_it() {
1249 let mut t = Tree::new();
1250 let root = t.root();
1251 let c = t.insert_delimiters(Cursor { seq: root, index: 0 }, '(', ')', None);
1252 let c = t.delete_backward(c);
1253 assert_eq!(t.len(root), 0);
1254 assert_eq!((c.seq, c.index), (root, 0));
1255 }
1256
1257 #[test]
1258 fn backspace_in_empty_bigop_limit_steps_into_nonempty_other() {
1259 let mut t = Tree::new();
1260 let root = t.root();
1261 let op = Symbol { latex: "\\sum".into(), class: MathClass::Op };
1262 t.insert_big_op(Cursor { seq: root, index: 0 }, op);
1263 let bigop = t.items(root)[0];
1264 let cs = t.child_seqs(bigop);
1265 let (upper, lower) = (cs[0], cs[1]);
1266 fill(&mut t, lower, "2");
1267 let c = t.delete_backward(Cursor { seq: upper, index: 0 });
1269 assert_eq!(t.len(root), 1);
1270 assert_eq!((c.seq, c.index), (lower, 1));
1271 }
1272
1273 #[test]
1274 fn backspace_in_empty_bigop_both_limits_empty_drops_op() {
1275 let mut t = Tree::new();
1276 let root = t.root();
1277 let op = Symbol { latex: "\\sum".into(), class: MathClass::Op };
1278 t.insert_big_op(Cursor { seq: root, index: 0 }, op);
1279 let bigop = t.items(root)[0];
1280 let lower = t.child_seqs(bigop)[1];
1281 let c = t.delete_backward(Cursor { seq: lower, index: 0 });
1282 assert_eq!(t.len(root), 0);
1283 assert_eq!((c.seq, c.index), (root, 0));
1284 }
1285
1286 #[test]
1287 fn backspace_in_underover_over_collapses_to_base() {
1288 let mut t = Tree::new();
1289 let root = t.root();
1290 let spec = UnderOverSpec { over: true, under: false, over_deco: Deco::None, under_deco: Deco::None };
1291 let c = t.insert_under_over(Cursor { seq: root, index: 0 }, spec, None);
1292 let node = t.items(root)[0];
1293 let over = match t.kind(node) {
1294 Some(Kind::UnderOver { over, .. }) => over.unwrap(),
1295 _ => panic!(),
1296 };
1297 fill(&mut t, c.seq, "x");
1298 let c = t.delete_backward(Cursor { seq: over, index: 0 });
1300 assert_eq!(t.len(root), 1);
1301 assert!(matches!(t.kind(t.items(root)[0]), Some(Kind::Atom(_))));
1302 assert_eq!((c.seq, c.index), (root, 1));
1303 }
1304
1305 #[test]
1306 fn backspace_in_all_empty_matrix_drops_it() {
1307 let mut t = Tree::new();
1308 let root = t.root();
1309 let c = t.insert_matrix(Cursor { seq: root, index: 0 }, MatrixEnv::Pmatrix, 2, 2);
1310 let c = t.delete_backward(c);
1311 assert_eq!(t.len(root), 0);
1312 assert_eq!((c.seq, c.index), (root, 0));
1313 }
1314
1315 #[test]
1316 fn vertical_nav_switches_fraction_slots() {
1317 let mut t = Tree::new();
1318 let root = t.root();
1319 let c = t.insert_fraction(Cursor { seq: root, index: 0 }, FracStyle::Bar, None);
1320 let num = c.seq;
1321 let frac = t.items(root)[0];
1322 let den = t.child_seqs(frac)[1];
1323 let down = nav::vertical(&t, Cursor { seq: num, index: 0 }, false).unwrap();
1324 assert_eq!(down.seq, den);
1325 let up = nav::vertical(&t, Cursor { seq: den, index: 0 }, true).unwrap();
1326 assert_eq!(up.seq, num);
1327 }
1328
1329 #[test]
1330 fn matrix_row_col_ops() {
1331 let mut t = Tree::new();
1332 let root = t.root();
1333 let c = t.insert_matrix(Cursor { seq: root, index: 0 }, MatrixEnv::Pmatrix, 2, 2);
1334 let m = t.items(root)[0];
1335 let count = |t: &Tree| match t.kind(m) {
1336 Some(Kind::Matrix { rows, .. }) => (rows.len(), rows[0].len()),
1337 _ => (0, 0),
1338 };
1339 assert_eq!(count(&t), (2, 2));
1340 let c = t.matrix_insert_row(c, Side::After);
1341 assert_eq!(count(&t), (3, 2));
1342 let c = t.matrix_insert_col(c, Side::Before);
1343 assert_eq!(count(&t), (3, 3));
1344 let c = t.matrix_delete_row(c);
1345 assert_eq!(count(&t), (2, 3));
1346 let _c = t.matrix_delete_col(c);
1347 assert_eq!(count(&t), (2, 2));
1348 }
1349
1350 #[test]
1351 fn matrix_shape_at_reports_current_grid_size_and_none_outside_a_matrix() {
1352 let mut t = Tree::new();
1353 let root = t.root();
1354 let c = t.insert_matrix(Cursor { seq: root, index: 0 }, MatrixEnv::Pmatrix, 2, 2);
1355 assert_eq!(t.matrix_shape_at(c), Some((2, 2)));
1356 let c = t.matrix_insert_row(c, Side::After);
1357 assert_eq!(t.matrix_shape_at(c), Some((3, 2)));
1358
1359 assert_eq!(t.matrix_shape_at(Cursor { seq: root, index: 0 }), None);
1361 }
1362
1363 #[test]
1364 fn swap_variants_for_delim_excludes_the_current_pair() {
1365 let mut t = Tree::new();
1366 let root = t.root();
1367 t.insert_delimiters(Cursor { seq: root, index: 0 }, '(', ')', None);
1368 let delim = t.items(root)[0];
1369 let variants = t.swap_variants(delim).expect("Delim should be swappable");
1370 assert!(variants.iter().all(|v| !matches!(
1371 v,
1372 SwapVariant::Delim { open: '(', close: ')', .. }
1373 )));
1374 assert!(variants
1375 .iter()
1376 .any(|v| matches!(v, SwapVariant::Delim { open: '[', close: ']', .. })));
1377 }
1378
1379 #[test]
1380 fn apply_swap_changes_only_the_tag_field() {
1381 let mut t = Tree::new();
1382 let root = t.root();
1383 let c = t.insert_delimiters(Cursor { seq: root, index: 0 }, '(', ')', None);
1384 fill(&mut t, c.seq, "x");
1385 let delim = t.items(root)[0];
1386 let body_before = t.child_seqs(delim)[0];
1387
1388 t.apply_swap(delim, &SwapVariant::Delim { open: '[', close: ']', label: "[ ]" });
1389 match t.kind(delim) {
1390 Some(Kind::Delim { open, close, body }) => {
1391 assert_eq!((*open, *close), ('[', ']'));
1392 assert_eq!(*body, body_before);
1393 assert_eq!(t.len(*body), 1);
1394 }
1395 _ => panic!("expected Delim"),
1396 }
1397 }
1398
1399 #[test]
1400 fn swap_variants_none_for_non_swappable_kinds() {
1401 let mut t = Tree::new();
1402 let root = t.root();
1403
1404 t.insert_atom(Cursor { seq: root, index: 0 }, atom("x"));
1405 let atom_node = t.items(root)[0];
1406 assert!(t.swap_variants(atom_node).is_none());
1407
1408 let c = t.insert_fraction(Cursor { seq: root, index: 1 }, FracStyle::Bar, None);
1409 let _ = c;
1410 let frac = t.items(root)[1];
1411 assert!(t.swap_variants(frac).is_none());
1412
1413 let c = t.insert_sqrt(Cursor { seq: root, index: 2 }, None);
1414 let _ = c;
1415 let sqrt = t.items(root)[2];
1416 assert!(t.swap_variants(sqrt).is_none());
1417 }
1418
1419 #[test]
1420 fn swap_variants_none_for_text_carve_out() {
1421 let mut t = Tree::new();
1422 let root = t.root();
1423 t.insert_styled(Cursor { seq: root, index: 0 }, Variant::Text, None);
1424 let text = t.items(root)[0];
1425 assert!(t.swap_variants(text).is_none());
1426 }
1427
1428 #[test]
1429 fn swap_variants_for_styled_font_excludes_current_and_text() {
1430 let mut t = Tree::new();
1431 let root = t.root();
1432 t.insert_styled(Cursor { seq: root, index: 0 }, Variant::Bold, None);
1433 let styled = t.items(root)[0];
1434 let variants = t.swap_variants(styled).expect("Styled(Bold) should be swappable");
1435 assert!(variants
1436 .iter()
1437 .all(|v| !matches!(v, SwapVariant::Styled { variant: Variant::Bold, .. })));
1438 assert!(variants
1439 .iter()
1440 .all(|v| !matches!(v, SwapVariant::Styled { variant: Variant::Text, .. })));
1441 }
1442
1443 #[test]
1444 fn swap_variants_for_underover_only_varies_present_slots() {
1445 let mut t = Tree::new();
1446 let root = t.root();
1447 let spec = UnderOverSpec {
1449 over: true,
1450 under: false,
1451 over_deco: Deco::Brace,
1452 under_deco: Deco::None,
1453 };
1454 t.insert_under_over(Cursor { seq: root, index: 0 }, spec, None);
1455 let node = t.items(root)[0];
1456 let variants = t.swap_variants(node).expect("UnderOver should be swappable");
1457 assert!(!variants.is_empty());
1458 for v in &variants {
1459 match v {
1460 SwapVariant::UnderOverDeco { over, under, .. } => {
1461 assert_ne!(*over, Deco::Brace);
1462 assert_eq!(*under, Deco::None);
1463 }
1464 _ => panic!("expected UnderOverDeco"),
1465 }
1466 }
1467 }
1468}