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