1use std::{any::Any, borrow::Cow, cmp, fmt, ops, sync::Arc};
9
10use parking_lot::Mutex;
11use zng_ext_font::*;
12use zng_ext_l10n::l10n;
13use zng_ext_undo::*;
14use zng_layout::unit::DistanceKey;
15
16use crate::node::{RichText, RichTextWidgetInfoExt, notify_leaf_select_op};
17
18use super::{node::TEXT, *};
19
20command! {
21 pub static EDIT_CMD;
25
26 pub static SELECT_CMD;
30
31 pub static SELECT_ALL_CMD {
35 l10n!: true,
36 name: "Select All",
37 shortcut: shortcut!(CTRL + 'A'),
38 shortcut_filter: ShortcutFilter::FOCUSED | ShortcutFilter::CMD_ENABLED,
39 };
40
41 pub static PARSE_CMD;
45}
46
47struct SharedTextEditOp {
48 data: Box<dyn Any + Send>,
49 op: Box<dyn FnMut(&mut dyn Any, UndoFullOp) + Send>,
50}
51
52#[derive(Clone)]
54pub struct TextEditOp(Arc<Mutex<SharedTextEditOp>>);
55impl fmt::Debug for TextEditOp {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.debug_struct("TextEditOp").finish_non_exhaustive()
58 }
59}
60impl TextEditOp {
61 pub fn new<D>(data: D, mut op: impl FnMut(&mut D, UndoFullOp) + Send + 'static) -> Self
81 where
82 D: Send + Any + 'static,
83 {
84 Self(Arc::new(Mutex::new(SharedTextEditOp {
85 data: Box::new(data),
86 op: Box::new(move |data, o| op(data.downcast_mut().unwrap(), o)),
87 })))
88 }
89
90 pub fn insert(insert: impl Into<Txt>) -> Self {
95 struct InsertData {
96 insert: Txt,
97 selection_state: SelectionState,
98 removed: Txt,
99 }
100 let data = InsertData {
101 insert: insert.into(),
102 selection_state: SelectionState::PreInit,
103 removed: Txt::from_static(""),
104 };
105
106 Self::new(data, move |data, op| match op {
107 UndoFullOp::Init { redo } => {
108 let ctx = TEXT.resolved();
109 let caret = &ctx.caret;
110
111 let mut rmv_range = 0..0;
112
113 if let Some(range) = caret.selection_range() {
114 rmv_range = range.start.index..range.end.index;
115
116 ctx.txt.with(|t| {
117 let r = &t[rmv_range.clone()];
118 if r != data.removed {
119 data.removed = Txt::from_str(r);
120 }
121 });
122
123 if range.start.index == caret.index.unwrap_or(CaretIndex::ZERO).index {
124 data.selection_state = SelectionState::CaretSelection(range.start, range.end);
125 } else {
126 data.selection_state = SelectionState::SelectionCaret(range.start, range.end);
127 }
128 } else {
129 data.selection_state = SelectionState::Caret(caret.index.unwrap_or(CaretIndex::ZERO));
130 }
131
132 Self::apply_max_count(redo, &ctx.txt, rmv_range, &mut data.insert)
133 }
134 UndoFullOp::Op(UndoOp::Redo) => {
135 let insert = &data.insert;
136
137 match data.selection_state {
138 SelectionState::PreInit => unreachable!(),
139 SelectionState::Caret(insert_idx) => {
140 let i = insert_idx.index;
141 TEXT.resolved().txt.modify(clmv!(insert, |args| {
142 args.to_mut().insert_str(i, insert.as_str());
143 }));
144
145 let mut i = insert_idx;
146 i.index += insert.len();
147
148 let mut caret = TEXT.resolve_caret();
149 caret.set_index(i);
150 caret.clear_selection();
151 }
152 SelectionState::CaretSelection(start, end) | SelectionState::SelectionCaret(start, end) => {
153 let char_range = start.index..end.index;
154 TEXT.resolved().txt.modify(clmv!(insert, |args| {
155 args.to_mut().replace_range(char_range, insert.as_str());
156 }));
157
158 let mut caret = TEXT.resolve_caret();
159 caret.set_char_index(start.index + insert.len());
160 caret.clear_selection();
161 }
162 }
163 }
164 UndoFullOp::Op(UndoOp::Undo) => {
165 let len = data.insert.len();
166 let (insert_idx, selection_idx, caret_idx) = match data.selection_state {
167 SelectionState::Caret(c) => (c, None, c),
168 SelectionState::CaretSelection(start, end) => (start, Some(end), start),
169 SelectionState::SelectionCaret(start, end) => (start, Some(start), end),
170 SelectionState::PreInit => unreachable!(),
171 };
172 let i = insert_idx.index;
173 let removed = &data.removed;
174
175 TEXT.resolved().txt.modify(clmv!(removed, |args| {
176 args.to_mut().replace_range(i..i + len, removed.as_str());
177 }));
178
179 let mut caret = TEXT.resolve_caret();
180 caret.set_index(caret_idx);
181 caret.selection_index = selection_idx;
182 }
183 UndoFullOp::Info { info } => {
184 let mut label = Txt::from_static("\"");
185 for (i, mut c) in data.insert.chars().take(21).enumerate() {
186 if i == 20 {
187 c = '…';
188 } else if c == '\n' {
189 c = '↵';
190 } else if c == '\t' {
191 c = '→';
192 } else if c == '\r' {
193 continue;
194 }
195 label.push(c);
196 }
197 label.push('"');
198 *info = Some(Arc::new(label));
199 }
200 UndoFullOp::Merge {
201 next_data,
202 within_undo_interval,
203 merged,
204 ..
205 } => {
206 if within_undo_interval
207 && let Some(next_data) = next_data.downcast_mut::<InsertData>()
208 && let SelectionState::Caret(mut after_idx) = data.selection_state
209 && let SelectionState::Caret(caret) = next_data.selection_state
210 {
211 after_idx.index += data.insert.len();
212
213 if after_idx.index == caret.index {
214 data.insert.push_str(&next_data.insert);
215 *merged = true;
216 }
217 }
218 }
219 })
220 }
221
222 pub fn backspace() -> Self {
228 Self::backspace_impl(SegmentedText::backspace_range)
229 }
230 pub fn backspace_word() -> Self {
236 Self::backspace_impl(SegmentedText::backspace_word_range)
237 }
238 fn backspace_impl(backspace_range: fn(&SegmentedText, usize, u32) -> std::ops::Range<usize>) -> Self {
239 struct BackspaceData {
240 selection_state: SelectionState,
241 count: u32,
242 removed: Txt,
243 }
244 let data = BackspaceData {
245 selection_state: SelectionState::PreInit,
246 count: 1,
247 removed: Txt::from_static(""),
248 };
249
250 Self::new(data, move |data, op| match op {
251 UndoFullOp::Init { .. } => {
252 let ctx = TEXT.resolved();
253 let caret = &ctx.caret;
254
255 if let Some(range) = caret.selection_range() {
256 if range.start.index == caret.index.unwrap_or(CaretIndex::ZERO).index {
257 data.selection_state = SelectionState::CaretSelection(range.start, range.end);
258 } else {
259 data.selection_state = SelectionState::SelectionCaret(range.start, range.end);
260 }
261 } else {
262 data.selection_state = SelectionState::Caret(caret.index.unwrap_or(CaretIndex::ZERO));
263 }
264 }
265 UndoFullOp::Op(UndoOp::Redo) => {
266 let rmv = match data.selection_state {
267 SelectionState::Caret(c) => backspace_range(&TEXT.resolved().segmented_text, c.index, data.count),
268 SelectionState::CaretSelection(s, e) | SelectionState::SelectionCaret(s, e) => s.index..e.index,
269 SelectionState::PreInit => unreachable!(),
270 };
271 if rmv.is_empty() {
272 data.removed = Txt::from_static("");
273 return;
274 }
275
276 {
277 let mut caret = TEXT.resolve_caret();
278 caret.set_char_index(rmv.start);
279 caret.clear_selection();
280 }
281
282 let ctx = TEXT.resolved();
283 ctx.txt.with(|t| {
284 let r = &t[rmv.clone()];
285 if r != data.removed {
286 data.removed = Txt::from_str(r);
287 }
288 });
289
290 ctx.txt.modify(move |args| {
291 args.to_mut().replace_range(rmv, "");
292 });
293 }
294 UndoFullOp::Op(UndoOp::Undo) => {
295 if data.removed.is_empty() {
296 return;
297 }
298
299 let (insert_idx, selection_idx, caret_idx) = match data.selection_state {
300 SelectionState::Caret(c) => (c.index - data.removed.len(), None, c),
301 SelectionState::CaretSelection(s, e) => (s.index, Some(e), s),
302 SelectionState::SelectionCaret(s, e) => (s.index, Some(s), e),
303 SelectionState::PreInit => unreachable!(),
304 };
305 let removed = &data.removed;
306
307 TEXT.resolved().txt.modify(clmv!(removed, |args| {
308 args.to_mut().insert_str(insert_idx, removed.as_str());
309 }));
310
311 let mut caret = TEXT.resolve_caret();
312 caret.set_index(caret_idx);
313 caret.selection_index = selection_idx;
314 }
315 UndoFullOp::Info { info } => {
316 *info = Some(if data.count == 1 {
317 Arc::new("⌫")
318 } else {
319 Arc::new(formatx!("⌫ (x{})", data.count))
320 })
321 }
322 UndoFullOp::Merge {
323 next_data,
324 within_undo_interval,
325 merged,
326 ..
327 } => {
328 if within_undo_interval
329 && let Some(next_data) = next_data.downcast_mut::<BackspaceData>()
330 && let SelectionState::Caret(mut after_idx) = data.selection_state
331 && let SelectionState::Caret(caret) = next_data.selection_state
332 {
333 after_idx.index -= data.removed.len();
334
335 if after_idx.index == caret.index {
336 data.count += next_data.count;
337
338 next_data.removed.push_str(&data.removed);
339 data.removed = std::mem::take(&mut next_data.removed);
340 *merged = true;
341 }
342 }
343 }
344 })
345 }
346
347 pub fn delete() -> Self {
353 Self::delete_impl(SegmentedText::delete_range)
354 }
355 pub fn delete_word() -> Self {
361 Self::delete_impl(SegmentedText::delete_word_range)
362 }
363 fn delete_impl(delete_range: fn(&SegmentedText, usize, u32) -> std::ops::Range<usize>) -> Self {
364 struct DeleteData {
365 selection_state: SelectionState,
366 count: u32,
367 removed: Txt,
368 }
369 let data = DeleteData {
370 selection_state: SelectionState::PreInit,
371 count: 1,
372 removed: Txt::from_static(""),
373 };
374
375 Self::new(data, move |data, op| match op {
376 UndoFullOp::Init { .. } => {
377 let ctx = TEXT.resolved();
378 let caret = &ctx.caret;
379
380 if let Some(range) = caret.selection_range() {
381 if range.start.index == caret.index.unwrap_or(CaretIndex::ZERO).index {
382 data.selection_state = SelectionState::CaretSelection(range.start, range.end);
383 } else {
384 data.selection_state = SelectionState::SelectionCaret(range.start, range.end);
385 }
386 } else {
387 data.selection_state = SelectionState::Caret(caret.index.unwrap_or(CaretIndex::ZERO));
388 }
389 }
390 UndoFullOp::Op(UndoOp::Redo) => {
391 let rmv = match data.selection_state {
392 SelectionState::CaretSelection(s, e) | SelectionState::SelectionCaret(s, e) => s.index..e.index,
393 SelectionState::Caret(c) => delete_range(&TEXT.resolved().segmented_text, c.index, data.count),
394 SelectionState::PreInit => unreachable!(),
395 };
396
397 if rmv.is_empty() {
398 data.removed = Txt::from_static("");
399 return;
400 }
401
402 {
403 let mut caret = TEXT.resolve_caret();
404 caret.set_char_index(rmv.start); caret.clear_selection();
406 }
407
408 let ctx = TEXT.resolved();
409 ctx.txt.with(|t| {
410 let r = &t[rmv.clone()];
411 if r != data.removed {
412 data.removed = Txt::from_str(r);
413 }
414 });
415 ctx.txt.modify(move |args| {
416 args.to_mut().replace_range(rmv, "");
417 });
418 }
419 UndoFullOp::Op(UndoOp::Undo) => {
420 let removed = &data.removed;
421
422 if data.removed.is_empty() {
423 return;
424 }
425
426 let (insert_idx, selection_idx, caret_idx) = match data.selection_state {
427 SelectionState::Caret(c) => (c.index, None, c),
428 SelectionState::CaretSelection(s, e) => (s.index, Some(e), s),
429 SelectionState::SelectionCaret(s, e) => (s.index, Some(s), e),
430 SelectionState::PreInit => unreachable!(),
431 };
432
433 TEXT.resolved().txt.modify(clmv!(removed, |args| {
434 args.to_mut().insert_str(insert_idx, removed.as_str());
435 }));
436
437 let mut caret = TEXT.resolve_caret();
438 caret.set_index(caret_idx); caret.selection_index = selection_idx;
440 }
441 UndoFullOp::Info { info } => {
442 *info = Some(if data.count == 1 {
443 Arc::new("⌦")
444 } else {
445 Arc::new(formatx!("⌦ (x{})", data.count))
446 })
447 }
448 UndoFullOp::Merge {
449 next_data,
450 within_undo_interval,
451 merged,
452 ..
453 } => {
454 if within_undo_interval
455 && let Some(next_data) = next_data.downcast_ref::<DeleteData>()
456 && let SelectionState::Caret(after_idx) = data.selection_state
457 && let SelectionState::Caret(caret) = next_data.selection_state
458 && after_idx.index == caret.index
459 {
460 data.count += next_data.count;
461 data.removed.push_str(&next_data.removed);
462 *merged = true;
463 }
464 }
465 })
466 }
467
468 fn apply_max_count(redo: &mut bool, txt: &Var<Txt>, rmv_range: ops::Range<usize>, insert: &mut Txt) {
469 let max_count = MAX_CHARS_COUNT_VAR.get();
470 if max_count > 0 {
471 let (txt_count, rmv_count) = txt.with(|t| (t.chars().count(), t[rmv_range].chars().count()));
473 let ins_count = insert.chars().count();
474
475 let final_count = txt_count - rmv_count + ins_count;
476 if final_count > max_count {
477 let ins_rmv = final_count - max_count;
479 if ins_rmv < ins_count {
480 let i = insert.char_indices().nth(ins_count - ins_rmv).unwrap().0;
482 insert.truncate(i);
483 } else {
484 debug_assert!(txt_count >= max_count);
486 *redo = false;
487 }
488 }
489 }
490 }
491
492 pub fn clear() -> Self {
494 #[derive(Default, Clone)]
495 struct Cleared {
496 txt: Txt,
497 selection: SelectionState,
498 }
499 Self::new(Cleared::default(), |data, op| match op {
500 UndoFullOp::Init { .. } => {
501 let ctx = TEXT.resolved();
502 data.txt = ctx.txt.get();
503 if let Some(range) = ctx.caret.selection_range() {
504 if range.start.index == ctx.caret.index.unwrap_or(CaretIndex::ZERO).index {
505 data.selection = SelectionState::CaretSelection(range.start, range.end);
506 } else {
507 data.selection = SelectionState::SelectionCaret(range.start, range.end);
508 }
509 } else {
510 data.selection = SelectionState::Caret(ctx.caret.index.unwrap_or(CaretIndex::ZERO));
511 };
512 }
513 UndoFullOp::Op(UndoOp::Redo) => {
514 TEXT.resolved().txt.set("");
515 }
516 UndoFullOp::Op(UndoOp::Undo) => {
517 TEXT.resolved().txt.set(data.txt.clone());
518
519 let (selection_idx, caret_idx) = match data.selection {
520 SelectionState::Caret(c) => (None, c),
521 SelectionState::CaretSelection(s, e) => (Some(e), s),
522 SelectionState::SelectionCaret(s, e) => (Some(s), e),
523 SelectionState::PreInit => unreachable!(),
524 };
525 let mut caret = TEXT.resolve_caret();
526 caret.set_index(caret_idx); caret.selection_index = selection_idx;
528 }
529 UndoFullOp::Info { info } => *info = Some(Arc::new(l10n!("text-edit-op.clear", "clear").get())),
530 UndoFullOp::Merge {
531 next_data,
532 within_undo_interval,
533 merged,
534 ..
535 } => *merged = within_undo_interval && next_data.is::<Cleared>(),
536 })
537 }
538
539 pub fn replace(mut select_before: ops::Range<usize>, insert: impl Into<Txt>, mut select_after: ops::Range<usize>) -> Self {
546 let mut insert = insert.into();
547 let mut removed = Txt::from_static("");
548
549 Self::new((), move |_, op| match op {
550 UndoFullOp::Init { redo } => {
551 let ctx = TEXT.resolved();
552
553 select_before.start = ctx.segmented_text.snap_grapheme_boundary(select_before.start);
554 select_before.end = ctx.segmented_text.snap_grapheme_boundary(select_before.end);
555
556 ctx.txt.with(|t| {
557 removed = Txt::from_str(&t[select_before.clone()]);
558 });
559
560 Self::apply_max_count(redo, &ctx.txt, select_before.clone(), &mut insert);
561 }
562 UndoFullOp::Op(UndoOp::Redo) => {
563 TEXT.resolved().txt.modify(clmv!(select_before, insert, |args| {
564 args.to_mut().replace_range(select_before, insert.as_str());
565 }));
566
567 TEXT.resolve_caret().set_char_selection(select_after.start, select_after.end);
568 }
569 UndoFullOp::Op(UndoOp::Undo) => {
570 let ctx = TEXT.resolved();
571
572 select_after.start = ctx.segmented_text.snap_grapheme_boundary(select_after.start);
573 select_after.end = ctx.segmented_text.snap_grapheme_boundary(select_after.end);
574
575 ctx.txt.modify(clmv!(select_after, removed, |args| {
576 args.to_mut().replace_range(select_after, removed.as_str());
577 }));
578
579 drop(ctx);
580 TEXT.resolve_caret().set_char_selection(select_before.start, select_before.end);
581 }
582 UndoFullOp::Info { info } => *info = Some(Arc::new(l10n!("text-edit-op.replace", "replace").get())),
583 UndoFullOp::Merge { .. } => {}
584 })
585 }
586
587 pub fn apply_transforms() -> Self {
589 let mut prev = Txt::from_static("");
590 let mut transform = None::<(TextTransformFn, WhiteSpace)>;
591 Self::new((), move |_, op| match op {
592 UndoFullOp::Init { .. } => {}
593 UndoFullOp::Op(UndoOp::Redo) => {
594 let (t, w) = transform.get_or_insert_with(|| (TEXT_TRANSFORM_VAR.get(), WHITE_SPACE_VAR.get()));
595
596 let ctx = TEXT.resolved();
597
598 let new_txt = ctx.txt.with(|txt| {
599 let transformed = t.transform(txt);
600 let white_spaced = w.transform(transformed.as_ref());
601 if let Cow::Owned(w) = white_spaced {
602 Some(w)
603 } else if let Cow::Owned(t) = transformed {
604 Some(t)
605 } else {
606 None
607 }
608 });
609
610 if let Some(t) = new_txt {
611 if ctx.txt.with(|t| t != prev.as_str()) {
612 prev = ctx.txt.get();
613 }
614 ctx.txt.set(t);
615 }
616 }
617 UndoFullOp::Op(UndoOp::Undo) => {
618 let ctx = TEXT.resolved();
619
620 if ctx.txt.with(|t| t != prev.as_str()) {
621 ctx.txt.set(prev.clone());
622 }
623 }
624 UndoFullOp::Info { info } => *info = Some(Arc::new(l10n!("text-edit-op.transform", "transform").get())),
625 UndoFullOp::Merge { .. } => {}
626 })
627 }
628
629 fn call(self) -> bool {
630 {
631 let mut op = self.0.lock();
632 let op = &mut *op;
633
634 let mut redo = true;
635 (op.op)(&mut *op.data, UndoFullOp::Init { redo: &mut redo });
636 if !redo {
637 return false;
638 }
639
640 (op.op)(&mut *op.data, UndoFullOp::Op(UndoOp::Redo));
641 }
642
643 if !OBSCURE_TXT_VAR.get() {
644 UNDO.register(UndoTextEditOp::new(self));
645 }
646 true
647 }
648
649 pub(super) fn call_edit_op(self) {
650 let registered = self.call();
651 if registered && !TEXT.resolved().pending_edit {
652 TEXT.resolve().pending_edit = true;
653 WIDGET.update(); }
655 }
656}
657#[derive(Clone, Copy, Default)]
659enum SelectionState {
660 #[default]
661 PreInit,
662 Caret(CaretIndex),
663 CaretSelection(CaretIndex, CaretIndex),
664 SelectionCaret(CaretIndex, CaretIndex),
665}
666
667#[derive(Debug, Clone)]
669pub(super) struct UndoTextEditOp {
670 pub target: WidgetId,
671 edit_op: TextEditOp,
672 exec_op: UndoOp,
673}
674impl UndoTextEditOp {
675 fn new(edit_op: TextEditOp) -> Self {
676 Self {
677 target: WIDGET.id(),
678 edit_op,
679 exec_op: UndoOp::Undo,
680 }
681 }
682
683 pub(super) fn call(&self) {
684 let mut op = self.edit_op.0.lock();
685 let op = &mut *op;
686 (op.op)(&mut *op.data, UndoFullOp::Op(self.exec_op))
687 }
688}
689impl UndoAction for UndoTextEditOp {
690 fn undo(self: Box<Self>) -> Box<dyn RedoAction> {
691 EDIT_CMD.scoped(self.target).notify_param(Self {
692 target: self.target,
693 edit_op: self.edit_op.clone(),
694 exec_op: UndoOp::Undo,
695 });
696 self
697 }
698
699 fn info(&mut self) -> Arc<dyn UndoInfo> {
700 let mut op = self.edit_op.0.lock();
701 let op = &mut *op;
702 let mut info = None;
703 (op.op)(&mut *op.data, UndoFullOp::Info { info: &mut info });
704
705 info.unwrap_or_else(|| Arc::new(l10n!("text-edit-op.generic", "text edit").get()))
706 }
707
708 fn as_any(&mut self) -> &mut dyn std::any::Any {
709 self
710 }
711
712 fn merge(self: Box<Self>, mut args: UndoActionMergeArgs) -> Result<Box<dyn UndoAction>, (Box<dyn UndoAction>, Box<dyn UndoAction>)> {
713 if let Some(next) = args.next.as_any().downcast_mut::<Self>() {
714 let mut merged = false;
715
716 {
717 let mut op = self.edit_op.0.lock();
718 let op = &mut *op;
719
720 let mut next_op = next.edit_op.0.lock();
721
722 (op.op)(
723 &mut *op.data,
724 UndoFullOp::Merge {
725 next_data: &mut *next_op.data,
726 prev_timestamp: args.prev_timestamp,
727 within_undo_interval: args.within_undo_interval,
728 merged: &mut merged,
729 },
730 );
731 }
732
733 if merged {
734 return Ok(self);
735 }
736 }
737
738 Err((self, args.next))
739 }
740}
741impl RedoAction for UndoTextEditOp {
742 fn redo(self: Box<Self>) -> Box<dyn UndoAction> {
743 EDIT_CMD.scoped(self.target).notify_param(Self {
744 target: self.target,
745 edit_op: self.edit_op.clone(),
746 exec_op: UndoOp::Redo,
747 });
748 self
749 }
750
751 fn info(&mut self) -> Arc<dyn UndoInfo> {
752 let mut op = self.edit_op.0.lock();
753 let op = &mut *op;
754 let mut info = None;
755 (op.op)(&mut *op.data, UndoFullOp::Info { info: &mut info });
756
757 info.unwrap_or_else(|| Arc::new(l10n!("text-edit-op.generic", "text edit").get()))
758 }
759}
760
761#[derive(Clone)]
768pub struct TextSelectOp {
769 op: Arc<Mutex<dyn FnMut() + Send>>,
770}
771impl fmt::Debug for TextSelectOp {
772 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
773 f.debug_struct("TextSelectOp").finish_non_exhaustive()
774 }
775}
776impl TextSelectOp {
777 pub fn next() -> Self {
781 rich_clear_next_prev(true, false)
782 }
783
784 pub fn select_next() -> Self {
788 rich_select_next_prev(true, false)
789 }
790
791 pub fn prev() -> Self {
795 rich_clear_next_prev(false, false)
796 }
797
798 pub fn select_prev() -> Self {
802 rich_select_next_prev(false, false)
803 }
804
805 pub fn next_word() -> Self {
809 rich_clear_next_prev(true, true)
810 }
811 pub fn select_next_word() -> Self {
815 rich_select_next_prev(true, true)
816 }
817 pub fn prev_word() -> Self {
821 rich_clear_next_prev(false, true)
822 }
823
824 pub fn select_prev_word() -> Self {
828 rich_select_next_prev(false, true)
829 }
830
831 pub fn line_up() -> Self {
835 rich_up_down(true, false, false)
836 }
837
838 pub fn select_line_up() -> Self {
842 rich_up_down(false, false, false)
843 }
844
845 pub fn line_down() -> Self {
849 rich_up_down(true, true, false)
850 }
851
852 pub fn select_line_down() -> Self {
856 rich_up_down(false, true, false)
857 }
858
859 pub fn page_up() -> Self {
863 rich_up_down(true, false, true)
864 }
865
866 pub fn select_page_up() -> Self {
870 rich_up_down(false, false, true)
871 }
872
873 pub fn page_down() -> Self {
877 rich_up_down(true, true, true)
878 }
879
880 pub fn select_page_down() -> Self {
884 rich_up_down(false, true, true)
885 }
886
887 pub fn line_start() -> Self {
891 rich_line_start_end(true, false)
892 }
893
894 pub fn select_line_start() -> Self {
898 rich_line_start_end(false, false)
899 }
900
901 pub fn line_end() -> Self {
905 rich_line_start_end(true, true)
906 }
907
908 pub fn select_line_end() -> Self {
912 rich_line_start_end(false, true)
913 }
914
915 pub fn text_start() -> Self {
919 rich_text_start_end(true, false)
920 }
921
922 pub fn select_text_start() -> Self {
926 rich_text_start_end(false, false)
927 }
928
929 pub fn text_end() -> Self {
933 rich_text_start_end(true, true)
934 }
935
936 pub fn select_text_end() -> Self {
940 rich_text_start_end(false, true)
941 }
942
943 pub fn nearest_to(window_point: DipPoint) -> Self {
947 rich_nearest_char_word_to(true, window_point, false)
948 }
949
950 pub fn select_nearest_to(window_point: DipPoint) -> Self {
954 rich_nearest_char_word_to(false, window_point, false)
955 }
956
957 pub fn select_word_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
961 rich_nearest_char_word_to(replace_selection, window_point, true)
962 }
963
964 pub fn select_line_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
968 rich_nearest_line_to(replace_selection, window_point)
969 }
970
971 pub fn select_index_nearest_to(window_point: DipPoint, move_selection_index: bool) -> Self {
975 rich_selection_index_nearest_to(window_point, move_selection_index)
976 }
977
978 pub fn select_all() -> Self {
980 Self::new_rich(
981 |ctx| (ctx.leaves_rev().next().map(|w| w.id()).unwrap_or_else(|| WIDGET.id()), ()),
982 |()| {
983 TEXT.resolve_caret().skip_next_scroll = true;
984 (
985 CaretIndex {
986 index: TEXT.resolved().segmented_text.text().len(),
987 line: 0,
988 },
989 (),
990 )
991 },
992 |ctx, ()| Some((ctx.leaves().next().map(|w| w.id()).unwrap_or_else(|| WIDGET.id()), ())),
993 |()| {
994 TEXT.resolve_caret().skip_next_scroll = true;
995 Some(CaretIndex::ZERO)
996 },
997 )
998 }
999
1000 pub fn clear_selection() -> Self {
1004 Self::new_rich(
1005 |ctx| (ctx.caret.index.unwrap_or_else(|| WIDGET.id()), ()),
1006 |()| (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), ()),
1007 |_, ()| None,
1008 |()| None,
1009 )
1010 }
1011}
1012
1013impl TextSelectOp {
1015 pub fn local_next() -> Self {
1019 Self::new(|| {
1020 local_clear_next_prev(true, false);
1021 })
1022 }
1023
1024 pub fn local_select_next() -> Self {
1028 Self::new(|| {
1029 local_select_next_prev(true, false);
1030 })
1031 }
1032
1033 pub fn local_prev() -> Self {
1037 Self::new(|| {
1038 local_clear_next_prev(false, false);
1039 })
1040 }
1041
1042 pub fn local_select_prev() -> Self {
1046 Self::new(|| {
1047 local_select_next_prev(false, false);
1048 })
1049 }
1050
1051 pub fn local_next_word() -> Self {
1055 Self::new(|| {
1056 local_clear_next_prev(true, true);
1057 })
1058 }
1059
1060 pub fn local_select_next_word() -> Self {
1064 Self::new(|| {
1065 local_select_next_prev(true, true);
1066 })
1067 }
1068
1069 pub fn local_prev_word() -> Self {
1073 Self::new(|| {
1074 local_clear_next_prev(false, true);
1075 })
1076 }
1077
1078 pub fn local_select_prev_word() -> Self {
1082 Self::new(|| {
1083 local_select_next_prev(false, true);
1084 })
1085 }
1086
1087 pub fn local_line_start() -> Self {
1091 Self::new(|| local_line_start_end(true, false))
1092 }
1093
1094 pub fn local_select_line_start() -> Self {
1098 Self::new(|| local_line_start_end(false, false))
1099 }
1100
1101 pub fn local_line_end() -> Self {
1105 Self::new(|| local_line_start_end(true, true))
1106 }
1107
1108 pub fn local_select_line_end() -> Self {
1112 Self::new(|| local_line_start_end(false, true))
1113 }
1114
1115 pub fn local_text_start() -> Self {
1119 Self::new(|| local_text_start_end(true, false))
1120 }
1121
1122 pub fn local_select_text_start() -> Self {
1126 Self::new(|| local_text_start_end(false, false))
1127 }
1128
1129 pub fn local_text_end() -> Self {
1133 Self::new(|| local_text_start_end(true, true))
1134 }
1135
1136 pub fn local_select_text_end() -> Self {
1140 Self::new(|| local_text_start_end(false, true))
1141 }
1142
1143 pub fn local_select_all() -> Self {
1147 Self::new(|| {
1148 let len = TEXT.resolved().segmented_text.text().len();
1149 let mut caret = TEXT.resolve_caret();
1150 caret.set_char_selection(0, len);
1151 caret.skip_next_scroll = true;
1152 })
1153 }
1154
1155 pub fn local_clear_selection() -> Self {
1159 Self::new(|| {
1160 let mut ctx = TEXT.resolve_caret();
1161 ctx.clear_selection();
1162 })
1163 }
1164
1165 pub fn local_line_up() -> Self {
1169 Self::new(|| local_line_up_down(true, -1))
1170 }
1171
1172 pub fn local_select_line_up() -> Self {
1176 Self::new(|| local_line_up_down(false, -1))
1177 }
1178
1179 pub fn local_line_down() -> Self {
1183 Self::new(|| local_line_up_down(true, 1))
1184 }
1185
1186 pub fn local_select_line_down() -> Self {
1190 Self::new(|| local_line_up_down(false, 1))
1191 }
1192
1193 pub fn local_page_up() -> Self {
1197 Self::new(|| local_page_up_down(true, -1))
1198 }
1199
1200 pub fn local_select_page_up() -> Self {
1204 Self::new(|| local_page_up_down(false, -1))
1205 }
1206
1207 pub fn local_page_down() -> Self {
1211 Self::new(|| local_page_up_down(true, 1))
1212 }
1213
1214 pub fn local_select_page_down() -> Self {
1218 Self::new(|| local_page_up_down(false, 1))
1219 }
1220
1221 pub fn local_nearest_to(window_point: DipPoint) -> Self {
1225 Self::new(move || {
1226 local_nearest_to(true, window_point);
1227 })
1228 }
1229
1230 pub fn local_select_nearest_to(window_point: DipPoint) -> Self {
1234 Self::new(move || {
1235 local_nearest_to(false, window_point);
1236 })
1237 }
1238
1239 pub fn local_select_index_nearest_to(window_point: DipPoint, move_selection_index: bool) -> Self {
1243 Self::new(move || {
1244 local_select_index_nearest_to(window_point, move_selection_index);
1245 })
1246 }
1247
1248 pub fn local_select_word_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
1252 Self::new(move || local_select_line_word_nearest_to(replace_selection, true, window_point))
1253 }
1254
1255 pub fn local_select_line_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
1259 Self::new(move || local_select_line_word_nearest_to(replace_selection, false, window_point))
1260 }
1261}
1262
1263impl TextSelectOp {
1264 pub fn new(op: impl FnMut() + Send + 'static) -> Self {
1274 Self {
1275 op: Arc::new(Mutex::new(op)),
1276 }
1277 }
1278
1279 pub fn new_rich<D0, D1, D2>(
1296 rich_caret_index: impl FnOnce(&RichText) -> (WidgetId, D0) + Send + 'static,
1297 local_caret_index: impl FnOnce(D0) -> (CaretIndex, D1) + Send + 'static,
1298 rich_selection_index: impl FnOnce(&RichText, D1) -> Option<(WidgetId, D2)> + Send + 'static,
1299 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex> + Send + 'static,
1300 ) -> Self
1301 where
1302 D0: Default + Send + 'static,
1303 D1: Send + 'static,
1304 D2: Default + Send + 'static,
1305 {
1306 let mut f0 = Some(rich_caret_index);
1307 let mut f1 = Some(local_caret_index);
1308 let mut f2 = Some(rich_selection_index);
1309 let mut f3 = Some(local_selection_index);
1310 Self::new(move || {
1311 if let Some(ctx) = TEXT.try_rich() {
1312 rich_select_op_start(ctx, f0.take().unwrap(), f1.take().unwrap(), f2.take().unwrap(), f3.take().unwrap());
1313 } else {
1314 let (index, _) = f1.take().unwrap()(D0::default());
1315 let selection_index = f3.take().unwrap()(D2::default());
1316 let mut ctx = TEXT.resolve_caret();
1317 ctx.selection_index = selection_index;
1318 ctx.set_index(index);
1319 }
1320 })
1321 }
1322
1323 pub(super) fn call(self) {
1324 (self.op.lock())();
1325 }
1326}
1327
1328fn rich_select_op_start<D0: Send + 'static, D1: Send + 'static, D2: Send + 'static>(
1329 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1330 rich_caret_index: impl FnOnce(&RichText) -> (WidgetId, D0),
1331 local_caret_index: impl FnOnce(D0) -> (CaretIndex, D1) + Send + 'static,
1332 rich_selection_index: impl FnOnce(&RichText, D1) -> Option<(WidgetId, D2)> + Send + 'static,
1333 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex> + Send + 'static,
1334) {
1335 let (index, d0) = rich_caret_index(&ctx);
1336 if index == WIDGET.id() {
1337 rich_select_op_get_caret(ctx, index, d0, local_caret_index, rich_selection_index, local_selection_index);
1338 } else {
1339 let mut d0 = Some(d0);
1340 let mut f0 = Some(local_caret_index);
1341 let mut f1 = Some(rich_selection_index);
1342 let mut f2 = Some(local_selection_index);
1343 notify_leaf_select_op(
1344 index,
1345 TextSelectOp::new(move || {
1346 if let Some(ctx) = TEXT.try_rich()
1347 && index == WIDGET.id()
1348 {
1349 rich_select_op_get_caret(
1350 ctx,
1351 index,
1352 d0.take().unwrap(),
1353 f0.take().unwrap(),
1354 f1.take().unwrap(),
1355 f2.take().unwrap(),
1356 );
1357 }
1358 }),
1359 );
1360 }
1361}
1362fn rich_select_op_get_caret<D0, D1, D2: Send + 'static>(
1363 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1364 rich_caret_index: WidgetId,
1365 d0: D0,
1366 local_caret_index: impl FnOnce(D0) -> (CaretIndex, D1),
1367 rich_selection_index: impl FnOnce(&RichText, D1) -> Option<(WidgetId, D2)>,
1368 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex> + Send + 'static,
1369) {
1370 let (index, d1) = local_caret_index(d0);
1371 {
1372 let mut ctx = TEXT.resolve_caret();
1373 ctx.set_index(index);
1374 }
1375
1376 match rich_selection_index(&ctx, d1) {
1377 Some((selection_index, d2)) => {
1378 if selection_index == WIDGET.id() {
1379 rich_select_op_get_selection(ctx, (rich_caret_index, index), selection_index, d2, local_selection_index);
1380 } else {
1381 let mut d2 = Some(d2);
1382 let mut f0 = Some(local_selection_index);
1383 notify_leaf_select_op(
1384 selection_index,
1385 TextSelectOp::new(move || {
1386 if let Some(ctx) = TEXT.try_rich()
1387 && selection_index == WIDGET.id()
1388 {
1389 rich_select_op_get_selection(
1390 ctx,
1391 (rich_caret_index, index),
1392 selection_index,
1393 d2.take().unwrap(),
1394 f0.take().unwrap(),
1395 );
1396 }
1397 }),
1398 );
1399 }
1400 }
1401 None => rich_select_op_finish(ctx, (rich_caret_index, index), None),
1402 }
1403}
1404fn rich_select_op_get_selection<D2>(
1405 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1406 rich_caret_index: (WidgetId, CaretIndex),
1407 rich_selection_index: WidgetId,
1408 d2: D2,
1409 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex>,
1410) {
1411 if let Some(index) = local_selection_index(d2) {
1412 let mut local_ctx = TEXT.resolve_caret();
1413 local_ctx.selection_index = Some(index);
1414 local_ctx.index_version += 1;
1415 rich_select_op_finish(ctx, rich_caret_index, Some((rich_selection_index, index)));
1416 } else {
1417 rich_select_op_finish(ctx, rich_caret_index, None);
1418 }
1419}
1420fn rich_select_op_finish(
1421 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1422 rich_caret_index: (WidgetId, CaretIndex),
1423 rich_selection_index: Option<(WidgetId, CaretIndex)>,
1424) {
1425 if let Some(mut index) = ctx.leaf_info(rich_caret_index.0) {
1426 if rich_caret_index.1.index == 0 {
1427 if let Some(prev) = index.rich_text_prev().next() {
1429 index = prev;
1430 notify_leaf_select_op(
1431 index.id(),
1432 TextSelectOp::new(move || {
1433 let end = TEXT.resolved().segmented_text.text().len();
1434 TEXT.resolve_caret().set_char_index(end);
1435 }),
1436 );
1437 }
1438 }
1439 if let Some(rich_selection_index) = rich_selection_index {
1440 if let Some(mut selection) = ctx.leaf_info(rich_selection_index.0) {
1441 if rich_selection_index.1.index == 0 {
1442 if let Some(prev) = selection.rich_text_prev().next() {
1444 selection = prev;
1445 notify_leaf_select_op(
1446 selection.id(),
1447 TextSelectOp::new(move || {
1448 let end = TEXT.resolved().segmented_text.text().len();
1449 TEXT.resolve_caret().set_char_index(end);
1450 }),
1451 );
1452 }
1453 }
1454
1455 drop(ctx);
1456 TEXT.resolve_rich_caret().update_selection(&index, Some(&selection), false, false);
1457 }
1458 } else {
1459 drop(ctx);
1462 TEXT.resolve_rich_caret().update_selection(&index, None, false, false);
1463 }
1464 }
1465}
1466
1467fn rich_clear_next_prev(is_next: bool, is_word: bool) -> TextSelectOp {
1468 TextSelectOp::new_rich(
1469 move |ctx| {
1471 if let Some(i) = ctx.caret_index_info()
1472 && let Some(s) = ctx.caret_selection_index_info()
1473 {
1474 let (a, b) = match i.cmp_sibling_in(&s, &i.root()).unwrap() {
1477 cmp::Ordering::Less | cmp::Ordering::Equal => (&i, &s),
1478 cmp::Ordering::Greater => (&s, &i),
1479 };
1480
1481 let c = if is_next { b } else { a };
1482
1483 (c.id(), false) } else {
1485 let local_ctx = TEXT.resolved();
1488 if is_next {
1489 let index = local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index;
1490 if index == local_ctx.segmented_text.text().len() {
1491 if let Some(info) = ctx.leaf_info(WIDGET.id())
1493 && let Some(next) = info.rich_text_next().next()
1494 {
1495 return (next.id(), true);
1496 }
1497 }
1498
1499 (WIDGET.id(), false)
1501 } else {
1502 let cutout = if is_word { local_ctx.segmented_text.next_word_index(0) } else { 1 };
1505 if local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index <= cutout {
1506 if let Some(info) = ctx.leaf_info(WIDGET.id())
1509 && let Some(prev) = info.rich_text_prev().next()
1510 {
1511 return (prev.id(), true);
1512 }
1513 }
1514
1515 (WIDGET.id(), false)
1516 }
1517 }
1518 },
1519 move |is_from_sibling| {
1521 if is_from_sibling {
1522 if is_next {
1523 (CaretIndex { index: 1, line: 0 }, ())
1524 } else {
1525 let local_ctx = TEXT.resolved();
1526 (
1527 CaretIndex {
1528 index: local_ctx.segmented_text.text().len(),
1529 line: 0,
1530 },
1531 (),
1532 )
1533 }
1534 } else {
1535 local_clear_next_prev(is_next, is_word);
1536 (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), ())
1537 }
1538 },
1539 |_, _| None,
1540 |()| None,
1541 )
1542}
1543fn local_clear_next_prev(is_next: bool, is_word: bool) {
1544 let ctx = TEXT.resolved();
1546 let current_index = ctx.caret.index.unwrap_or(CaretIndex::ZERO);
1547 let mut next_index = current_index;
1548 if let Some(selection) = ctx.caret.selection_range() {
1549 next_index.index = if is_next { selection.end.index } else { selection.start.index };
1550 } else {
1551 next_index.index = if is_next {
1552 let from = current_index.index;
1553 if is_word {
1554 ctx.segmented_text.next_word_index(from)
1555 } else {
1556 ctx.segmented_text.next_insert_index(from)
1557 }
1558 } else {
1559 let from = current_index.index;
1560 if is_word {
1561 ctx.segmented_text.prev_word_index(from)
1562 } else {
1563 ctx.segmented_text.prev_insert_index(from)
1564 }
1565 };
1566 }
1567
1568 drop(ctx);
1569
1570 let mut ctx = TEXT.resolve_caret();
1571 ctx.clear_selection();
1572 ctx.set_index(next_index);
1573 ctx.used_retained_x = false;
1574}
1575
1576fn rich_select_next_prev(is_next: bool, is_word: bool) -> TextSelectOp {
1577 TextSelectOp::new_rich(
1578 move |ctx| {
1580 let local_ctx = TEXT.resolved();
1581
1582 let index = local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index;
1583
1584 if is_next {
1585 if index == local_ctx.segmented_text.text().len() {
1586 if let Some(info) = ctx.leaf_info(WIDGET.id())
1588 && let Some(next) = info.rich_text_next().next()
1589 {
1590 return (next.id(), true);
1591 }
1592 }
1593 } else {
1594 let cutout = if is_word { local_ctx.segmented_text.next_word_index(0) } else { 1 };
1597 if local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index <= cutout {
1598 if let Some(info) = ctx.leaf_info(WIDGET.id())
1600 && let Some(prev) = info.rich_text_prev().next()
1601 {
1602 return (prev.id(), true);
1603 }
1604 }
1605 }
1606 (WIDGET.id(), false)
1607 },
1608 move |is_from_sibling| {
1610 let id = WIDGET.id();
1611 if is_from_sibling {
1612 if is_next {
1613 (CaretIndex { index: 1, line: 0 }, id)
1615 } else {
1616 let len = TEXT.resolved().segmented_text.text().len();
1618 (CaretIndex { index: len, line: 0 }, id)
1619 }
1620 } else {
1621 local_select_next_prev(is_next, is_word);
1622 (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), id)
1623 }
1624 },
1625 |ctx, index| Some((ctx.caret.selection_index.unwrap_or(index), ())),
1627 |()| {
1629 let local_ctx = TEXT.resolved();
1630 Some(
1631 local_ctx
1632 .caret
1633 .selection_index
1634 .unwrap_or(local_ctx.caret.index.unwrap_or(CaretIndex::ZERO)),
1635 )
1636 },
1637 )
1638}
1639fn local_select_next_prev(is_next: bool, is_word: bool) {
1640 let ctx = TEXT.resolved();
1642 let current_index = ctx.caret.index.unwrap_or(CaretIndex::ZERO);
1643 let mut next_index = current_index;
1644 next_index.index = if is_next {
1645 if is_word {
1646 ctx.segmented_text.next_word_index(current_index.index)
1647 } else {
1648 ctx.segmented_text.next_insert_index(current_index.index)
1649 }
1650 } else {
1651 if is_word {
1653 ctx.segmented_text.prev_word_index(current_index.index)
1654 } else {
1655 ctx.segmented_text.prev_insert_index(current_index.index)
1656 }
1657 };
1658 drop(ctx);
1659
1660 let mut ctx = TEXT.resolve_caret();
1661 if ctx.selection_index.is_none() {
1662 ctx.selection_index = Some(current_index);
1663 }
1664 ctx.set_index(next_index);
1665 ctx.used_retained_x = false;
1666}
1667
1668fn rich_up_down(clear_selection: bool, is_down: bool, is_page: bool) -> TextSelectOp {
1669 TextSelectOp::new_rich(
1670 move |ctx| {
1671 let resolved = TEXT.resolved();
1672 let laidout = TEXT.laidout();
1673
1674 let local_line_i = resolved.caret.index.unwrap_or(CaretIndex::ZERO).line;
1675 let last_line_i = laidout.shaped_text.lines_len().saturating_sub(1);
1676 let next_local_line_i = local_line_i.saturating_add_signed(if is_down { 1 } else { -1 }).min(last_line_i);
1677
1678 let page_h = if is_page { laidout.viewport.height } else { Px(0) };
1679
1680 let mut need_spatial_search = local_line_i == next_local_line_i; if !need_spatial_search {
1683 if is_page {
1684 if let Some(local_line) = laidout.shaped_text.line(local_line_i) {
1685 if is_down {
1686 if let Some(last_line) = laidout.shaped_text.line(last_line_i) {
1687 let max_local_y = last_line.rect().max_y() - local_line.rect().min_y();
1688 need_spatial_search = max_local_y < page_h; }
1690 } else if let Some(first_line) = laidout.shaped_text.line(0) {
1691 let max_local_y = local_line.rect().max_y() - first_line.rect().min_y();
1692 need_spatial_search = max_local_y < page_h; }
1694 }
1695 } else if let Some(next_local_line) = laidout.shaped_text.line(next_local_line_i) {
1696 let r = next_local_line.rect();
1697 let x = laidout.caret_retained_x;
1698 need_spatial_search = r.min_x() > x || r.max_x() < x; }
1700 }
1701
1702 if need_spatial_search
1703 && let Some(local_line) = laidout.shaped_text.line(local_line_i)
1704 && let Some(root_info) = ctx.root_info()
1705 {
1706 let r = local_line.rect();
1708 let local_point = PxPoint::new(laidout.caret_retained_x, r.origin.y + r.size.height / Px(2));
1709 let local_info = WIDGET.info();
1710 let local_to_window = local_info.inner_transform();
1711
1712 let local_cut_y = if is_down { local_point.y + page_h } else { local_point.y - page_h };
1713 let window_cut_y = local_to_window
1714 .transform_point(PxPoint::new(Px(0), local_cut_y))
1715 .unwrap_or_default()
1716 .y;
1717
1718 if let Some(window_point) = local_to_window.transform_point(local_point) {
1719 let local_line_info = local_info.rich_text_line_info();
1723 let filter = |other: &WidgetInfo, rect: PxRect, row_i, rows_len| {
1724 if is_down {
1725 if rect.max_y() < window_cut_y {
1726 return false;
1728 }
1729 } else if rect.min_y() > window_cut_y {
1730 return false;
1732 }
1733
1734 match local_info.cmp_sibling_in(other, &root_info).unwrap() {
1735 cmp::Ordering::Less => {
1736 if !is_down {
1739 return false;
1740 }
1741 if local_line_i < last_line_i {
1742 return true; }
1744 for next in local_info.rich_text_next() {
1745 let line_info = next.rich_text_line_info();
1746 if line_info.starts_new_line {
1747 return true; }
1749 if line_info.ends_in_new_line {
1750 if &next == other {
1751 return row_i > 0; }
1753 return true; }
1755 if &next == other {
1756 return false; }
1758 }
1759 unreachable!() }
1761 cmp::Ordering::Greater => {
1762 if is_down {
1765 return false;
1766 }
1767 if local_line_i > 0 || local_line_info.starts_new_line {
1768 return true; }
1770 for prev in local_info.rich_text_prev() {
1771 let line_info = prev.rich_text_line_info();
1772 if line_info.ends_in_new_line {
1773 if &prev == other {
1774 return row_i < rows_len - 1; }
1776 return true; }
1778 if line_info.starts_new_line {
1779 return &prev != other; }
1781
1782 if &prev == other {
1783 return false; }
1785 }
1786 unreachable!()
1787 }
1788 cmp::Ordering::Equal => false,
1789 }
1790 };
1791 if let Some(next) = root_info.rich_text_nearest_leaf_filtered(window_point, filter) {
1792 let next_info = next.clone();
1795
1796 let mut next_line = 0;
1798 if let Some(next_inline_rows_len) = next_info.bounds_info().inline().map(|i| i.rows.len())
1799 && next_inline_rows_len > 1
1800 {
1801 if is_down {
1802 if local_line_i == last_line_i {
1805 for l_next in local_info.rich_text_next() {
1808 let line_info = l_next.rich_text_line_info();
1809 if line_info.starts_new_line || line_info.ends_in_new_line {
1810 if l_next == next {
1812 next_line = 1;
1814 }
1815 break;
1816 }
1817 }
1818 }
1819 } else {
1820 next_line = next_inline_rows_len - 1;
1822
1823 if local_line_i == 0 && !local_line_info.starts_new_line {
1824 for l_prev in local_info.rich_text_prev() {
1827 let line_info = l_prev.rich_text_line_info();
1828 if line_info.starts_new_line || line_info.ends_in_new_line {
1829 if l_prev == next {
1831 next_line -= 1;
1833 }
1834 break;
1835 }
1836 }
1837 }
1838 }
1839 }
1840 return (next.id(), Some((window_point.x, next_line)));
1841 }
1842 }
1843 }
1844
1845 let mut cant_go_down_up = if is_down {
1847 local_line_i == last_line_i
1849 } else {
1850 local_line_i == 0
1852 };
1853 if is_page
1854 && !cant_go_down_up
1855 && let Some(local_line) = laidout.shaped_text.line(local_line_i)
1856 {
1857 if is_down {
1858 if let Some(last_line) = laidout.shaped_text.line(last_line_i) {
1859 let max_local_y = last_line.rect().max_y() - local_line.rect().min_y();
1861 cant_go_down_up = max_local_y < page_h;
1862 }
1863 } else if let Some(first_line) = laidout.shaped_text.line(0) {
1864 let max_local_y = local_line.rect().max_y() - first_line.rect().min_y();
1866 cant_go_down_up = max_local_y < page_h;
1867 }
1868 }
1869 if cant_go_down_up {
1870 if is_down {
1871 if let Some(end) = ctx.leaves_rev().next() {
1872 return (end.id(), None);
1873 }
1874 } else if let Some(start) = ctx.leaves().next() {
1875 return (start.id(), None);
1876 }
1877 }
1878
1879 (WIDGET.id(), None) },
1881 move |rich_request| {
1882 if let Some((window_x, line_i)) = rich_request {
1883 let local_x = WIDGET
1884 .info()
1885 .inner_transform()
1886 .inverse()
1887 .and_then(|t| t.transform_point(PxPoint::new(window_x, Px(0))))
1888 .unwrap_or_default()
1889 .x;
1890 TEXT.set_caret_retained_x(local_x);
1891 let local_ctx = TEXT.laidout();
1892 if let Some(line) = local_ctx.shaped_text.line(line_i) {
1893 let index = match line.nearest_seg(local_x) {
1894 Some(s) => s.nearest_char_index(local_x, TEXT.resolved().segmented_text.text()),
1895 None => line.text_range().end,
1896 };
1897 let index = CaretIndex { index, line: line_i };
1898 TEXT.resolve_caret().used_retained_x = true; return (index, ());
1900 }
1901 }
1902 let diff = if is_down { 1 } else { -1 };
1903 if is_page {
1904 local_page_up_down(clear_selection, diff);
1905 } else {
1906 local_line_up_down(clear_selection, diff);
1907 }
1908 (TEXT.resolved().caret.index.unwrap(), ())
1909 },
1910 move |ctx, ()| {
1911 if clear_selection {
1912 None
1913 } else {
1914 Some((ctx.caret.selection_index.or(ctx.caret.index).unwrap_or_else(|| WIDGET.id()), ()))
1915 }
1916 },
1917 move |()| {
1918 if clear_selection {
1919 None
1920 } else {
1921 let local_ctx = TEXT.resolved();
1922 Some(
1923 local_ctx
1924 .caret
1925 .selection_index
1926 .or(local_ctx.caret.index)
1927 .unwrap_or(CaretIndex::ZERO),
1928 )
1929 }
1930 },
1931 )
1932}
1933fn local_line_up_down(clear_selection: bool, diff: i8) {
1934 let diff = diff as isize;
1935
1936 let mut caret = TEXT.resolve_caret();
1937 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1938 if clear_selection {
1939 caret.clear_selection();
1940 } else if caret.selection_index.is_none() {
1941 caret.selection_index = Some(i);
1942 }
1943 caret.used_retained_x = true;
1944
1945 let laidout = TEXT.laidout();
1946
1947 if laidout.caret_origin.is_some() {
1948 let last_line = laidout.shaped_text.lines_len().saturating_sub(1);
1949 let li = i.line;
1950 let next_li = li.saturating_add_signed(diff).min(last_line);
1951 if li != next_li {
1952 drop(caret);
1953 let resolved = TEXT.resolved();
1954 match laidout.shaped_text.line(next_li) {
1955 Some(l) => {
1956 i.line = next_li;
1957 i.index = match l.nearest_seg(laidout.caret_retained_x) {
1958 Some(s) => s.nearest_char_index(laidout.caret_retained_x, resolved.segmented_text.text()),
1959 None => l.text_range().end,
1960 }
1961 }
1962 None => i = CaretIndex::ZERO,
1963 };
1964 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
1965 drop(resolved);
1966 caret = TEXT.resolve_caret();
1967 caret.set_index(i);
1968 } else if diff == -1 {
1969 caret.set_char_index(0);
1970 } else if diff == 1 {
1971 drop(caret);
1972 let len = TEXT.resolved().segmented_text.text().len();
1973 caret = TEXT.resolve_caret();
1974 caret.set_char_index(len);
1975 }
1976 }
1977
1978 if caret.index.is_none() {
1979 caret.set_index(CaretIndex::ZERO);
1980 caret.clear_selection();
1981 }
1982}
1983fn local_page_up_down(clear_selection: bool, diff: i8) {
1984 let diff = diff as i32;
1985
1986 let mut caret = TEXT.resolve_caret();
1987 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1988 if clear_selection {
1989 caret.clear_selection();
1990 } else if caret.selection_index.is_none() {
1991 caret.selection_index = Some(i);
1992 }
1993
1994 let laidout = TEXT.laidout();
1995
1996 let page_y = laidout.viewport.height * Px(diff);
1997 caret.used_retained_x = true;
1998 if laidout.caret_origin.is_some() {
1999 let li = i.line;
2000 if diff == -1 && li == 0 {
2001 caret.set_char_index(0);
2002 } else if diff == 1 && li == laidout.shaped_text.lines_len() - 1 {
2003 drop(caret);
2004 let len = TEXT.resolved().segmented_text.text().len();
2005 caret = TEXT.resolve_caret();
2006 caret.set_char_index(len);
2007 } else if let Some(li) = laidout.shaped_text.line(li) {
2008 drop(caret);
2009 let resolved = TEXT.resolved();
2010
2011 let target_line_y = li.rect().origin.y + page_y;
2012 match laidout.shaped_text.nearest_line(target_line_y) {
2013 Some(l) => {
2014 i.line = l.index();
2015 i.index = match l.nearest_seg(laidout.caret_retained_x) {
2016 Some(s) => s.nearest_char_index(laidout.caret_retained_x, resolved.segmented_text.text()),
2017 None => l.text_range().end,
2018 }
2019 }
2020 None => i = CaretIndex::ZERO,
2021 };
2022 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
2023
2024 drop(resolved);
2025 caret = TEXT.resolve_caret();
2026
2027 caret.set_index(i);
2028 }
2029 }
2030
2031 if caret.index.is_none() {
2032 caret.set_index(CaretIndex::ZERO);
2033 caret.clear_selection();
2034 }
2035}
2036
2037fn rich_line_start_end(clear_selection: bool, is_end: bool) -> TextSelectOp {
2038 TextSelectOp::new_rich(
2039 move |ctx| {
2041 let from_id = WIDGET.id();
2042 if let Some(c) = ctx.leaf_info(WIDGET.id()) {
2043 let local_line = TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO).line;
2044 if is_end {
2045 let last_line = TEXT.laidout().shaped_text.lines_len() - 1;
2046 if local_line == last_line {
2047 let mut prev_id = c.id();
2050 for c in c.rich_text_next() {
2051 let line_info = c.rich_text_line_info();
2052 if line_info.starts_new_line && !line_info.is_wrap_start {
2053 return (prev_id, Some(from_id));
2054 } else if line_info.ends_in_new_line {
2055 return (c.id(), Some(from_id));
2056 }
2057 prev_id = c.id();
2058 }
2059
2060 return (prev_id, Some(from_id));
2062 }
2063 } else {
2064 if local_line == 0 {
2067 let mut last_id = c.id();
2070 let mut first = true;
2071 for c in c.rich_text_self_and_prev() {
2072 let line_info = c.rich_text_line_info();
2073 if (line_info.starts_new_line && !line_info.is_wrap_start) || (line_info.ends_in_new_line && !first) {
2074 return (c.id(), Some(from_id));
2075 }
2076 last_id = c.id();
2077 first = false;
2078 }
2079
2080 return (last_id, Some(from_id));
2082 }
2083 }
2084 }
2085 (from_id, None)
2086 },
2087 move |from_id| {
2089 if let Some(from_id) = from_id
2090 && from_id != WIDGET.id()
2091 {
2092 if is_end {
2094 TEXT.resolve_caret().index = Some(CaretIndex::ZERO);
2095 } else {
2096 let local_ctx = TEXT.laidout();
2097 let line = local_ctx.shaped_text.lines_len() - 1;
2098 let index = local_ctx.shaped_text.line(line).unwrap().text_caret_range().end;
2099 drop(local_ctx);
2100 TEXT.resolve_caret().index = Some(CaretIndex { index, line })
2101 }
2102 }
2103 local_line_start_end(clear_selection, is_end);
2104
2105 (TEXT.resolved().caret.index.unwrap(), from_id)
2106 },
2107 move |ctx, from_id| {
2109 if clear_selection {
2110 return None;
2111 }
2112 Some((ctx.caret.selection_index.or(from_id).unwrap_or_else(|| WIDGET.id()), ()))
2113 },
2114 move |()| {
2116 if clear_selection {
2117 return None;
2118 }
2119 let local_ctx = TEXT.resolved();
2120 Some(
2121 local_ctx
2122 .caret
2123 .selection_index
2124 .or(local_ctx.caret.index)
2125 .unwrap_or(CaretIndex::ZERO),
2126 )
2127 },
2128 )
2129}
2130fn local_line_start_end(clear_selection: bool, is_end: bool) {
2131 let mut ctx = TEXT.resolve_caret();
2132 let mut i = ctx.index.unwrap_or(CaretIndex::ZERO);
2133
2134 if clear_selection {
2135 ctx.clear_selection();
2136 } else if ctx.selection_index.is_none() {
2137 ctx.selection_index = Some(i);
2138 }
2139
2140 if let Some(li) = TEXT.laidout().shaped_text.line(i.line) {
2141 i.index = if is_end {
2142 li.actual_text_caret_range().end
2143 } else {
2144 li.actual_text_range().start
2145 };
2146 ctx.set_index(i);
2147 ctx.used_retained_x = false;
2148 }
2149}
2150
2151fn rich_text_start_end(clear_selection: bool, is_end: bool) -> TextSelectOp {
2152 TextSelectOp::new_rich(
2153 move |ctx| {
2154 let from_id = WIDGET.id();
2155 let id = if is_end { ctx.leaves_rev().next() } else { ctx.leaves().next() }.map(|w| w.id());
2156 (id.unwrap_or(from_id), Some(from_id))
2157 },
2158 move |from_id| {
2159 local_text_start_end(clear_selection, is_end);
2160 (TEXT.resolved().caret.index.unwrap(), from_id)
2161 },
2162 move |ctx, from_id| {
2164 if clear_selection {
2165 return None;
2166 }
2167 Some((ctx.caret.selection_index.or(from_id).unwrap_or_else(|| WIDGET.id()), ()))
2168 },
2169 move |()| {
2171 if clear_selection {
2172 return None;
2173 }
2174 let local_ctx = TEXT.resolved();
2175 Some(
2176 local_ctx
2177 .caret
2178 .selection_index
2179 .or(local_ctx.caret.index)
2180 .unwrap_or(CaretIndex::ZERO),
2181 )
2182 },
2183 )
2184}
2185fn local_text_start_end(clear_selection: bool, is_end: bool) {
2186 let idx = if is_end { TEXT.resolved().segmented_text.text().len() } else { 0 };
2187
2188 let mut ctx = TEXT.resolve_caret();
2189 let mut i = ctx.index.unwrap_or(CaretIndex::ZERO);
2190 if clear_selection {
2191 ctx.clear_selection();
2192 } else if ctx.selection_index.is_none() {
2193 ctx.selection_index = Some(i);
2194 }
2195 i.index = idx;
2196 ctx.set_index(i);
2197 ctx.used_retained_x = false;
2198}
2199
2200fn rich_nearest_char_word_to(clear_selection: bool, window_point: DipPoint, is_word: bool) -> TextSelectOp {
2202 TextSelectOp::new_rich(
2203 move |ctx| {
2204 if let Some(root) = ctx.root_info()
2205 && let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point.to_px(root.tree().scale_factor()))
2206 {
2207 return (nearest_leaf.id(), ());
2208 }
2209 (WIDGET.id(), ())
2210 },
2211 move |()| {
2212 if is_word {
2213 local_select_line_word_nearest_to(clear_selection, true, window_point)
2214 } else {
2215 local_nearest_to(clear_selection, window_point)
2216 }
2217 (TEXT.resolved().caret.index.unwrap(), ())
2218 },
2219 move |ctx, ()| {
2220 if clear_selection {
2221 if is_word && TEXT.resolved().caret.selection_index.is_some() {
2222 Some((WIDGET.id(), ()))
2223 } else {
2224 None
2225 }
2226 } else {
2227 Some((ctx.caret.selection_index.unwrap_or_else(|| WIDGET.id()), ()))
2228 }
2229 },
2230 move |()| {
2231 if clear_selection {
2232 if is_word { TEXT.resolved().caret.selection_index } else { None }
2233 } else {
2234 let local_ctx = TEXT.resolved();
2235 Some(
2236 local_ctx
2237 .caret
2238 .selection_index
2239 .or(local_ctx.caret.index)
2240 .unwrap_or(CaretIndex::ZERO),
2241 )
2242 }
2243 },
2244 )
2245}
2246fn local_nearest_to(clear_selection: bool, window_point: DipPoint) {
2247 let mut caret = TEXT.resolve_caret();
2248 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
2249
2250 if clear_selection {
2251 caret.clear_selection();
2252 } else if caret.selection_index.is_none() {
2253 caret.selection_index = Some(i);
2254 } else if let Some((_, is_word)) = caret.initial_selection.clone() {
2255 drop(caret);
2256 return local_select_line_word_nearest_to(false, is_word, window_point);
2257 }
2258
2259 caret.used_retained_x = false;
2260
2261 let laidout = TEXT.laidout();
2263 if let Some(pos) = laidout
2264 .render_info
2265 .transform
2266 .inverse()
2267 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
2268 {
2269 drop(caret);
2270 let resolved = TEXT.resolved();
2271
2272 i = match laidout.shaped_text.nearest_line(pos.y) {
2274 Some(l) => CaretIndex {
2275 line: l.index(),
2276 index: match l.nearest_seg(pos.x) {
2277 Some(s) => s.nearest_char_index(pos.x, resolved.segmented_text.text()),
2278 None => l.text_range().end,
2279 },
2280 },
2281 None => CaretIndex::ZERO,
2282 };
2283 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
2284
2285 drop(resolved);
2286 caret = TEXT.resolve_caret();
2287
2288 caret.set_index(i);
2289 }
2290
2291 if caret.index.is_none() {
2292 caret.set_index(CaretIndex::ZERO);
2293 caret.clear_selection();
2294 }
2295}
2296
2297fn rich_selection_index_nearest_to(window_point: DipPoint, move_selection_index: bool) -> TextSelectOp {
2298 TextSelectOp::new_rich(
2299 move |ctx| {
2300 if move_selection_index {
2301 return (ctx.caret.index.unwrap_or_else(|| WIDGET.id()), ());
2302 }
2303
2304 if let Some(root) = ctx.root_info()
2305 && let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point.to_px(root.tree().scale_factor()))
2306 {
2307 return (nearest_leaf.id(), ());
2308 }
2309 (WIDGET.id(), ())
2310 },
2311 move |()| {
2312 if !move_selection_index {
2313 local_select_index_nearest_to(window_point, false);
2314 }
2315 (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), ())
2316 },
2317 move |ctx, ()| {
2318 if !move_selection_index {
2319 return Some((ctx.caret.selection_index.unwrap_or_else(|| WIDGET.id()), ()));
2320 }
2321
2322 if let Some(root) = ctx.root_info()
2323 && let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point.to_px(root.tree().scale_factor()))
2324 {
2325 return Some((nearest_leaf.id(), ()));
2326 }
2327 Some((WIDGET.id(), ()))
2328 },
2329 move |()| {
2330 if move_selection_index {
2331 local_select_index_nearest_to(window_point, true);
2332 }
2333 Some(TEXT.resolved().caret.selection_index.unwrap_or(CaretIndex::ZERO))
2334 },
2335 )
2336}
2337fn local_select_index_nearest_to(window_point: DipPoint, move_selection_index: bool) {
2338 let mut caret = TEXT.resolve_caret();
2339
2340 if caret.index.is_none() {
2341 caret.index = Some(CaretIndex::ZERO);
2342 }
2343 if caret.selection_index.is_none() {
2344 caret.selection_index = Some(caret.index.unwrap());
2345 }
2346
2347 caret.used_retained_x = false;
2348 caret.index_version += 1;
2349
2350 let laidout = TEXT.laidout();
2351 if let Some(pos) = laidout
2352 .render_info
2353 .transform
2354 .inverse()
2355 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
2356 {
2357 drop(caret);
2358 let resolved = TEXT.resolved();
2359
2360 let mut i = match laidout.shaped_text.nearest_line(pos.y) {
2361 Some(l) => CaretIndex {
2362 line: l.index(),
2363 index: match l.nearest_seg(pos.x) {
2364 Some(s) => s.nearest_char_index(pos.x, resolved.segmented_text.text()),
2365 None => l.text_range().end,
2366 },
2367 },
2368 None => CaretIndex::ZERO,
2369 };
2370 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
2371
2372 drop(resolved);
2373 caret = TEXT.resolve_caret();
2374
2375 if move_selection_index {
2376 caret.selection_index = Some(i);
2377 } else {
2378 caret.index = Some(i);
2379 }
2380 }
2381}
2382
2383fn rich_nearest_line_to(replace_selection: bool, window_point: DipPoint) -> TextSelectOp {
2384 TextSelectOp::new_rich(
2385 move |ctx| {
2386 if let Some(root) = ctx.root_info() {
2387 let window_point = window_point.to_px(root.tree().scale_factor());
2388 if let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point) {
2389 let mut nearest = usize::MAX;
2390 let mut nearest_dist = DistanceKey::NONE_MAX;
2391 let mut rows_len = 0;
2392 nearest_leaf.bounds_info().visit_inner_rects::<()>(|r, i, l| {
2393 rows_len = l;
2394 let dist = DistanceKey::from_rect_to_point(r, window_point);
2395 if dist < nearest_dist {
2396 nearest_dist = dist;
2397 nearest = i;
2398 }
2399 ops::ControlFlow::Continue(())
2400 });
2401
2402 if nearest < rows_len.saturating_sub(1) {
2404 return (nearest_leaf.id(), Some(nearest));
2406 } else {
2407 let mut end = nearest_leaf.clone();
2409 for next in nearest_leaf.rich_text_next() {
2410 let line_info = next.rich_text_line_info();
2411 if line_info.starts_new_line && !line_info.is_wrap_start {
2412 return (
2413 end.id(),
2414 Some(end.bounds_info().inline().map(|i| i.rows.len().saturating_sub(1)).unwrap_or(0)),
2415 );
2416 }
2417 end = next;
2418 if line_info.ends_in_new_line {
2419 break;
2420 }
2421 }
2422 return (end.id(), Some(0));
2423 }
2424 }
2425 }
2426 (WIDGET.id(), None)
2427 },
2428 move |rich_request| {
2429 if let Some(line_i) = rich_request {
2430 let local_ctx = TEXT.laidout();
2431 if let Some(line) = local_ctx.shaped_text.line(line_i) {
2432 return (
2433 CaretIndex {
2434 index: line.actual_text_caret_range().end,
2435 line: line_i,
2436 },
2437 line.actual_line_start().index() == 0,
2438 );
2439 }
2440 }
2441 local_select_line_word_nearest_to(replace_selection, true, window_point);
2442 (TEXT.resolved().caret.index.unwrap(), false)
2443 },
2444 move |ctx, rich_select_line_start| {
2445 if rich_select_line_start {
2446 let id = WIDGET.id();
2447 if let Some(line_end) = ctx.leaf_info(id) {
2448 let mut line_start = line_end;
2449 let mut first = true;
2450 for prev in line_start.rich_text_self_and_prev() {
2451 let line_info = prev.rich_text_line_info();
2452 line_start = prev;
2453 if (line_info.starts_new_line && !line_info.is_wrap_start) || (line_info.ends_in_new_line && !first) {
2454 break;
2455 }
2456 first = false;
2457 }
2458 if !replace_selection
2459 && let Some(sel) = ctx.caret_selection_index_info()
2460 && let Some(std::cmp::Ordering::Less) = sel.cmp_sibling_in(&line_start, &sel.root())
2461 {
2462 return Some((sel.id(), false));
2464 }
2465 return Some((line_start.id(), line_start.id() != id));
2466 }
2467 }
2468 if replace_selection {
2469 return Some((WIDGET.id(), false));
2470 }
2471 Some((ctx.caret.selection_index.unwrap_or_else(|| WIDGET.id()), false))
2472 },
2473 move |start_of_last_line| {
2474 if replace_selection {
2475 let local_ctx = TEXT.laidout();
2476 let mut line_i = local_ctx.shaped_text.lines_len().saturating_sub(1);
2477 if !start_of_last_line && let Some(i) = TEXT.resolved().caret.index {
2478 line_i = i.line;
2479 }
2480 if let Some(last_line) = local_ctx.shaped_text.line(line_i) {
2481 return Some(CaretIndex {
2482 index: last_line.actual_text_caret_range().start,
2483 line: line_i,
2484 });
2485 }
2486 None
2487 } else {
2488 let local_ctx = TEXT.resolved();
2489 Some(
2490 local_ctx
2491 .caret
2492 .selection_index
2493 .or(local_ctx.caret.index)
2494 .unwrap_or(CaretIndex::ZERO),
2495 )
2496 }
2497 },
2498 )
2499}
2500fn local_select_line_word_nearest_to(replace_selection: bool, select_word: bool, window_point: DipPoint) {
2501 let mut caret = TEXT.resolve_caret();
2502
2503 let laidout = TEXT.laidout();
2505 if let Some(pos) = laidout
2506 .render_info
2507 .transform
2508 .inverse()
2509 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
2510 {
2511 if let Some(l) = laidout.shaped_text.nearest_line(pos.y) {
2513 let range = if select_word {
2514 let max_char = l.actual_text_caret_range().end;
2515 let mut r = l.nearest_seg(pos.x).map(|seg| seg.text_range()).unwrap_or_else(|| l.text_range());
2516 r.start = r.start.min(max_char);
2518 r.end = r.end.min(max_char);
2519 r
2520 } else {
2521 l.actual_text_caret_range()
2522 };
2523
2524 let merge_with_selection = if replace_selection {
2525 None
2526 } else {
2527 caret.initial_selection.clone().map(|(s, _)| s).or_else(|| caret.selection_range())
2528 };
2529 if let Some(mut s) = merge_with_selection {
2530 let caret_at_start = range.start < s.start.index;
2531 s.start.index = s.start.index.min(range.start);
2532 s.end.index = s.end.index.max(range.end);
2533
2534 if caret_at_start {
2535 caret.selection_index = Some(s.end);
2536 caret.set_index(s.start);
2537 } else {
2538 caret.selection_index = Some(s.start);
2539 caret.set_index(s.end);
2540 }
2541 } else {
2542 let start = CaretIndex {
2543 line: l.index(),
2544 index: range.start,
2545 };
2546 let end = CaretIndex {
2547 line: l.index(),
2548 index: range.end,
2549 };
2550 caret.selection_index = Some(start);
2551 caret.set_index(end);
2552
2553 caret.initial_selection = Some((start..end, select_word));
2554 }
2555
2556 return;
2557 };
2558 }
2559
2560 if caret.index.is_none() {
2561 caret.set_index(CaretIndex::ZERO);
2562 caret.clear_selection();
2563 }
2564}