Skip to main content

gpui_base/
text_selection.rs

1use std::{
2    cell::{Cell, RefCell},
3    collections::HashMap,
4    ops::Range,
5    rc::Rc,
6    sync::atomic::{AtomicU64, Ordering},
7};
8
9use gpui::{
10    App, AppContext as _, Bounds, Context, Element, ElementId, Entity, EntityId, EventEmitter,
11    Global, GlobalElementId, Half, Hitbox, HitboxBehavior, Hsla, InputEvent as _,
12    InspectorElementId, IntoElement, LayoutId, LongPressEvent, MouseButton, MouseDownEvent,
13    MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollDelta, ScrollWheelEvent, SharedString,
14    Style, Subscription, TextLayout, TouchDragEvent, TouchPhase, WeakEntity, Window, point, px,
15};
16
17use crate::text_boundary::{line_range_at, word_range_at};
18use crate::touch_selection::{
19    EdgeDrag, SelectionEdge, TouchHandle, TouchSelectionSnapshot, caret_in_view,
20};
21use crate::{AutoScroll, GlobalState};
22
23/// An opaque selection layer identifier.
24#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
25pub struct TextSelectionScopeId(u64);
26
27impl TextSelectionScopeId {
28    /// Allocates a process-unique scope identifier.
29    ///
30    /// Keep the returned identifier for the semantic lifetime of the scope;
31    /// do not allocate a new identifier on every frame.
32    pub fn new() -> Self {
33        static NEXT_SCOPE_ID: AtomicU64 = AtomicU64::new(1);
34        let value = NEXT_SCOPE_ID
35            .try_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
36                value.checked_add(1)
37            })
38            .expect("text selection scope identifiers exhausted");
39        Self(value)
40    }
41
42    #[cfg(test)]
43    const fn from_raw(value: u64) -> Self {
44        Self(value)
45    }
46}
47
48/// Stable participant-defined identity for virtualized participant content.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
50pub struct TextSelectionContentKey(u64);
51
52impl TextSelectionContentKey {
53    /// Creates a key from a participant-defined stable content identity.
54    pub const fn new(value: u64) -> Self {
55        Self(value)
56    }
57
58    /// Returns the participant-defined value.
59    pub const fn value(self) -> u64 {
60        self.0
61    }
62}
63
64/// A selection endpoint anchored to a participant's content coordinates.
65#[derive(Clone, Copy, Debug, PartialEq)]
66pub struct TextSelectionEndpoint {
67    entity_id: Option<EntityId>,
68    point: Point<Pixels>,
69    content_key: Option<TextSelectionContentKey>,
70}
71
72impl TextSelectionEndpoint {
73    /// Creates an endpoint at a participant-relative content point.
74    pub(crate) const fn new(entity_id: Option<EntityId>, point: Point<Pixels>) -> Self {
75        Self {
76            entity_id,
77            point,
78            content_key: None,
79        }
80    }
81
82    /// Sets participant-defined endpoint metadata.
83    pub(crate) const fn with_content_key(mut self, content_key: TextSelectionContentKey) -> Self {
84        self.content_key = Some(content_key);
85        self
86    }
87
88    /// Returns the participant which owns this endpoint, when it hit one.
89    pub const fn entity_id(&self) -> Option<EntityId> {
90        self.entity_id
91    }
92
93    /// Returns the participant-relative content point.
94    pub const fn content_point(&self) -> Point<Pixels> {
95        self.point
96    }
97
98    /// Returns participant-defined endpoint metadata captured when it hit a participant.
99    pub const fn content_key(&self) -> Option<TextSelectionContentKey> {
100        self.content_key
101    }
102}
103
104/// Window-coordinate anchor and cursor points for painting a selection.
105#[derive(Clone, Copy, Debug, PartialEq)]
106pub struct TextSelectionWindowPoints {
107    anchor: Point<Pixels>,
108    cursor: Point<Pixels>,
109}
110
111impl TextSelectionWindowPoints {
112    /// Returns the stable anchor in window coordinates.
113    pub const fn anchor(&self) -> Point<Pixels> {
114        self.anchor
115    }
116
117    /// Returns the moving cursor in window coordinates.
118    pub const fn cursor(&self) -> Point<Pixels> {
119        self.cursor
120    }
121}
122
123/// Participant-relative selection endpoints with an optional rendering projection.
124#[derive(Clone, Copy, Debug, PartialEq)]
125pub struct TextSelectionSnapshot {
126    anchor: TextSelectionEndpoint,
127    cursor: TextSelectionEndpoint,
128    is_selecting: bool,
129    window_points: Option<TextSelectionWindowPoints>,
130    coverage: TextSelectionCoverage,
131}
132
133/// How much of one participant participates in a window selection.
134#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
135pub enum TextSelectionCoverage {
136    /// Only the interval between this participant's two endpoints is selected.
137    #[default]
138    Bounded,
139    /// The participant is selected from its beginning through its endpoint.
140    FromStart,
141    /// The participant is selected from its endpoint through its end.
142    ToEnd,
143    /// The entire participant lies between endpoints in other participants.
144    Full,
145}
146
147impl TextSelectionSnapshot {
148    /// Creates a snapshot from stable participant-relative endpoints.
149    pub(crate) const fn new(anchor: TextSelectionEndpoint, cursor: TextSelectionEndpoint) -> Self {
150        Self {
151            anchor,
152            cursor,
153            is_selecting: false,
154            window_points: None,
155            coverage: TextSelectionCoverage::Bounded,
156        }
157    }
158
159    /// Sets whether the pointer gesture is still active.
160    pub(crate) const fn with_selecting(mut self, is_selecting: bool) -> Self {
161        self.is_selecting = is_selecting;
162        self
163    }
164
165    /// Sets the current window-coordinate rendering projection.
166    pub(crate) const fn with_window_points(
167        mut self,
168        window_points: Option<TextSelectionWindowPoints>,
169    ) -> Self {
170        self.window_points = window_points;
171        self
172    }
173
174    /// Sets the portion of the receiving participant covered by this selection.
175    #[cfg(test)]
176    pub(crate) const fn with_coverage(mut self, coverage: TextSelectionCoverage) -> Self {
177        self.coverage = coverage;
178        self
179    }
180
181    /// Returns the stable anchor endpoint.
182    pub const fn anchor(&self) -> TextSelectionEndpoint {
183        self.anchor
184    }
185
186    /// Returns the moving cursor endpoint.
187    pub const fn cursor(&self) -> TextSelectionEndpoint {
188        self.cursor
189    }
190
191    /// Returns whether the pointer gesture is still active.
192    pub const fn is_selecting(&self) -> bool {
193        self.is_selecting
194    }
195
196    /// Returns the window-coordinate endpoints for participants that need them.
197    pub const fn window_points(&self) -> Option<TextSelectionWindowPoints> {
198        self.window_points
199    }
200
201    /// Returns the portion of the receiving participant covered by this selection.
202    pub const fn coverage(&self) -> TextSelectionCoverage {
203        self.coverage
204    }
205}
206
207/// Retained element state proving a participant is still in the window.
208///
209/// A participant reports its geometry from `paint`, which a cached view
210/// (`Entity::cached`) skips: GPUI replays the recorded frame instead. The
211/// element's retained state is replayed with it, so this marker outlives the
212/// frames whose paint never ran, and dies with the frame that drops the
213/// element for good.
214struct RenderedMarker;
215
216/// Per-frame geometry reported by a [`TextSelectionHandle`] participant.
217pub struct TextSelectionRegistration {
218    hitbox: Hitbox,
219    bounds: Bounds<Pixels>,
220    scroll_offset: Point<Pixels>,
221    scope: TextSelectionScopeId,
222    document_order: u64,
223    text_bounds: Vec<Bounds<Pixels>>,
224    self_scroll: bool,
225    selection_edges: Option<(Bounds<Pixels>, Bounds<Pixels>)>,
226    rendered: Option<WeakEntity<RenderedMarker>>,
227}
228
229impl TextSelectionRegistration {
230    /// Creates a registration with default scope, order, and scroll offset.
231    pub fn new(hitbox: Hitbox, bounds: Bounds<Pixels>) -> Self {
232        Self {
233            hitbox,
234            bounds,
235            scroll_offset: Point::default(),
236            scope: TextSelectionScopeId::default(),
237            document_order: 0,
238            text_bounds: Vec::new(),
239            self_scroll: false,
240            selection_edges: None,
241            rendered: None,
242        }
243    }
244
245    /// Ties this registration to the retained state of the element reporting
246    /// it, so that a frame replayed from that element's cached view keeps it.
247    ///
248    /// Call this from the reporting element's `prepaint` or `paint`. A
249    /// registration made outside a drawing element cannot be tied to one and
250    /// is swept the first frame it misses, as every registration used to be.
251    pub fn with_rendered_element(
252        mut self,
253        participant: &TextSelectionHandle,
254        window: &mut Window,
255        cx: &mut App,
256    ) -> Self {
257        self.rendered = Some(
258            window
259                .use_keyed_state(
260                    ElementId::NamedInteger(
261                        "text-selection-participant".into(),
262                        participant.entity_id().as_u64(),
263                    ),
264                    cx,
265                    |_, _| RenderedMarker,
266                )
267                .downgrade(),
268        );
269        self
270    }
271
272    /// Whether the element that reported this registration is still part of
273    /// the window, even if it did not paint the frame that just finished.
274    fn is_rendered(&self) -> bool {
275        self.rendered
276            .as_ref()
277            .is_some_and(|marker| marker.upgrade().is_some())
278    }
279
280    /// Marks a participant that scrolls its own content in response to
281    /// [`TextSelectionEvent::AutoScroll`]. Drag auto-scroll then drives it
282    /// directly, measured against its own bounds, instead of synthesizing a
283    /// wheel event for the nearest scrollable ancestor.
284    pub(crate) fn with_self_scroll(mut self, self_scroll: bool) -> Self {
285        self.self_scroll = self_scroll;
286        self
287    }
288
289    /// Sets the participant's content scroll offset.
290    pub fn with_scroll_offset(mut self, scroll_offset: Point<Pixels>) -> Self {
291        self.scroll_offset = scroll_offset;
292        self
293    }
294
295    /// Sets the opaque selection scope.
296    pub fn with_scope(mut self, scope: TextSelectionScopeId) -> Self {
297        self.scope = scope;
298        self
299    }
300
301    /// Sets the stable logical document order.
302    pub fn with_document_order(mut self, document_order: u64) -> Self {
303        self.document_order = document_order;
304        self
305    }
306
307    /// Sets the glyph-bearing bounds used to reject blank-only gestures.
308    pub fn with_text_bounds(mut self, text_bounds: Vec<Bounds<Pixels>>) -> Self {
309        self.text_bounds = text_bounds;
310        self
311    }
312
313    /// Sets where the participant painted the two ends of its selection: the
314    /// caret line box before its first selected character and the one after
315    /// its last, in window coordinates. The touch handles are drawn there.
316    pub fn with_selection_edges(mut self, start: Bounds<Pixels>, end: Bounds<Pixels>) -> Self {
317        self.selection_edges = Some((start, end));
318        self
319    }
320
321    /// Returns the participant hitbox.
322    pub fn hitbox(&self) -> &Hitbox {
323        &self.hitbox
324    }
325
326    /// Returns the participant's window-coordinate bounds.
327    pub const fn bounds(&self) -> Bounds<Pixels> {
328        self.bounds
329    }
330
331    /// Returns the participant's content scroll offset.
332    pub const fn scroll_offset(&self) -> Point<Pixels> {
333        self.scroll_offset
334    }
335
336    /// Returns the opaque selection scope.
337    pub const fn scope(&self) -> TextSelectionScopeId {
338        self.scope
339    }
340
341    /// Returns the stable logical document order.
342    pub const fn document_order(&self) -> u64 {
343        self.document_order
344    }
345
346    /// Returns the glyph-bearing bounds used to reject blank-only gestures.
347    pub fn text_bounds(&self) -> &[Bounds<Pixels>] {
348        &self.text_bounds
349    }
350
351    /// Returns the caret line boxes at the participant's selection ends.
352    pub const fn selection_edges(&self) -> Option<(Bounds<Pixels>, Bounds<Pixels>)> {
353        self.selection_edges
354    }
355}
356
357/// Laid-out text reported by a plain selection participant during paint.
358#[derive(Clone)]
359pub struct TextSelectionRun {
360    /// Logical order within the containing participant.
361    document_order: u64,
362    /// The exact text used to produce `layout`.
363    text: SharedString,
364    /// Laid-out glyph geometry in window coordinates.
365    layout: TextLayout,
366    /// The run's window-coordinate paint bounds.
367    bounds: Bounds<Pixels>,
368}
369
370impl TextSelectionRun {
371    /// Creates a laid-out text run.
372    pub fn new(text: impl Into<SharedString>, layout: TextLayout, bounds: Bounds<Pixels>) -> Self {
373        Self {
374            document_order: 0,
375            text: text.into(),
376            layout,
377            bounds,
378        }
379    }
380
381    /// Sets the run's logical order within the participant.
382    pub const fn with_document_order(mut self, document_order: u64) -> Self {
383        self.document_order = document_order;
384        self
385    }
386
387    /// Returns the run's logical order within its participant.
388    pub const fn document_order(&self) -> u64 {
389        self.document_order
390    }
391
392    /// Returns the exact text used to produce the layout.
393    pub fn text(&self) -> &SharedString {
394        &self.text
395    }
396
397    /// Returns the laid-out glyph geometry.
398    pub fn layout(&self) -> &TextLayout {
399        &self.layout
400    }
401
402    /// Returns the run's window-coordinate paint bounds.
403    pub const fn bounds(&self) -> Bounds<Pixels> {
404        self.bounds
405    }
406}
407
408/// Selection projected onto a participant's laid-out text runs.
409#[derive(Clone, Debug, Default, PartialEq, Eq)]
410pub struct TextSelectionProjection {
411    /// Selected UTF-8 byte ranges paired with the input runs.
412    ranges: Vec<Option<Range<usize>>>,
413    /// Whether the participant participates in the current selection.
414    is_active: bool,
415}
416
417impl TextSelectionProjection {
418    /// Returns selected UTF-8 byte ranges paired with the input runs.
419    pub fn ranges(&self) -> &[Option<Range<usize>>] {
420        &self.ranges
421    }
422
423    /// Returns whether the participant participates in the selection.
424    pub const fn is_active(&self) -> bool {
425        self.is_active
426    }
427}
428
429/// Projects a participant selection snapshot onto laid-out plain-text runs.
430///
431/// The returned states retain the input order so callers can pair every state
432/// with its run. The ranges are always character boundaries; `order` is used
433/// only when a participant caches selected text for copying.
434fn project_ranges(
435    snapshot: Option<TextSelectionSnapshot>,
436    runs: &[TextSelectionRun],
437) -> TextSelectionProjection {
438    let Some(snapshot) = snapshot else {
439        return TextSelectionProjection {
440            ranges: vec![None; runs.len()],
441            is_active: false,
442        };
443    };
444    let Some(window_points) = snapshot.window_points() else {
445        return TextSelectionProjection {
446            ranges: vec![None; runs.len()],
447            is_active: true,
448        };
449    };
450
451    TextSelectionProjection {
452        ranges: runs
453            .iter()
454            .map(|run| selection_range_for_run(run, window_points.anchor, window_points.cursor))
455            .collect(),
456        is_active: true,
457    }
458}
459
460fn selection_range_for_run(
461    run: &TextSelectionRun,
462    selection_start: Point<Pixels>,
463    selection_end: Point<Pixels>,
464) -> Option<Range<usize>> {
465    if run.text.len() != run.layout.len() {
466        return None;
467    }
468
469    if run.text.is_empty() {
470        return None;
471    }
472
473    let line_height = run.layout.line_height();
474    // Each character is tested with its row's top and height, so a run whose
475    // rows all miss the band, or all lie strictly inside it with no endpoint
476    // on any row, has the same answer for every character. Decide those
477    // without the walk below, which scans the layout twice per character.
478    let (rows_top, rows_bottom) = text_rows_extent(&run.layout, line_height);
479    let band_top = selection_start.y.min(selection_end.y);
480    let band_bottom = selection_start.y.max(selection_end.y);
481    if rows_bottom <= band_top || rows_top > band_bottom {
482        return None;
483    }
484    if band_top < rows_top && band_bottom >= rows_bottom {
485        return Some(0..run.text.len());
486    }
487
488    let mut range = None;
489    for (offset, character) in run.text.char_indices() {
490        let next_offset = offset + character.len_utf8();
491        let Some(position) = run.layout.position_for_index(offset) else {
492            continue;
493        };
494
495        let char_width = run
496            .layout
497            .position_for_index(next_offset)
498            .filter(|next| next.y == position.y)
499            .map_or_else(|| line_height.half(), |next| next.x - position.x);
500
501        if point_in_selection_band(
502            position,
503            char_width,
504            selection_start,
505            selection_end,
506            line_height,
507        ) {
508            range.get_or_insert(offset..offset).end = next_offset;
509        }
510    }
511    range
512}
513
514/// The top of the first laid-out row of `text_layout` and the bottom of its
515/// last one, each row `line_height` tall as a selection band test sees it.
516///
517/// Both ends are accumulated the way [`TextLayout::position_for_index`] places
518/// rows, so comparisons against them agree with a per-character walk to the
519/// bit. The last laid-out row may hold no character such a walk tests (a
520/// trailing empty line, or a row whose only character is placed at the end of
521/// the row before it); callers treat the extent as covering, never as exact.
522pub(crate) fn text_rows_extent(text_layout: &TextLayout, line_height: Pixels) -> (Pixels, Pixels) {
523    let top = text_layout.bounds().top();
524    let layout_line_height = text_layout.line_height();
525    let lines = text_layout.line_layouts();
526    let mut last_line_top = top;
527    for line in lines.iter().take(lines.len().saturating_sub(1)) {
528        last_line_top += line.size(layout_line_height).height;
529    }
530    let last_row_top = lines.last().map_or(top, |line| {
531        last_line_top + line.wrap_boundaries.len() as f32 * layout_line_height
532    });
533    (top, last_row_top + line_height)
534}
535
536fn points_for_multi_click(
537    runs: &[TextSelectionRun],
538    position: Point<Pixels>,
539    click_count: usize,
540) -> Option<(Point<Pixels>, Point<Pixels>)> {
541    let run = runs.iter().find(|run| run.bounds.contains(&position))?;
542    if run.text.len() != run.layout.len() {
543        return None;
544    }
545    let offset = run.layout.index_for_position(position).ok()?;
546    let range = match click_count {
547        2 => word_range_at(&run.text, offset)?,
548        3.. => line_range_at(&run.text, offset),
549        _ => return None,
550    };
551    if range.is_empty() {
552        return None;
553    }
554    Some((
555        run.layout.position_for_index(range.start)?,
556        run.layout.position_for_index(range.end)?,
557    ))
558}
559
560fn point_in_selection_band(
561    position: Point<Pixels>,
562    char_width: Pixels,
563    selection_start: Point<Pixels>,
564    selection_end: Point<Pixels>,
565    line_height: Pixels,
566) -> bool {
567    let point_in_line =
568        |point: Point<Pixels>| point.y >= position.y && point.y < position.y + line_height;
569    let top = selection_start.y.min(selection_end.y);
570    let bottom = selection_start.y.max(selection_end.y);
571    let x = position.x + char_width.half();
572
573    if position.y + line_height <= top || position.y > bottom {
574        return false;
575    }
576
577    if point_in_line(selection_start) && point_in_line(selection_end) {
578        let left = selection_start.x.min(selection_end.x);
579        let right = selection_start.x.max(selection_end.x);
580        return x >= left && x <= right;
581    }
582
583    let (top_point, bottom_point) = if selection_start.y < selection_end.y {
584        (selection_start, selection_end)
585    } else {
586        (selection_end, selection_start)
587    };
588    if point_in_line(top_point) {
589        x >= top_point.x
590    } else if point_in_line(bottom_point) {
591        x <= bottom_point.x
592    } else {
593        true
594    }
595}
596
597type FocusCallback = Rc<dyn Fn(&mut Window, &mut App)>;
598type ClearHandler = Rc<dyn Fn(&mut App)>;
599type CopyCallback = Rc<dyn Fn(&mut App) -> String>;
600type ContentKeyResolver = Rc<dyn Fn(Point<Pixels>, &App) -> Option<TextSelectionContentKey>>;
601
602/// Notifications emitted by a text-selection participant.
603#[derive(Clone, Copy, Debug, PartialEq)]
604pub enum TextSelectionEvent {
605    /// The participant's window-selection projection changed.
606    SelectionChanged(Option<TextSelectionSnapshot>),
607    /// The active drag requests vertical auto-scroll, or `None` to stop.
608    AutoScroll(Option<Pixels>),
609    /// Window selection cleared the participant's participant-local state.
610    Cleared,
611    /// The touch selection the participant takes part in changed: it
612    /// appeared, its ends moved, a handle drag began or ended, or it went
613    /// away. The participant paints the handles and lays their hitboxes out
614    /// from the ends it painted last frame, so it renders once more.
615    TouchSelectionChanged,
616}
617
618struct CopyItem {
619    document_order: u64,
620    callback: Option<CopyCallback>,
621    fallback: String,
622}
623
624fn resolve_copy_items(mut items: Vec<CopyItem>, cx: &mut App) -> String {
625    items.sort_by_key(|item| item.document_order);
626    items
627        .into_iter()
628        .map(|item| {
629            item.callback
630                .map(|callback| callback(cx))
631                .unwrap_or(item.fallback)
632        })
633        .filter(|text| !text.trim().is_empty())
634        .collect::<Vec<_>>()
635        .join("\n")
636}
637
638fn dispatch_clear_handlers(handlers: Vec<ClearHandler>, cx: &mut App) {
639    for handler in handlers {
640        handler(cx);
641    }
642}
643
644struct SelectableTextState {
645    fallback_copy_text: String,
646    /// The selected text as the participant last painted it. Painting
647    /// projects it, which is not a change of the participant.
648    projected_copy_text: RefCell<Option<String>>,
649    /// The participant's text as it last painted it, for hit testing.
650    runs: RefCell<Vec<TextSelectionRun>>,
651    local_selection: bool,
652    snapshot: Option<TextSelectionSnapshot>,
653    on_focus: Option<FocusCallback>,
654    clear: Option<ClearHandler>,
655    copy: Option<CopyCallback>,
656    content_key_resolver: Option<ContentKeyResolver>,
657}
658
659impl EventEmitter<TextSelectionEvent> for SelectableTextState {}
660
661impl SelectableTextState {
662    fn new(fallback_copy_text: impl Into<String>) -> Self {
663        Self {
664            fallback_copy_text: fallback_copy_text.into(),
665            projected_copy_text: RefCell::new(None),
666            runs: RefCell::new(Vec::new()),
667            local_selection: false,
668            snapshot: None,
669            on_focus: None,
670            clear: None,
671            copy: None,
672            content_key_resolver: None,
673        }
674    }
675
676    /// The current geometry selection snapshot for this participant.
677    fn snapshot(&self) -> Option<TextSelectionSnapshot> {
678        self.snapshot
679    }
680
681    /// Sets the text copied by this participant when it participates in selection.
682    fn set_fallback_copy_text(&mut self, text: impl Into<String>) {
683        self.fallback_copy_text = text.into();
684        *self.projected_copy_text.get_mut() = None;
685    }
686
687    /// Marks participant-local selection (for example select-all) as active.
688    fn set_local_selection(&mut self, active: bool) {
689        self.local_selection = active;
690    }
691
692    /// Projects this participant's current snapshot onto plain-text runs and caches
693    /// their selected substrings for the window selection query.
694    ///
695    /// Call this once per painted run. A snapshot change or
696    /// Clearing window selection invalidates the cache immediately, so copy
697    /// never returns text from a previous projection while waiting to repaint.
698    fn update_runs(&self, runs: &[TextSelectionRun]) -> TextSelectionProjection {
699        *self.runs.borrow_mut() = runs.to_vec();
700        let states = project_ranges(self.snapshot, runs);
701        let mut selected_runs = runs
702            .iter()
703            .zip(states.ranges())
704            .enumerate()
705            .filter_map(|(index, (run, state))| {
706                state.as_ref().map(|range| {
707                    debug_assert!(run.text.is_char_boundary(range.start));
708                    debug_assert!(run.text.is_char_boundary(range.end));
709                    (
710                        run.document_order,
711                        index,
712                        run.text[range.clone()].to_string(),
713                    )
714                })
715            })
716            .collect::<Vec<_>>();
717        selected_runs.sort_by_key(|(order, index, _)| (*order, *index));
718        *self.projected_copy_text.borrow_mut() =
719            Some(selected_runs.into_iter().map(|(_, _, text)| text).collect());
720        states
721    }
722
723    /// Installs the callback which focuses the participant when a drag begins in it.
724    fn set_focus_handler(&mut self, callback: impl Fn(&mut Window, &mut App) + 'static) {
725        self.on_focus = Some(Rc::new(callback));
726    }
727
728    fn clear_with(&mut self, callback: impl Fn(&mut App) + 'static) {
729        self.clear = Some(Rc::new(callback));
730    }
731
732    /// Installs a participant-specific copy projection.
733    fn copy_with(&mut self, callback: impl Fn(&mut App) -> String + 'static) {
734        self.copy = Some(Rc::new(callback));
735    }
736
737    /// Installs a participant-specific lookup for stable virtualized content keys.
738    fn resolve_content_key_with(
739        &mut self,
740        callback: impl Fn(Point<Pixels>, &App) -> Option<TextSelectionContentKey> + 'static,
741    ) {
742        self.content_key_resolver = Some(Rc::new(callback));
743    }
744
745    fn set_snapshot(&mut self, snapshot: Option<TextSelectionSnapshot>, cx: &mut Context<Self>) {
746        if self.snapshot == snapshot {
747            return;
748        }
749        self.snapshot = snapshot;
750        *self.projected_copy_text.get_mut() = None;
751        cx.emit(TextSelectionEvent::SelectionChanged(snapshot));
752    }
753
754    /// Whether clearing the participant would change anything: it is part
755    /// of the window selection, or selected on its own.
756    fn has_selection_to_clear(&self) -> bool {
757        self.snapshot.is_some() || self.local_selection
758    }
759
760    fn clear_state(&mut self, cx: &mut Context<Self>) -> Option<ClearHandler> {
761        self.snapshot = None;
762        *self.projected_copy_text.get_mut() = None;
763        self.local_selection = false;
764        cx.emit(TextSelectionEvent::Cleared);
765        cx.emit(TextSelectionEvent::SelectionChanged(None));
766        self.clear.clone()
767    }
768
769    fn set_auto_scroll(&self, delta: Option<Pixels>, cx: &mut Context<Self>) {
770        cx.emit(TextSelectionEvent::AutoScroll(delta));
771    }
772
773    fn focus(&self, window: &mut Window, cx: &mut App) {
774        if let Some(callback) = self.on_focus.clone() {
775            window.defer(cx, move |window, cx| callback(window, cx));
776        }
777    }
778
779    fn copy_item(&self, document_order: u64) -> Option<CopyItem> {
780        (self.snapshot.is_some() || self.local_selection).then(|| CopyItem {
781            document_order,
782            callback: self.copy.clone(),
783            fallback: self
784                .projected_copy_text
785                .borrow()
786                .clone()
787                .unwrap_or_else(|| self.fallback_copy_text.clone()),
788        })
789    }
790}
791
792/// The touch handles a participant laid out for a frame, with their hitboxes.
793#[derive(Default)]
794pub struct TouchHandleLayout {
795    hitboxes: Vec<(SelectionEdge, Hitbox)>,
796}
797
798/// A stable, participant-neutral handle for text that participates in window selection.
799#[derive(Clone)]
800pub struct TextSelectionHandle(Entity<SelectableTextState>);
801
802impl TextSelectionHandle {
803    /// Creates a selection participant handle with fallback text for copying.
804    pub fn new(fallback_copy_text: impl Into<String>, cx: &mut App) -> Self {
805        Self(cx.new(|_| SelectableTextState::new(fallback_copy_text)))
806    }
807
808    /// Returns this participant's stable identity.
809    pub fn entity_id(&self) -> EntityId {
810        self.0.entity_id()
811    }
812
813    /// Returns the current geometry selection snapshot for this participant.
814    pub fn snapshot(&self, cx: &App) -> Option<TextSelectionSnapshot> {
815        self.0.read(cx).snapshot()
816    }
817
818    /// Sets the fallback text copied while this participant participates.
819    pub fn set_fallback_copy_text(&self, text: impl Into<String>, cx: &mut App) {
820        self.0
821            .update(cx, |state, _| state.set_fallback_copy_text(text));
822    }
823
824    /// Marks participant-local selection, such as select-all, as active.
825    pub fn set_local_selection(&self, active: bool, cx: &mut App) {
826        self.0
827            .update(cx, |state, _| state.set_local_selection(active));
828    }
829
830    /// Returns whether participant-local selection is active.
831    pub fn has_local_selection(&self, cx: &App) -> bool {
832        self.0.read(cx).local_selection
833    }
834
835    /// Registers this participant and its geometry for the current frame.
836    pub fn register(
837        &self,
838        mut registration: TextSelectionRegistration,
839        window: &mut Window,
840        cx: &mut App,
841    ) {
842        if let Some(scope) = current_text_selection_scope(window.window_handle().window_id(), cx) {
843            registration.scope = scope;
844        }
845        let Some(state) = WindowSelectionState::existing(window, cx) else {
846            return;
847        };
848        let Some(registration) = state.read(cx).register_quietly(self, registration) else {
849            return;
850        };
851        state.update(cx, |state, cx| {
852            state.register_participant(self.clone(), registration, cx)
853        });
854    }
855
856    /// Projects the current snapshot onto plain-text runs and caches their copy text.
857    pub fn update_runs(&self, runs: &[TextSelectionRun], cx: &mut App) -> TextSelectionProjection {
858        self.0.read(cx).update_runs(runs)
859    }
860
861    // Rich text renders its own selection; retain geometry only for word hit testing.
862    pub(crate) fn set_hit_test_runs(&self, runs: &[TextSelectionRun], cx: &mut App) {
863        *self.0.read(cx).runs.borrow_mut() = runs.to_vec();
864    }
865
866    /// Subscribes to participant selection notifications.
867    pub fn subscribe(
868        &self,
869        mut callback: impl FnMut(&TextSelectionEvent, &mut App) + 'static,
870        cx: &mut App,
871    ) -> Subscription {
872        cx.subscribe(&self.0, move |_, event, cx| callback(event, cx))
873    }
874
875    /// Subscribes `window` to refresh whenever this participant's selection changes.
876    #[must_use = "retain the subscription or explicitly detach it"]
877    pub fn refresh_window_on_change(&self, window: &Window, cx: &mut App) -> Subscription {
878        let window = window.window_handle();
879        self.subscribe(
880            move |event, cx| {
881                if matches!(event, TextSelectionEvent::SelectionChanged(_)) {
882                    _ = window.update(cx, |_, window, _| window.refresh());
883                }
884            },
885            cx,
886        )
887    }
888
889    /// Sets the callback which focuses the participant when a drag begins in it.
890    pub fn focus_with(&self, callback: impl Fn(&mut Window, &mut App) + 'static, cx: &mut App) {
891        self.0
892            .update(cx, |state, _| state.set_focus_handler(callback));
893    }
894
895    /// Sets the synchronous participant cleanup command used by window clear.
896    pub fn clear_with(&self, callback: impl Fn(&mut App) + 'static, cx: &mut App) {
897        self.0.update(cx, |state, _| state.clear_with(callback));
898    }
899
900    /// Sets a participant-specific copy projection.
901    pub fn copy_with(&self, callback: impl Fn(&mut App) -> String + 'static, cx: &mut App) {
902        self.0.update(cx, |state, _| state.copy_with(callback));
903    }
904
905    /// Lays out the touch handles at the ends of this participant's
906    /// selection, as they were painted last frame, and gives each a hitbox
907    /// where the finger takes it. Call during the participant's prepaint;
908    /// hand the result to [`Self::paint_touch_handles`].
909    pub fn prepaint_touch_handles(&self, window: &mut Window, cx: &App) -> TouchHandleLayout {
910        let Some(state) = WindowSelectionState::existing(window, cx) else {
911            return TouchHandleLayout::default();
912        };
913        let hitboxes = state
914            .read(cx)
915            .touch_handles_of(self.entity_id())
916            .into_iter()
917            .map(|(edge, caret)| {
918                let hitbox = window.insert_hitbox(
919                    TouchHandle::hit_bounds(edge, caret),
920                    HitboxBehavior::BlockMouse,
921                );
922                (edge, hitbox)
923            })
924            .collect();
925        TouchHandleLayout { hitboxes }
926    }
927
928    /// Paints the touch handles at the ends of this participant's selection,
929    /// in `color`, where the participant is in the paint order — so whatever
930    /// is drawn over the text is drawn over its handles too. Call at the end
931    /// of the participant's paint, after [`Self::register`] for the frame.
932    ///
933    /// The handles take the finger's drag from there; Base moves the
934    /// selection with it.
935    pub fn paint_touch_handles(
936        &self,
937        layout: &TouchHandleLayout,
938        color: Hsla,
939        window: &mut Window,
940        cx: &mut App,
941    ) {
942        let Some(state) = WindowSelectionState::existing(window, cx) else {
943            return;
944        };
945        for (edge, caret) in state.read(cx).touch_handles_of(self.entity_id()) {
946            TouchHandle::paint(edge, caret, color, window);
947        }
948        let bounds: Vec<Bounds<Pixels>> = layout
949            .hitboxes
950            .iter()
951            .map(|(_, hitbox)| hitbox.bounds)
952            .collect();
953        if !state.read(cx).handles_painted_at(self.entity_id(), &bounds) {
954            state.update(cx, |state, _| {
955                state.register_handles(self.entity_id(), bounds)
956            });
957        }
958        for (edge, hitbox) in &layout.hitboxes {
959            let edge = *edge;
960            // Touch: the drag is offered on the first touch, before it can
961            // become a tap, a long press or a pan.
962            window.on_mouse_event({
963                let hitbox = hitbox.clone();
964                let state = state.downgrade();
965                move |event: &TouchDragEvent, phase, window, cx| {
966                    if !phase.bubble()
967                        || event.phase != TouchPhase::Started
968                        || window.default_prevented()
969                        || !hitbox.is_hovered(window)
970                    {
971                        return;
972                    }
973                    let Some(state) = state.upgrade() else {
974                        return;
975                    };
976                    window.prevent_default();
977                    cx.stop_propagation();
978                    state.update(cx, |state, cx| {
979                        state.begin_edge_drag(edge, event.position, window, cx)
980                    });
981                    WindowSelectionState::resolve_content_keys(&state, cx);
982                }
983            });
984            // Mouse: the same drag for a pointer.
985            window.on_mouse_event({
986                let hitbox = hitbox.clone();
987                let state = state.downgrade();
988                move |event: &MouseDownEvent, phase, window, cx| {
989                    if !phase.bubble()
990                        || event.button != MouseButton::Left
991                        || !hitbox.is_hovered(window)
992                    {
993                        return;
994                    }
995                    let Some(state) = state.upgrade() else {
996                        return;
997                    };
998                    cx.stop_propagation();
999                    state.update(cx, |state, cx| {
1000                        state.begin_edge_drag(edge, event.position, window, cx)
1001                    });
1002                    WindowSelectionState::resolve_content_keys(&state, cx);
1003                }
1004            });
1005        }
1006    }
1007
1008    /// Sets a participant-specific lookup for stable virtualized content keys.
1009    pub fn resolve_content_key_with(
1010        &self,
1011        callback: impl Fn(Point<Pixels>, &App) -> Option<TextSelectionContentKey> + 'static,
1012        cx: &mut App,
1013    ) {
1014        self.0
1015            .update(cx, |state, _| state.resolve_content_key_with(callback));
1016    }
1017
1018    fn downgrade(&self) -> WeakEntity<SelectableTextState> {
1019        self.0.downgrade()
1020    }
1021}
1022
1023#[derive(Clone)]
1024struct ParticipantRegistration {
1025    participant: WeakEntity<SelectableTextState>,
1026    registration: Rc<TextSelectionRegistration>,
1027    generation: u64,
1028}
1029
1030#[derive(Clone)]
1031struct SelectionEndpoint {
1032    participant: Option<WeakEntity<SelectableTextState>>,
1033    point: Point<Pixels>,
1034    inside: bool,
1035    inside_text: bool,
1036    content_key: Option<TextSelectionContentKey>,
1037    content_key_resolver: Option<(ContentKeyResolver, Point<Pixels>)>,
1038}
1039
1040impl SelectionEndpoint {
1041    fn snapshot(&self) -> TextSelectionEndpoint {
1042        let snapshot = TextSelectionEndpoint::new(self.entity_id(), self.point);
1043        if let Some(content_key) = self.content_key {
1044            snapshot.with_content_key(content_key)
1045        } else {
1046            snapshot
1047        }
1048    }
1049
1050    fn resolve(
1051        &self,
1052        participants: &HashMap<EntityId, ParticipantRegistration>,
1053    ) -> Option<Point<Pixels>> {
1054        let participant = self.participant.as_ref()?;
1055        let registration = participants.get(&participant.entity_id())?;
1056        participant.upgrade()?;
1057        Some(
1058            self.point
1059                + registration.registration.scroll_offset
1060                + registration.registration.bounds.origin,
1061        )
1062    }
1063
1064    fn entity_id(&self) -> Option<EntityId> {
1065        self.participant
1066            .as_ref()
1067            .map(|participant| participant.entity_id())
1068    }
1069}
1070
1071/// What a long press left behind: the handles and the edit menu.
1072///
1073/// The presentation layer draws both and reports their bounds each frame, so
1074/// that a press on a handle or a menu item is not taken for a press on the
1075/// text underneath, which would clear the very selection they belong to.
1076#[derive(Default)]
1077struct TouchSelection {
1078    /// The current selection was made by touch and carries handles.
1079    active: bool,
1080    menu_open: bool,
1081    drag: Option<EdgeDrag>,
1082    /// Where the menu was painted this frame, and the frame before: a press
1083    /// arrives between frames, and the surfaces may have moved in the one
1084    /// that has not painted yet.
1085    ui_bounds: Vec<Bounds<Pixels>>,
1086    previous_ui_bounds: Vec<Bounds<Pixels>>,
1087    /// Where each participant last painted its handles. A participant drawn
1088    /// from an earlier frame, which does not paint again, still shows its
1089    /// handles where it painted them; one that paints again replaces them.
1090    handle_bounds: HashMap<EntityId, Vec<Bounds<Pixels>>>,
1091    /// The ends as last laid out: `(start, end, start_visible, end_visible)`.
1092    /// A frame in which no participant paints a selection — the cursor sits
1093    /// between two characters mid-drag — would otherwise lose the handle the
1094    /// finger holds, and with it the rest of the drag.
1095    last_edges: std::cell::Cell<Option<(Bounds<Pixels>, Bounds<Pixels>, bool, bool)>>,
1096}
1097
1098impl TouchSelection {
1099    fn covers(&self, position: Point<Pixels>) -> bool {
1100        self.active
1101            && self
1102                .ui_bounds
1103                .iter()
1104                .chain(&self.previous_ui_bounds)
1105                .chain(self.handle_bounds.values().flatten())
1106                .any(|bounds| bounds.contains(&position))
1107    }
1108
1109    /// Whether the handles or the menu were painted in either of the last
1110    /// two frames, which [`Self::begin_frame`] has to shift.
1111    fn painted_any(&self) -> bool {
1112        !self.ui_bounds.is_empty() || !self.previous_ui_bounds.is_empty()
1113    }
1114
1115    /// A new frame begins: what was painted last frame is kept one frame more.
1116    fn begin_frame(&mut self) {
1117        self.previous_ui_bounds = std::mem::take(&mut self.ui_bounds);
1118    }
1119
1120    /// Drops the handles and the menu; returns whether there were any.
1121    fn reset(&mut self) -> bool {
1122        let had = self.active;
1123        self.handle_bounds.clear();
1124        self.active = false;
1125        self.menu_open = false;
1126        self.drag = None;
1127        self.last_edges.set(None);
1128        had
1129    }
1130}
1131
1132/// Window-local generic text-selection state.
1133#[derive(Default)]
1134struct WindowSelectionState {
1135    /// What each participant painted this frame, or last painted. Painting
1136    /// reports it without changing the selection: see
1137    /// [`WindowSelectionState::register_quietly`].
1138    participants: RefCell<HashMap<EntityId, ParticipantRegistration>>,
1139    active_scope: TextSelectionScopeId,
1140    anchor: Option<SelectionEndpoint>,
1141    cursor: Option<SelectionEndpoint>,
1142    pending_extension_anchor: Option<SelectionEndpoint>,
1143    is_selecting: bool,
1144    did_hit_text: bool,
1145    frame_generation: Cell<u64>,
1146    finish_frame_scheduled: Cell<bool>,
1147    refresh_held_cursor: bool,
1148    mouse_down_prepared: bool,
1149    auto_scroll: AutoScroll,
1150    /// Where the anchor's text was when the last synthetic wheel went out,
1151    /// and how many went out without moving it. A container at its end
1152    /// cannot scroll further; pushing it on would only make one that
1153    /// bounces stretch and snap back on every tick.
1154    auto_scroll_stall: (Option<(Bounds<Pixels>, Point<Pixels>)>, u8),
1155    touch: TouchSelection,
1156    /// This entity, so that touch changes can notify observers from paths that
1157    /// only hold an [`App`].
1158    entity_id: Option<EntityId>,
1159}
1160
1161impl WindowSelectionState {
1162    fn resolve_content_keys(state: &Entity<Self>, cx: &mut App) {
1163        let pending = state.read_with(cx, |state, _| {
1164            [
1165                state
1166                    .anchor
1167                    .as_ref()
1168                    .and_then(|endpoint| endpoint.content_key_resolver.clone()),
1169                state
1170                    .cursor
1171                    .as_ref()
1172                    .and_then(|endpoint| endpoint.content_key_resolver.clone()),
1173            ]
1174        });
1175        // Nothing to resolve and no selection to publish: an event that
1176        // changed nothing does not change the state either.
1177        if pending.iter().all(Option::is_none) && !state.read(cx).is_active() {
1178            return;
1179        }
1180        let resolved =
1181            pending.map(|pending| pending.and_then(|(callback, point)| callback(point, cx)));
1182        state.update(cx, |state, cx| {
1183            if let (Some(endpoint), Some(key)) = (state.anchor.as_mut(), resolved[0]) {
1184                endpoint.content_key = Some(key);
1185                endpoint.content_key_resolver = None;
1186            }
1187            if let (Some(endpoint), Some(key)) = (state.cursor.as_mut(), resolved[1]) {
1188                endpoint.content_key = Some(key);
1189                endpoint.content_key_resolver = None;
1190            }
1191            state.publish_snapshots(cx);
1192        });
1193    }
1194    fn acquire(window_id: gpui::WindowId, cx: &mut App) -> Entity<Self> {
1195        if !cx.has_global::<SelectionStateRegistry>() {
1196            cx.set_global(SelectionStateRegistry::default());
1197        }
1198        if let Some(state) = cx
1199            .global::<SelectionStateRegistry>()
1200            .0
1201            .get(&window_id)
1202            .and_then(WeakEntity::upgrade)
1203        {
1204            return state;
1205        }
1206
1207        let active_scope = if cx.has_global::<PendingTextSelectionScopes>() {
1208            cx.global_mut::<PendingTextSelectionScopes>()
1209                .0
1210                .remove(&window_id)
1211                .unwrap_or_default()
1212        } else {
1213            TextSelectionScopeId::default()
1214        };
1215
1216        let state = cx.new(move |cx| {
1217            let entity_id = cx.entity_id();
1218            cx.on_release(move |state: &mut WindowSelectionState, cx| {
1219                let handlers = state.clear_state(cx);
1220                if cx.has_global::<SelectionStateRegistry>() {
1221                    let registry = &mut cx.global_mut::<SelectionStateRegistry>().0;
1222                    if registry
1223                        .get(&window_id)
1224                        .is_some_and(|state| state.entity_id() == entity_id)
1225                    {
1226                        registry.remove(&window_id);
1227                    }
1228                }
1229                if !handlers.is_empty() {
1230                    cx.defer(move |cx| dispatch_clear_handlers(handlers, cx));
1231                }
1232            })
1233            .detach();
1234            Self {
1235                active_scope,
1236                entity_id: Some(entity_id),
1237                ..Self::default()
1238            }
1239        });
1240        cx.global_mut::<SelectionStateRegistry>()
1241            .0
1242            .insert(window_id, state.downgrade());
1243        state
1244    }
1245
1246    #[cfg(test)]
1247    fn ensure(window: &Window, cx: &mut App) -> Entity<Self> {
1248        Self::acquire(window.window_handle().window_id(), cx)
1249    }
1250
1251    fn existing(window: &Window, cx: &App) -> Option<Entity<Self>> {
1252        if !cx.has_global::<SelectionStateRegistry>() {
1253            return None;
1254        }
1255        cx.global::<SelectionStateRegistry>()
1256            .0
1257            .get(&window.window_handle().window_id())
1258            .and_then(WeakEntity::upgrade)
1259    }
1260
1261    /// Updates the active scope. Participants from other scopes cannot participate.
1262    #[cfg(test)]
1263    fn set_active_scope(&mut self, scope: TextSelectionScopeId, cx: &mut App) {
1264        let handlers = self.set_active_scope_state(scope, cx);
1265        dispatch_clear_handlers(handlers, cx);
1266    }
1267
1268    fn set_active_scope_state(
1269        &mut self,
1270        scope: TextSelectionScopeId,
1271        cx: &mut App,
1272    ) -> Vec<ClearHandler> {
1273        if self.active_scope == scope {
1274            return Vec::new();
1275        }
1276        let handlers = self.clear_state(cx);
1277        self.active_scope = scope;
1278        self.publish_snapshots(cx);
1279        handlers
1280    }
1281
1282    /// Sweeps participants after a rendered frame has completed.
1283    ///
1284    /// Registrations are stamped with the current generation while any sibling
1285    /// is painting. Sweeping only after paint makes registration independent of
1286    /// whether a participant or the lifecycle element paints first.
1287    ///
1288    /// A missed generation alone does not mean the participant left the window:
1289    /// a cached view replays its recorded frame, painting the same text at the
1290    /// same place without running any of its elements. Such a participant keeps
1291    /// the registration it last reported, which still describes what is on
1292    /// screen; only one whose element GPUI has dropped is swept.
1293    pub fn finish_frame(&mut self, cx: &mut App) -> Vec<ClearHandler> {
1294        self.finish_frame_scheduled.set(false);
1295        let generation = self.frame_generation.get();
1296        let stale = self
1297            .participants
1298            .get_mut()
1299            .iter()
1300            .filter_map(|(id, registration)| {
1301                (registration.generation != generation && !registration.registration.is_rendered())
1302                    .then(|| (*id, registration.participant.clone()))
1303            })
1304            .collect::<Vec<_>>();
1305        let mut handlers = Vec::new();
1306        for (id, participant) in stale {
1307            self.participants.get_mut().remove(&id);
1308            if let Some(participant) = participant.upgrade()
1309                && participant.read(cx).has_selection_to_clear()
1310            {
1311                if let Some(handler) = participant.update(cx, |state, cx| state.clear_state(cx)) {
1312                    handlers.push(handler);
1313                }
1314            }
1315        }
1316        self.publish_snapshots(cx);
1317        self.frame_generation.set(generation.wrapping_add(1));
1318        handlers
1319    }
1320
1321    fn schedule_finish_frame(&self) -> bool {
1322        !self.finish_frame_scheduled.replace(true)
1323    }
1324
1325    /// Whether there is a selection, or a gesture or touch selection that
1326    /// may make one: what painting the participants reports can change it.
1327    fn is_active(&self) -> bool {
1328        self.anchor.is_some()
1329            || self.cursor.is_some()
1330            || self.pending_extension_anchor.is_some()
1331            || self.is_selecting
1332            || self.refresh_held_cursor
1333            || self.touch.active
1334    }
1335
1336    /// Records this frame's geometry for a participant without updating
1337    /// this state, when there is no selection for the geometry to change.
1338    /// Every participant paints its geometry on every frame; doing so through
1339    /// an update would mark the state, and every view that reads it, changed
1340    /// on every frame. Returns the registration when it has to go through
1341    /// [`Self::register_participant`].
1342    fn register_quietly(
1343        &self,
1344        selection: &TextSelectionHandle,
1345        registration: TextSelectionRegistration,
1346    ) -> Option<TextSelectionRegistration> {
1347        if self.is_active() {
1348            return Some(registration);
1349        }
1350        self.participants.borrow_mut().insert(
1351            selection.entity_id(),
1352            ParticipantRegistration {
1353                participant: selection.downgrade(),
1354                registration: Rc::new(registration),
1355                generation: self.frame_generation.get(),
1356            },
1357        );
1358        None
1359    }
1360
1361    /// Ends the frame as [`Self::finish_frame`] does, without updating this
1362    /// state, when there is no selection and no participant that left the
1363    /// window has one to clear. Returns whether it could.
1364    fn finish_frame_quietly(&self, cx: &App) -> bool {
1365        if self.is_active() {
1366            return false;
1367        }
1368        let generation = self.frame_generation.get();
1369        let mut participants = self.participants.borrow_mut();
1370        let gone = |registration: &ParticipantRegistration| {
1371            registration.generation != generation && !registration.registration.is_rendered()
1372        };
1373        if participants.values().any(|registration| {
1374            gone(registration)
1375                && registration
1376                    .participant
1377                    .upgrade()
1378                    .is_some_and(|participant| participant.read(cx).has_selection_to_clear())
1379        }) {
1380            return false;
1381        }
1382        participants.retain(|_, registration| {
1383            !gone(registration) && registration.participant.upgrade().is_some()
1384        });
1385        drop(participants);
1386        self.finish_frame_scheduled.set(false);
1387        self.frame_generation.set(generation.wrapping_add(1));
1388        true
1389    }
1390
1391    /// Registers this frame's geometry for a participant.
1392    pub fn register_participant(
1393        &mut self,
1394        selection: TextSelectionHandle,
1395        registration: TextSelectionRegistration,
1396        cx: &mut App,
1397    ) {
1398        self.prune_dead_participants();
1399        if self.is_selecting
1400            && registration.self_scroll
1401            && self.anchor.as_ref().and_then(SelectionEndpoint::entity_id)
1402                == Some(selection.entity_id())
1403            && self
1404                .participants
1405                .get_mut()
1406                .get(&selection.entity_id())
1407                .is_some_and(|previous| {
1408                    previous.registration.scroll_offset != registration.scroll_offset
1409                        || previous.registration.bounds != registration.bounds
1410                })
1411        {
1412            self.refresh_held_cursor = true;
1413        }
1414        // The handles sit on the painted ends; when those moved — a scroll, a
1415        // reflow, a select-all — whoever draws the handles needs to know.
1416        let edges_moved = self.touch.active
1417            && self
1418                .participants
1419                .get_mut()
1420                .get(&selection.entity_id())
1421                .is_none_or(|previous| {
1422                    previous.registration.selection_edges != registration.selection_edges
1423                });
1424        self.participants.get_mut().insert(
1425            selection.entity_id(),
1426            ParticipantRegistration {
1427                participant: selection.downgrade(),
1428                registration: Rc::new(registration),
1429                generation: self.frame_generation.get(),
1430            },
1431        );
1432        self.publish_snapshots(cx);
1433        if edges_moved {
1434            self.touch_changed(cx);
1435        }
1436    }
1437
1438    /// Starts a selection gesture using bounds hit testing (useful to adapters/tests).
1439    #[cfg(test)]
1440    fn begin(&mut self, position: Point<Pixels>, extend: bool, cx: &mut App) {
1441        self.begin_impl(position, extend, false, None, cx);
1442    }
1443
1444    /// Updates the current gesture using bounds hit testing.
1445    #[cfg(test)]
1446    fn update(&mut self, position: Point<Pixels>, cx: &mut App) {
1447        self.update_impl(position, None, cx);
1448    }
1449
1450    /// Ends the current gesture and keeps its selection visible.
1451    pub fn end(&mut self, cx: &mut App) {
1452        self.pending_extension_anchor = None;
1453        if !self.is_selecting {
1454            return;
1455        }
1456        self.is_selecting = false;
1457        if !self.did_hit_text {
1458            self.anchor = None;
1459            self.cursor = None;
1460        }
1461        self.stop_anchor_auto_scroll(cx);
1462        self.publish_snapshots(cx);
1463    }
1464
1465    /// Clears both window selection and every participant's local selection.
1466    pub fn clear(&mut self, cx: &mut App) {
1467        let handlers = self.clear_state(cx);
1468        dispatch_clear_handlers(handlers, cx);
1469    }
1470
1471    fn clear_state(&mut self, cx: &mut App) -> Vec<ClearHandler> {
1472        self.stop_anchor_auto_scroll(cx);
1473        self.anchor = None;
1474        self.cursor = None;
1475        self.pending_extension_anchor = None;
1476        self.is_selecting = false;
1477        self.did_hit_text = false;
1478        if self.touch.reset() {
1479            self.touch_changed(cx);
1480        }
1481        self.prune_dead_participants();
1482        self.participants
1483            .borrow()
1484            .values()
1485            .filter_map(|registration| registration.participant.upgrade())
1486            .filter_map(|participant| participant.update(cx, |state, cx| state.clear_state(cx)))
1487            .collect()
1488    }
1489
1490    /// Tells whoever draws the handles and the menu that they changed.
1491    ///
1492    /// Deferred, because the change may come from a participant painting its
1493    /// selection ends: a notification raised inside a draw marks the view
1494    /// dirty but starts no frame, and the handles would sit where they were
1495    /// until something else redrew the window.
1496    ///
1497    /// The participants in the selection hear it too: they paint the
1498    /// handles, and lay the handles' hitboxes out from the ends they painted
1499    /// last frame, so they render once more. A participant under a cached
1500    /// view would otherwise not, and the next frame would replay this one,
1501    /// with no hitbox where a handle is.
1502    fn touch_changed(&self, cx: &mut App) {
1503        let participants = self
1504            .participants
1505            .borrow()
1506            .values()
1507            .filter_map(|registration| registration.participant.upgrade())
1508            .filter(|participant| participant.read(cx).snapshot.is_some())
1509            .collect::<Vec<_>>();
1510        let entity_id = self.entity_id;
1511        cx.defer(move |cx| {
1512            for participant in participants {
1513                participant.update(cx, |_, cx| {
1514                    cx.emit(TextSelectionEvent::TouchSelectionChanged)
1515                });
1516            }
1517            if let Some(entity_id) = entity_id {
1518                cx.notify(entity_id);
1519            }
1520        });
1521    }
1522
1523    fn copy_items(&self, cx: &App) -> Vec<CopyItem> {
1524        self.participants
1525            .borrow()
1526            .values()
1527            .filter_map(|registration| {
1528                let participant = registration.participant.upgrade()?;
1529                participant
1530                    .read(cx)
1531                    .copy_item(registration.registration.document_order)
1532            })
1533            .collect()
1534    }
1535
1536    #[cfg(test)]
1537    fn selected_text(&self, cx: &mut App) -> String {
1538        resolve_copy_items(self.copy_items(cx), cx)
1539    }
1540
1541    /// Whether the current endpoints take in at least one character of some
1542    /// participant's painted text, as opposed to two points with nothing
1543    /// between them.
1544    fn selects_text(&self, cx: &App) -> bool {
1545        self.snapshot().is_some()
1546            && self.participants.borrow().values().any(|registration| {
1547                registration
1548                    .participant
1549                    .upgrade()
1550                    .is_some_and(|participant| {
1551                        let participant = participant.read(cx);
1552                        project_ranges(participant.snapshot, &participant.runs.borrow())
1553                            .ranges()
1554                            .iter()
1555                            .any(|range| range.as_ref().is_some_and(|range| !range.is_empty()))
1556                    })
1557            })
1558    }
1559
1560    /// Returns whether a drag or a participant-local selection is active.
1561    pub fn has_selection(&self, cx: &App) -> bool {
1562        self.snapshot().is_some()
1563            || self.participants.borrow().values().any(|registration| {
1564                registration
1565                    .participant
1566                    .upgrade()
1567                    .is_some_and(|participant| participant.read(cx).local_selection)
1568            })
1569    }
1570
1571    /// Returns the current resolved selection endpoints.
1572    pub fn snapshot(&self) -> Option<TextSelectionSnapshot> {
1573        if !self.did_hit_text {
1574            return None;
1575        }
1576        let anchor_endpoint = self.anchor.as_ref()?;
1577        let cursor_endpoint = self.cursor.as_ref()?;
1578        let anchor = anchor_endpoint.resolve(&self.participants.borrow())?;
1579        let cursor = cursor_endpoint.resolve(&self.participants.borrow())?;
1580        (anchor != cursor).then(|| {
1581            TextSelectionSnapshot::new(anchor_endpoint.snapshot(), cursor_endpoint.snapshot())
1582                .with_selecting(self.is_selecting)
1583                .with_window_points(Some(TextSelectionWindowPoints { anchor, cursor }))
1584        })
1585    }
1586
1587    /// Returns whether a drag is currently in progress.
1588    #[cfg(test)]
1589    fn is_selecting(&self) -> bool {
1590        self.is_selecting
1591    }
1592
1593    fn prepare_for_mouse_down(&mut self, extend: bool, cx: &mut App) -> Vec<ClearHandler> {
1594        let pending_extension_anchor = extend.then(|| self.anchor.clone()).flatten();
1595        self.stop_anchor_auto_scroll(cx);
1596        self.anchor = None;
1597        self.cursor = None;
1598        self.pending_extension_anchor = None;
1599        self.is_selecting = false;
1600        self.did_hit_text = false;
1601        if self.touch.reset() {
1602            self.touch_changed(cx);
1603        }
1604        self.prune_dead_participants();
1605        let handlers = self
1606            .participants
1607            .borrow()
1608            .values()
1609            .filter_map(|registration| registration.participant.upgrade())
1610            .filter_map(|participant| participant.update(cx, |state, cx| state.clear_state(cx)))
1611            .collect();
1612        self.pending_extension_anchor = pending_extension_anchor;
1613        handlers
1614    }
1615
1616    /// The touch selection laid out for its handles and edit menu.
1617    ///
1618    /// The ends come from the participants: the first selected character of
1619    /// the earliest participant in document order and the last of the latest.
1620    /// A participant whose selection is entirely scrolled away paints no ends,
1621    /// so the handles disappear with the text they mark.
1622    fn touch_selection(&self) -> Option<TouchSelectionSnapshot> {
1623        if !self.touch.active {
1624            return None;
1625        }
1626        // Each end with its owner's viewport: an end scrolled out of its
1627        // participant gets no handle.
1628        let mut start: Option<(u64, Bounds<Pixels>, bool)> = None;
1629        let mut end: Option<(u64, Bounds<Pixels>, bool)> = None;
1630        for registration in self.participants.borrow().values() {
1631            let geometry = &registration.registration;
1632            if geometry.scope != self.active_scope || registration.participant.upgrade().is_none() {
1633                continue;
1634            }
1635            let Some((edge_start, edge_end)) = geometry.selection_edges else {
1636                continue;
1637            };
1638            let order = geometry.document_order;
1639            let viewport = geometry.hitbox.bounds;
1640            if start.is_none_or(|(best, ..)| order < best) {
1641                start = Some((order, edge_start, caret_in_view(edge_start, viewport)));
1642            }
1643            if end.is_none_or(|(best, ..)| order >= best) {
1644                end = Some((order, edge_end, caret_in_view(edge_end, viewport)));
1645            }
1646        }
1647        let edges = match (start, end) {
1648            (Some((_, start, start_visible)), Some((_, end, end_visible))) => {
1649                let edges = (start, end, start_visible, end_visible);
1650                self.touch.last_edges.set(Some(edges));
1651                edges
1652            }
1653            _ if self.touch.drag.is_some() => self.touch.last_edges.get()?,
1654            _ => return None,
1655        };
1656        let (start, end, start_visible, end_visible) = edges;
1657        Some(
1658            TouchSelectionSnapshot::new(start, end)
1659                .with_edge_visible(SelectionEdge::Start, start_visible)
1660                .with_edge_visible(SelectionEdge::End, end_visible)
1661                .with_menu_open(self.touch.menu_open)
1662                .with_dragging(self.touch.drag.map(|drag| drag.edge())),
1663        )
1664    }
1665
1666    /// The handles `participant` paints: the start when no participant
1667    /// earlier in document order has a selection end, the end when none
1668    /// later has. Participants paint in document order, so a later one that
1669    /// takes the end over has registered its ends by the time it asks.
1670    fn touch_handles_of(&self, participant: EntityId) -> Vec<(SelectionEdge, Bounds<Pixels>)> {
1671        if !self.touch.active {
1672            return Vec::new();
1673        }
1674        let Some(own) = self.participants.borrow().get(&participant).cloned() else {
1675            return Vec::new();
1676        };
1677        let Some((start, end)) = own.registration.selection_edges else {
1678            return Vec::new();
1679        };
1680        if start == end {
1681            return Vec::new();
1682        }
1683        let order = own.registration.document_order;
1684        let participants = self.participants.borrow();
1685        let others = participants.iter().filter(|(id, registration)| {
1686            **id != participant
1687                && registration.registration.scope == self.active_scope
1688                && registration.registration.selection_edges.is_some()
1689                && registration.participant.upgrade().is_some()
1690        });
1691        let (mut earlier, mut later) = (false, false);
1692        for (_, registration) in others {
1693            let other = registration.registration.document_order;
1694            earlier |= other < order;
1695            later |= other > order;
1696        }
1697        let viewport = own.registration.hitbox.bounds;
1698        let mut handles = Vec::with_capacity(2);
1699        if !earlier && caret_in_view(start, viewport) {
1700            handles.push((SelectionEdge::Start, start));
1701        }
1702        if !later && caret_in_view(end, viewport) {
1703            handles.push((SelectionEdge::End, end));
1704        }
1705        handles
1706    }
1707
1708    /// Keeps the selection a long press made, and opens the edit menu over it.
1709    fn keep_touch_selection(&mut self, cx: &mut App) {
1710        self.touch.active = self.snapshot().is_some();
1711        self.touch.menu_open = self.touch.active;
1712        self.touch.drag = None;
1713        self.touch_changed(cx);
1714    }
1715
1716    /// Records where the menu is painted this frame.
1717    fn register_touch_ui(&mut self, bounds: Bounds<Pixels>) {
1718        self.touch.ui_bounds.push(bounds);
1719    }
1720
1721    /// Whether `bounds` are where `participant` painted its handles last.
1722    fn handles_painted_at(&self, participant: EntityId, bounds: &[Bounds<Pixels>]) -> bool {
1723        self.touch
1724            .handle_bounds
1725            .get(&participant)
1726            .map_or(bounds.is_empty(), |painted| painted == bounds)
1727    }
1728
1729    /// Records where `participant` painted its handles, which replace those
1730    /// it painted before.
1731    fn register_handles(&mut self, participant: EntityId, bounds: Vec<Bounds<Pixels>>) {
1732        self.touch.handle_bounds.insert(participant, bounds);
1733    }
1734
1735    fn close_edit_menu(&mut self, cx: &mut App) {
1736        if !self.touch.menu_open {
1737            return;
1738        }
1739        self.touch.menu_open = false;
1740        self.touch_changed(cx);
1741    }
1742
1743    /// The handles follow the text, but the menu would sit over whatever
1744    /// scrolls underneath it: it steps aside while a finger scrolls and comes
1745    /// back over the handles once the finger lifts.
1746    fn edit_menu_on_scroll(&mut self, phase: TouchPhase, cx: &mut App) {
1747        if !self.touch.active || self.touch.drag.is_some() {
1748            return;
1749        }
1750        match phase {
1751            TouchPhase::Ended | TouchPhase::Cancelled => {
1752                if !self.touch.menu_open {
1753                    self.touch.menu_open = true;
1754                    self.touch_changed(cx);
1755                }
1756            }
1757            _ => self.close_edit_menu(cx),
1758        }
1759    }
1760
1761    /// Selects all of the participant the touch selection started in.
1762    ///
1763    /// This stays a point selection — anchored on the participant's first
1764    /// line of text, ending on its last — rather than switching the
1765    /// participant to a local select-all: one selection, painted, reported
1766    /// and copied the one way, with handles that drag on from its ends.
1767    fn select_all_touched(&mut self, cx: &mut App) {
1768        if !self.touch.active {
1769            return;
1770        }
1771        let Some((participant, registration)) = self.anchor_registration() else {
1772            return;
1773        };
1774        // From the participant's text runs, not its painted line bounds:
1775        // those are clipped to the viewport, and a message taller than the
1776        // screen would only select what is on it.
1777        let (anchor, cursor) = {
1778            let runs = &participant.read(cx).runs.borrow();
1779            let (Some(first), Some(last)) = (
1780                runs.iter().min_by_key(|run| run.document_order),
1781                runs.iter().max_by_key(|run| run.document_order),
1782            ) else {
1783                return;
1784            };
1785            let (Some(start), Some(end)) = (
1786                first.layout.position_for_index(0),
1787                last.layout.position_for_index(last.text.len()),
1788            ) else {
1789                return;
1790            };
1791            // Just inside the first and the last glyph, mid-line, so both
1792            // land on text.
1793            let inset = px(1.);
1794            (
1795                point(start.x + inset, start.y + first.layout.line_height() / 2.),
1796                point(end.x - inset, end.y + last.layout.line_height() / 2.),
1797            )
1798        };
1799        let content_key_resolver = participant.read(cx).content_key_resolver.clone();
1800        let to_endpoint = |window_point: Point<Pixels>| {
1801            let content_point =
1802                window_point - registration.bounds.origin - registration.scroll_offset;
1803            SelectionEndpoint {
1804                participant: Some(participant.downgrade()),
1805                point: content_point,
1806                inside: true,
1807                inside_text: true,
1808                content_key: None,
1809                content_key_resolver: content_key_resolver
1810                    .clone()
1811                    .map(|resolver| (resolver, content_point)),
1812            }
1813        };
1814        self.anchor = Some(to_endpoint(anchor));
1815        self.cursor = Some(to_endpoint(cursor));
1816        self.did_hit_text = true;
1817        self.is_selecting = false;
1818        self.touch.menu_open = true;
1819        self.publish_snapshots(cx);
1820        self.touch_changed(cx);
1821    }
1822
1823    /// Starts dragging one end of the touch selection from `finger`.
1824    ///
1825    /// The selection is rebuilt from the painted ends, anchored at the end that
1826    /// stays: a select-all or a word selection becomes an ordinary point
1827    /// selection which the drag then extends.
1828    fn begin_edge_drag(
1829        &mut self,
1830        edge: SelectionEdge,
1831        finger: Point<Pixels>,
1832        window: &mut Window,
1833        cx: &mut App,
1834    ) {
1835        let Some(snapshot) = self.touch_selection() else {
1836            return;
1837        };
1838        let handlers = self.prepare_for_mouse_down(false, cx);
1839        dispatch_clear_handlers(handlers, cx);
1840        let held = snapshot.edge(edge.opposite());
1841        // Just inside the held end, so the anchor lands on the character it
1842        // marks rather than on the boundary between two participants.
1843        let nudge = px(1.);
1844        let anchor_point = match edge {
1845            SelectionEdge::Start => point(held.left() - nudge, held.center().y),
1846            SelectionEdge::End => point(held.left() + nudge, held.center().y),
1847        };
1848        let anchor = self.endpoint(anchor_point, None, cx);
1849        let drag = EdgeDrag::begin(edge, snapshot.edge(edge), finger);
1850        let cursor = self.endpoint(drag.text_position(finger), Some(window), cx);
1851        self.did_hit_text = anchor.inside_text || cursor.inside_text;
1852        self.anchor = Some(anchor);
1853        self.cursor = Some(cursor);
1854        self.is_selecting = true;
1855        self.touch.active = true;
1856        self.touch.menu_open = false;
1857        self.touch.drag = Some(drag);
1858        self.publish_snapshots(cx);
1859        self.touch_changed(cx);
1860    }
1861
1862    fn update_edge_drag(&mut self, finger: Point<Pixels>, window: &Window, cx: &mut Context<Self>) {
1863        let Some(drag) = self.touch.drag else {
1864            return;
1865        };
1866        let before = self.cursor.clone();
1867        self.update_in_window(drag.text_position(finger), window, cx);
1868        // A handle never collapses the selection: at the other end it stops,
1869        // and the finger has to pass that end, onto text, to swap the two.
1870        if !self.selects_text(cx) {
1871            self.cursor = before;
1872            self.publish_snapshots(cx);
1873            return;
1874        }
1875        // Dragging one end past the other swaps them: the cursor now lies
1876        // before the anchor, so the finger holds what became the start.
1877        if let Some(points) = self
1878            .snapshot()
1879            .and_then(|snapshot| snapshot.window_points())
1880        {
1881            let (anchor, cursor) = (points.anchor(), points.cursor());
1882            let cursor_first = cursor.y < anchor.y || (cursor.y == anchor.y && cursor.x < anchor.x);
1883            let edge = if cursor_first {
1884                SelectionEdge::Start
1885            } else {
1886                SelectionEdge::End
1887            };
1888            if let Some(drag) = self.touch.drag.as_mut() {
1889                drag.set_edge(edge);
1890            }
1891        }
1892        self.touch_changed(cx);
1893    }
1894
1895    fn end_edge_drag(&mut self, cx: &mut App) {
1896        if self.touch.drag.take().is_none() {
1897            return;
1898        }
1899        self.end(cx);
1900        self.keep_touch_selection(cx);
1901    }
1902
1903    fn begin_in_window(
1904        &mut self,
1905        position: Point<Pixels>,
1906        extend: bool,
1907        window: &mut Window,
1908        cx: &mut App,
1909    ) {
1910        self.begin_impl(position, extend, true, Some(window), cx);
1911    }
1912
1913    fn update_in_window(
1914        &mut self,
1915        position: Point<Pixels>,
1916        window: &Window,
1917        cx: &mut Context<Self>,
1918    ) {
1919        if !cx.has_active_drag() {
1920            self.update_impl(position, Some(window), cx);
1921            self.update_auto_scroll(position, window, cx);
1922        }
1923    }
1924
1925    fn select_at(
1926        &mut self,
1927        position: Point<Pixels>,
1928        click_count: usize,
1929        window: &mut Window,
1930        cx: &mut App,
1931    ) {
1932        GlobalState::init(cx);
1933        if GlobalState::is_text_selection_suppressed(cx) {
1934            return;
1935        }
1936        let hit = self.endpoint(position, Some(window), cx);
1937        if !hit.inside_text {
1938            return;
1939        }
1940        let Some(participant) = hit
1941            .participant
1942            .and_then(|participant| participant.upgrade())
1943        else {
1944            return;
1945        };
1946        let points =
1947            points_for_multi_click(&participant.read(cx).runs.borrow(), position, click_count);
1948        let Some((anchor, cursor)) = points else {
1949            return;
1950        };
1951        let Some(registration) = self
1952            .participants
1953            .borrow()
1954            .get(&participant.entity_id())
1955            .cloned()
1956        else {
1957            return;
1958        };
1959        let content_key_resolver = participant.read(cx).content_key_resolver.clone();
1960        let to_endpoint = |point: Point<Pixels>| {
1961            let content_point = point
1962                - registration.registration.bounds.origin
1963                - registration.registration.scroll_offset;
1964            SelectionEndpoint {
1965                participant: Some(participant.downgrade()),
1966                point: content_point,
1967                inside: true,
1968                inside_text: true,
1969                content_key: None,
1970                content_key_resolver: content_key_resolver
1971                    .clone()
1972                    .map(|resolver| (resolver, content_point)),
1973            }
1974        };
1975        self.anchor = Some(to_endpoint(anchor));
1976        self.cursor = Some(to_endpoint(cursor));
1977        self.did_hit_text = true;
1978        self.is_selecting = false;
1979        participant.update(cx, |state, cx| state.focus(window, cx));
1980        self.publish_snapshots(cx);
1981    }
1982
1983    #[cfg(test)]
1984    fn update_in_window_with_active_drag(
1985        &mut self,
1986        position: Point<Pixels>,
1987        active_drag: bool,
1988        window: &Window,
1989        cx: &mut App,
1990    ) {
1991        if !active_drag {
1992            self.update_impl(position, Some(window), cx);
1993        }
1994    }
1995
1996    fn begin_impl(
1997        &mut self,
1998        position: Point<Pixels>,
1999        extend: bool,
2000        already_prepared: bool,
2001        window: Option<&mut Window>,
2002        cx: &mut App,
2003    ) {
2004        GlobalState::init(cx);
2005        if GlobalState::is_text_selection_suppressed(cx) {
2006            self.pending_extension_anchor = None;
2007            return;
2008        }
2009        let previous_anchor = extend
2010            .then(|| {
2011                self.pending_extension_anchor
2012                    .take()
2013                    .or_else(|| self.anchor.clone())
2014            })
2015            .flatten()
2016            .filter(|anchor| anchor.resolve(&self.participants.borrow()).is_some());
2017        if !extend && !already_prepared {
2018            self.clear(cx);
2019        }
2020        let endpoint = self.endpoint(position, window.as_deref(), cx);
2021        let focus_participant = endpoint
2022            .inside
2023            .then(|| endpoint.participant.clone())
2024            .flatten();
2025        let anchor = previous_anchor.unwrap_or_else(|| endpoint.clone());
2026        self.anchor = Some(anchor.clone());
2027        self.cursor = Some(endpoint.clone());
2028        self.did_hit_text = anchor.inside_text || endpoint.inside_text;
2029        self.is_selecting = true;
2030        if let Some(participant) = focus_participant.and_then(|participant| participant.upgrade()) {
2031            if let Some(window) = window {
2032                participant.update(cx, |state, cx| state.focus(window, cx));
2033            }
2034        }
2035        self.publish_snapshots(cx);
2036    }
2037
2038    fn update_impl(&mut self, position: Point<Pixels>, window: Option<&Window>, cx: &mut App) {
2039        if !self.is_selecting {
2040            return;
2041        }
2042        let endpoint = self.endpoint(position, window, cx);
2043        self.did_hit_text |= endpoint.inside_text;
2044        self.cursor = Some(endpoint);
2045        if window.is_none() {
2046            self.update_participant_auto_scroll(position, cx);
2047        }
2048        self.publish_snapshots(cx);
2049    }
2050
2051    fn endpoint(
2052        &mut self,
2053        position: Point<Pixels>,
2054        window: Option<&Window>,
2055        cx: &App,
2056    ) -> SelectionEndpoint {
2057        self.prune_dead_participants();
2058        let mut hit: Option<(
2059            WeakEntity<SelectableTextState>,
2060            Rc<TextSelectionRegistration>,
2061            f32,
2062        )> = None;
2063        let mut predecessor: Option<(
2064            WeakEntity<SelectableTextState>,
2065            Rc<TextSelectionRegistration>,
2066        )> = None;
2067        let mut first: Option<(
2068            WeakEntity<SelectableTextState>,
2069            Rc<TextSelectionRegistration>,
2070        )> = None;
2071
2072        for registration in self.participants.borrow().values() {
2073            if registration.registration.scope != self.active_scope
2074                || registration.participant.upgrade().is_none()
2075            {
2076                continue;
2077            }
2078            let participant_geometry = &registration.registration;
2079            let hovered = window.map_or_else(
2080                || participant_geometry.bounds.contains(&position),
2081                |window| participant_geometry.hitbox.is_hovered(window),
2082            );
2083            if hovered {
2084                let area = f32::from(participant_geometry.bounds.size.width)
2085                    * f32::from(participant_geometry.bounds.size.height);
2086                if hit.as_ref().is_none_or(|(_, best, best_area)| {
2087                    area < *best_area
2088                        || (area == *best_area
2089                            && participant_geometry.document_order < best.document_order)
2090                }) {
2091                    hit = Some((
2092                        registration.participant.clone(),
2093                        participant_geometry.clone(),
2094                        area,
2095                    ));
2096                }
2097            }
2098            if participant_geometry.bounds.top() <= position.y
2099                && predecessor.as_ref().is_none_or(|(_, best)| {
2100                    participant_geometry.bounds.top() > best.bounds.top()
2101                        || (participant_geometry.bounds.top() == best.bounds.top()
2102                            && participant_geometry.document_order < best.document_order)
2103                })
2104            {
2105                predecessor = Some((
2106                    registration.participant.clone(),
2107                    participant_geometry.clone(),
2108                ));
2109            }
2110            if first.as_ref().is_none_or(|(_, best)| {
2111                participant_geometry.bounds.top() < best.bounds.top()
2112                    || (participant_geometry.bounds.top() == best.bounds.top()
2113                        && participant_geometry.document_order < best.document_order)
2114            }) {
2115                first = Some((
2116                    registration.participant.clone(),
2117                    participant_geometry.clone(),
2118                ));
2119            }
2120        }
2121
2122        let selection = hit
2123            .map(|(participant, registration, _)| (participant, registration, true))
2124            .or_else(|| {
2125                predecessor
2126                    .or(first)
2127                    .map(|(participant, registration)| (participant, registration, false))
2128            });
2129        match selection {
2130            Some((participant, registration, inside)) => {
2131                let point = position - registration.bounds.origin - registration.scroll_offset;
2132                let content_key_resolver = participant.upgrade().and_then(|participant| {
2133                    participant
2134                        .read(cx)
2135                        .content_key_resolver
2136                        .clone()
2137                        .map(|callback| (callback, point))
2138                });
2139                SelectionEndpoint {
2140                    point,
2141                    participant: Some(participant),
2142                    inside,
2143                    inside_text: inside
2144                        && registration
2145                            .text_bounds
2146                            .iter()
2147                            .any(|bounds| bounds.contains(&position)),
2148                    content_key: None,
2149                    content_key_resolver,
2150                }
2151            }
2152            None => SelectionEndpoint {
2153                participant: None,
2154                point: position,
2155                inside: false,
2156                inside_text: false,
2157                content_key: None,
2158                content_key_resolver: None,
2159            },
2160        }
2161    }
2162
2163    fn publish_snapshots(&mut self, cx: &mut App) {
2164        self.prune_dead_participants();
2165        let snapshot = self.snapshot();
2166        let single_participant = self.single_participant();
2167        for (id, registration) in self.participants.borrow().iter() {
2168            let Some(participant) = registration.participant.upgrade() else {
2169                continue;
2170            };
2171            let participant_snapshot = (registration.registration.scope == self.active_scope
2172                && self.participates(*id, registration)
2173                && single_participant.is_none_or(|single| single == *id))
2174            .then_some(snapshot)
2175            .flatten()
2176            .map(|mut snapshot| {
2177                snapshot.coverage = self.coverage_for(*id);
2178                snapshot
2179            });
2180            if participant.read(cx).snapshot == participant_snapshot {
2181                continue;
2182            }
2183            participant.update(cx, |state, cx| state.set_snapshot(participant_snapshot, cx));
2184        }
2185    }
2186
2187    fn coverage_for(&self, id: EntityId) -> TextSelectionCoverage {
2188        let Some(anchor) = self.anchor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2189            return TextSelectionCoverage::Bounded;
2190        };
2191        let Some(cursor) = self.cursor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2192            return TextSelectionCoverage::Bounded;
2193        };
2194        if anchor == cursor {
2195            return TextSelectionCoverage::Bounded;
2196        }
2197        let anchor_order = self.participants.borrow()[&anchor]
2198            .registration
2199            .document_order;
2200        let cursor_order = self.participants.borrow()[&cursor]
2201            .registration
2202            .document_order;
2203        if id != anchor && id != cursor {
2204            TextSelectionCoverage::Full
2205        } else if (id == anchor) == (anchor_order < cursor_order) {
2206            TextSelectionCoverage::ToEnd
2207        } else {
2208            TextSelectionCoverage::FromStart
2209        }
2210    }
2211
2212    fn single_participant(&self) -> Option<EntityId> {
2213        let anchor = self.anchor.as_ref()?.entity_id()?;
2214        let cursor = self.cursor.as_ref()?.entity_id()?;
2215        (anchor == cursor).then_some(anchor)
2216    }
2217
2218    fn participates(&self, id: EntityId, registration: &ParticipantRegistration) -> bool {
2219        let Some(anchor) = self.anchor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2220            return false;
2221        };
2222        let Some(cursor) = self.cursor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2223            return false;
2224        };
2225        let Some(anchor_registration) = self.participants.borrow().get(&anchor).cloned() else {
2226            return false;
2227        };
2228        let Some(cursor_registration) = self.participants.borrow().get(&cursor).cloned() else {
2229            return false;
2230        };
2231        let start = anchor_registration
2232            .registration
2233            .document_order
2234            .min(cursor_registration.registration.document_order);
2235        let end = anchor_registration
2236            .registration
2237            .document_order
2238            .max(cursor_registration.registration.document_order);
2239        (start..=end).contains(&registration.registration.document_order)
2240            || id == anchor
2241            || id == cursor
2242    }
2243
2244    fn update_auto_scroll(
2245        &mut self,
2246        position: Point<Pixels>,
2247        window: &Window,
2248        cx: &mut Context<Self>,
2249    ) {
2250        // A finished gesture keeps its anchor for shift-click extension; only
2251        // a live drag may scroll.
2252        if !self.is_selecting {
2253            return;
2254        }
2255        let Some((_, registration)) = self.anchor_registration() else {
2256            return;
2257        };
2258        // Exactly one writer per drag: a participant that scrolls its own
2259        // content is notified directly; anything else gets a synthetic wheel.
2260        if registration.self_scroll {
2261            self.auto_scroll.stop();
2262            self.update_participant_auto_scroll(position, cx);
2263            return;
2264        }
2265        // The content mask is the nearest clipping viewport established by a
2266        // scrollable ancestor. It remains stable as the participant itself
2267        // moves, so selection keeps scrolling the same related region even
2268        // after the anchor text has moved out of view.
2269        let visible_bounds = registration.hitbox.content_mask.bounds;
2270        // Keeps the synthesized wheel event hit-testing inside the mask.
2271        const HIT_TEST_INSET: Pixels = px(1.);
2272        // A collapsed mask leaves an empty clamp range below — stop.
2273        if visible_bounds.size.width < HIT_TEST_INSET * 2.
2274            || visible_bounds.size.height < HIT_TEST_INSET * 2.
2275        {
2276            self.stop_anchor_auto_scroll(cx);
2277            return;
2278        }
2279        let delta = AutoScroll::compute_delta(position.y, visible_bounds);
2280        let event_position = point(
2281            position.x.clamp(
2282                visible_bounds.left() + HIT_TEST_INSET,
2283                visible_bounds.right() - HIT_TEST_INSET,
2284            ),
2285            position.y.clamp(
2286                visible_bounds.top() + HIT_TEST_INSET,
2287                visible_bounds.bottom() - HIT_TEST_INSET,
2288            ),
2289        );
2290        self.auto_scroll.last_drag_position = Some(event_position);
2291        self.auto_scroll_stall = (None, 0);
2292        let window = window.window_handle();
2293        self.auto_scroll.set(delta, cx, move |delta, state, cx| {
2294            let Some(position) = state.auto_scroll.last_drag_position else {
2295                return;
2296            };
2297            // Hold off once the container has shown, over a few ticks, that
2298            // it has nowhere left to go; the next move of the finger asks
2299            // again.
2300            const STALLED_TICKS: u8 = 3;
2301            let geometry = state
2302                .anchor_registration()
2303                .map(|(_, registration)| (registration.bounds, registration.scroll_offset));
2304            let (last, stalled) = &mut state.auto_scroll_stall;
2305            if *last == geometry {
2306                *stalled = stalled.saturating_add(1);
2307                if *stalled >= STALLED_TICKS {
2308                    return;
2309                }
2310            } else {
2311                *last = geometry;
2312                *stalled = 0;
2313            }
2314            let window = window;
2315            cx.defer(move |cx| {
2316                _ = window.update(cx, |_, window, cx| {
2317                    window.dispatch_event(
2318                        ScrollWheelEvent {
2319                            position,
2320                            delta: ScrollDelta::Pixels(point(px(0.), -delta)),
2321                            ..Default::default()
2322                        }
2323                        .to_platform_input(),
2324                        cx,
2325                    );
2326                });
2327            });
2328        });
2329    }
2330
2331    /// Drives the anchor participant's own scrolling, measured against the
2332    /// visible portion of the participant's element bounds.
2333    fn update_participant_auto_scroll(&self, position: Point<Pixels>, cx: &mut App) {
2334        let Some((participant, registration)) = self.anchor_registration() else {
2335            return;
2336        };
2337        let visible_bounds = registration
2338            .bounds
2339            .intersect(&registration.hitbox.content_mask.bounds);
2340        let delta = if visible_bounds.size.width > px(0.) && visible_bounds.size.height > px(0.) {
2341            AutoScroll::compute_delta(position.y, visible_bounds)
2342        } else {
2343            None
2344        };
2345        participant.update(cx, |state, cx| state.set_auto_scroll(delta, cx));
2346    }
2347
2348    fn stop_anchor_auto_scroll(&mut self, cx: &mut App) {
2349        self.auto_scroll.stop();
2350        let Some(participant) = self.anchor_participant() else {
2351            return;
2352        };
2353        participant.update(cx, |state, cx| state.set_auto_scroll(None, cx));
2354    }
2355
2356    /// The live participant owning the anchor of the current gesture.
2357    fn anchor_participant(&self) -> Option<Entity<SelectableTextState>> {
2358        self.anchor
2359            .as_ref()
2360            .filter(|anchor| anchor.inside)?
2361            .participant
2362            .as_ref()?
2363            .upgrade()
2364    }
2365
2366    /// The anchor participant together with its current frame registration.
2367    fn anchor_registration(
2368        &self,
2369    ) -> Option<(Entity<SelectableTextState>, Rc<TextSelectionRegistration>)> {
2370        let participant = self.anchor_participant()?;
2371        let registration = self
2372            .participants
2373            .borrow()
2374            .get(&participant.entity_id())?
2375            .clone();
2376        Some((participant, registration.registration.clone()))
2377    }
2378
2379    fn prune_dead_participants(&mut self) {
2380        self.participants
2381            .get_mut()
2382            .retain(|_, registration| registration.participant.upgrade().is_some());
2383    }
2384}
2385
2386#[derive(Default)]
2387/// Non-owning window locator; retained [`TextSelection`] element state owns
2388/// each live selection entity.
2389struct SelectionStateRegistry(HashMap<gpui::WindowId, WeakEntity<WindowSelectionState>>);
2390
2391impl Global for SelectionStateRegistry {}
2392
2393#[derive(Default)]
2394struct PendingTextSelectionScopes(HashMap<gpui::WindowId, TextSelectionScopeId>);
2395
2396impl Global for PendingTextSelectionScopes {}
2397
2398#[derive(Default)]
2399struct TextSelectionScopeStacks(HashMap<gpui::WindowId, Vec<TextSelectionScopeId>>);
2400
2401impl Global for TextSelectionScopeStacks {}
2402
2403fn push_text_selection_scope(window_id: gpui::WindowId, scope: TextSelectionScopeId, cx: &mut App) {
2404    if !cx.has_global::<TextSelectionScopeStacks>() {
2405        cx.set_global(TextSelectionScopeStacks::default());
2406    }
2407    cx.global_mut::<TextSelectionScopeStacks>()
2408        .0
2409        .entry(window_id)
2410        .or_default()
2411        .push(scope);
2412}
2413
2414fn pop_text_selection_scope(window_id: gpui::WindowId, cx: &mut App) {
2415    let stacks = &mut cx.global_mut::<TextSelectionScopeStacks>().0;
2416    let remove_stack = stacks.get_mut(&window_id).is_some_and(|stack| {
2417        stack.pop();
2418        stack.is_empty()
2419    });
2420    if remove_stack {
2421        stacks.remove(&window_id);
2422    }
2423}
2424
2425fn current_text_selection_scope(
2426    window_id: gpui::WindowId,
2427    cx: &App,
2428) -> Option<TextSelectionScopeId> {
2429    cx.has_global::<TextSelectionScopeStacks>()
2430        .then(|| {
2431            cx.global::<TextSelectionScopeStacks>()
2432                .0
2433                .get(&window_id)
2434                .and_then(|stack| stack.last().copied())
2435        })
2436        .flatten()
2437}
2438
2439fn with_text_selection_scope<T>(
2440    window_id: gpui::WindowId,
2441    scope: TextSelectionScopeId,
2442    cx: &mut App,
2443    callback: impl FnOnce(&mut App) -> T,
2444) -> T {
2445    push_text_selection_scope(window_id, scope, cx);
2446    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(cx)));
2447    pop_text_selection_scope(window_id, cx);
2448    match result {
2449        Ok(result) => result,
2450        Err(payload) => std::panic::resume_unwind(payload),
2451    }
2452}
2453
2454/// Window-level operations for text selection.
2455pub struct TextSelection;
2456
2457impl TextSelection {
2458    /// Returns whether `position` hits selectable text in the active scope.
2459    ///
2460    /// Uses the current frame's registered text geometry and hitboxes without
2461    /// starting or changing a selection. Blank space and text outside the active
2462    /// selection scope do not match. A containing control can yield a long press
2463    /// to the window's text selection before claiming the gesture itself.
2464    /// Call during pointer dispatch with the event's position, so the hitboxes
2465    /// reflect the point being queried. Touch-selection controls do not match.
2466    pub fn is_selectable_at(position: Point<Pixels>, window: &Window, cx: &mut App) -> bool {
2467        live_text_selection_state(window, cx).is_some_and(|state| {
2468            state.update(cx, |state, cx| {
2469                !state.touch.covers(position)
2470                    && state.endpoint(position, Some(window), cx).inside_text
2471            })
2472        })
2473    }
2474
2475    /// Returns the currently selected text in logical document order.
2476    pub fn selected_text(window: &mut Window, cx: &mut App) -> String {
2477        let Some(state) = live_text_selection_state(window, cx) else {
2478            return String::new();
2479        };
2480        let items = state.read(cx).copy_items(cx);
2481        resolve_copy_items(items, cx)
2482    }
2483
2484    /// Returns whether the window has a geometry selection or any participant
2485    /// has an active participant-local selection such as select-all.
2486    pub fn has_selection(window: &mut Window, cx: &mut App) -> bool {
2487        live_text_selection_state(window, cx).is_some_and(|state| state.read(cx).has_selection(cx))
2488    }
2489
2490    /// Clears window selection and every participant's local selection.
2491    pub fn clear(window: &mut Window, cx: &mut App) {
2492        if let Some(state) = live_text_selection_state(window, cx) {
2493            let handlers = state.update(cx, |state, cx| state.clear_state(cx));
2494            dispatch_clear_handlers(handlers, cx);
2495        }
2496    }
2497
2498    /// Clears selection for a known window identifier.
2499    ///
2500    /// Prefer [`Self::clear`] when a window reference is available. This
2501    /// narrow entry point supports hosts retiring deprecated window wrappers.
2502    pub fn clear_for_window(window_id: gpui::WindowId, cx: &mut App) {
2503        clear_window_text_selection(window_id, cx);
2504    }
2505
2506    /// Ends the current drag while leaving its selection visible.
2507    pub fn end(window: &mut Window, cx: &mut App) {
2508        if let Some(state) = live_text_selection_state(window, cx) {
2509            state.update(cx, |state, cx| state.end(cx));
2510        }
2511    }
2512
2513    /// Calls `callback` whenever the touch selection changes: it appears, its
2514    /// handles move, its menu opens or closes, or it goes away. Whoever draws
2515    /// the handles and the menu re-renders from here.
2516    pub fn observe_touch_selection(
2517        window: &Window,
2518        cx: &mut App,
2519        callback: impl Fn(&mut App) + 'static,
2520    ) -> Subscription {
2521        let state = WindowSelectionState::acquire(window.window_handle().window_id(), cx);
2522        cx.observe(&state, move |_, cx| callback(cx))
2523    }
2524
2525    /// Returns the selection a long press made, laid out for its handles and
2526    /// edit menu, or `None` when the selection was made with a pointer.
2527    pub fn touch_selection(window: &Window, cx: &App) -> Option<TouchSelectionSnapshot> {
2528        WindowSelectionState::existing(window, cx)?
2529            .read(cx)
2530            .touch_selection()
2531    }
2532
2533    /// Records where a touch handle or the edit menu is painted this frame, so
2534    /// that pressing it does not clear the selection it belongs to. Call from
2535    /// paint, every frame the surface is shown.
2536    pub fn register_touch_ui(bounds: Bounds<Pixels>, window: &Window, cx: &mut App) {
2537        if let Some(state) = WindowSelectionState::existing(window, cx) {
2538            state.update(cx, |state, _| state.register_touch_ui(bounds));
2539        }
2540    }
2541
2542    /// Closes the edit menu and keeps the selection with its handles.
2543    pub fn close_edit_menu(window: &mut Window, cx: &mut App) {
2544        if let Some(state) = live_text_selection_state(window, cx) {
2545            state.update(cx, |state, cx| state.close_edit_menu(cx));
2546        }
2547    }
2548
2549    /// Selects all of the text the touch selection started in, keeping the
2550    /// handles and the edit menu over the result.
2551    pub fn select_all(window: &mut Window, cx: &mut App) {
2552        if let Some(state) = live_text_selection_state(window, cx) {
2553            state.update(cx, |state, cx| state.select_all_touched(cx));
2554        }
2555    }
2556
2557    /// Starts dragging one end of the touch selection from `finger`. The other
2558    /// end stays; the menu closes until [`Self::end_edge_drag`].
2559    pub fn begin_edge_drag(
2560        edge: SelectionEdge,
2561        finger: Point<Pixels>,
2562        window: &mut Window,
2563        cx: &mut App,
2564    ) {
2565        if let Some(state) = live_text_selection_state(window, cx) {
2566            state.update(cx, |state, cx| {
2567                state.begin_edge_drag(edge, finger, window, cx)
2568            });
2569            WindowSelectionState::resolve_content_keys(&state, cx);
2570        }
2571    }
2572
2573    /// Moves the dragged end to the text under `finger`.
2574    pub fn update_edge_drag(finger: Point<Pixels>, window: &mut Window, cx: &mut App) {
2575        if let Some(state) = live_text_selection_state(window, cx) {
2576            state.update(cx, |state, cx| state.update_edge_drag(finger, window, cx));
2577            WindowSelectionState::resolve_content_keys(&state, cx);
2578        }
2579    }
2580
2581    /// Ends the handle drag and reopens the edit menu over the result.
2582    pub fn end_edge_drag(window: &mut Window, cx: &mut App) {
2583        if let Some(state) = live_text_selection_state(window, cx) {
2584            state.update(cx, |state, cx| state.end_edge_drag(cx));
2585        }
2586    }
2587
2588    /// Activates the opaque selection scope for this window.
2589    pub fn activate_scope(scope: TextSelectionScopeId, window: &mut Window, cx: &mut App) {
2590        let Some(state) = WindowSelectionState::existing(window, cx) else {
2591            if !cx.has_global::<PendingTextSelectionScopes>() {
2592                cx.set_global(PendingTextSelectionScopes::default());
2593            }
2594            cx.global_mut::<PendingTextSelectionScopes>()
2595                .0
2596                .insert(window.window_handle().window_id(), scope);
2597            return;
2598        };
2599        if state.read(cx).active_scope == scope {
2600            return;
2601        }
2602        let handlers = state.update(cx, |state, cx| state.set_active_scope_state(scope, cx));
2603        dispatch_clear_handlers(handlers, cx);
2604    }
2605}
2606
2607/// A zero-sized root layer which enables text selection for a window.
2608///
2609/// Mount one as the root's first child. Its stable `"window-text-selection"`
2610/// element identity retains the window-local selection entity across frames.
2611pub struct TextSelectionLayer;
2612
2613pub(crate) fn text_selection_scope(
2614    scope: TextSelectionScopeId,
2615    element: impl IntoElement,
2616) -> impl IntoElement {
2617    TextSelectionScopeMarker {
2618        scope,
2619        element: element.into_element(),
2620    }
2621}
2622
2623struct TextSelectionScopeMarker<E> {
2624    scope: TextSelectionScopeId,
2625    element: E,
2626}
2627
2628impl<E: Element> IntoElement for TextSelectionScopeMarker<E> {
2629    type Element = Self;
2630
2631    fn into_element(self) -> Self::Element {
2632        self
2633    }
2634}
2635
2636impl<E: Element> Element for TextSelectionScopeMarker<E> {
2637    type RequestLayoutState = E::RequestLayoutState;
2638    type PrepaintState = E::PrepaintState;
2639
2640    fn id(&self) -> Option<ElementId> {
2641        self.element.id()
2642    }
2643
2644    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
2645        self.element.source_location()
2646    }
2647
2648    fn request_layout(
2649        &mut self,
2650        id: Option<&GlobalElementId>,
2651        inspector_id: Option<&InspectorElementId>,
2652        window: &mut Window,
2653        cx: &mut App,
2654    ) -> (LayoutId, Self::RequestLayoutState) {
2655        let window_id = window.window_handle().window_id();
2656        with_text_selection_scope(window_id, self.scope, cx, |cx| {
2657            self.element.request_layout(id, inspector_id, window, cx)
2658        })
2659    }
2660
2661    fn prepaint(
2662        &mut self,
2663        id: Option<&GlobalElementId>,
2664        inspector_id: Option<&InspectorElementId>,
2665        bounds: Bounds<Pixels>,
2666        request_layout: &mut Self::RequestLayoutState,
2667        window: &mut Window,
2668        cx: &mut App,
2669    ) -> Self::PrepaintState {
2670        let window_id = window.window_handle().window_id();
2671        with_text_selection_scope(window_id, self.scope, cx, |cx| {
2672            self.element
2673                .prepaint(id, inspector_id, bounds, request_layout, window, cx)
2674        })
2675    }
2676
2677    fn paint(
2678        &mut self,
2679        id: Option<&GlobalElementId>,
2680        inspector_id: Option<&InspectorElementId>,
2681        bounds: Bounds<Pixels>,
2682        request_layout: &mut Self::RequestLayoutState,
2683        prepaint: &mut Self::PrepaintState,
2684        window: &mut Window,
2685        cx: &mut App,
2686    ) {
2687        let window_id = window.window_handle().window_id();
2688        with_text_selection_scope(window_id, self.scope, cx, |cx| {
2689            self.element.paint(
2690                id,
2691                inspector_id,
2692                bounds,
2693                request_layout,
2694                prepaint,
2695                window,
2696                cx,
2697            );
2698        });
2699    }
2700}
2701
2702#[doc(hidden)]
2703pub struct TextSelectionLayerPrepaintState(Entity<WindowSelectionState>);
2704
2705impl IntoElement for TextSelectionLayer {
2706    type Element = Self;
2707
2708    fn into_element(self) -> Self::Element {
2709        self
2710    }
2711}
2712
2713impl Element for TextSelectionLayer {
2714    type RequestLayoutState = ();
2715    type PrepaintState = TextSelectionLayerPrepaintState;
2716
2717    fn id(&self) -> Option<ElementId> {
2718        Some("window-text-selection".into())
2719    }
2720
2721    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
2722        None
2723    }
2724
2725    fn request_layout(
2726        &mut self,
2727        _: Option<&GlobalElementId>,
2728        _: Option<&InspectorElementId>,
2729        window: &mut Window,
2730        cx: &mut App,
2731    ) -> (LayoutId, Self::RequestLayoutState) {
2732        (window.request_layout(Style::default(), [], cx), ())
2733    }
2734
2735    fn prepaint(
2736        &mut self,
2737        global_id: Option<&GlobalElementId>,
2738        _: Option<&InspectorElementId>,
2739        _: Bounds<Pixels>,
2740        _: &mut Self::RequestLayoutState,
2741        window: &mut Window,
2742        cx: &mut App,
2743    ) -> Self::PrepaintState {
2744        // Automatic participant order is paint order within this frame. Keep
2745        // this lifecycle in base so base-only applications do not need a
2746        // separate root component to reset it. Otherwise, registering the
2747        // first of two selected TextViews temporarily reverses their order
2748        // against the previous frame and alternates coverage forever.
2749        GlobalState::init(cx);
2750        GlobalState::global(cx).begin_selection_frame();
2751        let state = retain_text_selection_state(global_id, window, cx);
2752        // The handles and the menu register again as they paint this frame.
2753        if state.read(cx).touch.painted_any() {
2754            state.update(cx, |state, _| state.touch.begin_frame());
2755        }
2756        TextSelectionLayerPrepaintState(state)
2757    }
2758
2759    fn paint(
2760        &mut self,
2761        _: Option<&GlobalElementId>,
2762        _: Option<&InspectorElementId>,
2763        _: Bounds<Pixels>,
2764        _: &mut Self::RequestLayoutState,
2765        state: &mut Self::PrepaintState,
2766        window: &mut Window,
2767        cx: &mut App,
2768    ) {
2769        paint_text_selection(&state.0, window, cx);
2770    }
2771}
2772
2773fn retain_text_selection_state(
2774    global_id: Option<&GlobalElementId>,
2775    window: &mut Window,
2776    cx: &mut App,
2777) -> Entity<WindowSelectionState> {
2778    let window_id = window.window_handle().window_id();
2779    let state = window.with_element_state::<Entity<WindowSelectionState>, _>(
2780        global_id.expect("TextSelection has a stable element id"),
2781        |retained, _| {
2782            let state = retained.unwrap_or_else(|| WindowSelectionState::acquire(window_id, cx));
2783            (state.clone(), state)
2784        },
2785    );
2786    // Registered once: writing the global again every frame would change it,
2787    // and every view that reads it, on every frame.
2788    let registered = cx.has_global::<SelectionStateRegistry>()
2789        && cx
2790            .global::<SelectionStateRegistry>()
2791            .0
2792            .get(&window_id)
2793            .is_some_and(|registered| registered.entity_id() == state.entity_id());
2794    if !registered {
2795        if !cx.has_global::<SelectionStateRegistry>() {
2796            cx.set_global(SelectionStateRegistry::default());
2797        }
2798        cx.global_mut::<SelectionStateRegistry>()
2799            .0
2800            .insert(window_id, state.downgrade());
2801    }
2802    state
2803}
2804
2805fn paint_text_selection(state: &Entity<WindowSelectionState>, window: &mut Window, cx: &mut App) {
2806    if state.read(cx).schedule_finish_frame() {
2807        let state = state.downgrade();
2808        window.defer(cx, move |window, cx| {
2809            let Some(state) = state.upgrade() else {
2810                return;
2811            };
2812            if state.read(cx).finish_frame_quietly(cx) {
2813                return;
2814            }
2815            let handlers = state.update(cx, |state, cx| state.finish_frame(cx));
2816            dispatch_clear_handlers(handlers, cx);
2817            // Direct participant scrolling produces no wheel event. Refresh
2818            // the held cursor after paint registers the new scroll geometry.
2819            let refresh_cursor = state.update(cx, |state, cx| {
2820                if std::mem::take(&mut state.refresh_held_cursor) && state.is_selecting {
2821                    // A handle drag holds the finger off the text; keep the
2822                    // same offset, or the cursor would hop between the two.
2823                    let position = window.mouse_position();
2824                    let position = state
2825                        .touch
2826                        .drag
2827                        .map_or(position, |drag| drag.text_position(position));
2828                    state.update_in_window(position, window, cx);
2829                    true
2830                } else {
2831                    false
2832                }
2833            });
2834            if refresh_cursor {
2835                WindowSelectionState::resolve_content_keys(&state, cx);
2836            }
2837        });
2838    }
2839
2840    let mouse_down_state = state.downgrade();
2841    window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2842        if event.button != MouseButton::Left {
2843            return;
2844        }
2845        let Some(state) = mouse_down_state.upgrade() else {
2846            return;
2847        };
2848        // A press on a handle or on the edit menu acts on the selection; it
2849        // must not clear it.
2850        if state.read(cx).touch.covers(event.position) {
2851            return;
2852        }
2853        if phase.capture() {
2854            GlobalState::init(cx);
2855            GlobalState::reset_text_selection_suppression(cx);
2856            let handlers = state.update(cx, |state, cx| {
2857                if state.mouse_down_prepared {
2858                    return Vec::new();
2859                }
2860                state.mouse_down_prepared = true;
2861                state.prepare_for_mouse_down(event.click_count == 1 && event.modifiers.shift, cx)
2862            });
2863            dispatch_clear_handlers(handlers, cx);
2864        } else if event.click_count == 1 {
2865            if GlobalState::is_text_selection_suppressed(cx) {
2866                state.update(cx, |state, _| state.pending_extension_anchor = None);
2867                return;
2868            }
2869            state.update(cx, |state, cx| {
2870                if !state.is_selecting {
2871                    state.begin_in_window(event.position, event.modifiers.shift, window, cx)
2872                }
2873            });
2874            WindowSelectionState::resolve_content_keys(&state, cx);
2875        } else if event.click_count >= 2 {
2876            if GlobalState::is_text_selection_suppressed(cx) {
2877                return;
2878            }
2879            let touch = GlobalState::is_touch_press(cx);
2880            state.update(cx, |state, cx| {
2881                state.select_at(event.position, event.click_count, window, cx);
2882                // A double tap is touch's other way to select a word, and it
2883                // gets the handles and the menu like a long press does.
2884                if touch {
2885                    state.keep_touch_selection(cx);
2886                }
2887            });
2888            WindowSelectionState::resolve_content_keys(&state, cx);
2889        }
2890    });
2891
2892    // Every touch is offered as a drag first; that is how a tap's mouse
2893    // events are later told apart from a mouse's.
2894    window.on_mouse_event(move |event: &TouchDragEvent, phase, _, cx| {
2895        if phase.capture() && event.phase == TouchPhase::Started {
2896            GlobalState::note_touch(cx);
2897        }
2898    });
2899
2900    // Touch panning remains scrolling until a long press actually hits text.
2901    // Claiming the gesture keeps subsequent moves out of the pan recognizer.
2902    let long_press_state = state.downgrade();
2903    window.on_mouse_event(move |event: &LongPressEvent, phase, window, cx| {
2904        if !phase.bubble() {
2905            return;
2906        }
2907        let Some(state) = long_press_state.upgrade() else {
2908            return;
2909        };
2910        if event.phase == TouchPhase::Started {
2911            if window.default_prevented()
2912                || state.read(cx).touch.covers(event.start_position)
2913                || !state.update(cx, |state, cx| {
2914                    state
2915                        .endpoint(event.start_position, Some(window), cx)
2916                        .inside_text
2917                })
2918            {
2919                return;
2920            }
2921            GlobalState::init(cx);
2922            GlobalState::reset_text_selection_suppression(cx);
2923            let handlers = state.update(cx, |state, cx| state.prepare_for_mouse_down(false, cx));
2924            dispatch_clear_handlers(handlers, cx);
2925            let selected = state.update(cx, |state, cx| {
2926                state.select_at(event.start_position, 2, window, cx);
2927                state.anchor.is_some()
2928            });
2929            if !selected {
2930                return;
2931            }
2932            window.capture_long_press(&state);
2933        } else if !window.has_long_press_capture(&state) {
2934            return;
2935        } else {
2936            state.update(cx, |state, cx| match event.phase {
2937                TouchPhase::Moved => {
2938                    state.is_selecting = true;
2939                    state.update_in_window(event.position, window, cx);
2940                }
2941                TouchPhase::Ended | TouchPhase::Cancelled => {
2942                    state.end(cx);
2943                    // The finger is up; the selection it made gets its
2944                    // handles and the edit menu.
2945                    state.keep_touch_selection(cx);
2946                }
2947                _ => {}
2948            });
2949        }
2950        window.prevent_default();
2951        cx.stop_propagation();
2952        WindowSelectionState::resolve_content_keys(&state, cx);
2953    });
2954
2955    // A handle drag in progress follows the finger or the pointer wherever
2956    // it goes, whether or not the handle it took is laid out this frame.
2957    let drag_state = state.downgrade();
2958    window.on_mouse_event(move |event: &TouchDragEvent, phase, window, cx| {
2959        if !phase.bubble() || event.phase == TouchPhase::Started {
2960            return;
2961        }
2962        let Some(state) = drag_state.upgrade() else {
2963            return;
2964        };
2965        if state.read(cx).touch.drag.is_none() {
2966            return;
2967        }
2968        cx.stop_propagation();
2969        state.update(cx, |state, cx| match event.phase {
2970            TouchPhase::Moved => state.update_edge_drag(event.position, window, cx),
2971            _ => state.end_edge_drag(cx),
2972        });
2973        WindowSelectionState::resolve_content_keys(&state, cx);
2974    });
2975    let drag_state = state.downgrade();
2976    window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2977        if phase.bubble()
2978            && event.pressed_button == Some(MouseButton::Left)
2979            && let Some(state) = drag_state.upgrade()
2980            && state.read(cx).touch.drag.is_some()
2981        {
2982            state.update(cx, |state, cx| {
2983                state.update_edge_drag(event.position, window, cx)
2984            });
2985            WindowSelectionState::resolve_content_keys(&state, cx);
2986        }
2987    });
2988    let drag_state = state.downgrade();
2989    window.on_mouse_event(move |event: &MouseUpEvent, phase, _, cx| {
2990        if phase.bubble()
2991            && event.button == MouseButton::Left
2992            && let Some(state) = drag_state.upgrade()
2993        {
2994            state.update(cx, |state, cx| state.end_edge_drag(cx));
2995        }
2996    });
2997
2998    let mouse_move_state = state.downgrade();
2999    window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
3000        if phase.bubble()
3001            && let Some(state) = mouse_move_state.upgrade()
3002            // Moving the pointer only moves a selection being made.
3003            && state.read(cx).is_selecting
3004        {
3005            state.update(cx, |state, cx| {
3006                // A handle drag maps the pointer through the handle's offset;
3007                // the raw pointer must not fight it.
3008                if state.touch.drag.is_some() {
3009                    return;
3010                }
3011                state.update_in_window(event.position, window, cx)
3012            });
3013            WindowSelectionState::resolve_content_keys(&state, cx);
3014        }
3015    });
3016
3017    let mouse_up_state = state.downgrade();
3018    window.on_mouse_event(move |_: &MouseUpEvent, phase, _, cx| {
3019        if phase.bubble()
3020            && let Some(state) = mouse_up_state.upgrade()
3021        {
3022            state.update(cx, |state, cx| {
3023                state.mouse_down_prepared = false;
3024                state.end(cx)
3025            });
3026        }
3027    });
3028
3029    let scroll_state = state.downgrade();
3030    window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
3031        let Some(state) = scroll_state.upgrade() else {
3032            return;
3033        };
3034        // On capture, before a scroll container can stop the event: one
3035        // stretched past its end swallows the whole stream, the lift
3036        // included, and the menu would never come back.
3037        if phase.capture() {
3038            if state.read(cx).touch.active {
3039                state.update(cx, |state, cx| {
3040                    state.edit_menu_on_scroll(event.touch_phase, cx)
3041                });
3042            }
3043            return;
3044        }
3045        // A scroll only moves a selection being made under the pointer.
3046        if phase.bubble() && state.read(cx).is_selecting {
3047            let position = window.mouse_position();
3048            state.update(cx, |state, cx| {
3049                // A handle drag holds the finger off the text; keep its offset.
3050                let position = state
3051                    .touch
3052                    .drag
3053                    .map_or(position, |drag| drag.text_position(position));
3054                state.update_in_window(position, window, cx)
3055            });
3056            WindowSelectionState::resolve_content_keys(&state, cx);
3057        }
3058    });
3059}
3060
3061fn live_text_selection_state(
3062    window: &Window,
3063    cx: &mut App,
3064) -> Option<Entity<WindowSelectionState>> {
3065    WindowSelectionState::existing(window, cx)
3066}
3067
3068pub(crate) fn clear_window_text_selection(window_id: gpui::WindowId, cx: &mut App) {
3069    if !cx.has_global::<SelectionStateRegistry>() {
3070        return;
3071    }
3072    let Some(state) = cx
3073        .global::<SelectionStateRegistry>()
3074        .0
3075        .get(&window_id)
3076        .and_then(WeakEntity::upgrade)
3077    else {
3078        return;
3079    };
3080    let handlers = state.update(cx, |state, cx| state.clear_state(cx));
3081    dispatch_clear_handlers(handlers, cx);
3082}
3083
3084#[cfg(test)]
3085mod tests {
3086    use super::*;
3087    use crate::ElementExt as _;
3088    use gpui::{
3089        Bounds, ContentMask, Context, Hitbox, HitboxBehavior, HitboxId, InteractiveElement as _,
3090        IntoElement, ParentElement as _, Render, SharedString, Styled as _, StyledText,
3091        TestAppContext, TextLayout, Window, div, point, prelude::FluentBuilder as _, px, size,
3092    };
3093    use std::{
3094        cell::{Cell, RefCell},
3095        rc::Rc,
3096    };
3097
3098    struct FakeParticipant {
3099        selection: TextSelectionHandle,
3100    }
3101
3102    struct WindowSelectionView {
3103        selection: TextSelectionHandle,
3104    }
3105
3106    struct SelectionElementOnlyView;
3107    struct ToggleSelectionElementView {
3108        enabled: bool,
3109        selection: TextSelectionHandle,
3110    }
3111
3112    struct DoubleSelectionElementView {
3113        selection: TextSelectionHandle,
3114    }
3115
3116    struct WindowOwnedSelectionView {
3117        selection: TextSelectionHandle,
3118    }
3119
3120    struct FirstFrameScopedSelectionView {
3121        selection: TextSelectionHandle,
3122    }
3123
3124    struct PlainRunLayoutView {
3125        texts: Vec<SharedString>,
3126        layouts: Vec<TextLayout>,
3127    }
3128
3129    impl Render for WindowSelectionView {
3130        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3131            div()
3132        }
3133    }
3134
3135    impl Render for SelectionElementOnlyView {
3136        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3137            div()
3138                .size_full()
3139                .child(TextSelectionLayer)
3140                .child(
3141                    div()
3142                        .size_full()
3143                        .on_mouse_down(MouseButton::Left, |_, _, cx| {
3144                            GlobalState::suppress_text_selection(cx);
3145                        }),
3146                )
3147        }
3148    }
3149
3150    impl Render for ToggleSelectionElementView {
3151        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3152            let selection = self.selection.clone();
3153            div().when(self.enabled, |this| {
3154                this.child(TextSelectionLayer)
3155                    .child(div().size_full().on_prepaint(move |bounds, window, cx| {
3156                        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3157                        selection.register(
3158                            TextSelectionRegistration::new(hitbox, bounds)
3159                                .with_text_bounds(vec![bounds]),
3160                            window,
3161                            cx,
3162                        );
3163                    }))
3164            })
3165        }
3166    }
3167
3168    impl Render for DoubleSelectionElementView {
3169        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3170            let selection = self.selection.clone();
3171            div()
3172                .size_full()
3173                .child(TextSelectionLayer)
3174                .child(TextSelectionLayer)
3175                .on_prepaint(move |bounds, window, cx| {
3176                    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3177                    selection.register(
3178                        TextSelectionRegistration::new(hitbox, bounds)
3179                            .with_text_bounds(vec![bounds]),
3180                        window,
3181                        cx,
3182                    );
3183                })
3184        }
3185    }
3186
3187    impl Render for WindowOwnedSelectionView {
3188        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3189            let selection = self.selection.clone();
3190            div()
3191                .size_full()
3192                .child(TextSelectionLayer)
3193                .child(div().size_full().on_prepaint(move |bounds, window, cx| {
3194                    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3195                    selection.register(
3196                        TextSelectionRegistration::new(hitbox, bounds)
3197                            .with_text_bounds(vec![bounds]),
3198                        window,
3199                        cx,
3200                    );
3201                }))
3202        }
3203    }
3204
3205    impl Render for FirstFrameScopedSelectionView {
3206        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3207            let scope = TextSelectionScopeId::from_raw(23);
3208            TextSelection::activate_scope(scope, window, cx);
3209            let selection = self.selection.clone();
3210
3211            div().child(TextSelectionLayer).child(
3212                div()
3213                    .size_full()
3214                    .on_prepaint(move |bounds, window, cx| {
3215                        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3216                        selection.register(
3217                            TextSelectionRegistration::new(hitbox, bounds)
3218                                .with_text_bounds(vec![bounds]),
3219                            window,
3220                            cx,
3221                        );
3222                    })
3223                    .text_selection_scope(scope),
3224            )
3225        }
3226    }
3227
3228    impl Render for PlainRunLayoutView {
3229        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3230            self.layouts.clear();
3231            let children = self
3232                .texts
3233                .iter()
3234                .enumerate()
3235                .map(|(index, text)| {
3236                    let text = StyledText::new(text.clone());
3237                    self.layouts.push(text.layout().clone());
3238                    div().absolute().top(px(index as f32 * 40.)).child(text)
3239                })
3240                .collect::<Vec<_>>();
3241            div().size_full().children(children)
3242        }
3243    }
3244
3245    impl FakeParticipant {
3246        fn new(text: &str, cx: &mut gpui::App) -> Self {
3247            let selection = TextSelectionHandle::new(text, cx);
3248            Self { selection }
3249        }
3250
3251        fn register(
3252            &self,
3253            selection_state: &mut WindowSelectionState,
3254            y: f32,
3255            scope: TextSelectionScopeId,
3256            document_order: u64,
3257            cx: &mut gpui::App,
3258        ) {
3259            let bounds = Bounds::new(point(px(0.), px(y)), size(px(100.), px(10.)));
3260            selection_state.register_participant(
3261                self.selection.clone(),
3262                TextSelectionRegistration::new(
3263                    Hitbox {
3264                        id: HitboxId::placeholder(),
3265                        bounds,
3266                        content_mask: ContentMask { bounds },
3267                        behavior: HitboxBehavior::Normal,
3268                    },
3269                    bounds,
3270                )
3271                .with_scope(scope)
3272                .with_document_order(document_order)
3273                .with_text_bounds(vec![bounds]),
3274                cx,
3275            );
3276        }
3277    }
3278
3279    fn laid_out_runs(texts: &[&str], cx: &mut TestAppContext) -> Vec<(SharedString, TextLayout)> {
3280        let texts = texts
3281            .iter()
3282            .map(|text| SharedString::from(*text))
3283            .collect::<Vec<_>>();
3284        let view = cx.add_window({
3285            let texts = texts.clone();
3286            move |_, _| PlainRunLayoutView {
3287                texts,
3288                layouts: Vec::new(),
3289            }
3290        });
3291        cx.update_window(*view, |_, window, cx| {
3292            let _ = window.draw(cx);
3293        })
3294        .unwrap();
3295        let layouts = cx.update(|cx| view.read(cx).unwrap().layouts.clone());
3296        texts.into_iter().zip(layouts).collect()
3297    }
3298
3299    fn plain_snapshot(anchor: Point<Pixels>, cursor: Point<Pixels>) -> TextSelectionSnapshot {
3300        TextSelectionSnapshot::new(
3301            TextSelectionEndpoint::new(None, anchor),
3302            TextSelectionEndpoint::new(None, cursor),
3303        )
3304        .with_window_points(Some(TextSelectionWindowPoints { anchor, cursor }))
3305    }
3306
3307    #[gpui::test]
3308    fn scope_stack_is_cleaned_after_panicking_subtree(cx: &mut TestAppContext) {
3309        let window_id = {
3310            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3311            window_cx.update(|window, _| window.window_handle().window_id())
3312        };
3313        let scope = TextSelectionScopeId::from_raw(41);
3314
3315        cx.update(|cx| {
3316            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3317                with_text_selection_scope(window_id, scope, cx, |_| panic!("subtree failed"));
3318            }));
3319
3320            assert!(result.is_err());
3321            assert_eq!(current_text_selection_scope(window_id, cx), None);
3322        });
3323    }
3324
3325    #[gpui::test]
3326    fn reentrant_scope_from_one_window_does_not_pollute_another(cx: &mut TestAppContext) {
3327        let first_window_id = {
3328            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3329            window_cx.update(|window, _| window.window_handle().window_id())
3330        };
3331        let second_window_id = {
3332            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3333            window_cx.update(|window, _| window.window_handle().window_id())
3334        };
3335        let scope = TextSelectionScopeId::from_raw(42);
3336
3337        cx.update(|cx| {
3338            with_text_selection_scope(first_window_id, scope, cx, |cx| {
3339                assert_eq!(current_text_selection_scope(second_window_id, cx), None);
3340                assert_eq!(
3341                    current_text_selection_scope(first_window_id, cx),
3342                    Some(scope)
3343                );
3344            });
3345        });
3346    }
3347
3348    #[gpui::test]
3349    fn selection_callback_can_reenter_its_selection_state(cx: &mut TestAppContext) {
3350        let called = Rc::new(Cell::new(false));
3351        let called_from_callback = called.clone();
3352        let (selection_state, participant) = cx.update(|cx| {
3353            let selection_state = cx.new(|_| WindowSelectionState::default());
3354            let selection_state_for_callback = selection_state.clone();
3355            let participant = FakeParticipant::new("participant", cx);
3356            participant
3357                .selection
3358                .subscribe(
3359                    move |event, cx| {
3360                        if matches!(event, TextSelectionEvent::SelectionChanged(Some(_))) {
3361                            selection_state_for_callback
3362                                .update(cx, |_, _| called_from_callback.set(true));
3363                        }
3364                    },
3365                    cx,
3366                )
3367                .detach();
3368            (selection_state, participant)
3369        });
3370        cx.run_until_parked();
3371        cx.update(|cx| {
3372            selection_state.update(cx, |selection_state, cx| {
3373                participant.register(selection_state, 0., TextSelectionScopeId::default(), 0, cx);
3374                selection_state.begin(point(px(1.), px(1.)), false, cx);
3375                selection_state.update(point(px(20.), px(1.)), cx);
3376            });
3377        });
3378        cx.run_until_parked();
3379        assert!(called.get());
3380    }
3381
3382    #[gpui::test]
3383    fn selection_events_preserve_snapshot_then_clear_order(cx: &mut TestAppContext) {
3384        let observed = Rc::new(RefCell::new(Vec::new()));
3385        let observed_for_callback = observed.clone();
3386        let selection = cx.update(|cx| {
3387            let selection = TextSelectionHandle::new("selection", cx);
3388            selection
3389                .subscribe(
3390                    move |event, _| {
3391                        if let TextSelectionEvent::SelectionChanged(snapshot) = event {
3392                            observed_for_callback.borrow_mut().push(snapshot.is_some());
3393                        }
3394                    },
3395                    cx,
3396                )
3397                .detach();
3398            selection
3399        });
3400        cx.run_until_parked();
3401        cx.update(|cx| {
3402            selection.0.update(cx, |state, cx| {
3403                state.set_snapshot(
3404                    Some(plain_snapshot(point(px(1.), px(1.)), point(px(8.), px(1.)))),
3405                    cx,
3406                );
3407                state.clear_state(cx);
3408            });
3409        });
3410        cx.run_until_parked();
3411        assert_eq!(&*observed.borrow(), &[true, false]);
3412    }
3413
3414    fn text_run(order: u64, text: SharedString, layout: TextLayout) -> TextSelectionRun {
3415        let bounds = layout.bounds();
3416        TextSelectionRun::new(text, layout, bounds).with_document_order(order)
3417    }
3418
3419    #[gpui::test]
3420    fn public_selection_data_uses_builders_and_readers(cx: &mut TestAppContext) {
3421        let bounds = Bounds::new(point(px(1.), px(2.)), size(px(30.), px(10.)));
3422        let hitbox = Hitbox {
3423            id: HitboxId::placeholder(),
3424            bounds,
3425            content_mask: ContentMask { bounds },
3426            behavior: HitboxBehavior::Normal,
3427        };
3428        let scope = TextSelectionScopeId::from_raw(7);
3429        let endpoint = TextSelectionEndpoint::new(None, bounds.origin)
3430            .with_content_key(TextSelectionContentKey::new(11));
3431        let snapshot = TextSelectionSnapshot::new(endpoint, endpoint)
3432            .with_selecting(true)
3433            .with_window_points(Some(TextSelectionWindowPoints {
3434                anchor: bounds.origin,
3435                cursor: bounds.bottom_right(),
3436            }))
3437            .with_coverage(TextSelectionCoverage::Full);
3438        let registration = TextSelectionRegistration::new(hitbox, bounds)
3439            .with_scroll_offset(point(px(3.), px(4.)))
3440            .with_scope(scope)
3441            .with_document_order(9)
3442            .with_text_bounds(vec![bounds]);
3443
3444        assert_eq!(endpoint.entity_id(), None);
3445        assert_eq!(endpoint.content_point(), bounds.origin);
3446        assert_eq!(
3447            endpoint.content_key(),
3448            Some(TextSelectionContentKey::new(11))
3449        );
3450        assert_eq!(snapshot.anchor(), endpoint);
3451        assert_eq!(snapshot.cursor(), endpoint);
3452        assert!(snapshot.is_selecting());
3453        assert_eq!(snapshot.coverage(), TextSelectionCoverage::Full);
3454        assert_eq!(
3455            snapshot.window_points(),
3456            Some(TextSelectionWindowPoints {
3457                anchor: bounds.origin,
3458                cursor: bounds.bottom_right(),
3459            })
3460        );
3461        assert_eq!(registration.bounds(), bounds);
3462        assert_eq!(registration.scroll_offset(), point(px(3.), px(4.)));
3463        assert_eq!(registration.scope(), scope);
3464        assert_eq!(registration.document_order(), 9);
3465        assert_eq!(registration.text_bounds(), &[bounds]);
3466
3467        let (text, layout) = laid_out_runs(&["aé"], cx).pop().unwrap();
3468        let text_run = TextSelectionRun::new(text.clone(), layout.clone(), layout.bounds())
3469            .with_document_order(3);
3470        assert_eq!(text_run.document_order(), 3);
3471        assert_eq!(text_run.text(), &text);
3472        assert_eq!(text_run.layout().len(), layout.len());
3473        assert_eq!(text_run.bounds(), layout.bounds());
3474
3475        let projection = TextSelectionProjection {
3476            ranges: vec![Some(1..3)],
3477            is_active: true,
3478        };
3479        assert_eq!(projection.ranges(), &[Some(1..3)]);
3480        assert!(projection.is_active());
3481    }
3482
3483    #[gpui::test]
3484    fn selection_handle_is_the_public_adapter_seam(cx: &mut TestAppContext) {
3485        let selected = Rc::new(Cell::new(false));
3486        let selected_from_callback = selected.clone();
3487        cx.update(|cx| {
3488            let selection = TextSelectionHandle::new("initial", cx);
3489            let entity_id = selection.entity_id();
3490            selection.set_fallback_copy_text("updated", cx);
3491            selection.set_local_selection(true, cx);
3492            selection
3493                .subscribe(
3494                    move |event, _| {
3495                        if let TextSelectionEvent::SelectionChanged(snapshot) = event {
3496                            selected_from_callback.set(snapshot.is_some());
3497                        }
3498                    },
3499                    cx,
3500                )
3501                .detach();
3502            selection.focus_with(|_, _| {}, cx);
3503            selection.copy_with(|_| "copied".to_string(), cx);
3504            selection.resolve_content_key_with(|_, _| Some(TextSelectionContentKey::new(3)), cx);
3505
3506            assert_eq!(selection.entity_id(), entity_id);
3507            assert_eq!(selection.snapshot(cx), None);
3508            assert_eq!(
3509                selection.update_runs(&[], cx),
3510                TextSelectionProjection::default()
3511            );
3512        });
3513        assert!(!selected.get());
3514    }
3515
3516    #[gpui::test]
3517    fn selection_handle_can_subscribe_its_window_to_refresh(cx: &mut TestAppContext) {
3518        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
3519            selection: TextSelectionHandle::new("refresh", cx),
3520        });
3521        cx.update(|window, cx| {
3522            let selection = TextSelectionHandle::new("refresh", cx);
3523            selection.refresh_window_on_change(window, cx).detach();
3524        });
3525    }
3526
3527    #[gpui::test]
3528    fn plain_projection_preserves_forward_reversed_and_unicode_ranges(cx: &mut TestAppContext) {
3529        let (text, layout) = laid_out_runs(&["aé🙂z"], cx).pop().unwrap();
3530        let run = text_run(0, text, layout.clone());
3531        let start = layout.position_for_index(1).unwrap();
3532        let end = layout.position_for_index(7).unwrap();
3533
3534        let forward = project_ranges(Some(plain_snapshot(start, end)), std::slice::from_ref(&run));
3535        let reversed = project_ranges(Some(plain_snapshot(end, start)), &[run]);
3536
3537        assert_eq!(forward.ranges(), &[Some(1..7)]);
3538        assert_eq!(reversed.ranges(), &[Some(1..7)]);
3539        assert!(forward.is_active());
3540        assert!(reversed.is_active());
3541    }
3542
3543    #[gpui::test]
3544    fn double_click_expands_a_plain_run_to_the_input_word_boundary(cx: &mut TestAppContext) {
3545        let (text, layout) = laid_out_runs(&["one café, three"], cx).pop().unwrap();
3546        let run = text_run(0, text, layout.clone());
3547        let click = layout.position_for_index(6).unwrap();
3548
3549        let (anchor, cursor) =
3550            points_for_multi_click(std::slice::from_ref(&run), click, 2).unwrap();
3551        let states = project_ranges(Some(plain_snapshot(anchor, cursor)), &[run]);
3552
3553        assert_eq!(states.ranges(), &[Some(4..9)]);
3554    }
3555
3556    #[gpui::test]
3557    fn multi_click_uses_text_layout_window_coordinates_at_a_nonzero_origin(
3558        cx: &mut TestAppContext,
3559    ) {
3560        let mut runs = laid_out_runs(&["above", "alpha beta"], cx);
3561        let (text, layout) = runs.pop().unwrap();
3562        assert!(layout.bounds().origin.y > px(0.));
3563        let run = text_run(0, text, layout.clone());
3564        let click = layout.position_for_index(7).unwrap();
3565
3566        let (anchor, cursor) =
3567            points_for_multi_click(std::slice::from_ref(&run), click, 2).unwrap();
3568        let projection = project_ranges(Some(plain_snapshot(anchor, cursor)), &[run]);
3569
3570        assert_eq!(projection.ranges(), &[Some(6..10)]);
3571    }
3572
3573    #[gpui::test]
3574    fn triple_click_expands_to_the_input_logical_line_not_the_visual_row(cx: &mut TestAppContext) {
3575        let (text, layout) = laid_out_runs(&["second line"], cx).pop().unwrap();
3576        let run = text_run(0, text, layout.clone());
3577        let click = layout.position_for_index(4).unwrap();
3578
3579        let (anchor, cursor) =
3580            points_for_multi_click(std::slice::from_ref(&run), click, 4).unwrap();
3581        let states = project_ranges(Some(plain_snapshot(anchor, cursor)), &[run]);
3582
3583        assert_eq!(states.ranges(), &[Some(0..11)]);
3584        assert_eq!(line_range_at("first line\nsecond line\nthird", 15), 11..22);
3585    }
3586
3587    #[gpui::test]
3588    fn plain_projection_spans_multiple_runs_and_leaves_empty_gutters_unselected(
3589        cx: &mut TestAppContext,
3590    ) {
3591        let mut runs = laid_out_runs(&["first", "", "second"], cx);
3592        let (first_text, first_layout) = runs.remove(0);
3593        let (gutter_text, gutter_layout) = runs.remove(0);
3594        let (second_text, second_layout) = runs.remove(0);
3595        let start = first_layout.position_for_index(2).unwrap();
3596        let end = second_layout.position_for_index(3).unwrap();
3597        let states = project_ranges(
3598            Some(plain_snapshot(start, end)),
3599            &[
3600                text_run(2, second_text, second_layout),
3601                text_run(1, gutter_text, gutter_layout),
3602                text_run(0, first_text, first_layout),
3603            ],
3604        );
3605
3606        assert_eq!(states.ranges(), &[Some(0..3), None, Some(2..5)]);
3607        assert!(states.is_active());
3608    }
3609
3610    #[gpui::test]
3611    fn plain_projection_caches_multiple_participant_copies_in_document_order(
3612        cx: &mut TestAppContext,
3613    ) {
3614        let mut runs = laid_out_runs(&["one", "two"], cx);
3615        let (first_text, first_layout) = runs.remove(0);
3616        let (second_text, second_layout) = runs.remove(0);
3617        let snapshot = plain_snapshot(
3618            first_layout.position_for_index(1).unwrap(),
3619            second_layout.position_for_index(2).unwrap(),
3620        );
3621        cx.update(|cx| {
3622            let mut selection_state = WindowSelectionState::default();
3623            let first = FakeParticipant::new("", cx);
3624            let second = FakeParticipant::new("", cx);
3625            first.register(
3626                &mut selection_state,
3627                0.,
3628                TextSelectionScopeId::default(),
3629                1,
3630                cx,
3631            );
3632            second.register(
3633                &mut selection_state,
3634                20.,
3635                TextSelectionScopeId::default(),
3636                0,
3637                cx,
3638            );
3639
3640            first
3641                .selection
3642                .0
3643                .update(cx, |state, cx| state.set_snapshot(Some(snapshot), cx));
3644            let projection = first
3645                .selection
3646                .update_runs(&[text_run(0, first_text, first_layout)], cx);
3647            assert_eq!(projection.ranges(), &[Some(1..3)]);
3648            assert!(projection.is_active());
3649            second
3650                .selection
3651                .0
3652                .update(cx, |state, cx| state.set_snapshot(Some(snapshot), cx));
3653            let projection = second
3654                .selection
3655                .update_runs(&[text_run(0, second_text, second_layout)], cx);
3656            assert_eq!(projection.ranges(), &[Some(0..2)]);
3657            assert!(projection.is_active());
3658
3659            assert_eq!(selection_state.selected_text(cx), "tw\nne");
3660        });
3661    }
3662
3663    #[gpui::test]
3664    fn plain_projection_invalidates_cached_copy_when_the_snapshot_changes(cx: &mut TestAppContext) {
3665        let (text, layout) = laid_out_runs(&["first"], cx).pop().unwrap();
3666        let first_snapshot = plain_snapshot(
3667            layout.position_for_index(1).unwrap(),
3668            layout.position_for_index(3).unwrap(),
3669        );
3670        let changed_snapshot = plain_snapshot(
3671            layout.position_for_index(3).unwrap(),
3672            layout.position_for_index(5).unwrap(),
3673        );
3674        let run = text_run(0, text, layout);
3675        cx.update(|cx| {
3676            let mut selection_state = WindowSelectionState::default();
3677            let participant = FakeParticipant::new("", cx);
3678            participant.register(
3679                &mut selection_state,
3680                0.,
3681                TextSelectionScopeId::default(),
3682                0,
3683                cx,
3684            );
3685            participant.selection.0.update(cx, |state, cx| {
3686                state.set_snapshot(Some(first_snapshot), cx);
3687                state.update_runs(std::slice::from_ref(&run));
3688            });
3689            assert_eq!(selection_state.selected_text(cx), "ir");
3690
3691            participant.selection.0.update(cx, |state, cx| {
3692                state.set_snapshot(Some(changed_snapshot), cx);
3693            });
3694            assert_eq!(selection_state.selected_text(cx), "");
3695
3696            participant.selection.update_runs(&[run], cx);
3697            assert_eq!(selection_state.selected_text(cx), "st");
3698            selection_state.clear(cx);
3699            participant.selection.set_local_selection(true, cx);
3700            assert_eq!(selection_state.selected_text(cx), "");
3701        });
3702    }
3703
3704    #[gpui::test]
3705    fn plain_projection_orders_cached_runs_by_frame_order_not_input_order(cx: &mut TestAppContext) {
3706        let mut runs = laid_out_runs(&["one", "two"], cx);
3707        let (first_text, first_layout) = runs.remove(0);
3708        let (second_text, second_layout) = runs.remove(0);
3709        let snapshot = plain_snapshot(
3710            first_layout.position_for_index(1).unwrap(),
3711            second_layout.position_for_index(2).unwrap(),
3712        );
3713        cx.update(|cx| {
3714            let mut selection_state = WindowSelectionState::default();
3715            let participant = FakeParticipant::new("", cx);
3716            participant.register(
3717                &mut selection_state,
3718                0.,
3719                TextSelectionScopeId::default(),
3720                0,
3721                cx,
3722            );
3723            participant.selection.0.update(cx, |state, cx| {
3724                state.set_snapshot(Some(snapshot), cx);
3725                state.update_runs(&[
3726                    text_run(1, first_text, first_layout),
3727                    text_run(0, second_text, second_layout),
3728                ]);
3729            });
3730
3731            assert_eq!(selection_state.selected_text(cx), "twne");
3732        });
3733    }
3734
3735    #[gpui::test]
3736    fn plain_projection_safely_rejects_a_text_layout_length_mismatch(cx: &mut TestAppContext) {
3737        let (_, layout) = laid_out_runs(&["short"], cx).pop().unwrap();
3738        let start = layout.position_for_index(0).unwrap();
3739        let end = layout.position_for_index(5).unwrap();
3740        let states = project_ranges(
3741            Some(plain_snapshot(start, end)),
3742            &[text_run(0, SharedString::from("longer"), layout)],
3743        );
3744
3745        assert_eq!(states.ranges(), &[None]);
3746        assert!(states.is_active());
3747    }
3748
3749    #[gpui::test]
3750    fn begin_update_and_end_publish_a_cross_participant_selection(cx: &mut TestAppContext) {
3751        cx.update(|cx| {
3752            let mut selection_state = WindowSelectionState::default();
3753            let first = FakeParticipant::new("first", cx);
3754            let second = FakeParticipant::new("second", cx);
3755            first.register(
3756                &mut selection_state,
3757                0.,
3758                TextSelectionScopeId::default(),
3759                0,
3760                cx,
3761            );
3762            second.register(
3763                &mut selection_state,
3764                20.,
3765                TextSelectionScopeId::default(),
3766                1,
3767                cx,
3768            );
3769
3770            selection_state.begin(point(px(1.), px(1.)), false, cx);
3771            selection_state.update(point(px(1.), px(25.)), cx);
3772            assert!(selection_state.has_selection(cx));
3773            assert_eq!(selection_state.selected_text(cx), "first\nsecond");
3774
3775            selection_state.end(cx);
3776            assert!(!selection_state.is_selecting());
3777        });
3778    }
3779
3780    #[gpui::test]
3781    fn shift_extension_keeps_its_original_anchor_when_reversed(cx: &mut TestAppContext) {
3782        cx.update(|cx| {
3783            let mut selection_state = WindowSelectionState::default();
3784            let participant = FakeParticipant::new("participant", cx);
3785            participant.register(
3786                &mut selection_state,
3787                0.,
3788                TextSelectionScopeId::default(),
3789                0,
3790                cx,
3791            );
3792
3793            selection_state.begin(point(px(2.), px(2.)), false, cx);
3794            selection_state.end(cx);
3795            selection_state.begin(point(px(8.), px(2.)), true, cx);
3796            selection_state.end(cx);
3797            let first_anchor = selection_state.snapshot().unwrap().anchor();
3798
3799            selection_state.begin(point(px(0.), px(2.)), true, cx);
3800            selection_state.end(cx);
3801            let reversed = selection_state.snapshot().unwrap();
3802            assert_eq!(reversed.anchor(), first_anchor);
3803            assert!(reversed.cursor().content_point().x < reversed.anchor().content_point().x);
3804        });
3805    }
3806
3807    #[gpui::test]
3808    fn content_key_resolver_runs_outside_the_window_state_lease(cx: &mut TestAppContext) {
3809        cx.update(|cx| {
3810            let state = cx.new(|_| WindowSelectionState::default());
3811            let participant = FakeParticipant::new("virtual", cx);
3812            let state_for_callback = state.clone();
3813            participant.selection.resolve_content_key_with(
3814                move |_, cx| {
3815                    let _ = state_for_callback.read(cx).snapshot();
3816                    Some(TextSelectionContentKey::new(7))
3817                },
3818                cx,
3819            );
3820            state.update(cx, |state, cx| {
3821                participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
3822                state.begin(point(px(1.), px(1.)), false, cx);
3823                state.update(point(px(8.), px(1.)), cx);
3824            });
3825
3826            WindowSelectionState::resolve_content_keys(&state, cx);
3827
3828            assert_eq!(
3829                state.read(cx).snapshot().unwrap().cursor().content_key(),
3830                Some(TextSelectionContentKey::new(7))
3831            );
3832        });
3833    }
3834
3835    #[gpui::test]
3836    fn active_dnd_does_not_move_a_text_selection_cursor(cx: &mut TestAppContext) {
3837        let window = cx.add_window(|_, cx| WindowSelectionView {
3838            selection: TextSelectionHandle::new("unused", cx),
3839        });
3840        window
3841            .update(cx, |_, window, cx| {
3842                let mut state = WindowSelectionState::default();
3843                let participant = FakeParticipant::new("participant", cx);
3844                participant.register(&mut state, 0., TextSelectionScopeId::default(), 0, cx);
3845                state.begin(point(px(1.), px(1.)), false, cx);
3846                let before = state.cursor.as_ref().unwrap().point;
3847                state.update_in_window_with_active_drag(point(px(80.), px(1.)), true, window, cx);
3848                assert_eq!(state.cursor.as_ref().unwrap().point, before);
3849            })
3850            .unwrap();
3851    }
3852
3853    #[gpui::test]
3854    fn shift_extension_falls_back_when_the_anchor_participant_was_swept(cx: &mut TestAppContext) {
3855        cx.update(|cx| {
3856            let mut selection_state = WindowSelectionState::default();
3857            let first = FakeParticipant::new("first", cx);
3858            let second = FakeParticipant::new("second", cx);
3859            first.register(
3860                &mut selection_state,
3861                0.,
3862                TextSelectionScopeId::default(),
3863                0,
3864                cx,
3865            );
3866            selection_state.begin(point(px(1.), px(1.)), false, cx);
3867            selection_state.update(point(px(8.), px(1.)), cx);
3868            selection_state.end(cx);
3869
3870            selection_state.finish_frame(cx);
3871            selection_state.finish_frame(cx);
3872            second.register(
3873                &mut selection_state,
3874                20.,
3875                TextSelectionScopeId::default(),
3876                1,
3877                cx,
3878            );
3879            selection_state.begin(point(px(1.), px(21.)), true, cx);
3880            selection_state.update(point(px(8.), px(21.)), cx);
3881            selection_state.end(cx);
3882
3883            assert_eq!(selection_state.selected_text(cx), "second");
3884        });
3885    }
3886
3887    #[gpui::test]
3888    fn scope_and_suppression_prevent_unrelated_participants_from_participating(
3889        cx: &mut TestAppContext,
3890    ) {
3891        cx.update(|cx| {
3892            let mut selection_state = WindowSelectionState::default();
3893            let base = FakeParticipant::new("base", cx);
3894            let modal = FakeParticipant::new("modal", cx);
3895            base.register(
3896                &mut selection_state,
3897                0.,
3898                TextSelectionScopeId::default(),
3899                0,
3900                cx,
3901            );
3902            modal.register(&mut selection_state, 20., TextSelectionScopeId(1), 1, cx);
3903
3904            selection_state.set_active_scope(TextSelectionScopeId(1), cx);
3905            selection_state.begin(point(px(1.), px(21.)), false, cx);
3906            selection_state.update(point(px(8.), px(21.)), cx);
3907            selection_state.end(cx);
3908            assert_eq!(selection_state.selected_text(cx), "modal");
3909
3910            selection_state.clear(cx);
3911            GlobalState::init(cx);
3912            GlobalState::suppress_text_selection(cx);
3913            selection_state.begin(point(px(1.), px(21.)), false, cx);
3914            selection_state.update(point(px(8.), px(21.)), cx);
3915            assert!(!selection_state.has_selection(cx));
3916        });
3917    }
3918
3919    #[gpui::test]
3920    fn dead_participants_are_pruned_and_empty_selection_falls_back_safely(cx: &mut TestAppContext) {
3921        let selection_state = cx.update(|cx| {
3922            let selection_state = cx.new(|_| WindowSelectionState::default());
3923            let participant = FakeParticipant::new("gone", cx);
3924            selection_state.update(cx, |selection_state, cx| {
3925                participant.register(selection_state, 0., TextSelectionScopeId::default(), 0, cx)
3926            });
3927            selection_state
3928        });
3929        cx.update(|cx| {
3930            selection_state.update(cx, |selection_state, cx| {
3931                selection_state.begin(point(px(1.), px(1.)), false, cx);
3932                selection_state.update(point(px(8.), px(1.)), cx);
3933                selection_state.end(cx);
3934
3935                assert_eq!(selection_state.selected_text(cx), "");
3936                assert!(!selection_state.has_selection(cx));
3937            });
3938        });
3939    }
3940
3941    #[gpui::test]
3942    fn text_selection_namespace_reports_copies_ends_and_clears_selection(cx: &mut TestAppContext) {
3943        let (view, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
3944            selection: TextSelectionHandle::new("copied", cx),
3945        });
3946        cx.update(|window, cx| {
3947            let selection = view.read(cx).selection.clone();
3948            let selection_state = WindowSelectionState::ensure(window, cx);
3949            selection_state.update(cx, |selection_state, cx| {
3950                FakeParticipant { selection }.register(
3951                    selection_state,
3952                    0.,
3953                    TextSelectionScopeId::default(),
3954                    0,
3955                    cx,
3956                );
3957                selection_state.begin(point(px(1.), px(1.)), false, cx);
3958                selection_state.update(point(px(8.), px(1.)), cx);
3959            });
3960
3961            assert!(TextSelection::has_selection(window, cx));
3962            assert_eq!(TextSelection::selected_text(window, cx), "copied");
3963            TextSelection::end(window, cx);
3964            assert!(TextSelection::has_selection(window, cx));
3965            TextSelection::clear(window, cx);
3966            assert!(!TextSelection::has_selection(window, cx));
3967            assert_eq!(TextSelection::selected_text(window, cx), "");
3968        });
3969    }
3970
3971    #[gpui::test]
3972    fn two_windows_isolate_selection_copy_clear_and_release_ownership(cx: &mut TestAppContext) {
3973        let first = cx.add_window(|_, cx| WindowOwnedSelectionView {
3974            selection: TextSelectionHandle::new("first", cx),
3975        });
3976        let second = cx.add_window(|_, cx| WindowOwnedSelectionView {
3977            selection: TextSelectionHandle::new("second", cx),
3978        });
3979        let first_selection = cx.update(|cx| first.read(cx).unwrap().selection.clone());
3980        let second_selection = cx.update(|cx| second.read(cx).unwrap().selection.clone());
3981
3982        let first_state = cx
3983            .update_window(*first, |_, window, cx| {
3984                let _ = window.draw(cx);
3985                first_selection.set_local_selection(true, cx);
3986                assert_eq!(TextSelection::selected_text(window, cx), "first");
3987                WindowSelectionState::existing(window, cx)
3988                    .unwrap()
3989                    .downgrade()
3990            })
3991            .unwrap();
3992        cx.update_window(*second, |_, window, cx| {
3993            let _ = window.draw(cx);
3994            second_selection.set_local_selection(true, cx);
3995            assert_eq!(TextSelection::selected_text(window, cx), "second");
3996        })
3997        .unwrap();
3998
3999        cx.update_window(*first, |_, window, cx| {
4000            TextSelection::clear(window, cx);
4001            assert_eq!(TextSelection::selected_text(window, cx), "");
4002        })
4003        .unwrap();
4004        cx.update_window(*second, |_, window, cx| {
4005            assert_eq!(TextSelection::selected_text(window, cx), "second");
4006        })
4007        .unwrap();
4008
4009        cx.update_window(*first, |_, window, _| window.remove_window())
4010            .unwrap();
4011        cx.run_until_parked();
4012
4013        assert!(first_state.upgrade().is_none());
4014        cx.update_window(*second, |_, window, cx| {
4015            assert_eq!(TextSelection::selected_text(window, cx), "second");
4016        })
4017        .unwrap();
4018        cx.update(|cx| {
4019            assert_eq!(cx.global::<SelectionStateRegistry>().0.len(), 1);
4020        });
4021    }
4022
4023    #[gpui::test]
4024    fn copy_callback_can_reenter_window_and_handle_selection(cx: &mut TestAppContext) {
4025        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
4026        cx.update(|window, cx| {
4027            let _ = window.draw(cx);
4028            let state = WindowSelectionState::existing(window, cx).unwrap();
4029            let selection = TextSelectionHandle::new("fallback", cx);
4030            let state_for_copy = state.clone();
4031            let selection_for_copy = selection.clone();
4032            selection.copy_with(
4033                move |cx: &mut App| {
4034                    state_for_copy.update(cx, |state, _| {
4035                        assert!(state.snapshot().is_some());
4036                    });
4037                    assert!(selection_for_copy.snapshot(cx).is_some());
4038                    selection_for_copy.set_fallback_copy_text("reentered", cx);
4039                    "reentrant copy".to_string()
4040                },
4041                cx,
4042            );
4043            state.update(cx, |state, cx| {
4044                FakeParticipant {
4045                    selection: selection.clone(),
4046                }
4047                .register(state, 0., TextSelectionScopeId::default(), 0, cx);
4048                state.begin(point(px(1.), px(1.)), false, cx);
4049                state.update(point(px(8.), px(1.)), cx);
4050                state.end(cx);
4051            });
4052
4053            assert_eq!(TextSelection::selected_text(window, cx), "reentrant copy");
4054        });
4055    }
4056
4057    #[gpui::test]
4058    fn cross_participant_selection_excludes_participants_outside_its_document_interval(
4059        cx: &mut TestAppContext,
4060    ) {
4061        cx.update(|cx| {
4062            let mut selection_state = WindowSelectionState::default();
4063            let first = FakeParticipant::new("first", cx);
4064            let second = FakeParticipant::new("second", cx);
4065            let third = FakeParticipant::new("third", cx);
4066            first.register(
4067                &mut selection_state,
4068                0.,
4069                TextSelectionScopeId::default(),
4070                0,
4071                cx,
4072            );
4073            second.register(
4074                &mut selection_state,
4075                20.,
4076                TextSelectionScopeId::default(),
4077                1,
4078                cx,
4079            );
4080            third.register(
4081                &mut selection_state,
4082                40.,
4083                TextSelectionScopeId::default(),
4084                2,
4085                cx,
4086            );
4087
4088            selection_state.begin(point(px(1.), px(1.)), false, cx);
4089            selection_state.update(point(px(1.), px(25.)), cx);
4090            selection_state.end(cx);
4091
4092            assert_eq!(selection_state.selected_text(cx), "first\nsecond");
4093            assert!(third.selection.snapshot(cx).is_none());
4094        });
4095    }
4096
4097    #[gpui::test]
4098    fn changing_scope_clears_the_previous_scope_selection(cx: &mut TestAppContext) {
4099        cx.update(|cx| {
4100            let mut selection_state = WindowSelectionState::default();
4101            let base = FakeParticipant::new("base", cx);
4102            let modal = FakeParticipant::new("modal", cx);
4103            base.register(
4104                &mut selection_state,
4105                0.,
4106                TextSelectionScopeId::default(),
4107                0,
4108                cx,
4109            );
4110            modal.register(
4111                &mut selection_state,
4112                20.,
4113                TextSelectionScopeId::from_raw(1),
4114                1,
4115                cx,
4116            );
4117
4118            selection_state.begin(point(px(1.), px(1.)), false, cx);
4119            selection_state.update(point(px(8.), px(1.)), cx);
4120            selection_state.end(cx);
4121            selection_state.set_active_scope(TextSelectionScopeId::from_raw(1), cx);
4122
4123            assert!(!selection_state.has_selection(cx));
4124            assert!(base.selection.snapshot(cx).is_none());
4125        });
4126    }
4127
4128    #[gpui::test]
4129    fn blank_only_drag_never_publishes_or_copies_selection(cx: &mut TestAppContext) {
4130        cx.update(|cx| {
4131            let mut selection_state = WindowSelectionState::default();
4132            let participant = FakeParticipant::new("participant", cx);
4133            participant.register(
4134                &mut selection_state,
4135                0.,
4136                TextSelectionScopeId::default(),
4137                0,
4138                cx,
4139            );
4140
4141            selection_state.begin(point(px(200.), px(1.)), false, cx);
4142            selection_state.update(point(px(200.), px(8.)), cx);
4143            selection_state.end(cx);
4144
4145            assert!(!selection_state.has_selection(cx));
4146            assert_eq!(selection_state.selected_text(cx), "");
4147            assert!(participant.selection.snapshot(cx).is_none());
4148        });
4149    }
4150
4151    #[gpui::test]
4152    fn stale_live_participants_are_removed_when_the_next_frame_begins(cx: &mut TestAppContext) {
4153        cx.update(|cx| {
4154            let mut selection_state = WindowSelectionState::default();
4155            let participant = FakeParticipant::new("stale", cx);
4156            participant.register(
4157                &mut selection_state,
4158                0.,
4159                TextSelectionScopeId::default(),
4160                0,
4161                cx,
4162            );
4163            selection_state.begin(point(px(1.), px(1.)), false, cx);
4164            selection_state.update(point(px(8.), px(1.)), cx);
4165            selection_state.end(cx);
4166
4167            selection_state.finish_frame(cx);
4168            selection_state.finish_frame(cx);
4169            assert_eq!(selection_state.selected_text(cx), "");
4170            assert!(participant.selection.snapshot(cx).is_none());
4171        });
4172    }
4173
4174    #[gpui::test]
4175    fn clear_stops_anchor_auto_scroll_before_discarding_the_anchor(cx: &mut TestAppContext) {
4176        let commands = Rc::new(RefCell::new(Vec::new()));
4177        let observed = commands.clone();
4178        let (mut selection_state, participant) = cx.update(|cx| {
4179            let selection_state = WindowSelectionState::default();
4180            let participant = FakeParticipant::new("scroll", cx);
4181            participant
4182                .selection
4183                .subscribe(
4184                    move |event, _| {
4185                        if let TextSelectionEvent::AutoScroll(delta) = event {
4186                            observed.borrow_mut().push(*delta);
4187                        }
4188                    },
4189                    cx,
4190                )
4191                .detach();
4192            (selection_state, participant)
4193        });
4194        cx.run_until_parked();
4195        cx.update(|cx| {
4196            participant.register(
4197                &mut selection_state,
4198                0.,
4199                TextSelectionScopeId::default(),
4200                0,
4201                cx,
4202            );
4203
4204            selection_state.begin(point(px(1.), px(1.)), false, cx);
4205            selection_state.update(point(px(1.), px(25.)), cx);
4206            selection_state.clear(cx);
4207        });
4208        cx.run_until_parked();
4209        assert!(commands.borrow().iter().any(Option::is_some));
4210        assert_eq!(commands.borrow().last(), Some(&None));
4211    }
4212
4213    #[gpui::test]
4214    fn drag_auto_scroll_stops_when_the_content_mask_collapses(cx: &mut TestAppContext) {
4215        let window = cx.add_window(|_, cx| WindowSelectionView {
4216            selection: TextSelectionHandle::new("unused", cx),
4217        });
4218        window
4219            .update(cx, |_, window, cx| {
4220                let state = cx.new(|_| WindowSelectionState::default());
4221                let participant = FakeParticipant::new("participant", cx);
4222                state.update(cx, |state, cx| {
4223                    participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
4224                    state.begin(point(px(1.), px(1.)), false, cx);
4225                });
4226                // The scrollable ancestor got clipped away mid-drag, so the
4227                // refreshed registration carries a collapsed content mask.
4228                let collapsed = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(0.)));
4229                state.update(cx, |state, cx| {
4230                    state.register_participant(
4231                        participant.selection.clone(),
4232                        TextSelectionRegistration::new(
4233                            Hitbox {
4234                                id: HitboxId::placeholder(),
4235                                bounds: collapsed,
4236                                content_mask: ContentMask { bounds: collapsed },
4237                                behavior: HitboxBehavior::Normal,
4238                            },
4239                            collapsed,
4240                        )
4241                        .with_text_bounds(vec![collapsed]),
4242                        cx,
4243                    );
4244                    state.update_in_window(point(px(1.), px(50.)), window, cx);
4245                    assert!(!state.auto_scroll.is_active());
4246                    assert!(state.auto_scroll.last_drag_position.is_none());
4247                });
4248            })
4249            .unwrap();
4250    }
4251
4252    #[gpui::test]
4253    fn pointer_moves_after_a_click_do_not_auto_scroll(cx: &mut TestAppContext) {
4254        let window = cx.add_window(|_, cx| WindowSelectionView {
4255            selection: TextSelectionHandle::new("unused", cx),
4256        });
4257        window
4258            .update(cx, |_, window, cx| {
4259                let state = cx.new(|_| WindowSelectionState::default());
4260                let participant = FakeParticipant::new("participant", cx);
4261                state.update(cx, |state, cx| {
4262                    participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
4263                    // A click on text keeps its anchor so shift-click can extend it.
4264                    state.begin(point(px(1.), px(1.)), false, cx);
4265                    state.end(cx);
4266                    assert!(state.anchor.is_some());
4267
4268                    state.update_in_window(point(px(1.), px(50.)), window, cx);
4269                    assert!(!state.auto_scroll.is_active());
4270                });
4271            })
4272            .unwrap();
4273    }
4274
4275    #[gpui::test]
4276    fn proxy_endpoints_break_equal_position_ties_by_document_order(cx: &mut TestAppContext) {
4277        cx.update(|cx| {
4278            let mut selection_state = WindowSelectionState::default();
4279            let later = FakeParticipant::new("later", cx);
4280            let earlier = FakeParticipant::new("earlier", cx);
4281            later.register(
4282                &mut selection_state,
4283                0.,
4284                TextSelectionScopeId::default(),
4285                2,
4286                cx,
4287            );
4288            earlier.register(
4289                &mut selection_state,
4290                0.,
4291                TextSelectionScopeId::default(),
4292                1,
4293                cx,
4294            );
4295
4296            selection_state.begin(point(px(1.), px(1.)), false, cx);
4297            selection_state.update(point(px(200.), px(25.)), cx);
4298            let endpoint = selection_state.snapshot().unwrap().cursor();
4299
4300            assert_eq!(endpoint.entity_id(), Some(earlier.selection.entity_id()));
4301        });
4302    }
4303
4304    #[gpui::test]
4305    fn equal_area_hovered_participants_break_ties_by_document_order(cx: &mut TestAppContext) {
4306        cx.update(|cx| {
4307            for _ in 0..64 {
4308                let mut selection_state = WindowSelectionState::default();
4309                let later = FakeParticipant::new("later", cx);
4310                let earliest = FakeParticipant::new("earliest", cx);
4311                let middle = FakeParticipant::new("middle", cx);
4312                later.register(
4313                    &mut selection_state,
4314                    0.,
4315                    TextSelectionScopeId::default(),
4316                    30,
4317                    cx,
4318                );
4319                earliest.register(
4320                    &mut selection_state,
4321                    0.,
4322                    TextSelectionScopeId::default(),
4323                    10,
4324                    cx,
4325                );
4326                middle.register(
4327                    &mut selection_state,
4328                    0.,
4329                    TextSelectionScopeId::default(),
4330                    20,
4331                    cx,
4332                );
4333
4334                selection_state.begin(point(px(1.), px(1.)), false, cx);
4335                selection_state.update(point(px(8.), px(1.)), cx);
4336
4337                assert_eq!(
4338                    selection_state.snapshot().unwrap().anchor().entity_id(),
4339                    Some(earliest.selection.entity_id())
4340                );
4341            }
4342        });
4343    }
4344
4345    #[gpui::test]
4346    fn text_selection_namespace_is_a_safe_no_op_until_the_element_is_rendered(
4347        cx: &mut TestAppContext,
4348    ) {
4349        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
4350            selection: TextSelectionHandle::new("not enabled", cx),
4351        });
4352        cx.update(|window, cx| {
4353            assert!(!TextSelection::has_selection(window, cx));
4354            assert_eq!(TextSelection::selected_text(window, cx), "");
4355            TextSelection::clear(window, cx);
4356            TextSelection::end(window, cx);
4357            assert!(!TextSelection::has_selection(window, cx));
4358        });
4359    }
4360
4361    #[gpui::test]
4362    fn unit_selection_element_supports_scope_and_registration_on_the_first_frame(
4363        cx: &mut TestAppContext,
4364    ) {
4365        let (view, cx) = cx.add_window_view(|_, cx| FirstFrameScopedSelectionView {
4366            selection: TextSelectionHandle::new("first frame", cx),
4367        });
4368        let selection = cx.update(|_, cx| view.read(cx).selection.clone());
4369
4370        cx.update(|window, cx| {
4371            let _ = window.draw(cx);
4372            let state = WindowSelectionState::existing(window, cx).unwrap();
4373            assert_eq!(
4374                state.read(cx).active_scope,
4375                TextSelectionScopeId::from_raw(23)
4376            );
4377            assert!(
4378                state
4379                    .read(cx)
4380                    .participants
4381                    .borrow()
4382                    .contains_key(&selection.entity_id())
4383            );
4384        });
4385    }
4386
4387    #[gpui::test]
4388    fn lazy_registration_does_not_enable_queries_without_the_element(cx: &mut TestAppContext) {
4389        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
4390            selection: TextSelectionHandle::new("registered", cx),
4391        });
4392        cx.update(|window, cx| {
4393            let selection = TextSelectionHandle::new("registered", cx);
4394            selection.set_local_selection(true, cx);
4395            let bounds = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(20.)));
4396            let hitbox = Hitbox {
4397                id: HitboxId::placeholder(),
4398                bounds,
4399                content_mask: ContentMask { bounds },
4400                behavior: HitboxBehavior::Normal,
4401            };
4402            selection.register(
4403                TextSelectionRegistration::new(hitbox, bounds).with_text_bounds(vec![bounds]),
4404                window,
4405                cx,
4406            );
4407            assert_eq!(TextSelection::selected_text(window, cx), "");
4408            assert!(!TextSelection::has_selection(window, cx));
4409            TextSelection::clear(window, cx);
4410            assert_eq!(TextSelection::selected_text(window, cx), "");
4411        });
4412    }
4413
4414    /// A view reading the window's selection state, beside selectable text,
4415    /// counting its renders.
4416    #[cfg(feature = "gpui-fast")]
4417    struct SelectionStateReader {
4418        renders: Rc<Cell<usize>>,
4419    }
4420
4421    #[cfg(feature = "gpui-fast")]
4422    impl Render for SelectionStateReader {
4423        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4424            self.renders.set(self.renders.get() + 1);
4425            if let Some(state) = WindowSelectionState::existing(window, cx) {
4426                let _ = state.read(cx).participants.borrow().len();
4427            }
4428            div().size_full()
4429        }
4430    }
4431
4432    /// The selectable text of a [`ToggleSelectionElementView`] beside a
4433    /// [`SelectionStateReader`].
4434    #[cfg(feature = "gpui-fast")]
4435    struct ReaderBesideSelectionView {
4436        text: gpui::Entity<ToggleSelectionElementView>,
4437        reader: gpui::Entity<SelectionStateReader>,
4438    }
4439
4440    #[cfg(feature = "gpui-fast")]
4441    impl Render for ReaderBesideSelectionView {
4442        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4443            div()
4444                .size_full()
4445                .child(self.reader.clone())
4446                .child(self.text.clone())
4447        }
4448    }
4449
4450    /// Selectable text drawn again, nothing about its selection changed,
4451    /// writes neither the window's selection state nor its own: a view
4452    /// reading them is retained, as it would not be had they changed.
4453    #[cfg(feature = "gpui-fast")]
4454    #[gpui::test]
4455    fn redrawing_selectable_text_leaves_its_readers_retained(cx: &mut TestAppContext) {
4456        let renders = Rc::new(Cell::new(0));
4457        let (view, cx) = cx.add_window_view(|_, cx| ReaderBesideSelectionView {
4458            text: cx.new(|cx| ToggleSelectionElementView {
4459                enabled: true,
4460                selection: TextSelectionHandle::new("text", cx),
4461            }),
4462            reader: cx.new(|_| SelectionStateReader {
4463                renders: renders.clone(),
4464            }),
4465        });
4466        let text = cx.update(|_, cx| view.read(cx).text.clone());
4467        let redraw = |cx: &mut gpui::VisualTestContext| {
4468            text.update(cx, |_, cx| cx.notify());
4469            cx.update(|window, cx| {
4470                let _ = window.draw(cx);
4471                window.simulate_next_frame(cx);
4472            });
4473            cx.run_until_parked();
4474        };
4475        redraw(cx);
4476        redraw(cx);
4477        let before = renders.get();
4478        for _ in 0..4 {
4479            redraw(cx);
4480        }
4481        assert_eq!(renders.get(), before, "the reader rendered again");
4482    }
4483
4484    #[gpui::test]
4485    fn retained_selection_state_releases_and_does_not_resurrect_selection(cx: &mut TestAppContext) {
4486        let (view, cx) = cx.add_window_view(|_, cx| ToggleSelectionElementView {
4487            enabled: true,
4488            selection: TextSelectionHandle::new("local", cx),
4489        });
4490        let selection = cx.update(|_, cx| view.read(cx).selection.clone());
4491        cx.update(|window, cx| {
4492            let _ = window.draw(cx);
4493            selection.set_local_selection(true, cx);
4494            assert!(TextSelection::has_selection(window, cx));
4495
4496            window.simulate_next_frame(cx);
4497            assert!(TextSelection::has_selection(window, cx));
4498            let _ = window.draw(cx);
4499            assert!(TextSelection::has_selection(window, cx));
4500        });
4501        view.update(cx, |view, cx| {
4502            view.enabled = false;
4503            cx.notify();
4504        });
4505        cx.update(|window, cx| {
4506            let _ = window.draw(cx);
4507        });
4508        cx.update(|window, cx| {
4509            window.simulate_next_frame(cx);
4510        });
4511        cx.update(|window, cx| {
4512            window.simulate_next_frame(cx);
4513        });
4514        cx.run_until_parked();
4515        cx.update(|window, cx| {
4516            assert!(!TextSelection::has_selection(window, cx));
4517            assert_eq!(TextSelection::selected_text(window, cx), "");
4518            assert!(!selection.has_local_selection(cx));
4519            TextSelection::clear(window, cx);
4520        });
4521
4522        view.update(cx, |view, cx| {
4523            view.enabled = true;
4524            cx.notify();
4525        });
4526        cx.update(|window, cx| {
4527            let _ = window.draw(cx);
4528            assert!(!TextSelection::has_selection(window, cx));
4529            assert_eq!(TextSelection::selected_text(window, cx), "");
4530        });
4531    }
4532
4533    #[gpui::test]
4534    fn mounted_selection_element_does_not_keep_an_idle_frame_queue_alive(cx: &mut TestAppContext) {
4535        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
4536        cx.update(|window, cx| {
4537            let _ = window.draw(cx);
4538            assert_eq!(window.simulate_next_frame(cx), 0);
4539            assert_eq!(window.simulate_next_frame(cx), 0);
4540            assert!(live_text_selection_state(window, cx).is_some());
4541        });
4542    }
4543
4544    #[gpui::test]
4545    fn selection_element_initializes_suppression_and_respects_bubble_suppression(
4546        cx: &mut TestAppContext,
4547    ) {
4548        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
4549        cx.update(|window, cx| {
4550            let _ = window.draw(cx);
4551        });
4552        cx.simulate_mouse_down(
4553            point(px(1.), px(1.)),
4554            MouseButton::Left,
4555            gpui::Modifiers::default(),
4556        );
4557        cx.simulate_mouse_up(
4558            point(px(1.), px(1.)),
4559            MouseButton::Left,
4560            gpui::Modifiers::default(),
4561        );
4562        cx.update(|window, cx| {
4563            assert!(GlobalState::is_text_selection_suppressed(cx));
4564            assert!(!TextSelection::has_selection(window, cx));
4565        });
4566    }
4567
4568    #[gpui::test]
4569    fn frame_sweep_keeps_a_participant_registered_before_the_selection_element_paints(
4570        cx: &mut TestAppContext,
4571    ) {
4572        cx.update(|cx| {
4573            let mut selection_state = WindowSelectionState::default();
4574            let participant = FakeParticipant::new("painted first", cx);
4575            participant.register(
4576                &mut selection_state,
4577                0.,
4578                TextSelectionScopeId::default(),
4579                0,
4580                cx,
4581            );
4582            selection_state.begin(point(px(1.), px(1.)), false, cx);
4583            selection_state.update(point(px(8.), px(1.)), cx);
4584            selection_state.end(cx);
4585
4586            selection_state.finish_frame(cx);
4587
4588            assert_eq!(selection_state.selected_text(cx), "painted first");
4589            assert!(participant.selection.snapshot(cx).is_some());
4590        });
4591    }
4592
4593    #[gpui::test]
4594    fn two_selection_elements_schedule_only_one_post_frame_sweep(cx: &mut TestAppContext) {
4595        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
4596            selection: TextSelectionHandle::new("once", cx),
4597        });
4598        cx.update(|window, cx| {
4599            let selection_state = WindowSelectionState::ensure(window, cx);
4600            let selection = view.read(cx).selection.clone();
4601            selection_state.update(cx, |selection_state, cx| {
4602                FakeParticipant { selection }.register(
4603                    selection_state,
4604                    0.,
4605                    TextSelectionScopeId::default(),
4606                    0,
4607                    cx,
4608                );
4609                selection_state.begin(point(px(1.), px(1.)), false, cx);
4610                selection_state.update(point(px(8.), px(1.)), cx);
4611                selection_state.end(cx);
4612            });
4613
4614            let _ = window.draw(cx);
4615            window.simulate_next_frame(cx);
4616
4617            let items = selection_state.read(cx).copy_items(cx);
4618            assert_eq!(resolve_copy_items(items, cx), "once");
4619        });
4620    }
4621
4622    #[gpui::test]
4623    fn duplicate_selection_elements_gate_real_pointer_gestures_and_reentrant_clear(
4624        cx: &mut TestAppContext,
4625    ) {
4626        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
4627            selection: TextSelectionHandle::new("once", cx),
4628        });
4629        let clear_count = Rc::new(Cell::new(0));
4630        cx.update(|window, cx| {
4631            let state = WindowSelectionState::ensure(window, cx);
4632            let state_for_clear = state.clone();
4633            let count = clear_count.clone();
4634            let selection = view.read(cx).selection.clone();
4635            selection
4636                .subscribe(
4637                    move |event, cx| {
4638                        if matches!(event, TextSelectionEvent::Cleared) {
4639                            count.set(count.get() + 1);
4640                            let _ = state_for_clear.read(cx).snapshot();
4641                        }
4642                    },
4643                    cx,
4644                )
4645                .detach();
4646            let _ = window.draw(cx);
4647        });
4648
4649        cx.simulate_mouse_down(
4650            point(px(10.), px(10.)),
4651            MouseButton::Left,
4652            gpui::Modifiers::default(),
4653        );
4654        cx.simulate_mouse_up(
4655            point(px(10.), px(10.)),
4656            MouseButton::Left,
4657            gpui::Modifiers::default(),
4658        );
4659        cx.simulate_mouse_down(
4660            point(px(70.), px(10.)),
4661            MouseButton::Left,
4662            gpui::Modifiers {
4663                shift: true,
4664                ..Default::default()
4665            },
4666        );
4667        cx.simulate_mouse_up(
4668            point(px(70.), px(10.)),
4669            MouseButton::Left,
4670            gpui::Modifiers::default(),
4671        );
4672        cx.update(|window, cx| assert!(TextSelection::has_selection(window, cx)));
4673
4674        cx.simulate_mouse_down(
4675            point(px(15.), px(10.)),
4676            MouseButton::Left,
4677            gpui::Modifiers::default(),
4678        );
4679        cx.simulate_mouse_move(
4680            point(px(85.), px(10.)),
4681            Some(MouseButton::Left),
4682            gpui::Modifiers::default(),
4683        );
4684        cx.simulate_mouse_up(
4685            point(px(85.), px(10.)),
4686            MouseButton::Left,
4687            gpui::Modifiers::default(),
4688        );
4689        cx.update(|window, cx| assert!(TextSelection::has_selection(window, cx)));
4690        assert_eq!(clear_count.get(), 3);
4691    }
4692
4693    #[gpui::test]
4694    fn selection_layer_handles_real_double_and_triple_click_events(cx: &mut TestAppContext) {
4695        let (text, layout) = laid_out_runs(&["alpha beta"], cx).pop().unwrap();
4696        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
4697            selection: TextSelectionHandle::new("", cx),
4698        });
4699        cx.update(|window, cx| {
4700            let _ = window.draw(cx);
4701            let selection = view.read(cx).selection.clone();
4702            selection.resolve_content_key_with(|_, _| Some(TextSelectionContentKey::new(17)), cx);
4703            selection.update_runs(&[text_run(0, text.clone(), layout.clone())], cx);
4704        });
4705
4706        let position = layout.position_for_index(7).unwrap();
4707        cx.simulate_event(MouseDownEvent {
4708            position,
4709            modifiers: gpui::Modifiers::default(),
4710            button: MouseButton::Left,
4711            click_count: 2,
4712            first_mouse: false,
4713        });
4714        cx.simulate_event(MouseUpEvent {
4715            position,
4716            modifiers: gpui::Modifiers::default(),
4717            button: MouseButton::Left,
4718            click_count: 2,
4719        });
4720        cx.update(|window, cx| {
4721            let selection = view.read(cx).selection.clone();
4722            selection.update_runs(&[text_run(0, text.clone(), layout.clone())], cx);
4723            assert_eq!(TextSelection::selected_text(window, cx), "beta");
4724            let snapshot = selection.snapshot(cx).unwrap();
4725            assert_eq!(
4726                snapshot.anchor().content_key(),
4727                Some(TextSelectionContentKey::new(17))
4728            );
4729            assert_eq!(
4730                snapshot.cursor().content_key(),
4731                Some(TextSelectionContentKey::new(17))
4732            );
4733        });
4734
4735        cx.simulate_event(MouseDownEvent {
4736            position,
4737            modifiers: gpui::Modifiers::default(),
4738            button: MouseButton::Left,
4739            click_count: 3,
4740            first_mouse: false,
4741        });
4742        cx.simulate_event(MouseUpEvent {
4743            position,
4744            modifiers: gpui::Modifiers::default(),
4745            button: MouseButton::Left,
4746            click_count: 3,
4747        });
4748        cx.update(|window, cx| {
4749            let selection = view.read(cx).selection.clone();
4750            selection.update_runs(&[text_run(0, text, layout)], cx);
4751            assert_eq!(TextSelection::selected_text(window, cx), "alpha beta");
4752        });
4753    }
4754}