Skip to main content

azul_core/
selection.rs

1//! Text selection and cursor positioning for inline content.
2//!
3//! This module provides data structures for managing text cursors and selection ranges
4//! in a bidirectional (Bidi) and line-breaking aware manner. It handles:
5//!
6//! - **Grapheme cluster identification**: Unicode-aware character boundaries
7//! - **Bidi support**: Cursor movement in mixed LTR/RTL text
8//! - **Stable positions**: Selection anchors survive layout changes
9//! - **Affinity tracking**: Cursor position at leading/trailing edges
10//! - **Multi-node selection**: Browser-style selection spanning multiple DOM nodes
11//!
12//! # Architecture
13//!
14//! Text positions are represented as:
15//! - `ContentIndex`: Logical position in the original inline content array
16//! - `GraphemeClusterId`: Stable identifier for a grapheme cluster (survives reordering)
17//! - `TextCursor`: Precise cursor location with leading/trailing affinity
18//! - `SelectionRange`: Start and end cursors defining a selection
19//!
20//! Multi-node selection uses an Anchor/Focus model (W3C Selection API):
21//! - `SelectionAnchor`: Fixed point where user started selection (mousedown)
22//! - `SelectionFocus`: Movable point where selection currently ends (drag position)
23//! - `TextSelection`: Complete selection state spanning potentially multiple IFC roots
24//!
25//! # Use Cases
26//!
27//! - Text editing: Insert/delete at cursor position
28//! - Selection rendering: Highlight selected text across multiple nodes
29//! - Keyboard navigation: Move cursor by grapheme/word/line
30//! - Mouse selection: Convert pixel coordinates to text positions
31//! - Drag selection: Extend selection across multiple DOM nodes
32//!
33//! # Examples
34//!
35//! ```rust,no_run
36//! use azul_core::selection::{CursorAffinity, GraphemeClusterId, TextCursor};
37//!
38//! let cursor = TextCursor {
39//!     cluster_id: GraphemeClusterId {
40//!         source_run: 0,
41//!         start_byte_in_run: 0,
42//!     },
43//!     affinity: CursorAffinity::Leading,
44//! };
45//! ```
46
47use alloc::collections::BTreeMap;
48use alloc::vec::Vec;
49use core::sync::atomic::{AtomicU64, Ordering};
50
51use crate::dom::{DomId, DomNodeId, NodeId};
52use crate::geom::{LogicalPosition, LogicalRect};
53
54/// A stable, logical pointer to an item within the original `InlineContent` array.
55///
56/// This structure eliminates the need for string concatenation and byte-offset math
57/// by tracking both the run index and the item index within that run.
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
59pub struct ContentIndex {
60    /// The index of the `InlineContent` run in the original input array.
61    pub run_index: u32,
62    /// The byte index of the character or item *within* that run's string.
63    pub item_index: u32,
64}
65
66/// A stable, logical identifier for a grapheme cluster.
67///
68/// This survives Bidi reordering and line breaking, making it ideal for tracking
69/// text positions for selection and cursor logic.
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
71#[repr(C)]
72pub struct GraphemeClusterId {
73    /// The `run_index` from the source `ContentIndex`.
74    pub source_run: u32,
75    /// The byte index of the start of the cluster in its original `StyledRun`.
76    pub start_byte_in_run: u32,
77}
78
79/// Represents the logical position of the cursor *between* two grapheme clusters
80/// or at the start/end of the text.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
82#[repr(C)]
83pub enum CursorAffinity {
84    /// The cursor is at the leading edge of the character (left in LTR, right in RTL).
85    Leading,
86    /// The cursor is at the trailing edge of the character (right in LTR, left in RTL).
87    Trailing,
88}
89
90/// Represents a precise cursor location in the logical text.
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
92#[repr(C)]
93pub struct TextCursor {
94    /// The grapheme cluster the cursor is associated with.
95    pub cluster_id: GraphemeClusterId,
96    /// The edge of the cluster the cursor is on.
97    pub affinity: CursorAffinity,
98}
99
100impl_option!(
101    TextCursor,
102    OptionTextCursor,
103    [Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd]
104);
105
106/// Represents a range of selected text. The direction is implicit (start can be
107/// logically after end if selecting backwards).
108#[derive(Debug, PartialOrd, Ord, Clone, Copy, PartialEq, Eq, Hash)]
109#[repr(C)]
110pub struct SelectionRange {
111    pub start: TextCursor,
112    pub end: TextCursor,
113}
114
115impl_option!(
116    SelectionRange,
117    OptionSelectionRange,
118    [Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd]
119);
120
121impl_vec!(SelectionRange, SelectionRangeVec, SelectionRangeVecDestructor, SelectionRangeVecDestructorType, SelectionRangeVecSlice, OptionSelectionRange);
122impl_vec_debug!(SelectionRange, SelectionRangeVec);
123impl_vec_clone!(
124    SelectionRange,
125    SelectionRangeVec,
126    SelectionRangeVecDestructor
127);
128impl_vec_partialeq!(SelectionRange, SelectionRangeVec);
129impl_vec_partialord!(SelectionRange, SelectionRangeVec);
130
131/// A single selection, which can be either a blinking cursor or a highlighted range.
132#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
133#[repr(C, u8)]
134pub enum Selection {
135    Cursor(TextCursor),
136    Range(SelectionRange),
137}
138
139impl_option!(
140    Selection,
141    OptionSelection,
142    [Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord]
143);
144
145impl_vec!(Selection, SelectionVec, SelectionVecDestructor, SelectionVecDestructorType, SelectionVecSlice, OptionSelection);
146impl_vec_debug!(Selection, SelectionVec);
147impl_vec_clone!(Selection, SelectionVec, SelectionVecDestructor);
148impl_vec_partialeq!(Selection, SelectionVec);
149impl_vec_partialord!(Selection, SelectionVec);
150
151/// The complete selection state for a single text block, supporting multiple cursors/ranges.
152#[derive(Debug, Clone, PartialEq)]
153#[repr(C)]
154pub struct SelectionState {
155    /// A list of all active selections. This list is kept sorted and non-overlapping.
156    pub selections: SelectionVec,
157    /// The DOM node this selection state applies to.
158    pub node_id: DomNodeId,
159}
160
161impl SelectionState {
162    /// Adds a new selection, merging it with any existing selections it overlaps with.
163    pub fn add(&mut self, new_selection: Selection) {
164        // A full implementation would handle merging overlapping ranges.
165        // For now, we simply add and sort for simplicity.
166        let mut selections: Vec<Selection> = self.selections.as_ref().to_vec();
167        selections.push(new_selection);
168        selections.sort_unstable();
169        selections.dedup(); // Removes duplicate cursors
170        self.selections = selections.into();
171    }
172
173}
174
175impl_option!(
176    SelectionState,
177    OptionSelectionState,
178    copy = false,
179    clone = false,
180    [Debug, Clone, PartialEq]
181);
182
183// ============================================================================
184// MULTI-CURSOR SUPPORT (Sublime Text style)
185// ============================================================================
186
187/// Stable identifier for a cursor/selection within a MultiCursorState.
188///
189/// Uses a monotonic u64 counter (not UUID) so it is `Copy` and C-API friendly.
190/// Each SelectionId is unique within the lifetime of the process.
191#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
192#[repr(C)]
193pub struct SelectionId {
194    pub inner: u64,
195}
196
197impl SelectionId {
198    /// Generate a new unique SelectionId.
199    pub fn new() -> Self {
200        static COUNTER: AtomicU64 = AtomicU64::new(1);
201        SelectionId { inner: COUNTER.fetch_add(1, Ordering::Relaxed) }
202    }
203}
204
205/// Note: `Default` generates a new unique ID (increments global counter),
206/// rather than returning a zero/sentinel value.
207impl Default for SelectionId {
208    fn default() -> Self {
209        Self::new()
210    }
211}
212
213impl_option!(
214    SelectionId,
215    OptionSelectionId,
216    [Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord]
217);
218
219impl_vec!(SelectionId, SelectionIdVec, SelectionIdVecDestructor, SelectionIdVecDestructorType, SelectionIdVecSlice, OptionSelectionId);
220impl_vec_debug!(SelectionId, SelectionIdVec);
221impl_vec_clone!(SelectionId, SelectionIdVec, SelectionIdVecDestructor);
222impl_vec_partialeq!(SelectionId, SelectionIdVec);
223impl_vec_partialord!(SelectionId, SelectionIdVec);
224
225/// A selection (cursor or range) paired with a stable identity.
226#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
227#[repr(C)]
228pub struct IdentifiedSelection {
229    pub id: SelectionId,
230    pub selection: Selection,
231}
232
233impl_option!(
234    IdentifiedSelection,
235    OptionIdentifiedSelection,
236    [Debug, Clone, Copy, PartialEq, Eq, Hash]
237);
238
239impl_vec!(IdentifiedSelection, IdentifiedSelectionVec, IdentifiedSelectionVecDestructor, IdentifiedSelectionVecDestructorType, IdentifiedSelectionVecSlice, OptionIdentifiedSelection);
240impl_vec_debug!(IdentifiedSelection, IdentifiedSelectionVec);
241impl_vec_clone!(IdentifiedSelection, IdentifiedSelectionVec, IdentifiedSelectionVecDestructor);
242impl_vec_partialeq!(IdentifiedSelection, IdentifiedSelectionVec);
243
244/// Multi-cursor state for a contenteditable element (Sublime Text style).
245///
246/// Replaces the split CursorManager + SelectionManager pattern for text editing.
247/// Supports multiple simultaneous cursors/selections, each with a stable ID.
248///
249/// ## Invariants
250///
251/// - `selections` is sorted by position and non-overlapping.
252/// - The **primary** selection is the last one added (highest index).
253/// - After any mutation, `merge_overlapping()` is called to maintain invariants.
254#[derive(Debug, Clone, PartialEq)]
255pub struct MultiCursorState {
256    /// Sorted by position, non-overlapping. Primary = last added (highest index).
257    pub selections: Vec<IdentifiedSelection>,
258    /// The DOM node this multi-cursor state applies to.
259    pub node_id: DomNodeId,
260    /// Stable key that survives DOM rebuilds (from `calculate_contenteditable_key`).
261    pub contenteditable_key: u64,
262}
263
264impl MultiCursorState {
265    /// Create a new MultiCursorState with a single cursor.
266    pub fn new_with_cursor(cursor: TextCursor, node_id: DomNodeId, contenteditable_key: u64) -> Self {
267        let id = SelectionId::new();
268        Self {
269            selections: vec![IdentifiedSelection {
270                id,
271                selection: Selection::Cursor(cursor),
272            }],
273            node_id,
274            contenteditable_key,
275        }
276    }
277
278    /// Add a cursor, merging if it overlaps with existing selections.
279    /// Returns the SelectionId of the new (or merged) cursor.
280    #[must_use]
281    pub fn add_cursor(&mut self, cursor: TextCursor) -> SelectionId {
282        let id = SelectionId::new();
283        self.selections.push(IdentifiedSelection {
284            id,
285            selection: Selection::Cursor(cursor),
286        });
287        self.merge_overlapping();
288        id
289    }
290
291    /// Add a selection range, merging if it overlaps.
292    /// Returns the SelectionId of the new (or merged) selection.
293    #[must_use]
294    pub fn add_selection(&mut self, range: SelectionRange) -> SelectionId {
295        let id = SelectionId::new();
296        self.selections.push(IdentifiedSelection {
297            id,
298            selection: Selection::Range(range),
299        });
300        self.merge_overlapping();
301        id
302    }
303
304    /// Remove a selection by its stable ID. Returns true if found and removed.
305    #[must_use]
306    pub fn remove_selection(&mut self, id: SelectionId) -> bool {
307        let len_before = self.selections.len();
308        self.selections.retain(|s| s.id != id);
309        self.selections.len() < len_before
310    }
311
312    /// Get the primary selection (last added = highest index).
313    pub fn get_primary(&self) -> Option<&IdentifiedSelection> {
314        self.selections.last()
315    }
316
317    /// Get a mutable reference to the primary selection.
318    pub fn get_primary_mut(&mut self) -> Option<&mut IdentifiedSelection> {
319        self.selections.last_mut()
320    }
321
322    /// Get the primary cursor position (for scroll-into-view, IME, etc.)
323    pub fn get_primary_cursor(&self) -> Option<TextCursor> {
324        self.get_primary().map(|s| match &s.selection {
325            Selection::Cursor(c) => *c,
326            Selection::Range(r) => r.end,
327        })
328    }
329
330    /// Convert to a Vec<Selection> for passing to `edit_text()`.
331    pub fn to_selections(&self) -> Vec<Selection> {
332        self.selections.iter().map(|s| s.selection).collect()
333    }
334
335    /// Update selections from the result of `edit_text()`.
336    ///
337    /// Preserves existing IDs where possible (by index), assigns new IDs for extras.
338    pub fn update_from_edit_result(&mut self, new_selections: &[Selection]) {
339        let old_ids: Vec<SelectionId> = self.selections.iter().map(|s| s.id).collect();
340        self.selections.clear();
341        for (i, sel) in new_selections.iter().enumerate() {
342            let id = old_ids.get(i).copied().unwrap_or_else(SelectionId::new);
343            self.selections.push(IdentifiedSelection {
344                id,
345                selection: *sel,
346            });
347        }
348        // Don't merge here — edit_text already returns correct positions
349    }
350
351    /// Set all selections to a single cursor (e.g., on plain click without Ctrl).
352    pub fn set_single_cursor(&mut self, cursor: TextCursor) {
353        let id = if let Some(primary) = self.selections.last() {
354            primary.id
355        } else {
356            SelectionId::new()
357        };
358        self.selections.clear();
359        self.selections.push(IdentifiedSelection {
360            id,
361            selection: Selection::Cursor(cursor),
362        });
363    }
364
365    /// Set all selections to a single range.
366    pub fn set_single_range(&mut self, range: SelectionRange) {
367        let id = if let Some(primary) = self.selections.last() {
368            primary.id
369        } else {
370            SelectionId::new()
371        };
372        self.selections.clear();
373        self.selections.push(IdentifiedSelection {
374            id,
375            selection: Selection::Range(range),
376        });
377    }
378
379    /// Number of active cursors/selections.
380    pub fn len(&self) -> usize {
381        self.selections.len()
382    }
383
384    /// Whether there are no selections (should not normally happen).
385    pub fn is_empty(&self) -> bool {
386        self.selections.is_empty()
387    }
388
389    /// Sort selections by position and merge any that overlap.
390    pub fn merge_overlapping(&mut self) {
391        if self.selections.len() <= 1 {
392            return;
393        }
394
395        // Sort by the start position of each selection
396        self.selections.sort_by(|a, b| {
397            let pos_a = selection_start_pos(&a.selection);
398            let pos_b = selection_start_pos(&b.selection);
399            pos_a.cmp(&pos_b)
400        });
401
402        // Merge overlapping: if selection[i+1] starts at or before selection[i] ends,
403        // merge them into one range (keeping the later ID as it's more recent).
404        let mut merged: Vec<IdentifiedSelection> = Vec::with_capacity(self.selections.len());
405        for sel in self.selections.drain(..) {
406            if let Some(last) = merged.last_mut() {
407                let last_end = selection_end_pos(&last.selection);
408                let cur_start = selection_start_pos(&sel.selection);
409                if cur_start <= last_end {
410                    // Overlap — merge into one range covering both
411                    let new_start = selection_start_pos(&last.selection);
412                    let cur_end = selection_end_pos(&sel.selection);
413                    let new_end = if cur_end > last_end { cur_end } else { last_end };
414                    if new_start == new_end {
415                        last.selection = Selection::Cursor(new_start);
416                    } else {
417                        last.selection = Selection::Range(SelectionRange {
418                            start: new_start,
419                            end: new_end,
420                        });
421                    }
422                    // Keep the newer ID (the one being merged in)
423                    last.id = sel.id;
424                    continue;
425                }
426            }
427            merged.push(sel);
428        }
429        self.selections = merged;
430    }
431
432    /// Move all cursors using a movement function. Merges collisions afterward.
433    ///
434    /// `move_fn` takes a TextCursor and returns the new TextCursor after movement.
435    /// If `extend_selection` is true, the anchor stays and only the focus moves,
436    /// creating or extending a range.
437    pub fn move_all_cursors(
438        &mut self,
439        extend_selection: bool,
440        move_fn: impl Fn(&TextCursor) -> TextCursor,
441    ) {
442        for sel in self.selections.iter_mut() {
443            match &sel.selection {
444                Selection::Cursor(c) => {
445                    let new_cursor = move_fn(c);
446                    if extend_selection {
447                        if *c != new_cursor {
448                            sel.selection = Selection::Range(SelectionRange {
449                                start: *c,
450                                end: new_cursor,
451                            });
452                        }
453                    } else {
454                        sel.selection = Selection::Cursor(new_cursor);
455                    }
456                }
457                Selection::Range(r) => {
458                    if extend_selection {
459                        let new_end = move_fn(&r.end);
460                        if r.start == new_end {
461                            sel.selection = Selection::Cursor(r.start);
462                        } else {
463                            sel.selection = Selection::Range(SelectionRange {
464                                start: r.start,
465                                end: new_end,
466                            });
467                        }
468                    } else {
469                        // Collapse to the moved end
470                        let new_cursor = move_fn(&r.end);
471                        sel.selection = Selection::Cursor(new_cursor);
472                    }
473                }
474            }
475        }
476        self.merge_overlapping();
477    }
478
479    /// Remap the NodeId in `node_id` after DOM reconciliation.
480    ///
481    /// If the node was removed (not in the map), the multi-cursor state is cleared.
482    pub fn remap_node_ids(
483        &mut self,
484        dom_id: DomId,
485        node_id_map: &alloc::collections::BTreeMap<crate::dom::NodeId, crate::dom::NodeId>,
486    ) {
487        if self.node_id.dom != dom_id {
488            return;
489        }
490        if let Some(old_node_id) = self.node_id.node.into_crate_internal() {
491            if let Some(&new_node_id) = node_id_map.get(&old_node_id) {
492                self.node_id.node = crate::styled_dom::NodeHierarchyItemId::from_crate_internal(Some(new_node_id));
493            } else {
494                // Node removed — clear selections
495                self.selections.clear();
496            }
497        }
498    }
499}
500
501/// Helper: get the start position of a Selection for sorting.
502fn selection_start_pos(sel: &Selection) -> TextCursor {
503    match sel {
504        Selection::Cursor(c) => *c,
505        Selection::Range(r) => {
506            if r.start <= r.end { r.start } else { r.end }
507        }
508    }
509}
510
511/// Helper: get the end position of a Selection for merging.
512fn selection_end_pos(sel: &Selection) -> TextCursor {
513    match sel {
514        Selection::Cursor(c) => *c,
515        Selection::Range(r) => {
516            if r.end >= r.start { r.end } else { r.start }
517        }
518    }
519}
520
521// ============================================================================
522// MULTI-NODE SELECTION (Browser-style Anchor/Focus model)
523// ============================================================================
524
525/// The anchor point of a text selection - where the user started selecting.
526///
527/// This is the fixed point during a drag operation. It records:
528/// - The IFC root node (where the `UnifiedLayout` lives)
529/// - The exact cursor position within that layout
530/// - The visual bounds of the anchor character (for logical rectangle calculations)
531///
532/// The anchor remains constant during a drag; only the focus moves.
533#[derive(Debug, Clone, PartialEq)]
534pub struct SelectionAnchor {
535    /// The IFC root node ID where selection started.
536    /// This is the node that has `inline_layout_result` (e.g., `<p>`, `<div>`).
537    pub ifc_root_node_id: NodeId,
538    
539    /// The exact cursor position within the IFC's `UnifiedLayout`.
540    pub cursor: TextCursor,
541    
542    /// Visual bounds of the anchor character in viewport coordinates.
543    /// Used for computing the logical selection rectangle during multi-line/multi-node selection.
544    pub char_bounds: LogicalRect,
545    
546    /// The mouse position when the selection started (viewport coordinates).
547    pub mouse_position: LogicalPosition,
548}
549
550/// The focus point of a text selection - where the selection currently ends.
551///
552/// This is the movable point during a drag operation. It updates on every mouse move.
553#[derive(Debug, Clone, PartialEq)]
554pub struct SelectionFocus {
555    /// The IFC root node ID where selection currently ends.
556    /// May differ from anchor's IFC root during cross-node selection.
557    pub ifc_root_node_id: NodeId,
558    
559    /// The exact cursor position within the IFC's `UnifiedLayout`.
560    pub cursor: TextCursor,
561    
562    /// Current mouse position in viewport coordinates.
563    pub mouse_position: LogicalPosition,
564}
565
566/// Complete selection state spanning potentially multiple DOM nodes.
567///
568/// This implements the W3C Selection API model with anchor/focus endpoints.
569/// The selection can span multiple IFC roots (e.g., multiple `<p>` elements).
570///
571/// ## Storage Model
572///
573/// Uses `BTreeMap<NodeId, SelectionRange>` for O(log N) lookup during rendering.
574/// The key is the **IFC root NodeId**, and the value is the `SelectionRange` for that IFC.
575///
576/// ## Example
577///
578/// ```text
579/// <p id="1">Hello [World</p>     <- Anchor in IFC 1, partial selection
580/// <p id="2">Complete line</p>    <- InBetween, fully selected
581/// <p id="3">Partial] end</p>     <- Focus in IFC 3, partial selection
582/// ```
583#[derive(Debug, Clone, PartialEq)]
584pub struct TextSelection {
585    /// The DOM this selection belongs to.
586    pub dom_id: DomId,
587    
588    /// The anchor point - where the selection started (fixed during drag).
589    pub anchor: SelectionAnchor,
590    
591    /// The focus point - where the selection currently ends (moves during drag).
592    pub focus: SelectionFocus,
593    
594    /// Map from IFC root NodeId to the SelectionRange for that IFC.
595    /// This allows O(log N) lookup during rendering.
596    ///
597    /// The `SelectionRange` contains the actual `TextCursor` positions for that IFC,
598    /// ready to be passed to `UnifiedLayout::get_selection_rects()`.
599    pub affected_nodes: BTreeMap<NodeId, SelectionRange>,
600    
601    /// Indicates whether anchor comes before focus in document order.
602    /// True = forward selection (left-to-right), False = backward selection.
603    pub is_forward: bool,
604}
605
606impl TextSelection {
607    /// Create a new collapsed selection (cursor) at the given position.
608    pub fn new_collapsed(
609        dom_id: DomId,
610        ifc_root_node_id: NodeId,
611        cursor: TextCursor,
612        char_bounds: LogicalRect,
613        mouse_position: LogicalPosition,
614    ) -> Self {
615        let anchor = SelectionAnchor {
616            ifc_root_node_id,
617            cursor,
618            char_bounds,
619            mouse_position,
620        };
621        
622        let focus = SelectionFocus {
623            ifc_root_node_id,
624            cursor,
625            mouse_position,
626        };
627        
628        // For a collapsed selection, the anchor node has a zero-width range
629        let mut affected_nodes = BTreeMap::new();
630        affected_nodes.insert(ifc_root_node_id, SelectionRange {
631            start: cursor,
632            end: cursor,
633        });
634        
635        TextSelection {
636            dom_id,
637            anchor,
638            focus,
639            affected_nodes,
640            is_forward: true, // Direction doesn't matter for collapsed selection
641        }
642    }
643    
644    /// Check if this is a collapsed selection (cursor with no range).
645    pub fn is_collapsed(&self) -> bool {
646        self.anchor.ifc_root_node_id == self.focus.ifc_root_node_id
647            && self.anchor.cursor == self.focus.cursor
648    }
649    
650    /// Get the selection range for a specific IFC root node.
651    /// Returns `None` if this node is not part of the selection.
652    pub fn get_range_for_node(&self, ifc_root_node_id: &NodeId) -> Option<&SelectionRange> {
653        self.affected_nodes.get(ifc_root_node_id)
654    }
655
656}
657
658impl_option!(
659    TextSelection,
660    OptionTextSelection,
661    copy = false,
662    clone = false,
663    [Debug, Clone, PartialEq]
664);