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 TEXT.resolve_caret().skip_next_scroll = true;
985 (
986 CaretIndex {
987 index: TEXT.resolved().segmented_text.text().len(),
988 line: 0,
989 },
990 (),
991 )
992 },
993 |ctx, ()| Some((ctx.leaves().next().map(|w| w.id()).unwrap_or_else(|| WIDGET.id()), ())),
994 |()| {
995 TEXT.resolve_caret().skip_next_scroll = true;
996 Some(CaretIndex::ZERO)
997 },
998 )
999 }
1000
1001 pub fn clear_selection() -> Self {
1005 Self::new_rich(
1006 |ctx| (ctx.caret.index.unwrap_or_else(|| WIDGET.id()), ()),
1007 |()| (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), ()),
1008 |_, ()| None,
1009 |()| None,
1010 )
1011 }
1012}
1013
1014impl TextSelectOp {
1016 pub fn local_next() -> Self {
1020 Self::new(|| {
1021 local_clear_next_prev(true, false);
1022 })
1023 }
1024
1025 pub fn local_select_next() -> Self {
1029 Self::new(|| {
1030 local_select_next_prev(true, false);
1031 })
1032 }
1033
1034 pub fn local_prev() -> Self {
1038 Self::new(|| {
1039 local_clear_next_prev(false, false);
1040 })
1041 }
1042
1043 pub fn local_select_prev() -> Self {
1047 Self::new(|| {
1048 local_select_next_prev(false, false);
1049 })
1050 }
1051
1052 pub fn local_next_word() -> Self {
1056 Self::new(|| {
1057 local_clear_next_prev(true, true);
1058 })
1059 }
1060
1061 pub fn local_select_next_word() -> Self {
1065 Self::new(|| {
1066 local_select_next_prev(true, true);
1067 })
1068 }
1069
1070 pub fn local_prev_word() -> Self {
1074 Self::new(|| {
1075 local_clear_next_prev(false, true);
1076 })
1077 }
1078
1079 pub fn local_select_prev_word() -> Self {
1083 Self::new(|| {
1084 local_select_next_prev(false, true);
1085 })
1086 }
1087
1088 pub fn local_line_start() -> Self {
1092 Self::new(|| local_line_start_end(true, false))
1093 }
1094
1095 pub fn local_select_line_start() -> Self {
1099 Self::new(|| local_line_start_end(false, false))
1100 }
1101
1102 pub fn local_line_end() -> Self {
1106 Self::new(|| local_line_start_end(true, true))
1107 }
1108
1109 pub fn local_select_line_end() -> Self {
1113 Self::new(|| local_line_start_end(false, true))
1114 }
1115
1116 pub fn local_text_start() -> Self {
1120 Self::new(|| local_text_start_end(true, false))
1121 }
1122
1123 pub fn local_select_text_start() -> Self {
1127 Self::new(|| local_text_start_end(false, false))
1128 }
1129
1130 pub fn local_text_end() -> Self {
1134 Self::new(|| local_text_start_end(true, true))
1135 }
1136
1137 pub fn local_select_text_end() -> Self {
1141 Self::new(|| local_text_start_end(false, true))
1142 }
1143
1144 pub fn local_select_all() -> Self {
1148 Self::new(|| {
1149 let len = TEXT.resolved().segmented_text.text().len();
1150 let mut caret = TEXT.resolve_caret();
1151 caret.set_char_selection(0, len);
1152 caret.skip_next_scroll = true;
1153 })
1154 }
1155
1156 pub fn local_clear_selection() -> Self {
1160 Self::new(|| {
1161 let mut ctx = TEXT.resolve_caret();
1162 ctx.clear_selection();
1163 })
1164 }
1165
1166 pub fn local_line_up() -> Self {
1170 Self::new(|| local_line_up_down(true, -1))
1171 }
1172
1173 pub fn local_select_line_up() -> Self {
1177 Self::new(|| local_line_up_down(false, -1))
1178 }
1179
1180 pub fn local_line_down() -> Self {
1184 Self::new(|| local_line_up_down(true, 1))
1185 }
1186
1187 pub fn local_select_line_down() -> Self {
1191 Self::new(|| local_line_up_down(false, 1))
1192 }
1193
1194 pub fn local_page_up() -> Self {
1198 Self::new(|| local_page_up_down(true, -1))
1199 }
1200
1201 pub fn local_select_page_up() -> Self {
1205 Self::new(|| local_page_up_down(false, -1))
1206 }
1207
1208 pub fn local_page_down() -> Self {
1212 Self::new(|| local_page_up_down(true, 1))
1213 }
1214
1215 pub fn local_select_page_down() -> Self {
1219 Self::new(|| local_page_up_down(false, 1))
1220 }
1221
1222 pub fn local_nearest_to(window_point: DipPoint) -> Self {
1226 Self::new(move || {
1227 local_nearest_to(true, window_point);
1228 })
1229 }
1230
1231 pub fn local_select_nearest_to(window_point: DipPoint) -> Self {
1235 Self::new(move || {
1236 local_nearest_to(false, window_point);
1237 })
1238 }
1239
1240 pub fn local_select_index_nearest_to(window_point: DipPoint, move_selection_index: bool) -> Self {
1244 Self::new(move || {
1245 local_select_index_nearest_to(window_point, move_selection_index);
1246 })
1247 }
1248
1249 pub fn local_select_word_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
1253 Self::new(move || local_select_line_word_nearest_to(replace_selection, true, window_point))
1254 }
1255
1256 pub fn local_select_line_nearest_to(replace_selection: bool, window_point: DipPoint) -> Self {
1260 Self::new(move || local_select_line_word_nearest_to(replace_selection, false, window_point))
1261 }
1262}
1263
1264impl TextSelectOp {
1265 pub fn new(op: impl FnMut() + Send + 'static) -> Self {
1275 Self {
1276 op: Arc::new(Mutex::new(op)),
1277 }
1278 }
1279
1280 pub fn new_rich<D0, D1, D2>(
1297 rich_caret_index: impl FnOnce(&RichText) -> (WidgetId, D0) + Send + 'static,
1298 local_caret_index: impl FnOnce(D0) -> (CaretIndex, D1) + Send + 'static,
1299 rich_selection_index: impl FnOnce(&RichText, D1) -> Option<(WidgetId, D2)> + Send + 'static,
1300 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex> + Send + 'static,
1301 ) -> Self
1302 where
1303 D0: Default + Send + 'static,
1304 D1: Send + 'static,
1305 D2: Default + Send + 'static,
1306 {
1307 let mut f0 = Some(rich_caret_index);
1308 let mut f1 = Some(local_caret_index);
1309 let mut f2 = Some(rich_selection_index);
1310 let mut f3 = Some(local_selection_index);
1311 Self::new(move || {
1312 if let Some(ctx) = TEXT.try_rich() {
1313 rich_select_op_start(ctx, f0.take().unwrap(), f1.take().unwrap(), f2.take().unwrap(), f3.take().unwrap());
1314 } else {
1315 let (index, _) = f1.take().unwrap()(D0::default());
1316 let selection_index = f3.take().unwrap()(D2::default());
1317 let mut ctx = TEXT.resolve_caret();
1318 ctx.selection_index = selection_index;
1319 ctx.set_index(index);
1320 }
1321 })
1322 }
1323
1324 pub(super) fn call(self) {
1325 (self.op.lock())();
1326 }
1327}
1328
1329fn rich_select_op_start<D0: Send + 'static, D1: Send + 'static, D2: Send + 'static>(
1330 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1331 rich_caret_index: impl FnOnce(&RichText) -> (WidgetId, D0),
1332 local_caret_index: impl FnOnce(D0) -> (CaretIndex, D1) + Send + 'static,
1333 rich_selection_index: impl FnOnce(&RichText, D1) -> Option<(WidgetId, D2)> + Send + 'static,
1334 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex> + Send + 'static,
1335) {
1336 let (index, d0) = rich_caret_index(&ctx);
1337 if index == WIDGET.id() {
1338 rich_select_op_get_caret(ctx, index, d0, local_caret_index, rich_selection_index, local_selection_index);
1339 } else {
1340 let mut d0 = Some(d0);
1341 let mut f0 = Some(local_caret_index);
1342 let mut f1 = Some(rich_selection_index);
1343 let mut f2 = Some(local_selection_index);
1344 notify_leaf_select_op(
1345 index,
1346 TextSelectOp::new(move || {
1347 if let Some(ctx) = TEXT.try_rich()
1348 && index == WIDGET.id()
1349 {
1350 rich_select_op_get_caret(
1351 ctx,
1352 index,
1353 d0.take().unwrap(),
1354 f0.take().unwrap(),
1355 f1.take().unwrap(),
1356 f2.take().unwrap(),
1357 );
1358 }
1359 }),
1360 );
1361 }
1362}
1363fn rich_select_op_get_caret<D0, D1, D2: Send + 'static>(
1364 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1365 rich_caret_index: WidgetId,
1366 d0: D0,
1367 local_caret_index: impl FnOnce(D0) -> (CaretIndex, D1),
1368 rich_selection_index: impl FnOnce(&RichText, D1) -> Option<(WidgetId, D2)>,
1369 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex> + Send + 'static,
1370) {
1371 let (index, d1) = local_caret_index(d0);
1372 {
1373 let mut ctx = TEXT.resolve_caret();
1374 ctx.set_index(index);
1375 }
1376
1377 match rich_selection_index(&ctx, d1) {
1378 Some((selection_index, d2)) => {
1379 if selection_index == WIDGET.id() {
1380 rich_select_op_get_selection(ctx, (rich_caret_index, index), selection_index, d2, local_selection_index);
1381 } else {
1382 let mut d2 = Some(d2);
1383 let mut f0 = Some(local_selection_index);
1384 notify_leaf_select_op(
1385 selection_index,
1386 TextSelectOp::new(move || {
1387 if let Some(ctx) = TEXT.try_rich()
1388 && selection_index == WIDGET.id()
1389 {
1390 rich_select_op_get_selection(
1391 ctx,
1392 (rich_caret_index, index),
1393 selection_index,
1394 d2.take().unwrap(),
1395 f0.take().unwrap(),
1396 );
1397 }
1398 }),
1399 );
1400 }
1401 }
1402 None => rich_select_op_finish(ctx, (rich_caret_index, index), None),
1403 }
1404}
1405fn rich_select_op_get_selection<D2>(
1406 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1407 rich_caret_index: (WidgetId, CaretIndex),
1408 rich_selection_index: WidgetId,
1409 d2: D2,
1410 local_selection_index: impl FnOnce(D2) -> Option<CaretIndex>,
1411) {
1412 if let Some(index) = local_selection_index(d2) {
1413 let mut local_ctx = TEXT.resolve_caret();
1414 local_ctx.selection_index = Some(index);
1415 local_ctx.index_version += 1;
1416 rich_select_op_finish(ctx, rich_caret_index, Some((rich_selection_index, index)));
1417 } else {
1418 rich_select_op_finish(ctx, rich_caret_index, None);
1419 }
1420}
1421fn rich_select_op_finish(
1422 ctx: zng_app_context::RwLockReadGuardOwned<RichText>,
1423 rich_caret_index: (WidgetId, CaretIndex),
1424 rich_selection_index: Option<(WidgetId, CaretIndex)>,
1425) {
1426 if let Some(mut index) = ctx.leaf_info(rich_caret_index.0) {
1427 if rich_caret_index.1.index == 0 {
1428 if let Some(prev) = index.rich_text_prev().next() {
1430 index = prev;
1431 notify_leaf_select_op(
1432 index.id(),
1433 TextSelectOp::new(move || {
1434 let end = TEXT.resolved().segmented_text.text().len();
1435 TEXT.resolve_caret().set_char_index(end);
1436 }),
1437 );
1438 }
1439 }
1440 if let Some(rich_selection_index) = rich_selection_index {
1441 if let Some(mut selection) = ctx.leaf_info(rich_selection_index.0) {
1442 if rich_selection_index.1.index == 0 {
1443 if let Some(prev) = selection.rich_text_prev().next() {
1445 selection = prev;
1446 notify_leaf_select_op(
1447 selection.id(),
1448 TextSelectOp::new(move || {
1449 let end = TEXT.resolved().segmented_text.text().len();
1450 TEXT.resolve_caret().set_char_index(end);
1451 }),
1452 );
1453 }
1454 }
1455
1456 drop(ctx);
1457 TEXT.resolve_rich_caret().update_selection(&index, Some(&selection), false, false);
1458 }
1459 } else {
1460 drop(ctx);
1463 TEXT.resolve_rich_caret().update_selection(&index, None, false, false);
1464 }
1465 }
1466}
1467
1468fn rich_clear_next_prev(is_next: bool, is_word: bool) -> TextSelectOp {
1469 TextSelectOp::new_rich(
1470 move |ctx| {
1472 if let Some(i) = ctx.caret_index_info()
1473 && let Some(s) = ctx.caret_selection_index_info()
1474 {
1475 let (a, b) = match i.cmp_sibling_in(&s, &i.root()).unwrap() {
1478 cmp::Ordering::Less | cmp::Ordering::Equal => (&i, &s),
1479 cmp::Ordering::Greater => (&s, &i),
1480 };
1481
1482 let c = if is_next { b } else { a };
1483
1484 (c.id(), false) } else {
1486 let local_ctx = TEXT.resolved();
1489 if is_next {
1490 let index = local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index;
1491 if index == local_ctx.segmented_text.text().len() {
1492 if let Some(info) = ctx.leaf_info(WIDGET.id())
1494 && let Some(next) = info.rich_text_next().next()
1495 {
1496 return (next.id(), true);
1497 }
1498 }
1499
1500 (WIDGET.id(), false)
1502 } else {
1503 let cutout = if is_word { local_ctx.segmented_text.next_word_index(0) } else { 1 };
1506 if local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index <= cutout {
1507 if let Some(info) = ctx.leaf_info(WIDGET.id())
1510 && let Some(prev) = info.rich_text_prev().next()
1511 {
1512 return (prev.id(), true);
1513 }
1514 }
1515
1516 (WIDGET.id(), false)
1517 }
1518 }
1519 },
1520 move |is_from_sibling| {
1522 if is_from_sibling {
1523 if is_next {
1524 (CaretIndex { index: 1, line: 0 }, ())
1525 } else {
1526 let local_ctx = TEXT.resolved();
1527 (
1528 CaretIndex {
1529 index: local_ctx.segmented_text.text().len(),
1530 line: 0,
1531 },
1532 (),
1533 )
1534 }
1535 } else {
1536 local_clear_next_prev(is_next, is_word);
1537 (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), ())
1538 }
1539 },
1540 |_, _| None,
1541 |()| None,
1542 )
1543}
1544fn local_clear_next_prev(is_next: bool, is_word: bool) {
1545 let ctx = TEXT.resolved();
1547 let current_index = ctx.caret.index.unwrap_or(CaretIndex::ZERO);
1548 let mut next_index = current_index;
1549 if let Some(selection) = ctx.caret.selection_range() {
1550 next_index.index = if is_next { selection.end.index } else { selection.start.index };
1551 } else {
1552 next_index.index = if is_next {
1553 let from = current_index.index;
1554 if is_word {
1555 ctx.segmented_text.next_word_index(from)
1556 } else {
1557 ctx.segmented_text.next_insert_index(from)
1558 }
1559 } else {
1560 let from = current_index.index;
1561 if is_word {
1562 ctx.segmented_text.prev_word_index(from)
1563 } else {
1564 ctx.segmented_text.prev_insert_index(from)
1565 }
1566 };
1567 }
1568
1569 drop(ctx);
1570
1571 let mut ctx = TEXT.resolve_caret();
1572 ctx.clear_selection();
1573 ctx.set_index(next_index);
1574 ctx.used_retained_x = false;
1575}
1576
1577fn rich_select_next_prev(is_next: bool, is_word: bool) -> TextSelectOp {
1578 TextSelectOp::new_rich(
1579 move |ctx| {
1581 let local_ctx = TEXT.resolved();
1582
1583 let index = local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index;
1584
1585 if is_next {
1586 if index == local_ctx.segmented_text.text().len() {
1587 if let Some(info) = ctx.leaf_info(WIDGET.id())
1589 && let Some(next) = info.rich_text_next().next()
1590 {
1591 return (next.id(), true);
1592 }
1593 }
1594 } else {
1595 let cutout = if is_word { local_ctx.segmented_text.next_word_index(0) } else { 1 };
1598 if local_ctx.caret.index.unwrap_or(CaretIndex::ZERO).index <= cutout {
1599 if let Some(info) = ctx.leaf_info(WIDGET.id())
1601 && let Some(prev) = info.rich_text_prev().next()
1602 {
1603 return (prev.id(), true);
1604 }
1605 }
1606 }
1607 (WIDGET.id(), false)
1608 },
1609 move |is_from_sibling| {
1611 let id = WIDGET.id();
1612 if is_from_sibling {
1613 if is_next {
1614 (CaretIndex { index: 1, line: 0 }, id)
1616 } else {
1617 let len = TEXT.resolved().segmented_text.text().len();
1619 (CaretIndex { index: len, line: 0 }, id)
1620 }
1621 } else {
1622 local_select_next_prev(is_next, is_word);
1623 (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), id)
1624 }
1625 },
1626 |ctx, index| Some((ctx.caret.selection_index.unwrap_or(index), ())),
1628 |()| {
1630 let local_ctx = TEXT.resolved();
1631 Some(
1632 local_ctx
1633 .caret
1634 .selection_index
1635 .unwrap_or(local_ctx.caret.index.unwrap_or(CaretIndex::ZERO)),
1636 )
1637 },
1638 )
1639}
1640fn local_select_next_prev(is_next: bool, is_word: bool) {
1641 let ctx = TEXT.resolved();
1643 let current_index = ctx.caret.index.unwrap_or(CaretIndex::ZERO);
1644 let mut next_index = current_index;
1645 next_index.index = if is_next {
1646 if is_word {
1647 ctx.segmented_text.next_word_index(current_index.index)
1648 } else {
1649 ctx.segmented_text.next_insert_index(current_index.index)
1650 }
1651 } else {
1652 if is_word {
1654 ctx.segmented_text.prev_word_index(current_index.index)
1655 } else {
1656 ctx.segmented_text.prev_insert_index(current_index.index)
1657 }
1658 };
1659 drop(ctx);
1660
1661 let mut ctx = TEXT.resolve_caret();
1662 if ctx.selection_index.is_none() {
1663 ctx.selection_index = Some(current_index);
1664 }
1665 ctx.set_index(next_index);
1666 ctx.used_retained_x = false;
1667}
1668
1669fn rich_up_down(clear_selection: bool, is_down: bool, is_page: bool) -> TextSelectOp {
1670 TextSelectOp::new_rich(
1671 move |ctx| {
1672 let resolved = TEXT.resolved();
1673 let laidout = TEXT.laidout();
1674
1675 let local_line_i = resolved.caret.index.unwrap_or(CaretIndex::ZERO).line;
1676 let last_line_i = laidout.shaped_text.lines_len().saturating_sub(1);
1677 let next_local_line_i = local_line_i.saturating_add_signed(if is_down { 1 } else { -1 }).min(last_line_i);
1678
1679 let page_h = if is_page { laidout.viewport.height } else { Px(0) };
1680
1681 let mut need_spatial_search = local_line_i == next_local_line_i; if !need_spatial_search {
1684 if is_page {
1685 if let Some(local_line) = laidout.shaped_text.line(local_line_i) {
1686 if is_down {
1687 if let Some(last_line) = laidout.shaped_text.line(last_line_i) {
1688 let max_local_y = last_line.rect().max_y() - local_line.rect().min_y();
1689 need_spatial_search = max_local_y < page_h; }
1691 } else if let Some(first_line) = laidout.shaped_text.line(0) {
1692 let max_local_y = local_line.rect().max_y() - first_line.rect().min_y();
1693 need_spatial_search = max_local_y < page_h; }
1695 }
1696 } else if let Some(next_local_line) = laidout.shaped_text.line(next_local_line_i) {
1697 let r = next_local_line.rect();
1698 let x = laidout.caret_retained_x;
1699 need_spatial_search = r.min_x() > x || r.max_x() < x; }
1701 }
1702
1703 if need_spatial_search
1704 && let Some(local_line) = laidout.shaped_text.line(local_line_i)
1705 && let Some(root_info) = ctx.root_info()
1706 {
1707 let r = local_line.rect();
1709 let local_point = PxPoint::new(laidout.caret_retained_x, r.origin.y + r.size.height / Px(2));
1710 let local_info = WIDGET.info();
1711 let local_to_window = local_info.inner_transform();
1712
1713 let local_cut_y = if is_down { local_point.y + page_h } else { local_point.y - page_h };
1714 let window_cut_y = local_to_window
1715 .transform_point(PxPoint::new(Px(0), local_cut_y))
1716 .unwrap_or_default()
1717 .y;
1718
1719 if let Some(window_point) = local_to_window.transform_point(local_point) {
1720 let local_line_info = local_info.rich_text_line_info();
1724 let filter = |other: &WidgetInfo, rect: PxRect, row_i, rows_len| {
1725 if is_down {
1726 if rect.max_y() < window_cut_y {
1727 return false;
1729 }
1730 } else if rect.min_y() > window_cut_y {
1731 return false;
1733 }
1734
1735 match local_info.cmp_sibling_in(other, &root_info).unwrap() {
1736 cmp::Ordering::Less => {
1737 if !is_down {
1740 return false;
1741 }
1742 if local_line_i < last_line_i {
1743 return true; }
1745 for next in local_info.rich_text_next() {
1746 let line_info = next.rich_text_line_info();
1747 if line_info.starts_new_line {
1748 return true; }
1750 if line_info.ends_in_new_line {
1751 if &next == other {
1752 return row_i > 0; }
1754 return true; }
1756 if &next == other {
1757 return false; }
1759 }
1760 unreachable!() }
1762 cmp::Ordering::Greater => {
1763 if is_down {
1766 return false;
1767 }
1768 if local_line_i > 0 || local_line_info.starts_new_line {
1769 return true; }
1771 for prev in local_info.rich_text_prev() {
1772 let line_info = prev.rich_text_line_info();
1773 if line_info.ends_in_new_line {
1774 if &prev == other {
1775 return row_i < rows_len - 1; }
1777 return true; }
1779 if line_info.starts_new_line {
1780 return &prev != other; }
1782
1783 if &prev == other {
1784 return false; }
1786 }
1787 unreachable!()
1788 }
1789 cmp::Ordering::Equal => false,
1790 }
1791 };
1792 if let Some(next) = root_info.rich_text_nearest_leaf_filtered(window_point, filter) {
1793 let next_info = next.clone();
1796
1797 let mut next_line = 0;
1799 if let Some(next_inline_rows_len) = next_info.bounds_info().inline().map(|i| i.rows.len())
1800 && next_inline_rows_len > 1
1801 {
1802 if is_down {
1803 if local_line_i == last_line_i {
1806 for l_next in local_info.rich_text_next() {
1809 let line_info = l_next.rich_text_line_info();
1810 if line_info.starts_new_line || line_info.ends_in_new_line {
1811 if l_next == next {
1813 next_line = 1;
1815 }
1816 break;
1817 }
1818 }
1819 }
1820 } else {
1821 next_line = next_inline_rows_len - 1;
1823
1824 if local_line_i == 0 && !local_line_info.starts_new_line {
1825 for l_prev in local_info.rich_text_prev() {
1828 let line_info = l_prev.rich_text_line_info();
1829 if line_info.starts_new_line || line_info.ends_in_new_line {
1830 if l_prev == next {
1832 next_line -= 1;
1834 }
1835 break;
1836 }
1837 }
1838 }
1839 }
1840 }
1841 return (next.id(), Some((window_point.x, next_line)));
1842 }
1843 }
1844 }
1845
1846 let mut cant_go_down_up = if is_down {
1848 local_line_i == last_line_i
1850 } else {
1851 local_line_i == 0
1853 };
1854 if is_page
1855 && !cant_go_down_up
1856 && let Some(local_line) = laidout.shaped_text.line(local_line_i)
1857 {
1858 if is_down {
1859 if let Some(last_line) = laidout.shaped_text.line(last_line_i) {
1860 let max_local_y = last_line.rect().max_y() - local_line.rect().min_y();
1862 cant_go_down_up = max_local_y < page_h;
1863 }
1864 } else if let Some(first_line) = laidout.shaped_text.line(0) {
1865 let max_local_y = local_line.rect().max_y() - first_line.rect().min_y();
1867 cant_go_down_up = max_local_y < page_h;
1868 }
1869 }
1870 if cant_go_down_up {
1871 if is_down {
1872 if let Some(end) = ctx.leaves_rev().next() {
1873 return (end.id(), None);
1874 }
1875 } else if let Some(start) = ctx.leaves().next() {
1876 return (start.id(), None);
1877 }
1878 }
1879
1880 (WIDGET.id(), None) },
1882 move |rich_request| {
1883 if let Some((window_x, line_i)) = rich_request {
1884 let local_x = WIDGET
1885 .info()
1886 .inner_transform()
1887 .inverse()
1888 .and_then(|t| t.transform_point(PxPoint::new(window_x, Px(0))))
1889 .unwrap_or_default()
1890 .x;
1891 TEXT.set_caret_retained_x(local_x);
1892 let local_ctx = TEXT.laidout();
1893 if let Some(line) = local_ctx.shaped_text.line(line_i) {
1894 let index = match line.nearest_seg(local_x) {
1895 Some(s) => s.nearest_char_index(local_x, TEXT.resolved().segmented_text.text()),
1896 None => line.text_range().end,
1897 };
1898 let index = CaretIndex { index, line: line_i };
1899 TEXT.resolve_caret().used_retained_x = true; return (index, ());
1901 }
1902 }
1903 let diff = if is_down { 1 } else { -1 };
1904 if is_page {
1905 local_page_up_down(clear_selection, diff);
1906 } else {
1907 local_line_up_down(clear_selection, diff);
1908 }
1909 (TEXT.resolved().caret.index.unwrap(), ())
1910 },
1911 move |ctx, ()| {
1912 if clear_selection {
1913 None
1914 } else {
1915 Some((ctx.caret.selection_index.or(ctx.caret.index).unwrap_or_else(|| WIDGET.id()), ()))
1916 }
1917 },
1918 move |()| {
1919 if clear_selection {
1920 None
1921 } else {
1922 let local_ctx = TEXT.resolved();
1923 Some(
1924 local_ctx
1925 .caret
1926 .selection_index
1927 .or(local_ctx.caret.index)
1928 .unwrap_or(CaretIndex::ZERO),
1929 )
1930 }
1931 },
1932 )
1933}
1934fn local_line_up_down(clear_selection: bool, diff: i8) {
1935 let diff = diff as isize;
1936
1937 let mut caret = TEXT.resolve_caret();
1938 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1939 if clear_selection {
1940 caret.clear_selection();
1941 } else if caret.selection_index.is_none() {
1942 caret.selection_index = Some(i);
1943 }
1944 caret.used_retained_x = true;
1945
1946 let laidout = TEXT.laidout();
1947
1948 if laidout.caret_origin.is_some() {
1949 let last_line = laidout.shaped_text.lines_len().saturating_sub(1);
1950 let li = i.line;
1951 let next_li = li.saturating_add_signed(diff).min(last_line);
1952 if li != next_li {
1953 drop(caret);
1954 let resolved = TEXT.resolved();
1955 match laidout.shaped_text.line(next_li) {
1956 Some(l) => {
1957 i.line = next_li;
1958 i.index = match l.nearest_seg(laidout.caret_retained_x) {
1959 Some(s) => s.nearest_char_index(laidout.caret_retained_x, resolved.segmented_text.text()),
1960 None => l.text_range().end,
1961 }
1962 }
1963 None => i = CaretIndex::ZERO,
1964 };
1965 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
1966 drop(resolved);
1967 caret = TEXT.resolve_caret();
1968 caret.set_index(i);
1969 } else if diff == -1 {
1970 caret.set_char_index(0);
1971 } else if diff == 1 {
1972 drop(caret);
1973 let len = TEXT.resolved().segmented_text.text().len();
1974 caret = TEXT.resolve_caret();
1975 caret.set_char_index(len);
1976 }
1977 }
1978
1979 if caret.index.is_none() {
1980 caret.set_index(CaretIndex::ZERO);
1981 caret.clear_selection();
1982 }
1983}
1984fn local_page_up_down(clear_selection: bool, diff: i8) {
1985 let diff = diff as i32;
1986
1987 let mut caret = TEXT.resolve_caret();
1988 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
1989 if clear_selection {
1990 caret.clear_selection();
1991 } else if caret.selection_index.is_none() {
1992 caret.selection_index = Some(i);
1993 }
1994
1995 let laidout = TEXT.laidout();
1996
1997 let page_y = laidout.viewport.height * Px(diff);
1998 caret.used_retained_x = true;
1999 if laidout.caret_origin.is_some() {
2000 let li = i.line;
2001 if diff == -1 && li == 0 {
2002 caret.set_char_index(0);
2003 } else if diff == 1 && li == laidout.shaped_text.lines_len() - 1 {
2004 drop(caret);
2005 let len = TEXT.resolved().segmented_text.text().len();
2006 caret = TEXT.resolve_caret();
2007 caret.set_char_index(len);
2008 } else if let Some(li) = laidout.shaped_text.line(li) {
2009 drop(caret);
2010 let resolved = TEXT.resolved();
2011
2012 let target_line_y = li.rect().origin.y + page_y;
2013 match laidout.shaped_text.nearest_line(target_line_y) {
2014 Some(l) => {
2015 i.line = l.index();
2016 i.index = match l.nearest_seg(laidout.caret_retained_x) {
2017 Some(s) => s.nearest_char_index(laidout.caret_retained_x, resolved.segmented_text.text()),
2018 None => l.text_range().end,
2019 }
2020 }
2021 None => i = CaretIndex::ZERO,
2022 };
2023 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
2024
2025 drop(resolved);
2026 caret = TEXT.resolve_caret();
2027
2028 caret.set_index(i);
2029 }
2030 }
2031
2032 if caret.index.is_none() {
2033 caret.set_index(CaretIndex::ZERO);
2034 caret.clear_selection();
2035 }
2036}
2037
2038fn rich_line_start_end(clear_selection: bool, is_end: bool) -> TextSelectOp {
2039 TextSelectOp::new_rich(
2040 move |ctx| {
2042 let from_id = WIDGET.id();
2043 if let Some(c) = ctx.leaf_info(WIDGET.id()) {
2044 let local_line = TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO).line;
2045 if is_end {
2046 let last_line = TEXT.laidout().shaped_text.lines_len() - 1;
2047 if local_line == last_line {
2048 let mut prev_id = c.id();
2051 for c in c.rich_text_next() {
2052 let line_info = c.rich_text_line_info();
2053 if line_info.starts_new_line && !line_info.is_wrap_start {
2054 return (prev_id, Some(from_id));
2055 } else if line_info.ends_in_new_line {
2056 return (c.id(), Some(from_id));
2057 }
2058 prev_id = c.id();
2059 }
2060
2061 return (prev_id, Some(from_id));
2063 }
2064 } else {
2065 if local_line == 0 {
2068 let mut last_id = c.id();
2071 let mut first = true;
2072 for c in c.rich_text_self_and_prev() {
2073 let line_info = c.rich_text_line_info();
2074 if (line_info.starts_new_line && !line_info.is_wrap_start) || (line_info.ends_in_new_line && !first) {
2075 return (c.id(), Some(from_id));
2076 }
2077 last_id = c.id();
2078 first = false;
2079 }
2080
2081 return (last_id, Some(from_id));
2083 }
2084 }
2085 }
2086 (from_id, None)
2087 },
2088 move |from_id| {
2090 if let Some(from_id) = from_id
2091 && from_id != WIDGET.id()
2092 {
2093 if is_end {
2095 TEXT.resolve_caret().index = Some(CaretIndex::ZERO);
2096 } else {
2097 let local_ctx = TEXT.laidout();
2098 let line = local_ctx.shaped_text.lines_len() - 1;
2099 let index = local_ctx.shaped_text.line(line).unwrap().text_caret_range().end;
2100 drop(local_ctx);
2101 TEXT.resolve_caret().index = Some(CaretIndex { index, line })
2102 }
2103 }
2104 local_line_start_end(clear_selection, is_end);
2105
2106 (TEXT.resolved().caret.index.unwrap(), from_id)
2107 },
2108 move |ctx, from_id| {
2110 if clear_selection {
2111 return None;
2112 }
2113 Some((ctx.caret.selection_index.or(from_id).unwrap_or_else(|| WIDGET.id()), ()))
2114 },
2115 move |()| {
2117 if clear_selection {
2118 return None;
2119 }
2120 let local_ctx = TEXT.resolved();
2121 Some(
2122 local_ctx
2123 .caret
2124 .selection_index
2125 .or(local_ctx.caret.index)
2126 .unwrap_or(CaretIndex::ZERO),
2127 )
2128 },
2129 )
2130}
2131fn local_line_start_end(clear_selection: bool, is_end: bool) {
2132 let mut ctx = TEXT.resolve_caret();
2133 let mut i = ctx.index.unwrap_or(CaretIndex::ZERO);
2134
2135 if clear_selection {
2136 ctx.clear_selection();
2137 } else if ctx.selection_index.is_none() {
2138 ctx.selection_index = Some(i);
2139 }
2140
2141 if let Some(li) = TEXT.laidout().shaped_text.line(i.line) {
2142 i.index = if is_end {
2143 li.actual_text_caret_range().end
2144 } else {
2145 li.actual_text_range().start
2146 };
2147 ctx.set_index(i);
2148 ctx.used_retained_x = false;
2149 }
2150}
2151
2152fn rich_text_start_end(clear_selection: bool, is_end: bool) -> TextSelectOp {
2153 TextSelectOp::new_rich(
2154 move |ctx| {
2155 let from_id = WIDGET.id();
2156 let id = if is_end { ctx.leaves_rev().next() } else { ctx.leaves().next() }.map(|w| w.id());
2157 (id.unwrap_or(from_id), Some(from_id))
2158 },
2159 move |from_id| {
2160 local_text_start_end(clear_selection, is_end);
2161 (TEXT.resolved().caret.index.unwrap(), from_id)
2162 },
2163 move |ctx, from_id| {
2165 if clear_selection {
2166 return None;
2167 }
2168 Some((ctx.caret.selection_index.or(from_id).unwrap_or_else(|| WIDGET.id()), ()))
2169 },
2170 move |()| {
2172 if clear_selection {
2173 return None;
2174 }
2175 let local_ctx = TEXT.resolved();
2176 Some(
2177 local_ctx
2178 .caret
2179 .selection_index
2180 .or(local_ctx.caret.index)
2181 .unwrap_or(CaretIndex::ZERO),
2182 )
2183 },
2184 )
2185}
2186fn local_text_start_end(clear_selection: bool, is_end: bool) {
2187 let idx = if is_end { TEXT.resolved().segmented_text.text().len() } else { 0 };
2188
2189 let mut ctx = TEXT.resolve_caret();
2190 let mut i = ctx.index.unwrap_or(CaretIndex::ZERO);
2191 if clear_selection {
2192 ctx.clear_selection();
2193 } else if ctx.selection_index.is_none() {
2194 ctx.selection_index = Some(i);
2195 }
2196 i.index = idx;
2197 ctx.set_index(i);
2198 ctx.used_retained_x = false;
2199}
2200
2201fn rich_nearest_char_word_to(clear_selection: bool, window_point: DipPoint, is_word: bool) -> TextSelectOp {
2203 TextSelectOp::new_rich(
2204 move |ctx| {
2205 if let Some(root) = ctx.root_info()
2206 && let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point.to_px(root.tree().scale_factor()))
2207 {
2208 return (nearest_leaf.id(), ());
2209 }
2210 (WIDGET.id(), ())
2211 },
2212 move |()| {
2213 if is_word {
2214 local_select_line_word_nearest_to(clear_selection, true, window_point)
2215 } else {
2216 local_nearest_to(clear_selection, window_point)
2217 }
2218 (TEXT.resolved().caret.index.unwrap(), ())
2219 },
2220 move |ctx, ()| {
2221 if clear_selection {
2222 if is_word && TEXT.resolved().caret.selection_index.is_some() {
2223 Some((WIDGET.id(), ()))
2224 } else {
2225 None
2226 }
2227 } else {
2228 Some((ctx.caret.selection_index.unwrap_or_else(|| WIDGET.id()), ()))
2229 }
2230 },
2231 move |()| {
2232 if clear_selection {
2233 if is_word { TEXT.resolved().caret.selection_index } else { None }
2234 } else {
2235 let local_ctx = TEXT.resolved();
2236 Some(
2237 local_ctx
2238 .caret
2239 .selection_index
2240 .or(local_ctx.caret.index)
2241 .unwrap_or(CaretIndex::ZERO),
2242 )
2243 }
2244 },
2245 )
2246}
2247fn local_nearest_to(clear_selection: bool, window_point: DipPoint) {
2248 let mut caret = TEXT.resolve_caret();
2249 let mut i = caret.index.unwrap_or(CaretIndex::ZERO);
2250
2251 if clear_selection {
2252 caret.clear_selection();
2253 } else if caret.selection_index.is_none() {
2254 caret.selection_index = Some(i);
2255 } else if let Some((_, is_word)) = caret.initial_selection.clone() {
2256 drop(caret);
2257 return local_select_line_word_nearest_to(false, is_word, window_point);
2258 }
2259
2260 caret.used_retained_x = false;
2261
2262 let laidout = TEXT.laidout();
2264 if let Some(pos) = laidout
2265 .render_info
2266 .transform
2267 .inverse()
2268 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
2269 {
2270 drop(caret);
2271 let resolved = TEXT.resolved();
2272
2273 i = match laidout.shaped_text.nearest_line(pos.y) {
2275 Some(l) => CaretIndex {
2276 line: l.index(),
2277 index: match l.nearest_seg(pos.x) {
2278 Some(s) => s.nearest_char_index(pos.x, resolved.segmented_text.text()),
2279 None => l.text_range().end,
2280 },
2281 },
2282 None => CaretIndex::ZERO,
2283 };
2284 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
2285
2286 drop(resolved);
2287 caret = TEXT.resolve_caret();
2288
2289 caret.set_index(i);
2290 }
2291
2292 if caret.index.is_none() {
2293 caret.set_index(CaretIndex::ZERO);
2294 caret.clear_selection();
2295 }
2296}
2297
2298fn rich_selection_index_nearest_to(window_point: DipPoint, move_selection_index: bool) -> TextSelectOp {
2299 TextSelectOp::new_rich(
2300 move |ctx| {
2301 if move_selection_index {
2302 return (ctx.caret.index.unwrap_or_else(|| WIDGET.id()), ());
2303 }
2304
2305 if let Some(root) = ctx.root_info()
2306 && let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point.to_px(root.tree().scale_factor()))
2307 {
2308 return (nearest_leaf.id(), ());
2309 }
2310 (WIDGET.id(), ())
2311 },
2312 move |()| {
2313 if !move_selection_index {
2314 local_select_index_nearest_to(window_point, false);
2315 }
2316 (TEXT.resolved().caret.index.unwrap_or(CaretIndex::ZERO), ())
2317 },
2318 move |ctx, ()| {
2319 if !move_selection_index {
2320 return Some((ctx.caret.selection_index.unwrap_or_else(|| WIDGET.id()), ()));
2321 }
2322
2323 if let Some(root) = ctx.root_info()
2324 && let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point.to_px(root.tree().scale_factor()))
2325 {
2326 return Some((nearest_leaf.id(), ()));
2327 }
2328 Some((WIDGET.id(), ()))
2329 },
2330 move |()| {
2331 if move_selection_index {
2332 local_select_index_nearest_to(window_point, true);
2333 }
2334 Some(TEXT.resolved().caret.selection_index.unwrap_or(CaretIndex::ZERO))
2335 },
2336 )
2337}
2338fn local_select_index_nearest_to(window_point: DipPoint, move_selection_index: bool) {
2339 let mut caret = TEXT.resolve_caret();
2340
2341 if caret.index.is_none() {
2342 caret.index = Some(CaretIndex::ZERO);
2343 }
2344 if caret.selection_index.is_none() {
2345 caret.selection_index = Some(caret.index.unwrap());
2346 }
2347
2348 caret.used_retained_x = false;
2349 caret.index_version += 1;
2350
2351 let laidout = TEXT.laidout();
2352 if let Some(pos) = laidout
2353 .render_info
2354 .transform
2355 .inverse()
2356 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
2357 {
2358 drop(caret);
2359 let resolved = TEXT.resolved();
2360
2361 let mut i = match laidout.shaped_text.nearest_line(pos.y) {
2362 Some(l) => CaretIndex {
2363 line: l.index(),
2364 index: match l.nearest_seg(pos.x) {
2365 Some(s) => s.nearest_char_index(pos.x, resolved.segmented_text.text()),
2366 None => l.text_range().end,
2367 },
2368 },
2369 None => CaretIndex::ZERO,
2370 };
2371 i.index = resolved.segmented_text.snap_grapheme_boundary(i.index);
2372
2373 drop(resolved);
2374 caret = TEXT.resolve_caret();
2375
2376 if move_selection_index {
2377 caret.selection_index = Some(i);
2378 } else {
2379 caret.index = Some(i);
2380 }
2381 }
2382}
2383
2384fn rich_nearest_line_to(replace_selection: bool, window_point: DipPoint) -> TextSelectOp {
2385 TextSelectOp::new_rich(
2386 move |ctx| {
2387 if let Some(root) = ctx.root_info() {
2388 let window_point = window_point.to_px(root.tree().scale_factor());
2389 if let Some(nearest_leaf) = root.rich_text_nearest_leaf(window_point) {
2390 let mut nearest = usize::MAX;
2391 let mut nearest_dist = DistanceKey::NONE_MAX;
2392 let mut rows_len = 0;
2393 nearest_leaf.bounds_info().visit_inner_rects::<()>(|r, i, l| {
2394 rows_len = l;
2395 let dist = DistanceKey::from_rect_to_point(r, window_point);
2396 if dist < nearest_dist {
2397 nearest_dist = dist;
2398 nearest = i;
2399 }
2400 ops::ControlFlow::Continue(())
2401 });
2402
2403 if nearest < rows_len.saturating_sub(1) {
2405 return (nearest_leaf.id(), Some(nearest));
2407 } else {
2408 let mut end = nearest_leaf.clone();
2410 for next in nearest_leaf.rich_text_next() {
2411 let line_info = next.rich_text_line_info();
2412 if line_info.starts_new_line && !line_info.is_wrap_start {
2413 return (
2414 end.id(),
2415 Some(end.bounds_info().inline().map(|i| i.rows.len().saturating_sub(1)).unwrap_or(0)),
2416 );
2417 }
2418 end = next;
2419 if line_info.ends_in_new_line {
2420 break;
2421 }
2422 }
2423 return (end.id(), Some(0));
2424 }
2425 }
2426 }
2427 (WIDGET.id(), None)
2428 },
2429 move |rich_request| {
2430 if let Some(line_i) = rich_request {
2431 let local_ctx = TEXT.laidout();
2432 if let Some(line) = local_ctx.shaped_text.line(line_i) {
2433 return (
2434 CaretIndex {
2435 index: line.actual_text_caret_range().end,
2436 line: line_i,
2437 },
2438 line.actual_line_start().index() == 0,
2439 );
2440 }
2441 }
2442 local_select_line_word_nearest_to(replace_selection, true, window_point);
2443 (TEXT.resolved().caret.index.unwrap(), false)
2444 },
2445 move |ctx, rich_select_line_start| {
2446 if rich_select_line_start {
2447 let id = WIDGET.id();
2448 if let Some(line_end) = ctx.leaf_info(id) {
2449 let mut line_start = line_end;
2450 let mut first = true;
2451 for prev in line_start.rich_text_self_and_prev() {
2452 let line_info = prev.rich_text_line_info();
2453 line_start = prev;
2454 if (line_info.starts_new_line && !line_info.is_wrap_start) || (line_info.ends_in_new_line && !first) {
2455 break;
2456 }
2457 first = false;
2458 }
2459 if !replace_selection
2460 && let Some(sel) = ctx.caret_selection_index_info()
2461 && let Some(std::cmp::Ordering::Less) = sel.cmp_sibling_in(&line_start, &sel.root())
2462 {
2463 return Some((sel.id(), false));
2465 }
2466 return Some((line_start.id(), line_start.id() != id));
2467 }
2468 }
2469 if replace_selection {
2470 return Some((WIDGET.id(), false));
2471 }
2472 Some((ctx.caret.selection_index.unwrap_or_else(|| WIDGET.id()), false))
2473 },
2474 move |start_of_last_line| {
2475 if replace_selection {
2476 let local_ctx = TEXT.laidout();
2477 let mut line_i = local_ctx.shaped_text.lines_len().saturating_sub(1);
2478 if !start_of_last_line && let Some(i) = TEXT.resolved().caret.index {
2479 line_i = i.line;
2480 }
2481 if let Some(last_line) = local_ctx.shaped_text.line(line_i) {
2482 return Some(CaretIndex {
2483 index: last_line.actual_text_caret_range().start,
2484 line: line_i,
2485 });
2486 }
2487 None
2488 } else {
2489 let local_ctx = TEXT.resolved();
2490 Some(
2491 local_ctx
2492 .caret
2493 .selection_index
2494 .or(local_ctx.caret.index)
2495 .unwrap_or(CaretIndex::ZERO),
2496 )
2497 }
2498 },
2499 )
2500}
2501fn local_select_line_word_nearest_to(replace_selection: bool, select_word: bool, window_point: DipPoint) {
2502 let mut caret = TEXT.resolve_caret();
2503
2504 let laidout = TEXT.laidout();
2506 if let Some(pos) = laidout
2507 .render_info
2508 .transform
2509 .inverse()
2510 .and_then(|t| t.project_point(window_point.to_px(laidout.render_info.scale_factor)))
2511 {
2512 if let Some(l) = laidout.shaped_text.nearest_line(pos.y) {
2514 let range = if select_word {
2515 let max_char = l.actual_text_caret_range().end;
2516 let mut r = l.nearest_seg(pos.x).map(|seg| seg.text_range()).unwrap_or_else(|| l.text_range());
2517 r.start = r.start.min(max_char);
2519 r.end = r.end.min(max_char);
2520 r
2521 } else {
2522 l.actual_text_caret_range()
2523 };
2524
2525 let merge_with_selection = if replace_selection {
2526 None
2527 } else {
2528 caret.initial_selection.clone().map(|(s, _)| s).or_else(|| caret.selection_range())
2529 };
2530 if let Some(mut s) = merge_with_selection {
2531 let caret_at_start = range.start < s.start.index;
2532 s.start.index = s.start.index.min(range.start);
2533 s.end.index = s.end.index.max(range.end);
2534
2535 if caret_at_start {
2536 caret.selection_index = Some(s.end);
2537 caret.set_index(s.start);
2538 } else {
2539 caret.selection_index = Some(s.start);
2540 caret.set_index(s.end);
2541 }
2542 } else {
2543 let start = CaretIndex {
2544 line: l.index(),
2545 index: range.start,
2546 };
2547 let end = CaretIndex {
2548 line: l.index(),
2549 index: range.end,
2550 };
2551 caret.selection_index = Some(start);
2552 caret.set_index(end);
2553
2554 caret.initial_selection = Some((start..end, select_word));
2555 }
2556
2557 return;
2558 };
2559 }
2560
2561 if caret.index.is_none() {
2562 caret.set_index(CaretIndex::ZERO);
2563 caret.clear_selection();
2564 }
2565}