zng_wgt_text/
cmd.rs

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