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