1use std::{any::Any, borrow::Cow, fmt, ops, sync::Arc};
9
10use parking_lot::Mutex;
11use zng_ext_font::*;
12use zng_ext_l10n::l10n;
13use zng_ext_undo::*;
14use zng_wgt::prelude::*;
15
16use super::{node::TEXT, *};
17
18command! {
19 pub static EDIT_CMD;
23
24 pub static SELECT_CMD;
28
29 pub static SELECT_ALL_CMD = {
33 l10n!: true,
34 name: "Select All",
35 shortcut: shortcut!(CTRL+'A'),
36 shortcut_filter: ShortcutFilter::FOCUSED | ShortcutFilter::CMD_ENABLED,
37 };
38
39 pub static PARSE_CMD;
43}
44
45struct SharedTextEditOp {
46 data: Box<dyn Any + Send>,
47 op: Box<dyn FnMut(&mut dyn Any, UndoFullOp) + Send>,
48}
49
50#[derive(Clone)]
52pub struct TextEditOp(Arc<Mutex<SharedTextEditOp>>);
53impl fmt::Debug for TextEditOp {
54 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55 f.debug_struct("TextEditOp").finish_non_exhaustive()
56 }
57}
58impl TextEditOp {
59 pub fn new<D>(data: D, mut op: impl FnMut(&mut D, UndoFullOp) + Send + 'static) -> Self
79 where
80 D: Send + Any + 'static,
81 {
82 Self(Arc::new(Mutex::new(SharedTextEditOp {
83 data: Box::new(data),
84 op: Box::new(move |data, o| op(data.downcast_mut().unwrap(), o)),
85 })))
86 }
87
88 pub fn insert(insert: impl Into<Txt>) -> Self {
93 struct InsertData {
94 insert: Txt,
95 selection_state: SelectionState,
96 removed: Txt,
97 }
98 let data = InsertData {
99 insert: insert.into(),
100 selection_state: SelectionState::PreInit,
101 removed: Txt::from_static(""),
102 };
103
104 Self::new(data, move |data, op| match op {
105 UndoFullOp::Init { redo } => {
106 let ctx = TEXT.resolved();
107 let caret = &ctx.caret;
108
109 let mut rmv_range = 0..0;
110
111 if let Some(range) = caret.selection_range() {
112 rmv_range = range.start.index..range.end.index;
113
114 ctx.txt.with(|t| {
115 let r = &t[rmv_range.clone()];
116 if r != data.removed {
117 data.removed = Txt::from_str(r);
118 }
119 });
120
121 if range.start.index == caret.index.unwrap_or(CaretIndex::ZERO).index {
122 data.selection_state = SelectionState::CaretSelection(range.start, range.end);
123 } else {
124 data.selection_state = SelectionState::SelectionCaret(range.start, range.end);
125 }
126 } else {
127 data.selection_state = SelectionState::Caret(caret.index.unwrap_or(CaretIndex::ZERO));
128 }
129
130 Self::apply_max_count(redo, &ctx.txt, rmv_range, &mut data.insert)
131 }
132 UndoFullOp::Op(UndoOp::Redo) => {
133 let insert = &data.insert;
134
135 match data.selection_state {
136 SelectionState::PreInit => unreachable!(),
137 SelectionState::Caret(insert_idx) => {
138 let i = insert_idx.index;
139 TEXT.resolved()
140 .txt
141 .modify(clmv!(insert, |args| {
142 args.to_mut().to_mut().insert_str(i, insert.as_str());
143 }))
144 .unwrap();
145
146 let mut i = insert_idx;
147 i.index += insert.len();
148
149 let mut caret = TEXT.resolve_caret();
150 caret.set_index(i);
151 caret.clear_selection();
152 }
153 SelectionState::CaretSelection(start, end) | SelectionState::SelectionCaret(start, end) => {
154 let char_range = start.index..end.index;
155 TEXT.resolved()
156 .txt
157 .modify(clmv!(insert, |args| {
158 args.to_mut().to_mut().replace_range(char_range, insert.as_str());
159 }))
160 .unwrap();
161
162 let mut caret = TEXT.resolve_caret();
163 caret.set_char_index(start.index + insert.len());
164 caret.clear_selection();
165 }
166 }
167 }
168 UndoFullOp::Op(UndoOp::Undo) => {
169 let len = data.insert.len();
170 let (insert_idx, selection_idx, caret_idx) = match data.selection_state {
171 SelectionState::Caret(c) => (c, None, c),
172 SelectionState::CaretSelection(start, end) => (start, Some(end), start),
173 SelectionState::SelectionCaret(start, end) => (start, Some(start), end),
174 SelectionState::PreInit => unreachable!(),
175 };
176 let i = insert_idx.index;
177 let removed = &data.removed;
178
179 TEXT.resolved()
180 .txt
181 .modify(clmv!(removed, |args| {
182 args.to_mut().to_mut().replace_range(i..i + len, removed.as_str());
183 }))
184 .unwrap();
185
186 let mut caret = TEXT.resolve_caret();
187 caret.set_index(caret_idx);
188 caret.selection_index = selection_idx;
189 }
190 UndoFullOp::Info { info } => {
191 let mut label = Txt::from_static("\"");
192 for (i, mut c) in data.insert.chars().take(21).enumerate() {
193 if i == 20 {
194 c = '…';
195 } else if c == '\n' {
196 c = '↵';
197 } else if c == '\t' {
198 c = '→';
199 } else if c == '\r' {
200 continue;
201 }
202 label.push(c);
203 }
204 label.push('"');
205 *info = Some(Arc::new(label));
206 }
207 UndoFullOp::Merge {
208 next_data,
209 within_undo_interval,
210 merged,
211 ..
212 } => {
213 if within_undo_interval {
214 if let Some(next_data) = next_data.downcast_mut::<InsertData>() {
215 if let (SelectionState::Caret(mut after_idx), SelectionState::Caret(caret)) =
216 (data.selection_state, next_data.selection_state)
217 {
218 after_idx.index += data.insert.len();
219
220 if after_idx.index == caret.index {
221 data.insert.push_str(&next_data.insert);
222 *merged = true;
223 }
224 }
225 }
226 }
227 }
228 })
229 }
230
231 pub fn backspace() -> Self {
237 Self::backspace_impl(SegmentedText::backspace_range)
238 }
239 pub fn backspace_word() -> Self {
245 Self::backspace_impl(SegmentedText::backspace_word_range)
246 }
247 fn backspace_impl(backspace_range: fn(&SegmentedText, usize, u32) -> std::ops::Range<usize>) -> Self {
248 struct BackspaceData {
249 selection_state: SelectionState,
250 count: u32,
251 removed: Txt,
252 }
253 let data = BackspaceData {
254 selection_state: SelectionState::PreInit,
255 count: 1,
256 removed: Txt::from_static(""),
257 };
258
259 Self::new(data, move |data, op| match op {
260 UndoFullOp::Init { .. } => {
261 let ctx = TEXT.resolved();
262 let caret = &ctx.caret;
263
264 if let Some(range) = caret.selection_range() {
265 if range.start.index == caret.index.unwrap_or(CaretIndex::ZERO).index {
266 data.selection_state = SelectionState::CaretSelection(range.start, range.end);
267 } else {
268 data.selection_state = SelectionState::SelectionCaret(range.start, range.end);
269 }
270 } else {
271 data.selection_state = SelectionState::Caret(caret.index.unwrap_or(CaretIndex::ZERO));
272 }
273 }
274 UndoFullOp::Op(UndoOp::Redo) => {
275 let rmv = match data.selection_state {
276 SelectionState::Caret(c) => backspace_range(&TEXT.resolved().segmented_text, c.index, data.count),
277 SelectionState::CaretSelection(s, e) | SelectionState::SelectionCaret(s, e) => s.index..e.index,
278 SelectionState::PreInit => unreachable!(),
279 };
280 if rmv.is_empty() {
281 data.removed = Txt::from_static("");
282 return;
283 }
284
285 {
286 let mut caret = TEXT.resolve_caret();
287 caret.set_char_index(rmv.start);
288 caret.clear_selection();
289 }
290
291 let ctx = TEXT.resolved();
292 ctx.txt.with(|t| {
293 let r = &t[rmv.clone()];
294 if r != data.removed {
295 data.removed = Txt::from_str(r);
296 }
297 });
298
299 ctx.txt
300 .modify(move |args| {
301 args.to_mut().to_mut().replace_range(rmv, "");
302 })
303 .unwrap();
304 }
305 UndoFullOp::Op(UndoOp::Undo) => {
306 if data.removed.is_empty() {
307 return;
308 }
309
310 let (insert_idx, selection_idx, caret_idx) = match data.selection_state {
311 SelectionState::Caret(c) => (c.index - data.removed.len(), None, c),
312 SelectionState::CaretSelection(s, e) => (s.index, Some(e), s),
313 SelectionState::SelectionCaret(s, e) => (s.index, Some(s), e),
314 SelectionState::PreInit => unreachable!(),
315 };
316 let removed = &data.removed;
317
318 TEXT.resolved()
319 .txt
320 .modify(clmv!(removed, |args| {
321 args.to_mut().to_mut().insert_str(insert_idx, removed.as_str());
322 }))
323 .unwrap();
324
325 let mut caret = TEXT.resolve_caret();
326 caret.set_index(caret_idx);
327 caret.selection_index = selection_idx;
328 }
329 UndoFullOp::Info { info } => {
330 *info = Some(if data.count == 1 {
331 Arc::new("⌫")
332 } else {
333 Arc::new(formatx!("⌫ (x{})", data.count))
334 })
335 }
336 UndoFullOp::Merge {
337 next_data,
338 within_undo_interval,
339 merged,
340 ..
341 } => {
342 if within_undo_interval {
343 if let Some(next_data) = next_data.downcast_mut::<BackspaceData>() {
344 if let (SelectionState::Caret(mut after_idx), SelectionState::Caret(caret)) =
345 (data.selection_state, next_data.selection_state)
346 {
347 after_idx.index -= data.removed.len();
348
349 if after_idx.index == caret.index {
350 data.count += next_data.count;
351
352 next_data.removed.push_str(&data.removed);
353 data.removed = std::mem::take(&mut next_data.removed);
354 *merged = true;
355 }
356 }
357 }
358 }
359 }
360 })
361 }
362
363 pub fn delete() -> Self {
369 Self::delete_impl(SegmentedText::delete_range)
370 }
371 pub fn delete_word() -> Self {
377 Self::delete_impl(SegmentedText::delete_word_range)
378 }
379 fn delete_impl(delete_range: fn(&SegmentedText, usize, u32) -> std::ops::Range<usize>) -> Self {
380 struct DeleteData {
381 selection_state: SelectionState,
382 count: u32,
383 removed: Txt,
384 }
385 let data = DeleteData {
386 selection_state: SelectionState::PreInit,
387 count: 1,
388 removed: Txt::from_static(""),
389 };
390
391 Self::new(data, move |data, op| match op {
392 UndoFullOp::Init { .. } => {
393 let ctx = TEXT.resolved();
394 let caret = &ctx.caret;
395
396 if let Some(range) = caret.selection_range() {
397 if range.start.index == caret.index.unwrap_or(CaretIndex::ZERO).index {
398 data.selection_state = SelectionState::CaretSelection(range.start, range.end);
399 } else {
400 data.selection_state = SelectionState::SelectionCaret(range.start, range.end);
401 }
402 } else {
403 data.selection_state = SelectionState::Caret(caret.index.unwrap_or(CaretIndex::ZERO));
404 }
405 }
406 UndoFullOp::Op(UndoOp::Redo) => {
407 let rmv = match data.selection_state {
408 SelectionState::CaretSelection(s, e) | SelectionState::SelectionCaret(s, e) => s.index..e.index,
409 SelectionState::Caret(c) => delete_range(&TEXT.resolved().segmented_text, c.index, data.count),
410 SelectionState::PreInit => unreachable!(),
411 };
412
413 if rmv.is_empty() {
414 data.removed = Txt::from_static("");
415 return;
416 }
417
418 {
419 let mut caret = TEXT.resolve_caret();
420 caret.set_char_index(rmv.start); caret.clear_selection();
422 }
423
424 let ctx = TEXT.resolved();
425 ctx.txt.with(|t| {
426 let r = &t[rmv.clone()];
427 if r != data.removed {
428 data.removed = Txt::from_str(r);
429 }
430 });
431 ctx.txt
432 .modify(move |args| {
433 args.to_mut().to_mut().replace_range(rmv, "");
434 })
435 .unwrap();
436 }
437 UndoFullOp::Op(UndoOp::Undo) => {
438 let removed = &data.removed;
439
440 if data.removed.is_empty() {
441 return;
442 }
443
444 let (insert_idx, selection_idx, caret_idx) = match data.selection_state {
445 SelectionState::Caret(c) => (c.index, None, c),
446 SelectionState::CaretSelection(s, e) => (s.index, Some(e), s),
447 SelectionState::SelectionCaret(s, e) => (s.index, Some(s), e),
448 SelectionState::PreInit => unreachable!(),
449 };
450
451 TEXT.resolved()
452 .txt
453 .modify(clmv!(removed, |args| {
454 args.to_mut().to_mut().insert_str(insert_idx, removed.as_str());
455 }))
456 .unwrap();
457
458 let mut caret = TEXT.resolve_caret();
459 caret.set_index(caret_idx); caret.selection_index = selection_idx;
461 }
462 UndoFullOp::Info { info } => {
463 *info = Some(if data.count == 1 {
464 Arc::new("⌦")
465 } else {
466 Arc::new(formatx!("⌦ (x{})", data.count))
467 })
468 }
469 UndoFullOp::Merge {
470 next_data,
471 within_undo_interval,
472 merged,
473 ..
474 } => {
475 if within_undo_interval {
476 if let Some(next_data) = next_data.downcast_ref::<DeleteData>() {
477 if let (SelectionState::Caret(after_idx), SelectionState::Caret(caret)) =
478 (data.selection_state, next_data.selection_state)
479 {
480 if after_idx.index == caret.index {
481 data.count += next_data.count;
482 data.removed.push_str(&next_data.removed);
483 *merged = true;
484 }
485 }
486 }
487 }
488 }
489 })
490 }
491
492 fn apply_max_count(redo: &mut bool, txt: &BoxedVar<Txt>, rmv_range: ops::Range<usize>, insert: &mut Txt) {
493 let max_count = MAX_CHARS_COUNT_VAR.get();
494 if max_count > 0 {
495 let (txt_count, rmv_count) = txt.with(|t| (t.chars().count(), t[rmv_range].chars().count()));
497 let ins_count = insert.chars().count();
498
499 let final_count = txt_count - rmv_count + ins_count;
500 if final_count > max_count {
501 let ins_rmv = final_count - max_count;
503 if ins_rmv < ins_count {
504 let i = insert.char_indices().nth(ins_count - ins_rmv).unwrap().0;
506 insert.truncate(i);
507 } else {
508 debug_assert!(txt_count >= max_count);
510 *redo = false;
511 }
512 }
513 }
514 }
515
516 pub fn clear() -> Self {
518 #[derive(Default, Clone)]
519 struct Cleared {
520 txt: Txt,
521 selection: SelectionState,
522 }
523 Self::new(Cleared::default(), |data, op| match op {
524 UndoFullOp::Init { .. } => {
525 let ctx = TEXT.resolved();
526 data.txt = ctx.txt.get();
527 if let Some(range) = ctx.caret.selection_range() {
528 if range.start.index == ctx.caret.index.unwrap_or(CaretIndex::ZERO).index {
529 data.selection = SelectionState::CaretSelection(range.start, range.end);
530 } else {
531 data.selection = SelectionState::SelectionCaret(range.start, range.end);
532 }
533 } else {
534 data.selection = SelectionState::Caret(ctx.caret.index.unwrap_or(CaretIndex::ZERO));
535 };
536 }
537 UndoFullOp::Op(UndoOp::Redo) => {
538 let _ = TEXT.resolved().txt.set("");
539 }
540 UndoFullOp::Op(UndoOp::Undo) => {
541 let _ = TEXT.resolved().txt.set(data.txt.clone());
542
543 let (selection_idx, caret_idx) = match data.selection {
544 SelectionState::Caret(c) => (None, c),
545 SelectionState::CaretSelection(s, e) => (Some(e), s),
546 SelectionState::SelectionCaret(s, e) => (Some(s), e),
547 SelectionState::PreInit => unreachable!(),
548 };
549 let mut caret = TEXT.resolve_caret();
550 caret.set_index(caret_idx); caret.selection_index = selection_idx;
552 }
553 UndoFullOp::Info { info } => *info = Some(Arc::new(l10n!("text-edit-op.clear", "clear").get())),
554 UndoFullOp::Merge {
555 next_data,
556 within_undo_interval,
557 merged,
558 ..
559 } => *merged = within_undo_interval && next_data.is::<Cleared>(),
560 })
561 }
562
563 pub fn replace(mut select_before: ops::Range<usize>, insert: impl Into<Txt>, mut select_after: ops::Range<usize>) -> Self {
570 let mut insert = insert.into();
571 let mut removed = Txt::from_static("");
572
573 Self::new((), move |_, op| match op {
574 UndoFullOp::Init { redo } => {
575 let ctx = TEXT.resolved();
576
577 select_before.start = ctx.segmented_text.snap_grapheme_boundary(select_before.start);
578 select_before.end = ctx.segmented_text.snap_grapheme_boundary(select_before.end);
579
580 ctx.txt.with(|t| {
581 removed = Txt::from_str(&t[select_before.clone()]);
582 });
583
584 Self::apply_max_count(redo, &ctx.txt, select_before.clone(), &mut insert);
585 }
586 UndoFullOp::Op(UndoOp::Redo) => {
587 TEXT.resolved()
588 .txt
589 .modify(clmv!(select_before, insert, |args| {
590 args.to_mut().to_mut().replace_range(select_before, insert.as_str());
591 }))
592 .unwrap();
593
594 TEXT.resolve_caret().set_char_selection(select_after.start, select_after.end);
595 }
596 UndoFullOp::Op(UndoOp::Undo) => {
597 let ctx = TEXT.resolved();
598
599 select_after.start = ctx.segmented_text.snap_grapheme_boundary(select_after.start);
600 select_after.end = ctx.segmented_text.snap_grapheme_boundary(select_after.end);
601
602 ctx.txt
603 .modify(clmv!(select_after, removed, |args| {
604 args.to_mut().to_mut().replace_range(select_after, removed.as_str());
605 }))
606 .unwrap();
607
608 drop(ctx);
609 TEXT.resolve_caret().set_char_selection(select_before.start, select_before.end);
610 }
611 UndoFullOp::Info { info } => *info = Some(Arc::new(l10n!("text-edit-op.replace", "replace").get())),
612 UndoFullOp::Merge { .. } => {}
613 })
614 }
615
616 pub fn apply_transforms() -> Self {
618 let mut prev = Txt::from_static("");
619 let mut transform = None::<(TextTransformFn, WhiteSpace)>;
620 Self::new((), move |_, op| match op {
621 UndoFullOp::Init { .. } => {}
622 UndoFullOp::Op(UndoOp::Redo) => {
623 let (t, w) = transform.get_or_insert_with(|| (TEXT_TRANSFORM_VAR.get(), WHITE_SPACE_VAR.get()));
624
625 let ctx = TEXT.resolved();
626
627 let new_txt = ctx.txt.with(|txt| {
628 let transformed = t.transform(txt);
629 let white_spaced = w.transform(transformed.as_ref());
630 if let Cow::Owned(w) = white_spaced {
631 Some(w)
632 } else if let Cow::Owned(t) = transformed {
633 Some(t)
634 } else {
635 None
636 }
637 });
638
639 if let Some(t) = new_txt {
640 if ctx.txt.with(|t| t != prev.as_str()) {
641 prev = ctx.txt.get();
642 }
643 let _ = ctx.txt.set(t);
644 }
645 }
646 UndoFullOp::Op(UndoOp::Undo) => {
647 let ctx = TEXT.resolved();
648
649 if ctx.txt.with(|t| t != prev.as_str()) {
650 let _ = ctx.txt.set(prev.clone());
651 }
652 }
653 UndoFullOp::Info { info } => *info = Some(Arc::new(l10n!("text-edit-op.transform", "transform").get())),
654 UndoFullOp::Merge { .. } => {}
655 })
656 }
657
658 fn call(self) -> bool {
659 {
660 let mut op = self.0.lock();
661 let op = &mut *op;
662
663 let mut redo = true;
664 (op.op)(&mut *op.data, UndoFullOp::Init { redo: &mut redo });
665 if !redo {
666 return false;
667 }
668
669 (op.op)(&mut *op.data, UndoFullOp::Op(UndoOp::Redo));
670 }
671
672 if !OBSCURE_TXT_VAR.get() {
673 UNDO.register(UndoTextEditOp::new(self));
674 }
675 true
676 }
677
678 pub(super) fn call_edit_op(self) {
679 let registered = self.call();
680 if registered && !TEXT.resolved().pending_edit {
681 TEXT.resolve().pending_edit = true;
682 WIDGET.update(); }
684 }
685}
686#[derive(Clone, Copy, Default)]
688enum SelectionState {
689 #[default]
690 PreInit,
691 Caret(CaretIndex),
692 CaretSelection(CaretIndex, CaretIndex),
693 SelectionCaret(CaretIndex, CaretIndex),
694}
695
696#[derive(Debug, Clone)]
698pub(super) struct UndoTextEditOp {
699 pub target: WidgetId,
700 edit_op: TextEditOp,
701 exec_op: UndoOp,
702}
703impl UndoTextEditOp {
704 fn new(edit_op: TextEditOp) -> Self {
705 Self {
706 target: WIDGET.id(),
707 edit_op,
708 exec_op: UndoOp::Undo,
709 }
710 }
711
712 pub(super) fn call(&self) {
713 let mut op = self.edit_op.0.lock();
714 let op = &mut *op;
715 (op.op)(&mut *op.data, UndoFullOp::Op(self.exec_op))
716 }
717}
718impl UndoAction for UndoTextEditOp {
719 fn undo(self: Box<Self>) -> Box<dyn RedoAction> {
720 EDIT_CMD.scoped(self.target).notify_param(Self {
721 target: self.target,
722 edit_op: self.edit_op.clone(),
723 exec_op: UndoOp::Undo,
724 });
725 self
726 }
727
728 fn info(&mut self) -> Arc<dyn UndoInfo> {
729 let mut op = self.edit_op.0.lock();
730 let op = &mut *op;
731 let mut info = None;
732 (op.op)(&mut *op.data, UndoFullOp::Info { info: &mut info });
733
734 info.unwrap_or_else(|| Arc::new(l10n!("text-edit-op.generic", "text edit").get()))
735 }
736
737 fn as_any(&mut self) -> &mut dyn std::any::Any {
738 self
739 }
740
741 fn merge(self: Box<Self>, mut args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
742 if let Some(next) = args.next.as_any().downcast_mut::<Self>() {
743 let mut merged = false;
744
745 {
746 let mut op = self.edit_op.0.lock();
747 let op = &mut *op;
748
749 let mut next_op = next.edit_op.0.lock();
750
751 (op.op)(
752 &mut *op.data,
753 UndoFullOp::Merge {
754 next_data: &mut *next_op.data,
755 prev_timestamp: args.prev_timestamp,
756 within_undo_interval: args.within_undo_interval,
757 merged: &mut merged,
758 },
759 );
760 }
761
762 if merged {
763 return Ok(self);
764 }
765 }
766
767 Err((self, args.next))
768 }
769}
770impl RedoAction for UndoTextEditOp {
771 fn redo(self: Box<Self>) -> Box<dyn UndoAction> {
772 EDIT_CMD.scoped(self.target).notify_param(Self {
773 target: self.target,
774 edit_op: self.edit_op.clone(),
775 exec_op: UndoOp::Redo,
776 });
777 self
778 }
779
780 fn info(&mut self) -> Arc<dyn UndoInfo> {
781 let mut op = self.edit_op.0.lock();
782 let op = &mut *op;
783 let mut info = None;
784 (op.op)(&mut *op.data, UndoFullOp::Info { info: &mut info });
785
786 info.unwrap_or_else(|| Arc::new(l10n!("text-edit-op.generic", "text edit").get()))
787 }
788}
789
790#[derive(Clone)]
792pub struct TextSelectOp {
793 op: Arc<Mutex<dyn FnMut() + Send>>,
794}
795impl fmt::Debug for TextSelectOp {
796 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
797 f.debug_struct("TextSelectOp").finish_non_exhaustive()
798 }
799}
800impl TextSelectOp {
801 pub fn new(op: impl FnMut() + Send + 'static) -> Self {
810 Self {
811 op: Arc::new(Mutex::new(op)),
812 }
813 }
814
815 pub fn next() -> Self {
819 Self::new(|| next_prev(true, SegmentedText::next_insert_index, |_, s| s.end.index))
820 }
821
822 pub fn select_next() -> Self {
826 Self::new(|| next_prev(false, SegmentedText::next_insert_index, |_, _| unreachable!()))
827 }
828
829 pub fn prev() -> Self {
833 Self::new(|| next_prev(true, SegmentedText::prev_insert_index, |_, s| s.start.index))
834 }
835
836 pub fn select_prev() -> Self {
840 Self::new(|| next_prev(false, SegmentedText::prev_insert_index, |_, _| unreachable!()))
841 }
842
843 pub fn next_word() -> Self {
847 Self::new(|| next_prev(true, SegmentedText::next_word_index, |t, s| t.next_word_index(s.end.index)))
848 }
849
850 pub fn select_next_word() -> Self {
854 Self::new(|| next_prev(false, SegmentedText::next_word_index, |_, _| unreachable!()))
855 }
856
857 pub fn prev_word() -> Self {
861 Self::new(|| next_prev(true, SegmentedText::prev_word_index, |t, s| t.prev_word_index(s.start.index)))
862 }
863
864 pub fn select_prev_word() -> Self {
868 Self::new(|| next_prev(false, SegmentedText::prev_word_index, |_, _| unreachable!()))
869 }
870
871 pub fn line_up() -> Self {
875 Self::new(|| line_up_down(true, -1))
876 }
877
878 pub fn select_line_up() -> Self {
882 Self::new(|| line_up_down(false, -1))
883 }
884
885 pub fn line_down() -> Self {
889 Self::new(|| line_up_down(true, 1))
890 }
891
892 pub fn select_line_down() -> Self {
896 Self::new(|| line_up_down(false, 1))
897 }
898
899 pub fn page_up() -> Self {
903 Self::new(|| page_up_down(true, -1))
904 }
905
906 pub fn select_page_up() -> Self {
910 Self::new(|| page_up_down(false, -1))
911 }
912
913 pub fn page_down() -> Self {
917 Self::new(|| page_up_down(true, 1))
918 }
919
920 pub fn select_page_down() -> Self {
924 Self::new(|| page_up_down(false, 1))
925 }
926
927 pub fn line_start() -> Self {
931 Self::new(|| line_start_end(true, |li| li.text_range().start))
932 }
933
934 pub fn select_line_start() -> Self {
938 Self::new(|| line_start_end(false, |li| li.text_range().start))
939 }
940
941 pub fn line_end() -> Self {
945 Self::new(|| line_start_end(true, |li| li.text_caret_range().end))
946 }
947
948 pub fn select_line_end() -> Self {
952 Self::new(|| line_start_end(false, |li| li.text_caret_range().end))
953 }
954
955 pub fn text_start() -> Self {
959 Self::new(|| text_start_end(true, |_| 0))
960 }
961
962 pub fn select_text_start() -> Self {
966 Self::new(|| text_start_end(false, |_| 0))
967 }
968
969 pub fn text_end() -> Self {
973 Self::new(|| text_start_end(true, |s| s.len()))
974 }
975
976 pub fn select_text_end() -> Self {
980 Self::new(|| text_start_end(false, |s| s.len()))
981 }
982
983 pub fn nearest_to(window_point: DipPoint) -> Self {
987 Self::new(move || {
988 nearest_to(true, window_point);
989 })
990 }
991
992 pub fn select_nearest_to(window_point: DipPoint) -> Self {
996 Self::new(move || {
997 nearest_to(false, window_point);
998 })
999 }
1000
1001 pub fn select_index_nearest_to(window_point: DipPoint, move_selection_index: bool) -> Self {
1005 Self::new(move || {
1006 index_nearest_to(window_point, move_selection_index);
1007 })
1008 }
1009
1010 pub fn select_word_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
1014 Self::new(move || select_line_word_nearest_to(replace_selection, true, window_point))
1015 }
1016
1017 pub fn select_line_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
1021 Self::new(move || select_line_word_nearest_to(replace_selection, false, window_point))
1022 }
1023
1024 pub fn select_all() -> Self {
1026 Self::new(|| {
1027 let len = TEXT.resolved().segmented_text.text().len();
1028 let mut caret = TEXT.resolve_caret();
1029 caret.set_char_selection(0, len);
1030 caret.skip_next_scroll = true;
1031 })
1032 }
1033
1034 pub(super) fn call(self) {
1035 (self.op.lock())();
1036 }
1037}
1038
1039fn next_prev(
1040 clear_selection: bool,
1041 insert_index_fn: fn(&SegmentedText, usize) -> usize,
1042 selection_index: fn(&SegmentedText, ops::Range<CaretIndex>) -> usize,
1043) {
1044 let resolved = TEXT.resolved();
1045 let mut i = resolved.caret.index.unwrap_or(CaretIndex::ZERO);
1046 if clear_selection {
1047 i.index = if let Some(s) = resolved.caret.selection_range() {
1048 selection_index(&resolved.segmented_text, s)
1049 } else {
1050 insert_index_fn(&resolved.segmented_text, i.index)
1051 };
1052 } else {
1053 i.index = insert_index_fn(&resolved.segmented_text, i.index);
1054 }
1055 drop(resolved);
1056
1057 let mut c = TEXT.resolve_caret();
1058 if clear_selection {
1059 c.clear_selection();
1060 } else if c.selection_index.is_none() {
1061 c.selection_index = Some(i);
1062 }
1063 c.set_index(i);
1064 c.used_retained_x = false;
1065}
1066
1067fn line_up_down(clear_selection: bool, diff: i8) {
1068 let diff = diff as isize;
1069
1070 let mut caret = TEXT.resolve_caret();
1071 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1072 if clear_selection {
1073 caret.clear_selection();
1074 } else if caret.selection_index.is_none() {
1075 caret.selection_index = Some(i);
1076 }
1077 caret.used_retained_x = true;
1078
1079 let laidout = TEXT.laidout();
1080
1081 if laidout.caret_origin.is_some() {
1082 let last_line = laidout.shaped_text.lines_len().saturating_sub(1);
1083 let li = i.line;
1084 let next_li = li.saturating_add_signed(diff).min(last_line);
1085 if li != next_li {
1086 drop(caret);
1087 let resolved = TEXT.resolved();
1088 match laidout.shaped_text.line(next_li) {
1089 Some(l) => {
1090 i.line = next_li;
1091 i.index = match l.nearest_seg(laidout.caret_retained_x) {
1092 Some(s) => s.nearest_char_index(laidout.caret_retained_x, resolved.segmented_text.text()),
1093 None => l.text_range().end,
1094 }
1095 }
1096 None => i = CaretIndex::ZERO,
1097 };
1098 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
1099 drop(resolved);
1100 caret = TEXT.resolve_caret();
1101 caret.set_index(i);
1102 } else if diff == -1 {
1103 caret.set_char_index(0);
1104 } else if diff == 1 {
1105 drop(caret);
1106 let len = TEXT.resolved().segmented_text.text().len();
1107 caret = TEXT.resolve_caret();
1108 caret.set_char_index(len);
1109 }
1110 }
1111
1112 if caret.index.is_none() {
1113 caret.set_index(CaretIndex::ZERO);
1114 caret.clear_selection();
1115 }
1116}
1117
1118fn page_up_down(clear_selection: bool, diff: i8) {
1119 let diff = diff as i32;
1120
1121 let mut caret = TEXT.resolve_caret();
1122 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1123 if clear_selection {
1124 caret.clear_selection();
1125 } else if caret.selection_index.is_none() {
1126 caret.selection_index = Some(i);
1127 }
1128
1129 let laidout = TEXT.laidout();
1130
1131 let page_y = laidout.viewport.height * Px(diff);
1132 caret.used_retained_x = true;
1133 if laidout.caret_origin.is_some() {
1134 let li = i.line;
1135 if diff == -1 && li == 0 {
1136 caret.set_char_index(0);
1137 } else if diff == 1 && li == laidout.shaped_text.lines_len() - 1 {
1138 drop(caret);
1139 let len = TEXT.resolved().segmented_text.text().len();
1140 caret = TEXT.resolve_caret();
1141 caret.set_char_index(len);
1142 } else if let Some(li) = laidout.shaped_text.line(li) {
1143 drop(caret);
1144 let resolved = TEXT.resolved();
1145
1146 let target_line_y = li.rect().origin.y + page_y;
1147 match laidout.shaped_text.nearest_line(target_line_y) {
1148 Some(l) => {
1149 i.line = l.index();
1150 i.index = match l.nearest_seg(laidout.caret_retained_x) {
1151 Some(s) => s.nearest_char_index(laidout.caret_retained_x, resolved.segmented_text.text()),
1152 None => l.text_range().end,
1153 }
1154 }
1155 None => i = CaretIndex::ZERO,
1156 };
1157 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
1158
1159 drop(resolved);
1160 caret = TEXT.resolve_caret();
1161
1162 caret.set_index(i);
1163 }
1164 }
1165
1166 if caret.index.is_none() {
1167 caret.set_index(CaretIndex::ZERO);
1168 caret.clear_selection();
1169 }
1170}
1171
1172fn line_start_end(clear_selection: bool, index: impl FnOnce(ShapedLine) -> usize) {
1173 let mut caret = TEXT.resolve_caret();
1174 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1175 if clear_selection {
1176 caret.clear_selection();
1177 } else if caret.selection_index.is_none() {
1178 caret.selection_index = Some(i);
1179 }
1180
1181 if let Some(li) = TEXT.laidout().shaped_text.line(i.line) {
1182 i.index = index(li);
1183 caret.set_index(i);
1184 caret.used_retained_x = false;
1185 }
1186}
1187
1188fn text_start_end(clear_selection: bool, index: impl FnOnce(&str) -> usize) {
1189 let idx = index(TEXT.resolved().segmented_text.text());
1190
1191 let mut caret = TEXT.resolve_caret();
1192 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1193 if clear_selection {
1194 caret.clear_selection();
1195 } else if caret.selection_index.is_none() {
1196 caret.selection_index = Some(i);
1197 }
1198
1199 i.index = idx;
1200
1201 caret.set_index(i);
1202 caret.used_retained_x = false;
1203}
1204
1205fn nearest_to(clear_selection: bool, window_point: DipPoint) {
1206 let mut caret = TEXT.resolve_caret();
1207 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1208
1209 if clear_selection {
1210 caret.clear_selection();
1211 } else if caret.selection_index.is_none() {
1212 caret.selection_index = Some(i);
1213 } else if let Some((_, is_word)) = caret.initial_selection.clone() {
1214 drop(caret);
1215 return select_line_word_nearest_to(false, is_word, window_point);
1216 }
1217
1218 caret.used_retained_x = false;
1219
1220 let laidout = TEXT.laidout();
1222 if let Some(pos) = laidout
1223 .render_info
1224 .transform
1225 .inverse()
1226 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
1227 {
1228 drop(caret);
1229 let resolved = TEXT.resolved();
1230
1231 i = match laidout.shaped_text.nearest_line(pos.y) {
1233 Some(l) => CaretIndex {
1234 line: l.index(),
1235 index: match l.nearest_seg(pos.x) {
1236 Some(s) => s.nearest_char_index(pos.x, resolved.segmented_text.text()),
1237 None => l.text_range().end,
1238 },
1239 },
1240 None => CaretIndex::ZERO,
1241 };
1242 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
1243
1244 drop(resolved);
1245 caret = TEXT.resolve_caret();
1246
1247 caret.set_index(i);
1248 }
1249
1250 if caret.index.is_none() {
1251 caret.set_index(CaretIndex::ZERO);
1252 caret.clear_selection();
1253 }
1254}
1255
1256fn index_nearest_to(window_point: DipPoint, move_selection_index: bool) {
1257 let mut caret = TEXT.resolve_caret();
1258
1259 if caret.index.is_none() {
1260 caret.index = Some(CaretIndex::ZERO);
1261 }
1262 if caret.selection_index.is_none() {
1263 caret.selection_index = Some(caret.index.unwrap());
1264 }
1265
1266 caret.used_retained_x = false;
1267 caret.index_version += 1;
1268
1269 let laidout = TEXT.laidout();
1270 if let Some(pos) = laidout
1271 .render_info
1272 .transform
1273 .inverse()
1274 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
1275 {
1276 drop(caret);
1277 let resolved = TEXT.resolved();
1278
1279 let mut i = match laidout.shaped_text.nearest_line(pos.y) {
1280 Some(l) => CaretIndex {
1281 line: l.index(),
1282 index: match l.nearest_seg(pos.x) {
1283 Some(s) => s.nearest_char_index(pos.x, resolved.segmented_text.text()),
1284 None => l.text_range().end,
1285 },
1286 },
1287 None => CaretIndex::ZERO,
1288 };
1289 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
1290
1291 drop(resolved);
1292 caret = TEXT.resolve_caret();
1293
1294 if move_selection_index {
1295 caret.selection_index = Some(i);
1296 } else {
1297 caret.index = Some(i);
1298 }
1299 }
1300}
1301
1302fn select_line_word_nearest_to(replace_selection: bool, select_word: bool, window_point: DipPoint) {
1303 let mut caret = TEXT.resolve_caret();
1304
1305 let laidout = TEXT.laidout();
1307 if let Some(pos) = laidout
1308 .render_info
1309 .transform
1310 .inverse()
1311 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
1312 {
1313 if let Some(l) = laidout.shaped_text.nearest_line(pos.y) {
1315 let range = if select_word {
1316 let max_char = l.actual_text_caret_range().end;
1317 let mut r = l.nearest_seg(pos.x).map(|seg| seg.text_range()).unwrap_or_else(|| l.text_range());
1318 r.start = r.start.min(max_char);
1320 r.end = r.end.min(max_char);
1321 r
1322 } else {
1323 l.actual_text_caret_range()
1324 };
1325
1326 let merge_with_selection = if replace_selection {
1327 None
1328 } else {
1329 caret.initial_selection.clone().map(|(s, _)| s).or_else(|| caret.selection_range())
1330 };
1331 if let Some(mut s) = merge_with_selection {
1332 let caret_at_start = range.start < s.start.index;
1333 s.start.index = s.start.index.min(range.start);
1334 s.end.index = s.end.index.max(range.end);
1335
1336 if caret_at_start {
1337 caret.selection_index = Some(s.end);
1338 caret.set_index(s.start);
1339 } else {
1340 caret.selection_index = Some(s.start);
1341 caret.set_index(s.end);
1342 }
1343 } else {
1344 let start = CaretIndex {
1345 line: l.index(),
1346 index: range.start,
1347 };
1348 let end = CaretIndex {
1349 line: l.index(),
1350 index: range.end,
1351 };
1352 caret.selection_index = Some(start);
1353 caret.set_index(end);
1354
1355 caret.initial_selection = Some((start..end, select_word));
1356 }
1357
1358 return;
1359 };
1360 }
1361
1362 if caret.index.is_none() {
1363 caret.set_index(CaretIndex::ZERO);
1364 caret.clear_selection();
1365 }
1366}