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    ///
1294    /// Removing an endpoint also retires the window's gesture. Clearing only
1295    /// the participant snapshot would restore that selection if its retained
1296    /// entity were registered again.
1297    pub fn finish_frame(&mut self, cx: &mut App) -> Vec<ClearHandler> {
1298        self.finish_frame_scheduled.set(false);
1299        let generation = self.frame_generation.get();
1300        let stale = self
1301            .participants
1302            .get_mut()
1303            .iter()
1304            .filter_map(|(id, registration)| {
1305                (registration.generation != generation && !registration.registration.is_rendered())
1306                    .then(|| (*id, registration.participant.clone()))
1307            })
1308            .collect::<Vec<_>>();
1309        let endpoint_departed = stale.iter().any(|(id, _)| {
1310            [
1311                self.anchor.as_ref(),
1312                self.cursor.as_ref(),
1313                self.pending_extension_anchor.as_ref(),
1314            ]
1315            .into_iter()
1316            .flatten()
1317            .any(|endpoint| endpoint.entity_id() == Some(*id))
1318        });
1319        let mut handlers = if endpoint_departed {
1320            self.clear_state(cx)
1321        } else {
1322            Vec::new()
1323        };
1324        for (id, participant) in stale {
1325            self.participants.get_mut().remove(&id);
1326            if let Some(participant) = participant.upgrade()
1327                && participant.read(cx).has_selection_to_clear()
1328            {
1329                if let Some(handler) = participant.update(cx, |state, cx| state.clear_state(cx)) {
1330                    handlers.push(handler);
1331                }
1332            }
1333        }
1334        self.publish_snapshots(cx);
1335        self.frame_generation.set(generation.wrapping_add(1));
1336        handlers
1337    }
1338
1339    fn schedule_finish_frame(&self) -> bool {
1340        !self.finish_frame_scheduled.replace(true)
1341    }
1342
1343    /// Whether there is a selection, or a gesture or touch selection that
1344    /// may make one: what painting the participants reports can change it.
1345    fn is_active(&self) -> bool {
1346        self.anchor.is_some()
1347            || self.cursor.is_some()
1348            || self.pending_extension_anchor.is_some()
1349            || self.is_selecting
1350            || self.refresh_held_cursor
1351            || self.touch.active
1352    }
1353
1354    /// Records this frame's geometry for a participant without updating
1355    /// this state, when there is no selection for the geometry to change.
1356    /// Every participant paints its geometry on every frame; doing so through
1357    /// an update would mark the state, and every view that reads it, changed
1358    /// on every frame. Returns the registration when it has to go through
1359    /// [`Self::register_participant`].
1360    fn register_quietly(
1361        &self,
1362        selection: &TextSelectionHandle,
1363        registration: TextSelectionRegistration,
1364    ) -> Option<TextSelectionRegistration> {
1365        if self.is_active() {
1366            return Some(registration);
1367        }
1368        self.participants.borrow_mut().insert(
1369            selection.entity_id(),
1370            ParticipantRegistration {
1371                participant: selection.downgrade(),
1372                registration: Rc::new(registration),
1373                generation: self.frame_generation.get(),
1374            },
1375        );
1376        None
1377    }
1378
1379    /// Ends the frame as [`Self::finish_frame`] does, without updating this
1380    /// state, when there is no selection and no participant that left the
1381    /// window has one to clear. Returns whether it could.
1382    fn finish_frame_quietly(&self, cx: &App) -> bool {
1383        if self.is_active() {
1384            return false;
1385        }
1386        let generation = self.frame_generation.get();
1387        let mut participants = self.participants.borrow_mut();
1388        let gone = |registration: &ParticipantRegistration| {
1389            registration.generation != generation && !registration.registration.is_rendered()
1390        };
1391        if participants.values().any(|registration| {
1392            gone(registration)
1393                && registration
1394                    .participant
1395                    .upgrade()
1396                    .is_some_and(|participant| participant.read(cx).has_selection_to_clear())
1397        }) {
1398            return false;
1399        }
1400        participants.retain(|_, registration| {
1401            !gone(registration) && registration.participant.upgrade().is_some()
1402        });
1403        drop(participants);
1404        self.finish_frame_scheduled.set(false);
1405        self.frame_generation.set(generation.wrapping_add(1));
1406        true
1407    }
1408
1409    /// Registers this frame's geometry for a participant.
1410    pub fn register_participant(
1411        &mut self,
1412        selection: TextSelectionHandle,
1413        registration: TextSelectionRegistration,
1414        cx: &mut App,
1415    ) {
1416        self.prune_dead_participants();
1417        if self.is_selecting
1418            && registration.self_scroll
1419            && self.anchor.as_ref().and_then(SelectionEndpoint::entity_id)
1420                == Some(selection.entity_id())
1421            && self
1422                .participants
1423                .get_mut()
1424                .get(&selection.entity_id())
1425                .is_some_and(|previous| {
1426                    previous.registration.scroll_offset != registration.scroll_offset
1427                        || previous.registration.bounds != registration.bounds
1428                })
1429        {
1430            self.refresh_held_cursor = true;
1431        }
1432        // The handles sit on the painted ends; when those moved — a scroll, a
1433        // reflow, a select-all — whoever draws the handles needs to know.
1434        let edges_moved = self.touch.active
1435            && self
1436                .participants
1437                .get_mut()
1438                .get(&selection.entity_id())
1439                .is_none_or(|previous| {
1440                    previous.registration.selection_edges != registration.selection_edges
1441                });
1442        self.participants.get_mut().insert(
1443            selection.entity_id(),
1444            ParticipantRegistration {
1445                participant: selection.downgrade(),
1446                registration: Rc::new(registration),
1447                generation: self.frame_generation.get(),
1448            },
1449        );
1450        self.publish_snapshots(cx);
1451        if edges_moved {
1452            self.touch_changed(cx);
1453        }
1454    }
1455
1456    /// Starts a selection gesture using bounds hit testing (useful to adapters/tests).
1457    #[cfg(test)]
1458    fn begin(&mut self, position: Point<Pixels>, extend: bool, cx: &mut App) {
1459        self.begin_impl(position, extend, false, None, cx);
1460    }
1461
1462    /// Updates the current gesture using bounds hit testing.
1463    #[cfg(test)]
1464    fn update(&mut self, position: Point<Pixels>, cx: &mut App) {
1465        self.update_impl(position, None, cx);
1466    }
1467
1468    /// Ends the current gesture and keeps its selection visible.
1469    pub fn end(&mut self, cx: &mut App) {
1470        self.pending_extension_anchor = None;
1471        if !self.is_selecting {
1472            return;
1473        }
1474        self.is_selecting = false;
1475        if !self.did_hit_text {
1476            self.anchor = None;
1477            self.cursor = None;
1478        }
1479        self.stop_anchor_auto_scroll(cx);
1480        self.publish_snapshots(cx);
1481    }
1482
1483    /// Clears both window selection and every participant's local selection.
1484    pub fn clear(&mut self, cx: &mut App) {
1485        let handlers = self.clear_state(cx);
1486        dispatch_clear_handlers(handlers, cx);
1487    }
1488
1489    fn clear_state(&mut self, cx: &mut App) -> Vec<ClearHandler> {
1490        self.stop_anchor_auto_scroll(cx);
1491        self.anchor = None;
1492        self.cursor = None;
1493        self.pending_extension_anchor = None;
1494        self.is_selecting = false;
1495        self.did_hit_text = false;
1496        if self.touch.reset() {
1497            self.touch_changed(cx);
1498        }
1499        self.prune_dead_participants();
1500        self.participants
1501            .borrow()
1502            .values()
1503            .filter_map(|registration| registration.participant.upgrade())
1504            .filter_map(|participant| participant.update(cx, |state, cx| state.clear_state(cx)))
1505            .collect()
1506    }
1507
1508    /// Tells whoever draws the handles and the menu that they changed.
1509    ///
1510    /// Deferred, because the change may come from a participant painting its
1511    /// selection ends: a notification raised inside a draw marks the view
1512    /// dirty but starts no frame, and the handles would sit where they were
1513    /// until something else redrew the window.
1514    ///
1515    /// The participants in the selection hear it too: they paint the
1516    /// handles, and lay the handles' hitboxes out from the ends they painted
1517    /// last frame, so they render once more. A participant under a cached
1518    /// view would otherwise not, and the next frame would replay this one,
1519    /// with no hitbox where a handle is.
1520    fn touch_changed(&self, cx: &mut App) {
1521        let participants = self
1522            .participants
1523            .borrow()
1524            .values()
1525            .filter_map(|registration| registration.participant.upgrade())
1526            .filter(|participant| participant.read(cx).snapshot.is_some())
1527            .collect::<Vec<_>>();
1528        let entity_id = self.entity_id;
1529        cx.defer(move |cx| {
1530            for participant in participants {
1531                participant.update(cx, |_, cx| {
1532                    cx.emit(TextSelectionEvent::TouchSelectionChanged)
1533                });
1534            }
1535            if let Some(entity_id) = entity_id {
1536                cx.notify(entity_id);
1537            }
1538        });
1539    }
1540
1541    fn copy_items(&self, cx: &App) -> Vec<CopyItem> {
1542        self.participants
1543            .borrow()
1544            .values()
1545            .filter_map(|registration| {
1546                let participant = registration.participant.upgrade()?;
1547                participant
1548                    .read(cx)
1549                    .copy_item(registration.registration.document_order)
1550            })
1551            .collect()
1552    }
1553
1554    #[cfg(test)]
1555    fn selected_text(&self, cx: &mut App) -> String {
1556        resolve_copy_items(self.copy_items(cx), cx)
1557    }
1558
1559    /// Whether the current endpoints take in at least one character of some
1560    /// participant's painted text, as opposed to two points with nothing
1561    /// between them.
1562    fn selects_text(&self, cx: &App) -> bool {
1563        self.snapshot().is_some()
1564            && self.participants.borrow().values().any(|registration| {
1565                registration
1566                    .participant
1567                    .upgrade()
1568                    .is_some_and(|participant| {
1569                        let participant = participant.read(cx);
1570                        project_ranges(participant.snapshot, &participant.runs.borrow())
1571                            .ranges()
1572                            .iter()
1573                            .any(|range| range.as_ref().is_some_and(|range| !range.is_empty()))
1574                    })
1575            })
1576    }
1577
1578    /// Returns whether a drag or a participant-local selection is active.
1579    pub fn has_selection(&self, cx: &App) -> bool {
1580        self.snapshot().is_some()
1581            || self.participants.borrow().values().any(|registration| {
1582                registration
1583                    .participant
1584                    .upgrade()
1585                    .is_some_and(|participant| participant.read(cx).local_selection)
1586            })
1587    }
1588
1589    /// Returns the current resolved selection endpoints.
1590    pub fn snapshot(&self) -> Option<TextSelectionSnapshot> {
1591        if !self.did_hit_text {
1592            return None;
1593        }
1594        let anchor_endpoint = self.anchor.as_ref()?;
1595        let cursor_endpoint = self.cursor.as_ref()?;
1596        let anchor = anchor_endpoint.resolve(&self.participants.borrow())?;
1597        let cursor = cursor_endpoint.resolve(&self.participants.borrow())?;
1598        (anchor != cursor).then(|| {
1599            TextSelectionSnapshot::new(anchor_endpoint.snapshot(), cursor_endpoint.snapshot())
1600                .with_selecting(self.is_selecting)
1601                .with_window_points(Some(TextSelectionWindowPoints { anchor, cursor }))
1602        })
1603    }
1604
1605    /// Returns whether a drag is currently in progress.
1606    #[cfg(test)]
1607    fn is_selecting(&self) -> bool {
1608        self.is_selecting
1609    }
1610
1611    fn prepare_for_mouse_down(&mut self, extend: bool, cx: &mut App) -> Vec<ClearHandler> {
1612        let pending_extension_anchor = extend.then(|| self.anchor.clone()).flatten();
1613        self.stop_anchor_auto_scroll(cx);
1614        self.anchor = None;
1615        self.cursor = None;
1616        self.pending_extension_anchor = None;
1617        self.is_selecting = false;
1618        self.did_hit_text = false;
1619        if self.touch.reset() {
1620            self.touch_changed(cx);
1621        }
1622        self.prune_dead_participants();
1623        let handlers = self
1624            .participants
1625            .borrow()
1626            .values()
1627            .filter_map(|registration| registration.participant.upgrade())
1628            .filter_map(|participant| participant.update(cx, |state, cx| state.clear_state(cx)))
1629            .collect();
1630        self.pending_extension_anchor = pending_extension_anchor;
1631        handlers
1632    }
1633
1634    /// The touch selection laid out for its handles and edit menu.
1635    ///
1636    /// The ends come from the participants: the first selected character of
1637    /// the earliest participant in document order and the last of the latest.
1638    /// A participant whose selection is entirely scrolled away paints no ends,
1639    /// so the handles disappear with the text they mark.
1640    fn touch_selection(&self) -> Option<TouchSelectionSnapshot> {
1641        if !self.touch.active {
1642            return None;
1643        }
1644        // Each end with its owner's viewport: an end scrolled out of its
1645        // participant gets no handle.
1646        let mut start: Option<(u64, Bounds<Pixels>, bool)> = None;
1647        let mut end: Option<(u64, Bounds<Pixels>, bool)> = None;
1648        for registration in self.participants.borrow().values() {
1649            let geometry = &registration.registration;
1650            if geometry.scope != self.active_scope || registration.participant.upgrade().is_none() {
1651                continue;
1652            }
1653            let Some((edge_start, edge_end)) = geometry.selection_edges else {
1654                continue;
1655            };
1656            let order = geometry.document_order;
1657            let viewport = geometry.hitbox.bounds;
1658            if start.is_none_or(|(best, ..)| order < best) {
1659                start = Some((order, edge_start, caret_in_view(edge_start, viewport)));
1660            }
1661            if end.is_none_or(|(best, ..)| order >= best) {
1662                end = Some((order, edge_end, caret_in_view(edge_end, viewport)));
1663            }
1664        }
1665        let edges = match (start, end) {
1666            (Some((_, start, start_visible)), Some((_, end, end_visible))) => {
1667                let edges = (start, end, start_visible, end_visible);
1668                self.touch.last_edges.set(Some(edges));
1669                edges
1670            }
1671            _ if self.touch.drag.is_some() => self.touch.last_edges.get()?,
1672            _ => return None,
1673        };
1674        let (start, end, start_visible, end_visible) = edges;
1675        Some(
1676            TouchSelectionSnapshot::new(start, end)
1677                .with_edge_visible(SelectionEdge::Start, start_visible)
1678                .with_edge_visible(SelectionEdge::End, end_visible)
1679                .with_menu_open(self.touch.menu_open)
1680                .with_dragging(self.touch.drag.map(|drag| drag.edge())),
1681        )
1682    }
1683
1684    /// The handles `participant` paints: the start when no participant
1685    /// earlier in document order has a selection end, the end when none
1686    /// later has. Participants paint in document order, so a later one that
1687    /// takes the end over has registered its ends by the time it asks.
1688    fn touch_handles_of(&self, participant: EntityId) -> Vec<(SelectionEdge, Bounds<Pixels>)> {
1689        if !self.touch.active {
1690            return Vec::new();
1691        }
1692        let Some(own) = self.participants.borrow().get(&participant).cloned() else {
1693            return Vec::new();
1694        };
1695        let Some((start, end)) = own.registration.selection_edges else {
1696            return Vec::new();
1697        };
1698        if start == end {
1699            return Vec::new();
1700        }
1701        let order = own.registration.document_order;
1702        let participants = self.participants.borrow();
1703        let others = participants.iter().filter(|(id, registration)| {
1704            **id != participant
1705                && registration.registration.scope == self.active_scope
1706                && registration.registration.selection_edges.is_some()
1707                && registration.participant.upgrade().is_some()
1708        });
1709        let (mut earlier, mut later) = (false, false);
1710        for (_, registration) in others {
1711            let other = registration.registration.document_order;
1712            earlier |= other < order;
1713            later |= other > order;
1714        }
1715        let viewport = own.registration.hitbox.bounds;
1716        let mut handles = Vec::with_capacity(2);
1717        if !earlier && caret_in_view(start, viewport) {
1718            handles.push((SelectionEdge::Start, start));
1719        }
1720        if !later && caret_in_view(end, viewport) {
1721            handles.push((SelectionEdge::End, end));
1722        }
1723        handles
1724    }
1725
1726    /// Keeps the selection a long press made, and opens the edit menu over it.
1727    fn keep_touch_selection(&mut self, cx: &mut App) {
1728        self.touch.active = self.snapshot().is_some();
1729        self.touch.menu_open = self.touch.active;
1730        self.touch.drag = None;
1731        self.touch_changed(cx);
1732    }
1733
1734    /// Records where the menu is painted this frame.
1735    fn register_touch_ui(&mut self, bounds: Bounds<Pixels>) {
1736        self.touch.ui_bounds.push(bounds);
1737    }
1738
1739    /// Whether `bounds` are where `participant` painted its handles last.
1740    fn handles_painted_at(&self, participant: EntityId, bounds: &[Bounds<Pixels>]) -> bool {
1741        self.touch
1742            .handle_bounds
1743            .get(&participant)
1744            .map_or(bounds.is_empty(), |painted| painted == bounds)
1745    }
1746
1747    /// Records where `participant` painted its handles, which replace those
1748    /// it painted before.
1749    fn register_handles(&mut self, participant: EntityId, bounds: Vec<Bounds<Pixels>>) {
1750        self.touch.handle_bounds.insert(participant, bounds);
1751    }
1752
1753    fn close_edit_menu(&mut self, cx: &mut App) {
1754        if !self.touch.menu_open {
1755            return;
1756        }
1757        self.touch.menu_open = false;
1758        self.touch_changed(cx);
1759    }
1760
1761    /// The handles follow the text, but the menu would sit over whatever
1762    /// scrolls underneath it: it steps aside while a finger scrolls and comes
1763    /// back over the handles once the finger lifts.
1764    fn edit_menu_on_scroll(&mut self, phase: TouchPhase, cx: &mut App) {
1765        if !self.touch.active || self.touch.drag.is_some() {
1766            return;
1767        }
1768        match phase {
1769            TouchPhase::Ended | TouchPhase::Cancelled => {
1770                if !self.touch.menu_open {
1771                    self.touch.menu_open = true;
1772                    self.touch_changed(cx);
1773                }
1774            }
1775            _ => self.close_edit_menu(cx),
1776        }
1777    }
1778
1779    /// Selects all of the participant the touch selection started in.
1780    ///
1781    /// This stays a point selection — anchored on the participant's first
1782    /// line of text, ending on its last — rather than switching the
1783    /// participant to a local select-all: one selection, painted, reported
1784    /// and copied the one way, with handles that drag on from its ends.
1785    fn select_all_touched(&mut self, cx: &mut App) {
1786        if !self.touch.active {
1787            return;
1788        }
1789        let Some((participant, registration)) = self.anchor_registration() else {
1790            return;
1791        };
1792        // From the participant's text runs, not its painted line bounds:
1793        // those are clipped to the viewport, and a message taller than the
1794        // screen would only select what is on it.
1795        let (anchor, cursor) = {
1796            let runs = &participant.read(cx).runs.borrow();
1797            let (Some(first), Some(last)) = (
1798                runs.iter().min_by_key(|run| run.document_order),
1799                runs.iter().max_by_key(|run| run.document_order),
1800            ) else {
1801                return;
1802            };
1803            let (Some(start), Some(end)) = (
1804                first.layout.position_for_index(0),
1805                last.layout.position_for_index(last.text.len()),
1806            ) else {
1807                return;
1808            };
1809            // Just inside the first and the last glyph, mid-line, so both
1810            // land on text.
1811            let inset = px(1.);
1812            (
1813                point(start.x + inset, start.y + first.layout.line_height() / 2.),
1814                point(end.x - inset, end.y + last.layout.line_height() / 2.),
1815            )
1816        };
1817        let content_key_resolver = participant.read(cx).content_key_resolver.clone();
1818        let to_endpoint = |window_point: Point<Pixels>| {
1819            let content_point =
1820                window_point - registration.bounds.origin - registration.scroll_offset;
1821            SelectionEndpoint {
1822                participant: Some(participant.downgrade()),
1823                point: content_point,
1824                inside: true,
1825                inside_text: true,
1826                content_key: None,
1827                content_key_resolver: content_key_resolver
1828                    .clone()
1829                    .map(|resolver| (resolver, content_point)),
1830            }
1831        };
1832        self.anchor = Some(to_endpoint(anchor));
1833        self.cursor = Some(to_endpoint(cursor));
1834        self.did_hit_text = true;
1835        self.is_selecting = false;
1836        self.touch.menu_open = true;
1837        self.publish_snapshots(cx);
1838        self.touch_changed(cx);
1839    }
1840
1841    /// Starts dragging one end of the touch selection from `finger`.
1842    ///
1843    /// The selection is rebuilt from the painted ends, anchored at the end that
1844    /// stays: a select-all or a word selection becomes an ordinary point
1845    /// selection which the drag then extends.
1846    fn begin_edge_drag(
1847        &mut self,
1848        edge: SelectionEdge,
1849        finger: Point<Pixels>,
1850        window: &mut Window,
1851        cx: &mut App,
1852    ) {
1853        let Some(snapshot) = self.touch_selection() else {
1854            return;
1855        };
1856        let handlers = self.prepare_for_mouse_down(false, cx);
1857        dispatch_clear_handlers(handlers, cx);
1858        let held = snapshot.edge(edge.opposite());
1859        // Just inside the held end, so the anchor lands on the character it
1860        // marks rather than on the boundary between two participants.
1861        let nudge = px(1.);
1862        let anchor_point = match edge {
1863            SelectionEdge::Start => point(held.left() - nudge, held.center().y),
1864            SelectionEdge::End => point(held.left() + nudge, held.center().y),
1865        };
1866        let anchor = self.endpoint(anchor_point, None, cx);
1867        let drag = EdgeDrag::begin(edge, snapshot.edge(edge), finger);
1868        let cursor = self.endpoint(drag.text_position(finger), Some(window), cx);
1869        self.did_hit_text = anchor.inside_text || cursor.inside_text;
1870        self.anchor = Some(anchor);
1871        self.cursor = Some(cursor);
1872        self.is_selecting = true;
1873        self.touch.active = true;
1874        self.touch.menu_open = false;
1875        self.touch.drag = Some(drag);
1876        self.publish_snapshots(cx);
1877        self.touch_changed(cx);
1878    }
1879
1880    fn update_edge_drag(&mut self, finger: Point<Pixels>, window: &Window, cx: &mut Context<Self>) {
1881        let Some(drag) = self.touch.drag else {
1882            return;
1883        };
1884        let before = self.cursor.clone();
1885        self.update_in_window(drag.text_position(finger), window, cx);
1886        // A handle never collapses the selection: at the other end it stops,
1887        // and the finger has to pass that end, onto text, to swap the two.
1888        if !self.selects_text(cx) {
1889            self.cursor = before;
1890            self.publish_snapshots(cx);
1891            return;
1892        }
1893        // Dragging one end past the other swaps them: the cursor now lies
1894        // before the anchor, so the finger holds what became the start.
1895        if let Some(points) = self
1896            .snapshot()
1897            .and_then(|snapshot| snapshot.window_points())
1898        {
1899            let (anchor, cursor) = (points.anchor(), points.cursor());
1900            let cursor_first = cursor.y < anchor.y || (cursor.y == anchor.y && cursor.x < anchor.x);
1901            let edge = if cursor_first {
1902                SelectionEdge::Start
1903            } else {
1904                SelectionEdge::End
1905            };
1906            if let Some(drag) = self.touch.drag.as_mut() {
1907                drag.set_edge(edge);
1908            }
1909        }
1910        self.touch_changed(cx);
1911    }
1912
1913    fn end_edge_drag(&mut self, cx: &mut App) {
1914        if self.touch.drag.take().is_none() {
1915            return;
1916        }
1917        self.end(cx);
1918        self.keep_touch_selection(cx);
1919    }
1920
1921    fn begin_in_window(
1922        &mut self,
1923        position: Point<Pixels>,
1924        extend: bool,
1925        window: &mut Window,
1926        cx: &mut App,
1927    ) {
1928        self.begin_impl(position, extend, true, Some(window), cx);
1929    }
1930
1931    fn update_in_window(
1932        &mut self,
1933        position: Point<Pixels>,
1934        window: &Window,
1935        cx: &mut Context<Self>,
1936    ) {
1937        if !cx.has_active_drag() {
1938            self.update_impl(position, Some(window), cx);
1939            self.update_auto_scroll(position, window, cx);
1940        }
1941    }
1942
1943    fn select_at(
1944        &mut self,
1945        position: Point<Pixels>,
1946        click_count: usize,
1947        window: &mut Window,
1948        cx: &mut App,
1949    ) {
1950        GlobalState::init(cx);
1951        if GlobalState::is_text_selection_suppressed(cx) {
1952            return;
1953        }
1954        let hit = self.endpoint(position, Some(window), cx);
1955        if !hit.inside_text {
1956            return;
1957        }
1958        let Some(participant) = hit
1959            .participant
1960            .and_then(|participant| participant.upgrade())
1961        else {
1962            return;
1963        };
1964        let points =
1965            points_for_multi_click(&participant.read(cx).runs.borrow(), position, click_count);
1966        let Some((anchor, cursor)) = points else {
1967            return;
1968        };
1969        let Some(registration) = self
1970            .participants
1971            .borrow()
1972            .get(&participant.entity_id())
1973            .cloned()
1974        else {
1975            return;
1976        };
1977        let content_key_resolver = participant.read(cx).content_key_resolver.clone();
1978        let to_endpoint = |point: Point<Pixels>| {
1979            let content_point = point
1980                - registration.registration.bounds.origin
1981                - registration.registration.scroll_offset;
1982            SelectionEndpoint {
1983                participant: Some(participant.downgrade()),
1984                point: content_point,
1985                inside: true,
1986                inside_text: true,
1987                content_key: None,
1988                content_key_resolver: content_key_resolver
1989                    .clone()
1990                    .map(|resolver| (resolver, content_point)),
1991            }
1992        };
1993        self.anchor = Some(to_endpoint(anchor));
1994        self.cursor = Some(to_endpoint(cursor));
1995        self.did_hit_text = true;
1996        self.is_selecting = false;
1997        participant.update(cx, |state, cx| state.focus(window, cx));
1998        self.publish_snapshots(cx);
1999    }
2000
2001    #[cfg(test)]
2002    fn update_in_window_with_active_drag(
2003        &mut self,
2004        position: Point<Pixels>,
2005        active_drag: bool,
2006        window: &Window,
2007        cx: &mut App,
2008    ) {
2009        if !active_drag {
2010            self.update_impl(position, Some(window), cx);
2011        }
2012    }
2013
2014    fn begin_impl(
2015        &mut self,
2016        position: Point<Pixels>,
2017        extend: bool,
2018        already_prepared: bool,
2019        window: Option<&mut Window>,
2020        cx: &mut App,
2021    ) {
2022        GlobalState::init(cx);
2023        if GlobalState::is_text_selection_suppressed(cx) {
2024            self.pending_extension_anchor = None;
2025            return;
2026        }
2027        let previous_anchor = extend
2028            .then(|| {
2029                self.pending_extension_anchor
2030                    .take()
2031                    .or_else(|| self.anchor.clone())
2032            })
2033            .flatten()
2034            .filter(|anchor| anchor.resolve(&self.participants.borrow()).is_some());
2035        if !extend && !already_prepared {
2036            self.clear(cx);
2037        }
2038        let endpoint = self.endpoint(position, window.as_deref(), cx);
2039        let focus_participant = endpoint
2040            .inside
2041            .then(|| endpoint.participant.clone())
2042            .flatten();
2043        let anchor = previous_anchor.unwrap_or_else(|| endpoint.clone());
2044        self.anchor = Some(anchor.clone());
2045        self.cursor = Some(endpoint.clone());
2046        self.did_hit_text = anchor.inside_text || endpoint.inside_text;
2047        self.is_selecting = true;
2048        if let Some(participant) = focus_participant.and_then(|participant| participant.upgrade()) {
2049            if let Some(window) = window {
2050                participant.update(cx, |state, cx| state.focus(window, cx));
2051            }
2052        }
2053        self.publish_snapshots(cx);
2054    }
2055
2056    fn update_impl(&mut self, position: Point<Pixels>, window: Option<&Window>, cx: &mut App) {
2057        if !self.is_selecting {
2058            return;
2059        }
2060        let endpoint = self.endpoint(position, window, cx);
2061        self.did_hit_text |= endpoint.inside_text;
2062        self.cursor = Some(endpoint);
2063        if window.is_none() {
2064            self.update_participant_auto_scroll(position, cx);
2065        }
2066        self.publish_snapshots(cx);
2067    }
2068
2069    fn endpoint(
2070        &mut self,
2071        position: Point<Pixels>,
2072        window: Option<&Window>,
2073        cx: &App,
2074    ) -> SelectionEndpoint {
2075        self.prune_dead_participants();
2076        let mut hit: Option<(
2077            WeakEntity<SelectableTextState>,
2078            Rc<TextSelectionRegistration>,
2079            f32,
2080        )> = None;
2081        let mut predecessor: Option<(
2082            WeakEntity<SelectableTextState>,
2083            Rc<TextSelectionRegistration>,
2084        )> = None;
2085        let mut first: Option<(
2086            WeakEntity<SelectableTextState>,
2087            Rc<TextSelectionRegistration>,
2088        )> = None;
2089
2090        for registration in self.participants.borrow().values() {
2091            if registration.registration.scope != self.active_scope
2092                || registration.participant.upgrade().is_none()
2093            {
2094                continue;
2095            }
2096            let participant_geometry = &registration.registration;
2097            let hovered = window.map_or_else(
2098                || participant_geometry.bounds.contains(&position),
2099                |window| participant_geometry.hitbox.is_hovered(window),
2100            );
2101            if hovered {
2102                let area = f32::from(participant_geometry.bounds.size.width)
2103                    * f32::from(participant_geometry.bounds.size.height);
2104                if hit.as_ref().is_none_or(|(_, best, best_area)| {
2105                    area < *best_area
2106                        || (area == *best_area
2107                            && participant_geometry.document_order < best.document_order)
2108                }) {
2109                    hit = Some((
2110                        registration.participant.clone(),
2111                        participant_geometry.clone(),
2112                        area,
2113                    ));
2114                }
2115            }
2116            if participant_geometry.bounds.top() <= position.y
2117                && predecessor.as_ref().is_none_or(|(_, best)| {
2118                    participant_geometry.bounds.top() > best.bounds.top()
2119                        || (participant_geometry.bounds.top() == best.bounds.top()
2120                            && participant_geometry.document_order < best.document_order)
2121                })
2122            {
2123                predecessor = Some((
2124                    registration.participant.clone(),
2125                    participant_geometry.clone(),
2126                ));
2127            }
2128            if first.as_ref().is_none_or(|(_, best)| {
2129                participant_geometry.bounds.top() < best.bounds.top()
2130                    || (participant_geometry.bounds.top() == best.bounds.top()
2131                        && participant_geometry.document_order < best.document_order)
2132            }) {
2133                first = Some((
2134                    registration.participant.clone(),
2135                    participant_geometry.clone(),
2136                ));
2137            }
2138        }
2139
2140        let selection = hit
2141            .map(|(participant, registration, _)| (participant, registration, true))
2142            .or_else(|| {
2143                predecessor
2144                    .or(first)
2145                    .map(|(participant, registration)| (participant, registration, false))
2146            });
2147        match selection {
2148            Some((participant, registration, inside)) => {
2149                let point = position - registration.bounds.origin - registration.scroll_offset;
2150                let content_key_resolver = participant.upgrade().and_then(|participant| {
2151                    participant
2152                        .read(cx)
2153                        .content_key_resolver
2154                        .clone()
2155                        .map(|callback| (callback, point))
2156                });
2157                SelectionEndpoint {
2158                    point,
2159                    participant: Some(participant),
2160                    inside,
2161                    inside_text: inside
2162                        && registration
2163                            .text_bounds
2164                            .iter()
2165                            .any(|bounds| bounds.contains(&position)),
2166                    content_key: None,
2167                    content_key_resolver,
2168                }
2169            }
2170            None => SelectionEndpoint {
2171                participant: None,
2172                point: position,
2173                inside: false,
2174                inside_text: false,
2175                content_key: None,
2176                content_key_resolver: None,
2177            },
2178        }
2179    }
2180
2181    fn publish_snapshots(&mut self, cx: &mut App) {
2182        self.prune_dead_participants();
2183        let snapshot = self.snapshot();
2184        let single_participant = self.single_participant();
2185        for (id, registration) in self.participants.borrow().iter() {
2186            let Some(participant) = registration.participant.upgrade() else {
2187                continue;
2188            };
2189            let participant_snapshot = (registration.registration.scope == self.active_scope
2190                && self.participates(*id, registration)
2191                && single_participant.is_none_or(|single| single == *id))
2192            .then_some(snapshot)
2193            .flatten()
2194            .map(|mut snapshot| {
2195                snapshot.coverage = self.coverage_for(*id);
2196                snapshot
2197            });
2198            if participant.read(cx).snapshot == participant_snapshot {
2199                continue;
2200            }
2201            participant.update(cx, |state, cx| state.set_snapshot(participant_snapshot, cx));
2202        }
2203    }
2204
2205    fn coverage_for(&self, id: EntityId) -> TextSelectionCoverage {
2206        let Some(anchor) = self.anchor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2207            return TextSelectionCoverage::Bounded;
2208        };
2209        let Some(cursor) = self.cursor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2210            return TextSelectionCoverage::Bounded;
2211        };
2212        if anchor == cursor {
2213            return TextSelectionCoverage::Bounded;
2214        }
2215        let anchor_order = self.participants.borrow()[&anchor]
2216            .registration
2217            .document_order;
2218        let cursor_order = self.participants.borrow()[&cursor]
2219            .registration
2220            .document_order;
2221        if id != anchor && id != cursor {
2222            TextSelectionCoverage::Full
2223        } else if (id == anchor) == (anchor_order < cursor_order) {
2224            TextSelectionCoverage::ToEnd
2225        } else {
2226            TextSelectionCoverage::FromStart
2227        }
2228    }
2229
2230    fn single_participant(&self) -> Option<EntityId> {
2231        let anchor = self.anchor.as_ref()?.entity_id()?;
2232        let cursor = self.cursor.as_ref()?.entity_id()?;
2233        (anchor == cursor).then_some(anchor)
2234    }
2235
2236    fn participates(&self, id: EntityId, registration: &ParticipantRegistration) -> bool {
2237        let Some(anchor) = self.anchor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2238            return false;
2239        };
2240        let Some(cursor) = self.cursor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2241            return false;
2242        };
2243        let Some(anchor_registration) = self.participants.borrow().get(&anchor).cloned() else {
2244            return false;
2245        };
2246        let Some(cursor_registration) = self.participants.borrow().get(&cursor).cloned() else {
2247            return false;
2248        };
2249        let start = anchor_registration
2250            .registration
2251            .document_order
2252            .min(cursor_registration.registration.document_order);
2253        let end = anchor_registration
2254            .registration
2255            .document_order
2256            .max(cursor_registration.registration.document_order);
2257        (start..=end).contains(&registration.registration.document_order)
2258            || id == anchor
2259            || id == cursor
2260    }
2261
2262    fn update_auto_scroll(
2263        &mut self,
2264        position: Point<Pixels>,
2265        window: &Window,
2266        cx: &mut Context<Self>,
2267    ) {
2268        // A finished gesture keeps its anchor for shift-click extension; only
2269        // a live drag may scroll.
2270        if !self.is_selecting {
2271            return;
2272        }
2273        let Some((_, registration)) = self.anchor_registration() else {
2274            return;
2275        };
2276        // Exactly one writer per drag: a participant that scrolls its own
2277        // content is notified directly; anything else gets a synthetic wheel.
2278        if registration.self_scroll {
2279            self.auto_scroll.stop();
2280            self.update_participant_auto_scroll(position, cx);
2281            return;
2282        }
2283        // The content mask is the nearest clipping viewport established by a
2284        // scrollable ancestor. It remains stable as the participant itself
2285        // moves, so selection keeps scrolling the same related region even
2286        // after the anchor text has moved out of view.
2287        let visible_bounds = registration.hitbox.content_mask.bounds;
2288        // Keeps the synthesized wheel event hit-testing inside the mask.
2289        const HIT_TEST_INSET: Pixels = px(1.);
2290        // A collapsed mask leaves an empty clamp range below — stop.
2291        if visible_bounds.size.width < HIT_TEST_INSET * 2.
2292            || visible_bounds.size.height < HIT_TEST_INSET * 2.
2293        {
2294            self.stop_anchor_auto_scroll(cx);
2295            return;
2296        }
2297        let delta = AutoScroll::compute_delta(position.y, visible_bounds);
2298        let event_position = point(
2299            position.x.clamp(
2300                visible_bounds.left() + HIT_TEST_INSET,
2301                visible_bounds.right() - HIT_TEST_INSET,
2302            ),
2303            position.y.clamp(
2304                visible_bounds.top() + HIT_TEST_INSET,
2305                visible_bounds.bottom() - HIT_TEST_INSET,
2306            ),
2307        );
2308        self.auto_scroll.last_drag_position = Some(event_position);
2309        self.auto_scroll_stall = (None, 0);
2310        let window = window.window_handle();
2311        self.auto_scroll.set(delta, cx, move |delta, state, cx| {
2312            let Some(position) = state.auto_scroll.last_drag_position else {
2313                return;
2314            };
2315            // Hold off once the container has shown, over a few ticks, that
2316            // it has nowhere left to go; the next move of the finger asks
2317            // again.
2318            const STALLED_TICKS: u8 = 3;
2319            let geometry = state
2320                .anchor_registration()
2321                .map(|(_, registration)| (registration.bounds, registration.scroll_offset));
2322            let (last, stalled) = &mut state.auto_scroll_stall;
2323            if *last == geometry {
2324                *stalled = stalled.saturating_add(1);
2325                if *stalled >= STALLED_TICKS {
2326                    return;
2327                }
2328            } else {
2329                *last = geometry;
2330                *stalled = 0;
2331            }
2332            let window = window;
2333            cx.defer(move |cx| {
2334                _ = window.update(cx, |_, window, cx| {
2335                    window.dispatch_event(
2336                        ScrollWheelEvent {
2337                            position,
2338                            delta: ScrollDelta::Pixels(point(px(0.), -delta)),
2339                            ..Default::default()
2340                        }
2341                        .to_platform_input(),
2342                        cx,
2343                    );
2344                });
2345            });
2346        });
2347    }
2348
2349    /// Drives the anchor participant's own scrolling, measured against the
2350    /// visible portion of the participant's element bounds.
2351    fn update_participant_auto_scroll(&self, position: Point<Pixels>, cx: &mut App) {
2352        let Some((participant, registration)) = self.anchor_registration() else {
2353            return;
2354        };
2355        let visible_bounds = registration
2356            .bounds
2357            .intersect(&registration.hitbox.content_mask.bounds);
2358        let delta = if visible_bounds.size.width > px(0.) && visible_bounds.size.height > px(0.) {
2359            AutoScroll::compute_delta(position.y, visible_bounds)
2360        } else {
2361            None
2362        };
2363        participant.update(cx, |state, cx| state.set_auto_scroll(delta, cx));
2364    }
2365
2366    fn stop_anchor_auto_scroll(&mut self, cx: &mut App) {
2367        self.auto_scroll.stop();
2368        let Some(participant) = self.anchor_participant() else {
2369            return;
2370        };
2371        participant.update(cx, |state, cx| state.set_auto_scroll(None, cx));
2372    }
2373
2374    /// The live participant owning the anchor of the current gesture.
2375    fn anchor_participant(&self) -> Option<Entity<SelectableTextState>> {
2376        self.anchor
2377            .as_ref()
2378            .filter(|anchor| anchor.inside)?
2379            .participant
2380            .as_ref()?
2381            .upgrade()
2382    }
2383
2384    /// The anchor participant together with its current frame registration.
2385    fn anchor_registration(
2386        &self,
2387    ) -> Option<(Entity<SelectableTextState>, Rc<TextSelectionRegistration>)> {
2388        let participant = self.anchor_participant()?;
2389        let registration = self
2390            .participants
2391            .borrow()
2392            .get(&participant.entity_id())?
2393            .clone();
2394        Some((participant, registration.registration.clone()))
2395    }
2396
2397    fn prune_dead_participants(&mut self) {
2398        self.participants
2399            .get_mut()
2400            .retain(|_, registration| registration.participant.upgrade().is_some());
2401    }
2402}
2403
2404#[derive(Default)]
2405/// Non-owning window locator; retained [`TextSelection`] element state owns
2406/// each live selection entity.
2407struct SelectionStateRegistry(HashMap<gpui::WindowId, WeakEntity<WindowSelectionState>>);
2408
2409impl Global for SelectionStateRegistry {}
2410
2411#[derive(Default)]
2412struct PendingTextSelectionScopes(HashMap<gpui::WindowId, TextSelectionScopeId>);
2413
2414impl Global for PendingTextSelectionScopes {}
2415
2416#[derive(Default)]
2417struct TextSelectionScopeStacks(HashMap<gpui::WindowId, Vec<TextSelectionScopeId>>);
2418
2419impl Global for TextSelectionScopeStacks {}
2420
2421fn push_text_selection_scope(window_id: gpui::WindowId, scope: TextSelectionScopeId, cx: &mut App) {
2422    if !cx.has_global::<TextSelectionScopeStacks>() {
2423        cx.set_global(TextSelectionScopeStacks::default());
2424    }
2425    cx.global_mut::<TextSelectionScopeStacks>()
2426        .0
2427        .entry(window_id)
2428        .or_default()
2429        .push(scope);
2430}
2431
2432fn pop_text_selection_scope(window_id: gpui::WindowId, cx: &mut App) {
2433    let stacks = &mut cx.global_mut::<TextSelectionScopeStacks>().0;
2434    let remove_stack = stacks.get_mut(&window_id).is_some_and(|stack| {
2435        stack.pop();
2436        stack.is_empty()
2437    });
2438    if remove_stack {
2439        stacks.remove(&window_id);
2440    }
2441}
2442
2443fn current_text_selection_scope(
2444    window_id: gpui::WindowId,
2445    cx: &App,
2446) -> Option<TextSelectionScopeId> {
2447    cx.has_global::<TextSelectionScopeStacks>()
2448        .then(|| {
2449            cx.global::<TextSelectionScopeStacks>()
2450                .0
2451                .get(&window_id)
2452                .and_then(|stack| stack.last().copied())
2453        })
2454        .flatten()
2455}
2456
2457fn with_text_selection_scope<T>(
2458    window_id: gpui::WindowId,
2459    scope: TextSelectionScopeId,
2460    cx: &mut App,
2461    callback: impl FnOnce(&mut App) -> T,
2462) -> T {
2463    push_text_selection_scope(window_id, scope, cx);
2464    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(cx)));
2465    pop_text_selection_scope(window_id, cx);
2466    match result {
2467        Ok(result) => result,
2468        Err(payload) => std::panic::resume_unwind(payload),
2469    }
2470}
2471
2472/// Window-level operations for text selection.
2473pub struct TextSelection;
2474
2475impl TextSelection {
2476    /// Returns whether `position` hits selectable text in the active scope.
2477    ///
2478    /// Uses the current frame's registered text geometry and hitboxes without
2479    /// starting or changing a selection. Blank space and text outside the active
2480    /// selection scope do not match. A containing control can yield a long press
2481    /// to the window's text selection before claiming the gesture itself.
2482    /// Call during pointer dispatch with the event's position, so the hitboxes
2483    /// reflect the point being queried. Touch-selection controls do not match.
2484    pub fn is_selectable_at(position: Point<Pixels>, window: &Window, cx: &mut App) -> bool {
2485        live_text_selection_state(window, cx).is_some_and(|state| {
2486            state.update(cx, |state, cx| {
2487                !state.touch.covers(position)
2488                    && state.endpoint(position, Some(window), cx).inside_text
2489            })
2490        })
2491    }
2492
2493    /// Returns the currently selected text in logical document order.
2494    pub fn selected_text(window: &mut Window, cx: &mut App) -> String {
2495        let Some(state) = live_text_selection_state(window, cx) else {
2496            return String::new();
2497        };
2498        let items = state.read(cx).copy_items(cx);
2499        resolve_copy_items(items, cx)
2500    }
2501
2502    /// Returns whether the window has a geometry selection or any participant
2503    /// has an active participant-local selection such as select-all.
2504    pub fn has_selection(window: &mut Window, cx: &mut App) -> bool {
2505        live_text_selection_state(window, cx).is_some_and(|state| state.read(cx).has_selection(cx))
2506    }
2507
2508    /// Clears window selection and every participant's local selection.
2509    pub fn clear(window: &mut Window, cx: &mut App) {
2510        if let Some(state) = live_text_selection_state(window, cx) {
2511            let handlers = state.update(cx, |state, cx| state.clear_state(cx));
2512            dispatch_clear_handlers(handlers, cx);
2513        }
2514    }
2515
2516    /// Clears selection for a known window identifier.
2517    ///
2518    /// Prefer [`Self::clear`] when a window reference is available. This
2519    /// narrow entry point supports hosts retiring deprecated window wrappers.
2520    pub fn clear_for_window(window_id: gpui::WindowId, cx: &mut App) {
2521        clear_window_text_selection(window_id, cx);
2522    }
2523
2524    /// Ends the current drag while leaving its selection visible.
2525    pub fn end(window: &mut Window, cx: &mut App) {
2526        if let Some(state) = live_text_selection_state(window, cx) {
2527            state.update(cx, |state, cx| state.end(cx));
2528        }
2529    }
2530
2531    /// Calls `callback` whenever the touch selection changes: it appears, its
2532    /// handles move, its menu opens or closes, or it goes away. Whoever draws
2533    /// the handles and the menu re-renders from here.
2534    pub fn observe_touch_selection(
2535        window: &Window,
2536        cx: &mut App,
2537        callback: impl Fn(&mut App) + 'static,
2538    ) -> Subscription {
2539        let state = WindowSelectionState::acquire(window.window_handle().window_id(), cx);
2540        cx.observe(&state, move |_, cx| callback(cx))
2541    }
2542
2543    /// Returns the selection a long press made, laid out for its handles and
2544    /// edit menu, or `None` when the selection was made with a pointer.
2545    pub fn touch_selection(window: &Window, cx: &App) -> Option<TouchSelectionSnapshot> {
2546        WindowSelectionState::existing(window, cx)?
2547            .read(cx)
2548            .touch_selection()
2549    }
2550
2551    /// Records where a touch handle or the edit menu is painted this frame, so
2552    /// that pressing it does not clear the selection it belongs to. Call from
2553    /// paint, every frame the surface is shown.
2554    pub fn register_touch_ui(bounds: Bounds<Pixels>, window: &Window, cx: &mut App) {
2555        if let Some(state) = WindowSelectionState::existing(window, cx) {
2556            state.update(cx, |state, _| state.register_touch_ui(bounds));
2557        }
2558    }
2559
2560    /// Closes the edit menu and keeps the selection with its handles.
2561    pub fn close_edit_menu(window: &mut Window, cx: &mut App) {
2562        if let Some(state) = live_text_selection_state(window, cx) {
2563            state.update(cx, |state, cx| state.close_edit_menu(cx));
2564        }
2565    }
2566
2567    /// Selects all of the text the touch selection started in, keeping the
2568    /// handles and the edit menu over the result.
2569    pub fn select_all(window: &mut Window, cx: &mut App) {
2570        if let Some(state) = live_text_selection_state(window, cx) {
2571            state.update(cx, |state, cx| state.select_all_touched(cx));
2572        }
2573    }
2574
2575    /// Starts dragging one end of the touch selection from `finger`. The other
2576    /// end stays; the menu closes until [`Self::end_edge_drag`].
2577    pub fn begin_edge_drag(
2578        edge: SelectionEdge,
2579        finger: Point<Pixels>,
2580        window: &mut Window,
2581        cx: &mut App,
2582    ) {
2583        if let Some(state) = live_text_selection_state(window, cx) {
2584            state.update(cx, |state, cx| {
2585                state.begin_edge_drag(edge, finger, window, cx)
2586            });
2587            WindowSelectionState::resolve_content_keys(&state, cx);
2588        }
2589    }
2590
2591    /// Moves the dragged end to the text under `finger`.
2592    pub fn update_edge_drag(finger: Point<Pixels>, window: &mut Window, cx: &mut App) {
2593        if let Some(state) = live_text_selection_state(window, cx) {
2594            state.update(cx, |state, cx| state.update_edge_drag(finger, window, cx));
2595            WindowSelectionState::resolve_content_keys(&state, cx);
2596        }
2597    }
2598
2599    /// Ends the handle drag and reopens the edit menu over the result.
2600    pub fn end_edge_drag(window: &mut Window, cx: &mut App) {
2601        if let Some(state) = live_text_selection_state(window, cx) {
2602            state.update(cx, |state, cx| state.end_edge_drag(cx));
2603        }
2604    }
2605
2606    /// Activates the opaque selection scope for this window.
2607    pub fn activate_scope(scope: TextSelectionScopeId, window: &mut Window, cx: &mut App) {
2608        let Some(state) = WindowSelectionState::existing(window, cx) else {
2609            if !cx.has_global::<PendingTextSelectionScopes>() {
2610                cx.set_global(PendingTextSelectionScopes::default());
2611            }
2612            cx.global_mut::<PendingTextSelectionScopes>()
2613                .0
2614                .insert(window.window_handle().window_id(), scope);
2615            return;
2616        };
2617        if state.read(cx).active_scope == scope {
2618            return;
2619        }
2620        let handlers = state.update(cx, |state, cx| state.set_active_scope_state(scope, cx));
2621        dispatch_clear_handlers(handlers, cx);
2622    }
2623}
2624
2625/// A zero-sized root layer which enables text selection for a window.
2626///
2627/// Mount one as the root's first child. Its stable `"window-text-selection"`
2628/// element identity retains the window-local selection entity across frames.
2629pub struct TextSelectionLayer;
2630
2631pub(crate) fn text_selection_scope(
2632    scope: TextSelectionScopeId,
2633    element: impl IntoElement,
2634) -> impl IntoElement {
2635    TextSelectionScopeMarker {
2636        scope,
2637        element: element.into_element(),
2638    }
2639}
2640
2641struct TextSelectionScopeMarker<E> {
2642    scope: TextSelectionScopeId,
2643    element: E,
2644}
2645
2646impl<E: Element> IntoElement for TextSelectionScopeMarker<E> {
2647    type Element = Self;
2648
2649    fn into_element(self) -> Self::Element {
2650        self
2651    }
2652}
2653
2654impl<E: Element> Element for TextSelectionScopeMarker<E> {
2655    type RequestLayoutState = E::RequestLayoutState;
2656    type PrepaintState = E::PrepaintState;
2657
2658    fn id(&self) -> Option<ElementId> {
2659        self.element.id()
2660    }
2661
2662    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
2663        self.element.source_location()
2664    }
2665
2666    fn request_layout(
2667        &mut self,
2668        id: Option<&GlobalElementId>,
2669        inspector_id: Option<&InspectorElementId>,
2670        window: &mut Window,
2671        cx: &mut App,
2672    ) -> (LayoutId, Self::RequestLayoutState) {
2673        let window_id = window.window_handle().window_id();
2674        with_text_selection_scope(window_id, self.scope, cx, |cx| {
2675            self.element.request_layout(id, inspector_id, window, cx)
2676        })
2677    }
2678
2679    fn prepaint(
2680        &mut self,
2681        id: Option<&GlobalElementId>,
2682        inspector_id: Option<&InspectorElementId>,
2683        bounds: Bounds<Pixels>,
2684        request_layout: &mut Self::RequestLayoutState,
2685        window: &mut Window,
2686        cx: &mut App,
2687    ) -> Self::PrepaintState {
2688        let window_id = window.window_handle().window_id();
2689        with_text_selection_scope(window_id, self.scope, cx, |cx| {
2690            self.element
2691                .prepaint(id, inspector_id, bounds, request_layout, window, cx)
2692        })
2693    }
2694
2695    fn paint(
2696        &mut self,
2697        id: Option<&GlobalElementId>,
2698        inspector_id: Option<&InspectorElementId>,
2699        bounds: Bounds<Pixels>,
2700        request_layout: &mut Self::RequestLayoutState,
2701        prepaint: &mut Self::PrepaintState,
2702        window: &mut Window,
2703        cx: &mut App,
2704    ) {
2705        let window_id = window.window_handle().window_id();
2706        with_text_selection_scope(window_id, self.scope, cx, |cx| {
2707            self.element.paint(
2708                id,
2709                inspector_id,
2710                bounds,
2711                request_layout,
2712                prepaint,
2713                window,
2714                cx,
2715            );
2716        });
2717    }
2718}
2719
2720#[doc(hidden)]
2721pub struct TextSelectionLayerPrepaintState(Entity<WindowSelectionState>);
2722
2723impl IntoElement for TextSelectionLayer {
2724    type Element = Self;
2725
2726    fn into_element(self) -> Self::Element {
2727        self
2728    }
2729}
2730
2731impl Element for TextSelectionLayer {
2732    type RequestLayoutState = ();
2733    type PrepaintState = TextSelectionLayerPrepaintState;
2734
2735    fn id(&self) -> Option<ElementId> {
2736        Some("window-text-selection".into())
2737    }
2738
2739    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
2740        None
2741    }
2742
2743    fn request_layout(
2744        &mut self,
2745        _: Option<&GlobalElementId>,
2746        _: Option<&InspectorElementId>,
2747        window: &mut Window,
2748        cx: &mut App,
2749    ) -> (LayoutId, Self::RequestLayoutState) {
2750        (window.request_layout(Style::default(), [], cx), ())
2751    }
2752
2753    fn prepaint(
2754        &mut self,
2755        global_id: Option<&GlobalElementId>,
2756        _: Option<&InspectorElementId>,
2757        _: Bounds<Pixels>,
2758        _: &mut Self::RequestLayoutState,
2759        window: &mut Window,
2760        cx: &mut App,
2761    ) -> Self::PrepaintState {
2762        // Automatic participant order is paint order within this frame. Keep
2763        // this lifecycle in base so base-only applications do not need a
2764        // separate root component to reset it. Otherwise, registering the
2765        // first of two selected TextViews temporarily reverses their order
2766        // against the previous frame and alternates coverage forever.
2767        GlobalState::init(cx);
2768        GlobalState::global(cx).begin_selection_frame();
2769        let state = retain_text_selection_state(global_id, window, cx);
2770        // The handles and the menu register again as they paint this frame.
2771        if state.read(cx).touch.painted_any() {
2772            state.update(cx, |state, _| state.touch.begin_frame());
2773        }
2774        TextSelectionLayerPrepaintState(state)
2775    }
2776
2777    fn paint(
2778        &mut self,
2779        _: Option<&GlobalElementId>,
2780        _: Option<&InspectorElementId>,
2781        _: Bounds<Pixels>,
2782        _: &mut Self::RequestLayoutState,
2783        state: &mut Self::PrepaintState,
2784        window: &mut Window,
2785        cx: &mut App,
2786    ) {
2787        paint_text_selection(&state.0, window, cx);
2788    }
2789}
2790
2791fn retain_text_selection_state(
2792    global_id: Option<&GlobalElementId>,
2793    window: &mut Window,
2794    cx: &mut App,
2795) -> Entity<WindowSelectionState> {
2796    let window_id = window.window_handle().window_id();
2797    let state = window.with_element_state::<Entity<WindowSelectionState>, _>(
2798        global_id.expect("TextSelection has a stable element id"),
2799        |retained, _| {
2800            let state = retained.unwrap_or_else(|| WindowSelectionState::acquire(window_id, cx));
2801            (state.clone(), state)
2802        },
2803    );
2804    // Registered once: writing the global again every frame would change it,
2805    // and every view that reads it, on every frame.
2806    let registered = cx.has_global::<SelectionStateRegistry>()
2807        && cx
2808            .global::<SelectionStateRegistry>()
2809            .0
2810            .get(&window_id)
2811            .is_some_and(|registered| registered.entity_id() == state.entity_id());
2812    if !registered {
2813        if !cx.has_global::<SelectionStateRegistry>() {
2814            cx.set_global(SelectionStateRegistry::default());
2815        }
2816        cx.global_mut::<SelectionStateRegistry>()
2817            .0
2818            .insert(window_id, state.downgrade());
2819    }
2820    state
2821}
2822
2823fn paint_text_selection(state: &Entity<WindowSelectionState>, window: &mut Window, cx: &mut App) {
2824    if state.read(cx).schedule_finish_frame() {
2825        let state = state.downgrade();
2826        window.defer(cx, move |window, cx| {
2827            let Some(state) = state.upgrade() else {
2828                return;
2829            };
2830            if state.read(cx).finish_frame_quietly(cx) {
2831                return;
2832            }
2833            let handlers = state.update(cx, |state, cx| state.finish_frame(cx));
2834            dispatch_clear_handlers(handlers, cx);
2835            // Direct participant scrolling produces no wheel event. Refresh
2836            // the held cursor after paint registers the new scroll geometry.
2837            let refresh_cursor = state.update(cx, |state, cx| {
2838                if std::mem::take(&mut state.refresh_held_cursor) && state.is_selecting {
2839                    // A handle drag holds the finger off the text; keep the
2840                    // same offset, or the cursor would hop between the two.
2841                    let position = window.mouse_position();
2842                    let position = state
2843                        .touch
2844                        .drag
2845                        .map_or(position, |drag| drag.text_position(position));
2846                    state.update_in_window(position, window, cx);
2847                    true
2848                } else {
2849                    false
2850                }
2851            });
2852            if refresh_cursor {
2853                WindowSelectionState::resolve_content_keys(&state, cx);
2854            }
2855        });
2856    }
2857
2858    let mouse_down_state = state.downgrade();
2859    window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
2860        if event.button != MouseButton::Left {
2861            return;
2862        }
2863        let Some(state) = mouse_down_state.upgrade() else {
2864            return;
2865        };
2866        // A press on a handle or on the edit menu acts on the selection; it
2867        // must not clear it.
2868        if state.read(cx).touch.covers(event.position) {
2869            return;
2870        }
2871        if phase.capture() {
2872            GlobalState::init(cx);
2873            GlobalState::reset_text_selection_suppression(cx);
2874            let handlers = state.update(cx, |state, cx| {
2875                if state.mouse_down_prepared {
2876                    return Vec::new();
2877                }
2878                state.mouse_down_prepared = true;
2879                state.prepare_for_mouse_down(event.click_count == 1 && event.modifiers.shift, cx)
2880            });
2881            dispatch_clear_handlers(handlers, cx);
2882        } else if event.click_count == 1 {
2883            if GlobalState::is_text_selection_suppressed(cx) {
2884                state.update(cx, |state, _| state.pending_extension_anchor = None);
2885                return;
2886            }
2887            state.update(cx, |state, cx| {
2888                if !state.is_selecting {
2889                    state.begin_in_window(event.position, event.modifiers.shift, window, cx)
2890                }
2891            });
2892            WindowSelectionState::resolve_content_keys(&state, cx);
2893        } else if event.click_count >= 2 {
2894            if GlobalState::is_text_selection_suppressed(cx) {
2895                return;
2896            }
2897            let touch = GlobalState::is_touch_press(cx);
2898            state.update(cx, |state, cx| {
2899                state.select_at(event.position, event.click_count, window, cx);
2900                // A double tap is touch's other way to select a word, and it
2901                // gets the handles and the menu like a long press does.
2902                if touch {
2903                    state.keep_touch_selection(cx);
2904                }
2905            });
2906            WindowSelectionState::resolve_content_keys(&state, cx);
2907        }
2908    });
2909
2910    // Every touch is offered as a drag first; that is how a tap's mouse
2911    // events are later told apart from a mouse's.
2912    window.on_mouse_event(move |event: &TouchDragEvent, phase, _, cx| {
2913        if phase.capture() && event.phase == TouchPhase::Started {
2914            GlobalState::note_touch(cx);
2915        }
2916    });
2917
2918    // Touch panning remains scrolling until a long press actually hits text.
2919    // Claiming the gesture keeps subsequent moves out of the pan recognizer.
2920    let long_press_state = state.downgrade();
2921    window.on_mouse_event(move |event: &LongPressEvent, phase, window, cx| {
2922        if !phase.bubble() {
2923            return;
2924        }
2925        let Some(state) = long_press_state.upgrade() else {
2926            return;
2927        };
2928        if event.phase == TouchPhase::Started {
2929            if window.default_prevented()
2930                || state.read(cx).touch.covers(event.start_position)
2931                || !state.update(cx, |state, cx| {
2932                    state
2933                        .endpoint(event.start_position, Some(window), cx)
2934                        .inside_text
2935                })
2936            {
2937                return;
2938            }
2939            GlobalState::init(cx);
2940            GlobalState::reset_text_selection_suppression(cx);
2941            let handlers = state.update(cx, |state, cx| state.prepare_for_mouse_down(false, cx));
2942            dispatch_clear_handlers(handlers, cx);
2943            let selected = state.update(cx, |state, cx| {
2944                state.select_at(event.start_position, 2, window, cx);
2945                state.anchor.is_some()
2946            });
2947            if !selected {
2948                return;
2949            }
2950            window.capture_long_press(&state);
2951        } else if !window.has_long_press_capture(&state) {
2952            return;
2953        } else {
2954            state.update(cx, |state, cx| match event.phase {
2955                TouchPhase::Moved => {
2956                    state.is_selecting = true;
2957                    state.update_in_window(event.position, window, cx);
2958                }
2959                TouchPhase::Ended | TouchPhase::Cancelled => {
2960                    state.end(cx);
2961                    // The finger is up; the selection it made gets its
2962                    // handles and the edit menu.
2963                    state.keep_touch_selection(cx);
2964                }
2965                _ => {}
2966            });
2967        }
2968        window.prevent_default();
2969        cx.stop_propagation();
2970        WindowSelectionState::resolve_content_keys(&state, cx);
2971    });
2972
2973    // A handle drag in progress follows the finger or the pointer wherever
2974    // it goes, whether or not the handle it took is laid out this frame.
2975    let drag_state = state.downgrade();
2976    window.on_mouse_event(move |event: &TouchDragEvent, phase, window, cx| {
2977        if !phase.bubble() || event.phase == TouchPhase::Started {
2978            return;
2979        }
2980        let Some(state) = drag_state.upgrade() else {
2981            return;
2982        };
2983        if state.read(cx).touch.drag.is_none() {
2984            return;
2985        }
2986        cx.stop_propagation();
2987        state.update(cx, |state, cx| match event.phase {
2988            TouchPhase::Moved => state.update_edge_drag(event.position, window, cx),
2989            _ => state.end_edge_drag(cx),
2990        });
2991        WindowSelectionState::resolve_content_keys(&state, cx);
2992    });
2993    let drag_state = state.downgrade();
2994    window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
2995        if phase.bubble()
2996            && event.pressed_button == Some(MouseButton::Left)
2997            && let Some(state) = drag_state.upgrade()
2998            && state.read(cx).touch.drag.is_some()
2999        {
3000            state.update(cx, |state, cx| {
3001                state.update_edge_drag(event.position, window, cx)
3002            });
3003            WindowSelectionState::resolve_content_keys(&state, cx);
3004        }
3005    });
3006    let drag_state = state.downgrade();
3007    window.on_mouse_event(move |event: &MouseUpEvent, phase, _, cx| {
3008        if phase.bubble()
3009            && event.button == MouseButton::Left
3010            && let Some(state) = drag_state.upgrade()
3011        {
3012            state.update(cx, |state, cx| state.end_edge_drag(cx));
3013        }
3014    });
3015
3016    let mouse_move_state = state.downgrade();
3017    window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
3018        if phase.bubble()
3019            && let Some(state) = mouse_move_state.upgrade()
3020            // Moving the pointer only moves a selection being made.
3021            && state.read(cx).is_selecting
3022        {
3023            state.update(cx, |state, cx| {
3024                // A handle drag maps the pointer through the handle's offset;
3025                // the raw pointer must not fight it.
3026                if state.touch.drag.is_some() {
3027                    return;
3028                }
3029                state.update_in_window(event.position, window, cx)
3030            });
3031            WindowSelectionState::resolve_content_keys(&state, cx);
3032        }
3033    });
3034
3035    let mouse_up_state = state.downgrade();
3036    window.on_mouse_event(move |_: &MouseUpEvent, phase, _, cx| {
3037        if phase.bubble()
3038            && let Some(state) = mouse_up_state.upgrade()
3039        {
3040            state.update(cx, |state, cx| {
3041                state.mouse_down_prepared = false;
3042                state.end(cx)
3043            });
3044        }
3045    });
3046
3047    let scroll_state = state.downgrade();
3048    window.on_mouse_event(move |event: &ScrollWheelEvent, phase, window, cx| {
3049        let Some(state) = scroll_state.upgrade() else {
3050            return;
3051        };
3052        // On capture, before a scroll container can stop the event: one
3053        // stretched past its end swallows the whole stream, the lift
3054        // included, and the menu would never come back.
3055        if phase.capture() {
3056            if state.read(cx).touch.active {
3057                state.update(cx, |state, cx| {
3058                    state.edit_menu_on_scroll(event.touch_phase, cx)
3059                });
3060            }
3061            return;
3062        }
3063        // A scroll only moves a selection being made under the pointer.
3064        if phase.bubble() && state.read(cx).is_selecting {
3065            let position = window.mouse_position();
3066            state.update(cx, |state, cx| {
3067                // A handle drag holds the finger off the text; keep its offset.
3068                let position = state
3069                    .touch
3070                    .drag
3071                    .map_or(position, |drag| drag.text_position(position));
3072                state.update_in_window(position, window, cx)
3073            });
3074            WindowSelectionState::resolve_content_keys(&state, cx);
3075        }
3076    });
3077}
3078
3079fn live_text_selection_state(
3080    window: &Window,
3081    cx: &mut App,
3082) -> Option<Entity<WindowSelectionState>> {
3083    WindowSelectionState::existing(window, cx)
3084}
3085
3086pub(crate) fn clear_window_text_selection(window_id: gpui::WindowId, cx: &mut App) {
3087    if !cx.has_global::<SelectionStateRegistry>() {
3088        return;
3089    }
3090    let Some(state) = cx
3091        .global::<SelectionStateRegistry>()
3092        .0
3093        .get(&window_id)
3094        .and_then(WeakEntity::upgrade)
3095    else {
3096        return;
3097    };
3098    let handlers = state.update(cx, |state, cx| state.clear_state(cx));
3099    dispatch_clear_handlers(handlers, cx);
3100}
3101
3102#[cfg(test)]
3103mod tests {
3104    use super::*;
3105    use crate::ElementExt as _;
3106    use gpui::{
3107        Bounds, ContentMask, Context, Hitbox, HitboxBehavior, HitboxId, InteractiveElement as _,
3108        IntoElement, ParentElement as _, Render, SharedString, Styled as _, StyledText,
3109        TestAppContext, TextLayout, Window, div, point, prelude::FluentBuilder as _, px, size,
3110    };
3111    use std::{
3112        cell::{Cell, RefCell},
3113        rc::Rc,
3114    };
3115
3116    struct FakeParticipant {
3117        selection: TextSelectionHandle,
3118    }
3119
3120    struct WindowSelectionView {
3121        selection: TextSelectionHandle,
3122    }
3123
3124    struct SelectionElementOnlyView;
3125    struct ToggleSelectionElementView {
3126        enabled: bool,
3127        selection: TextSelectionHandle,
3128    }
3129
3130    struct DoubleSelectionElementView {
3131        selection: TextSelectionHandle,
3132    }
3133
3134    struct WindowOwnedSelectionView {
3135        selection: TextSelectionHandle,
3136    }
3137
3138    struct FirstFrameScopedSelectionView {
3139        selection: TextSelectionHandle,
3140    }
3141
3142    struct PlainRunLayoutView {
3143        texts: Vec<SharedString>,
3144        layouts: Vec<TextLayout>,
3145    }
3146
3147    impl Render for WindowSelectionView {
3148        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3149            div()
3150        }
3151    }
3152
3153    impl Render for SelectionElementOnlyView {
3154        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3155            div()
3156                .size_full()
3157                .child(TextSelectionLayer)
3158                .child(
3159                    div()
3160                        .size_full()
3161                        .on_mouse_down(MouseButton::Left, |_, _, cx| {
3162                            GlobalState::suppress_text_selection(cx);
3163                        }),
3164                )
3165        }
3166    }
3167
3168    impl Render for ToggleSelectionElementView {
3169        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3170            let selection = self.selection.clone();
3171            div().when(self.enabled, |this| {
3172                this.child(TextSelectionLayer)
3173                    .child(div().size_full().on_prepaint(move |bounds, window, cx| {
3174                        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3175                        selection.register(
3176                            TextSelectionRegistration::new(hitbox, bounds)
3177                                .with_text_bounds(vec![bounds]),
3178                            window,
3179                            cx,
3180                        );
3181                    }))
3182            })
3183        }
3184    }
3185
3186    impl Render for DoubleSelectionElementView {
3187        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3188            let selection = self.selection.clone();
3189            div()
3190                .size_full()
3191                .child(TextSelectionLayer)
3192                .child(TextSelectionLayer)
3193                .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 WindowOwnedSelectionView {
3206        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3207            let selection = self.selection.clone();
3208            div()
3209                .size_full()
3210                .child(TextSelectionLayer)
3211                .child(div().size_full().on_prepaint(move |bounds, window, cx| {
3212                    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3213                    selection.register(
3214                        TextSelectionRegistration::new(hitbox, bounds)
3215                            .with_text_bounds(vec![bounds]),
3216                        window,
3217                        cx,
3218                    );
3219                }))
3220        }
3221    }
3222
3223    impl Render for FirstFrameScopedSelectionView {
3224        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3225            let scope = TextSelectionScopeId::from_raw(23);
3226            TextSelection::activate_scope(scope, window, cx);
3227            let selection = self.selection.clone();
3228
3229            div().child(TextSelectionLayer).child(
3230                div()
3231                    .size_full()
3232                    .on_prepaint(move |bounds, window, cx| {
3233                        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3234                        selection.register(
3235                            TextSelectionRegistration::new(hitbox, bounds)
3236                                .with_text_bounds(vec![bounds]),
3237                            window,
3238                            cx,
3239                        );
3240                    })
3241                    .text_selection_scope(scope),
3242            )
3243        }
3244    }
3245
3246    impl Render for PlainRunLayoutView {
3247        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3248            self.layouts.clear();
3249            let children = self
3250                .texts
3251                .iter()
3252                .enumerate()
3253                .map(|(index, text)| {
3254                    let text = StyledText::new(text.clone());
3255                    self.layouts.push(text.layout().clone());
3256                    div().absolute().top(px(index as f32 * 40.)).child(text)
3257                })
3258                .collect::<Vec<_>>();
3259            div().size_full().children(children)
3260        }
3261    }
3262
3263    impl FakeParticipant {
3264        fn new(text: &str, cx: &mut gpui::App) -> Self {
3265            let selection = TextSelectionHandle::new(text, cx);
3266            Self { selection }
3267        }
3268
3269        fn register(
3270            &self,
3271            selection_state: &mut WindowSelectionState,
3272            y: f32,
3273            scope: TextSelectionScopeId,
3274            document_order: u64,
3275            cx: &mut gpui::App,
3276        ) {
3277            let bounds = Bounds::new(point(px(0.), px(y)), size(px(100.), px(10.)));
3278            selection_state.register_participant(
3279                self.selection.clone(),
3280                TextSelectionRegistration::new(
3281                    Hitbox {
3282                        id: HitboxId::placeholder(),
3283                        bounds,
3284                        content_mask: ContentMask { bounds },
3285                        behavior: HitboxBehavior::Normal,
3286                    },
3287                    bounds,
3288                )
3289                .with_scope(scope)
3290                .with_document_order(document_order)
3291                .with_text_bounds(vec![bounds]),
3292                cx,
3293            );
3294        }
3295    }
3296
3297    fn laid_out_runs(texts: &[&str], cx: &mut TestAppContext) -> Vec<(SharedString, TextLayout)> {
3298        let texts = texts
3299            .iter()
3300            .map(|text| SharedString::from(*text))
3301            .collect::<Vec<_>>();
3302        let view = cx.add_window({
3303            let texts = texts.clone();
3304            move |_, _| PlainRunLayoutView {
3305                texts,
3306                layouts: Vec::new(),
3307            }
3308        });
3309        cx.update_window(*view, |_, window, cx| {
3310            let _ = window.draw(cx);
3311        })
3312        .unwrap();
3313        let layouts = cx.update(|cx| view.read(cx).unwrap().layouts.clone());
3314        texts.into_iter().zip(layouts).collect()
3315    }
3316
3317    fn plain_snapshot(anchor: Point<Pixels>, cursor: Point<Pixels>) -> TextSelectionSnapshot {
3318        TextSelectionSnapshot::new(
3319            TextSelectionEndpoint::new(None, anchor),
3320            TextSelectionEndpoint::new(None, cursor),
3321        )
3322        .with_window_points(Some(TextSelectionWindowPoints { anchor, cursor }))
3323    }
3324
3325    #[gpui::test]
3326    fn scope_stack_is_cleaned_after_panicking_subtree(cx: &mut TestAppContext) {
3327        let window_id = {
3328            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3329            window_cx.update(|window, _| window.window_handle().window_id())
3330        };
3331        let scope = TextSelectionScopeId::from_raw(41);
3332
3333        cx.update(|cx| {
3334            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3335                with_text_selection_scope(window_id, scope, cx, |_| panic!("subtree failed"));
3336            }));
3337
3338            assert!(result.is_err());
3339            assert_eq!(current_text_selection_scope(window_id, cx), None);
3340        });
3341    }
3342
3343    #[gpui::test]
3344    fn reentrant_scope_from_one_window_does_not_pollute_another(cx: &mut TestAppContext) {
3345        let first_window_id = {
3346            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3347            window_cx.update(|window, _| window.window_handle().window_id())
3348        };
3349        let second_window_id = {
3350            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3351            window_cx.update(|window, _| window.window_handle().window_id())
3352        };
3353        let scope = TextSelectionScopeId::from_raw(42);
3354
3355        cx.update(|cx| {
3356            with_text_selection_scope(first_window_id, scope, cx, |cx| {
3357                assert_eq!(current_text_selection_scope(second_window_id, cx), None);
3358                assert_eq!(
3359                    current_text_selection_scope(first_window_id, cx),
3360                    Some(scope)
3361                );
3362            });
3363        });
3364    }
3365
3366    #[gpui::test]
3367    fn selection_callback_can_reenter_its_selection_state(cx: &mut TestAppContext) {
3368        let called = Rc::new(Cell::new(false));
3369        let called_from_callback = called.clone();
3370        let (selection_state, participant) = cx.update(|cx| {
3371            let selection_state = cx.new(|_| WindowSelectionState::default());
3372            let selection_state_for_callback = selection_state.clone();
3373            let participant = FakeParticipant::new("participant", cx);
3374            participant
3375                .selection
3376                .subscribe(
3377                    move |event, cx| {
3378                        if matches!(event, TextSelectionEvent::SelectionChanged(Some(_))) {
3379                            selection_state_for_callback
3380                                .update(cx, |_, _| called_from_callback.set(true));
3381                        }
3382                    },
3383                    cx,
3384                )
3385                .detach();
3386            (selection_state, participant)
3387        });
3388        cx.run_until_parked();
3389        cx.update(|cx| {
3390            selection_state.update(cx, |selection_state, cx| {
3391                participant.register(selection_state, 0., TextSelectionScopeId::default(), 0, cx);
3392                selection_state.begin(point(px(1.), px(1.)), false, cx);
3393                selection_state.update(point(px(20.), px(1.)), cx);
3394            });
3395        });
3396        cx.run_until_parked();
3397        assert!(called.get());
3398    }
3399
3400    #[gpui::test]
3401    fn selection_events_preserve_snapshot_then_clear_order(cx: &mut TestAppContext) {
3402        let observed = Rc::new(RefCell::new(Vec::new()));
3403        let observed_for_callback = observed.clone();
3404        let selection = cx.update(|cx| {
3405            let selection = TextSelectionHandle::new("selection", cx);
3406            selection
3407                .subscribe(
3408                    move |event, _| {
3409                        if let TextSelectionEvent::SelectionChanged(snapshot) = event {
3410                            observed_for_callback.borrow_mut().push(snapshot.is_some());
3411                        }
3412                    },
3413                    cx,
3414                )
3415                .detach();
3416            selection
3417        });
3418        cx.run_until_parked();
3419        cx.update(|cx| {
3420            selection.0.update(cx, |state, cx| {
3421                state.set_snapshot(
3422                    Some(plain_snapshot(point(px(1.), px(1.)), point(px(8.), px(1.)))),
3423                    cx,
3424                );
3425                state.clear_state(cx);
3426            });
3427        });
3428        cx.run_until_parked();
3429        assert_eq!(&*observed.borrow(), &[true, false]);
3430    }
3431
3432    fn text_run(order: u64, text: SharedString, layout: TextLayout) -> TextSelectionRun {
3433        let bounds = layout.bounds();
3434        TextSelectionRun::new(text, layout, bounds).with_document_order(order)
3435    }
3436
3437    #[gpui::test]
3438    fn public_selection_data_uses_builders_and_readers(cx: &mut TestAppContext) {
3439        let bounds = Bounds::new(point(px(1.), px(2.)), size(px(30.), px(10.)));
3440        let hitbox = Hitbox {
3441            id: HitboxId::placeholder(),
3442            bounds,
3443            content_mask: ContentMask { bounds },
3444            behavior: HitboxBehavior::Normal,
3445        };
3446        let scope = TextSelectionScopeId::from_raw(7);
3447        let endpoint = TextSelectionEndpoint::new(None, bounds.origin)
3448            .with_content_key(TextSelectionContentKey::new(11));
3449        let snapshot = TextSelectionSnapshot::new(endpoint, endpoint)
3450            .with_selecting(true)
3451            .with_window_points(Some(TextSelectionWindowPoints {
3452                anchor: bounds.origin,
3453                cursor: bounds.bottom_right(),
3454            }))
3455            .with_coverage(TextSelectionCoverage::Full);
3456        let registration = TextSelectionRegistration::new(hitbox, bounds)
3457            .with_scroll_offset(point(px(3.), px(4.)))
3458            .with_scope(scope)
3459            .with_document_order(9)
3460            .with_text_bounds(vec![bounds]);
3461
3462        assert_eq!(endpoint.entity_id(), None);
3463        assert_eq!(endpoint.content_point(), bounds.origin);
3464        assert_eq!(
3465            endpoint.content_key(),
3466            Some(TextSelectionContentKey::new(11))
3467        );
3468        assert_eq!(snapshot.anchor(), endpoint);
3469        assert_eq!(snapshot.cursor(), endpoint);
3470        assert!(snapshot.is_selecting());
3471        assert_eq!(snapshot.coverage(), TextSelectionCoverage::Full);
3472        assert_eq!(
3473            snapshot.window_points(),
3474            Some(TextSelectionWindowPoints {
3475                anchor: bounds.origin,
3476                cursor: bounds.bottom_right(),
3477            })
3478        );
3479        assert_eq!(registration.bounds(), bounds);
3480        assert_eq!(registration.scroll_offset(), point(px(3.), px(4.)));
3481        assert_eq!(registration.scope(), scope);
3482        assert_eq!(registration.document_order(), 9);
3483        assert_eq!(registration.text_bounds(), &[bounds]);
3484
3485        let (text, layout) = laid_out_runs(&["aé"], cx).pop().unwrap();
3486        let text_run = TextSelectionRun::new(text.clone(), layout.clone(), layout.bounds())
3487            .with_document_order(3);
3488        assert_eq!(text_run.document_order(), 3);
3489        assert_eq!(text_run.text(), &text);
3490        assert_eq!(text_run.layout().len(), layout.len());
3491        assert_eq!(text_run.bounds(), layout.bounds());
3492
3493        let projection = TextSelectionProjection {
3494            ranges: vec![Some(1..3)],
3495            is_active: true,
3496        };
3497        assert_eq!(projection.ranges(), &[Some(1..3)]);
3498        assert!(projection.is_active());
3499    }
3500
3501    #[gpui::test]
3502    fn selection_handle_is_the_public_adapter_seam(cx: &mut TestAppContext) {
3503        let selected = Rc::new(Cell::new(false));
3504        let selected_from_callback = selected.clone();
3505        cx.update(|cx| {
3506            let selection = TextSelectionHandle::new("initial", cx);
3507            let entity_id = selection.entity_id();
3508            selection.set_fallback_copy_text("updated", cx);
3509            selection.set_local_selection(true, cx);
3510            selection
3511                .subscribe(
3512                    move |event, _| {
3513                        if let TextSelectionEvent::SelectionChanged(snapshot) = event {
3514                            selected_from_callback.set(snapshot.is_some());
3515                        }
3516                    },
3517                    cx,
3518                )
3519                .detach();
3520            selection.focus_with(|_, _| {}, cx);
3521            selection.copy_with(|_| "copied".to_string(), cx);
3522            selection.resolve_content_key_with(|_, _| Some(TextSelectionContentKey::new(3)), cx);
3523
3524            assert_eq!(selection.entity_id(), entity_id);
3525            assert_eq!(selection.snapshot(cx), None);
3526            assert_eq!(
3527                selection.update_runs(&[], cx),
3528                TextSelectionProjection::default()
3529            );
3530        });
3531        assert!(!selected.get());
3532    }
3533
3534    #[gpui::test]
3535    fn selection_handle_can_subscribe_its_window_to_refresh(cx: &mut TestAppContext) {
3536        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
3537            selection: TextSelectionHandle::new("refresh", cx),
3538        });
3539        cx.update(|window, cx| {
3540            let selection = TextSelectionHandle::new("refresh", cx);
3541            selection.refresh_window_on_change(window, cx).detach();
3542        });
3543    }
3544
3545    #[gpui::test]
3546    fn plain_projection_preserves_forward_reversed_and_unicode_ranges(cx: &mut TestAppContext) {
3547        let (text, layout) = laid_out_runs(&["aé🙂z"], cx).pop().unwrap();
3548        let run = text_run(0, text, layout.clone());
3549        let start = layout.position_for_index(1).unwrap();
3550        let end = layout.position_for_index(7).unwrap();
3551
3552        let forward = project_ranges(Some(plain_snapshot(start, end)), std::slice::from_ref(&run));
3553        let reversed = project_ranges(Some(plain_snapshot(end, start)), &[run]);
3554
3555        assert_eq!(forward.ranges(), &[Some(1..7)]);
3556        assert_eq!(reversed.ranges(), &[Some(1..7)]);
3557        assert!(forward.is_active());
3558        assert!(reversed.is_active());
3559    }
3560
3561    #[gpui::test]
3562    fn double_click_expands_a_plain_run_to_the_input_word_boundary(cx: &mut TestAppContext) {
3563        let (text, layout) = laid_out_runs(&["one café, three"], cx).pop().unwrap();
3564        let run = text_run(0, text, layout.clone());
3565        let click = layout.position_for_index(6).unwrap();
3566
3567        let (anchor, cursor) =
3568            points_for_multi_click(std::slice::from_ref(&run), click, 2).unwrap();
3569        let states = project_ranges(Some(plain_snapshot(anchor, cursor)), &[run]);
3570
3571        assert_eq!(states.ranges(), &[Some(4..9)]);
3572    }
3573
3574    #[gpui::test]
3575    fn multi_click_uses_text_layout_window_coordinates_at_a_nonzero_origin(
3576        cx: &mut TestAppContext,
3577    ) {
3578        let mut runs = laid_out_runs(&["above", "alpha beta"], cx);
3579        let (text, layout) = runs.pop().unwrap();
3580        assert!(layout.bounds().origin.y > px(0.));
3581        let run = text_run(0, text, layout.clone());
3582        let click = layout.position_for_index(7).unwrap();
3583
3584        let (anchor, cursor) =
3585            points_for_multi_click(std::slice::from_ref(&run), click, 2).unwrap();
3586        let projection = project_ranges(Some(plain_snapshot(anchor, cursor)), &[run]);
3587
3588        assert_eq!(projection.ranges(), &[Some(6..10)]);
3589    }
3590
3591    #[gpui::test]
3592    fn triple_click_expands_to_the_input_logical_line_not_the_visual_row(cx: &mut TestAppContext) {
3593        let (text, layout) = laid_out_runs(&["second line"], cx).pop().unwrap();
3594        let run = text_run(0, text, layout.clone());
3595        let click = layout.position_for_index(4).unwrap();
3596
3597        let (anchor, cursor) =
3598            points_for_multi_click(std::slice::from_ref(&run), click, 4).unwrap();
3599        let states = project_ranges(Some(plain_snapshot(anchor, cursor)), &[run]);
3600
3601        assert_eq!(states.ranges(), &[Some(0..11)]);
3602        assert_eq!(line_range_at("first line\nsecond line\nthird", 15), 11..22);
3603    }
3604
3605    #[gpui::test]
3606    fn plain_projection_spans_multiple_runs_and_leaves_empty_gutters_unselected(
3607        cx: &mut TestAppContext,
3608    ) {
3609        let mut runs = laid_out_runs(&["first", "", "second"], cx);
3610        let (first_text, first_layout) = runs.remove(0);
3611        let (gutter_text, gutter_layout) = runs.remove(0);
3612        let (second_text, second_layout) = runs.remove(0);
3613        let start = first_layout.position_for_index(2).unwrap();
3614        let end = second_layout.position_for_index(3).unwrap();
3615        let states = project_ranges(
3616            Some(plain_snapshot(start, end)),
3617            &[
3618                text_run(2, second_text, second_layout),
3619                text_run(1, gutter_text, gutter_layout),
3620                text_run(0, first_text, first_layout),
3621            ],
3622        );
3623
3624        assert_eq!(states.ranges(), &[Some(0..3), None, Some(2..5)]);
3625        assert!(states.is_active());
3626    }
3627
3628    #[gpui::test]
3629    fn plain_projection_caches_multiple_participant_copies_in_document_order(
3630        cx: &mut TestAppContext,
3631    ) {
3632        let mut runs = laid_out_runs(&["one", "two"], cx);
3633        let (first_text, first_layout) = runs.remove(0);
3634        let (second_text, second_layout) = runs.remove(0);
3635        let snapshot = plain_snapshot(
3636            first_layout.position_for_index(1).unwrap(),
3637            second_layout.position_for_index(2).unwrap(),
3638        );
3639        cx.update(|cx| {
3640            let mut selection_state = WindowSelectionState::default();
3641            let first = FakeParticipant::new("", cx);
3642            let second = FakeParticipant::new("", cx);
3643            first.register(
3644                &mut selection_state,
3645                0.,
3646                TextSelectionScopeId::default(),
3647                1,
3648                cx,
3649            );
3650            second.register(
3651                &mut selection_state,
3652                20.,
3653                TextSelectionScopeId::default(),
3654                0,
3655                cx,
3656            );
3657
3658            first
3659                .selection
3660                .0
3661                .update(cx, |state, cx| state.set_snapshot(Some(snapshot), cx));
3662            let projection = first
3663                .selection
3664                .update_runs(&[text_run(0, first_text, first_layout)], cx);
3665            assert_eq!(projection.ranges(), &[Some(1..3)]);
3666            assert!(projection.is_active());
3667            second
3668                .selection
3669                .0
3670                .update(cx, |state, cx| state.set_snapshot(Some(snapshot), cx));
3671            let projection = second
3672                .selection
3673                .update_runs(&[text_run(0, second_text, second_layout)], cx);
3674            assert_eq!(projection.ranges(), &[Some(0..2)]);
3675            assert!(projection.is_active());
3676
3677            assert_eq!(selection_state.selected_text(cx), "tw\nne");
3678        });
3679    }
3680
3681    #[gpui::test]
3682    fn plain_projection_invalidates_cached_copy_when_the_snapshot_changes(cx: &mut TestAppContext) {
3683        let (text, layout) = laid_out_runs(&["first"], cx).pop().unwrap();
3684        let first_snapshot = plain_snapshot(
3685            layout.position_for_index(1).unwrap(),
3686            layout.position_for_index(3).unwrap(),
3687        );
3688        let changed_snapshot = plain_snapshot(
3689            layout.position_for_index(3).unwrap(),
3690            layout.position_for_index(5).unwrap(),
3691        );
3692        let run = text_run(0, text, layout);
3693        cx.update(|cx| {
3694            let mut selection_state = WindowSelectionState::default();
3695            let participant = FakeParticipant::new("", cx);
3696            participant.register(
3697                &mut selection_state,
3698                0.,
3699                TextSelectionScopeId::default(),
3700                0,
3701                cx,
3702            );
3703            participant.selection.0.update(cx, |state, cx| {
3704                state.set_snapshot(Some(first_snapshot), cx);
3705                state.update_runs(std::slice::from_ref(&run));
3706            });
3707            assert_eq!(selection_state.selected_text(cx), "ir");
3708
3709            participant.selection.0.update(cx, |state, cx| {
3710                state.set_snapshot(Some(changed_snapshot), cx);
3711            });
3712            assert_eq!(selection_state.selected_text(cx), "");
3713
3714            participant.selection.update_runs(&[run], cx);
3715            assert_eq!(selection_state.selected_text(cx), "st");
3716            selection_state.clear(cx);
3717            participant.selection.set_local_selection(true, cx);
3718            assert_eq!(selection_state.selected_text(cx), "");
3719        });
3720    }
3721
3722    #[gpui::test]
3723    fn plain_projection_orders_cached_runs_by_frame_order_not_input_order(cx: &mut TestAppContext) {
3724        let mut runs = laid_out_runs(&["one", "two"], cx);
3725        let (first_text, first_layout) = runs.remove(0);
3726        let (second_text, second_layout) = runs.remove(0);
3727        let snapshot = plain_snapshot(
3728            first_layout.position_for_index(1).unwrap(),
3729            second_layout.position_for_index(2).unwrap(),
3730        );
3731        cx.update(|cx| {
3732            let mut selection_state = WindowSelectionState::default();
3733            let participant = FakeParticipant::new("", cx);
3734            participant.register(
3735                &mut selection_state,
3736                0.,
3737                TextSelectionScopeId::default(),
3738                0,
3739                cx,
3740            );
3741            participant.selection.0.update(cx, |state, cx| {
3742                state.set_snapshot(Some(snapshot), cx);
3743                state.update_runs(&[
3744                    text_run(1, first_text, first_layout),
3745                    text_run(0, second_text, second_layout),
3746                ]);
3747            });
3748
3749            assert_eq!(selection_state.selected_text(cx), "twne");
3750        });
3751    }
3752
3753    #[gpui::test]
3754    fn plain_projection_safely_rejects_a_text_layout_length_mismatch(cx: &mut TestAppContext) {
3755        let (_, layout) = laid_out_runs(&["short"], cx).pop().unwrap();
3756        let start = layout.position_for_index(0).unwrap();
3757        let end = layout.position_for_index(5).unwrap();
3758        let states = project_ranges(
3759            Some(plain_snapshot(start, end)),
3760            &[text_run(0, SharedString::from("longer"), layout)],
3761        );
3762
3763        assert_eq!(states.ranges(), &[None]);
3764        assert!(states.is_active());
3765    }
3766
3767    #[gpui::test]
3768    fn begin_update_and_end_publish_a_cross_participant_selection(cx: &mut TestAppContext) {
3769        cx.update(|cx| {
3770            let mut selection_state = WindowSelectionState::default();
3771            let first = FakeParticipant::new("first", cx);
3772            let second = FakeParticipant::new("second", cx);
3773            first.register(
3774                &mut selection_state,
3775                0.,
3776                TextSelectionScopeId::default(),
3777                0,
3778                cx,
3779            );
3780            second.register(
3781                &mut selection_state,
3782                20.,
3783                TextSelectionScopeId::default(),
3784                1,
3785                cx,
3786            );
3787
3788            selection_state.begin(point(px(1.), px(1.)), false, cx);
3789            selection_state.update(point(px(1.), px(25.)), cx);
3790            assert!(selection_state.has_selection(cx));
3791            assert_eq!(selection_state.selected_text(cx), "first\nsecond");
3792
3793            selection_state.end(cx);
3794            assert!(!selection_state.is_selecting());
3795        });
3796    }
3797
3798    #[gpui::test]
3799    fn shift_extension_keeps_its_original_anchor_when_reversed(cx: &mut TestAppContext) {
3800        cx.update(|cx| {
3801            let mut selection_state = WindowSelectionState::default();
3802            let participant = FakeParticipant::new("participant", cx);
3803            participant.register(
3804                &mut selection_state,
3805                0.,
3806                TextSelectionScopeId::default(),
3807                0,
3808                cx,
3809            );
3810
3811            selection_state.begin(point(px(2.), px(2.)), false, cx);
3812            selection_state.end(cx);
3813            selection_state.begin(point(px(8.), px(2.)), true, cx);
3814            selection_state.end(cx);
3815            let first_anchor = selection_state.snapshot().unwrap().anchor();
3816
3817            selection_state.begin(point(px(0.), px(2.)), true, cx);
3818            selection_state.end(cx);
3819            let reversed = selection_state.snapshot().unwrap();
3820            assert_eq!(reversed.anchor(), first_anchor);
3821            assert!(reversed.cursor().content_point().x < reversed.anchor().content_point().x);
3822        });
3823    }
3824
3825    #[gpui::test]
3826    fn content_key_resolver_runs_outside_the_window_state_lease(cx: &mut TestAppContext) {
3827        cx.update(|cx| {
3828            let state = cx.new(|_| WindowSelectionState::default());
3829            let participant = FakeParticipant::new("virtual", cx);
3830            let state_for_callback = state.clone();
3831            participant.selection.resolve_content_key_with(
3832                move |_, cx| {
3833                    let _ = state_for_callback.read(cx).snapshot();
3834                    Some(TextSelectionContentKey::new(7))
3835                },
3836                cx,
3837            );
3838            state.update(cx, |state, cx| {
3839                participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
3840                state.begin(point(px(1.), px(1.)), false, cx);
3841                state.update(point(px(8.), px(1.)), cx);
3842            });
3843
3844            WindowSelectionState::resolve_content_keys(&state, cx);
3845
3846            assert_eq!(
3847                state.read(cx).snapshot().unwrap().cursor().content_key(),
3848                Some(TextSelectionContentKey::new(7))
3849            );
3850        });
3851    }
3852
3853    #[gpui::test]
3854    fn active_dnd_does_not_move_a_text_selection_cursor(cx: &mut TestAppContext) {
3855        let window = cx.add_window(|_, cx| WindowSelectionView {
3856            selection: TextSelectionHandle::new("unused", cx),
3857        });
3858        window
3859            .update(cx, |_, window, cx| {
3860                let mut state = WindowSelectionState::default();
3861                let participant = FakeParticipant::new("participant", cx);
3862                participant.register(&mut state, 0., TextSelectionScopeId::default(), 0, cx);
3863                state.begin(point(px(1.), px(1.)), false, cx);
3864                let before = state.cursor.as_ref().unwrap().point;
3865                state.update_in_window_with_active_drag(point(px(80.), px(1.)), true, window, cx);
3866                assert_eq!(state.cursor.as_ref().unwrap().point, before);
3867            })
3868            .unwrap();
3869    }
3870
3871    #[gpui::test]
3872    fn shift_extension_falls_back_when_the_anchor_participant_was_swept(cx: &mut TestAppContext) {
3873        cx.update(|cx| {
3874            let mut selection_state = WindowSelectionState::default();
3875            let first = FakeParticipant::new("first", cx);
3876            let second = FakeParticipant::new("second", cx);
3877            first.register(
3878                &mut selection_state,
3879                0.,
3880                TextSelectionScopeId::default(),
3881                0,
3882                cx,
3883            );
3884            selection_state.begin(point(px(1.), px(1.)), false, cx);
3885            selection_state.update(point(px(8.), px(1.)), cx);
3886            selection_state.end(cx);
3887
3888            selection_state.finish_frame(cx);
3889            selection_state.finish_frame(cx);
3890            second.register(
3891                &mut selection_state,
3892                20.,
3893                TextSelectionScopeId::default(),
3894                1,
3895                cx,
3896            );
3897            selection_state.begin(point(px(1.), px(21.)), true, cx);
3898            selection_state.update(point(px(8.), px(21.)), cx);
3899            selection_state.end(cx);
3900
3901            assert_eq!(selection_state.selected_text(cx), "second");
3902        });
3903    }
3904
3905    #[gpui::test]
3906    fn scope_and_suppression_prevent_unrelated_participants_from_participating(
3907        cx: &mut TestAppContext,
3908    ) {
3909        cx.update(|cx| {
3910            let mut selection_state = WindowSelectionState::default();
3911            let base = FakeParticipant::new("base", cx);
3912            let modal = FakeParticipant::new("modal", cx);
3913            base.register(
3914                &mut selection_state,
3915                0.,
3916                TextSelectionScopeId::default(),
3917                0,
3918                cx,
3919            );
3920            modal.register(&mut selection_state, 20., TextSelectionScopeId(1), 1, cx);
3921
3922            selection_state.set_active_scope(TextSelectionScopeId(1), cx);
3923            selection_state.begin(point(px(1.), px(21.)), false, cx);
3924            selection_state.update(point(px(8.), px(21.)), cx);
3925            selection_state.end(cx);
3926            assert_eq!(selection_state.selected_text(cx), "modal");
3927
3928            selection_state.clear(cx);
3929            GlobalState::init(cx);
3930            GlobalState::suppress_text_selection(cx);
3931            selection_state.begin(point(px(1.), px(21.)), false, cx);
3932            selection_state.update(point(px(8.), px(21.)), cx);
3933            assert!(!selection_state.has_selection(cx));
3934        });
3935    }
3936
3937    #[gpui::test]
3938    fn dead_participants_are_pruned_and_empty_selection_falls_back_safely(cx: &mut TestAppContext) {
3939        let selection_state = cx.update(|cx| {
3940            let selection_state = cx.new(|_| WindowSelectionState::default());
3941            let participant = FakeParticipant::new("gone", cx);
3942            selection_state.update(cx, |selection_state, cx| {
3943                participant.register(selection_state, 0., TextSelectionScopeId::default(), 0, cx)
3944            });
3945            selection_state
3946        });
3947        cx.update(|cx| {
3948            selection_state.update(cx, |selection_state, cx| {
3949                selection_state.begin(point(px(1.), px(1.)), false, cx);
3950                selection_state.update(point(px(8.), px(1.)), cx);
3951                selection_state.end(cx);
3952
3953                assert_eq!(selection_state.selected_text(cx), "");
3954                assert!(!selection_state.has_selection(cx));
3955            });
3956        });
3957    }
3958
3959    #[gpui::test]
3960    fn text_selection_namespace_reports_copies_ends_and_clears_selection(cx: &mut TestAppContext) {
3961        let (view, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
3962            selection: TextSelectionHandle::new("copied", cx),
3963        });
3964        cx.update(|window, cx| {
3965            let selection = view.read(cx).selection.clone();
3966            let selection_state = WindowSelectionState::ensure(window, cx);
3967            selection_state.update(cx, |selection_state, cx| {
3968                FakeParticipant { selection }.register(
3969                    selection_state,
3970                    0.,
3971                    TextSelectionScopeId::default(),
3972                    0,
3973                    cx,
3974                );
3975                selection_state.begin(point(px(1.), px(1.)), false, cx);
3976                selection_state.update(point(px(8.), px(1.)), cx);
3977            });
3978
3979            assert!(TextSelection::has_selection(window, cx));
3980            assert_eq!(TextSelection::selected_text(window, cx), "copied");
3981            TextSelection::end(window, cx);
3982            assert!(TextSelection::has_selection(window, cx));
3983            TextSelection::clear(window, cx);
3984            assert!(!TextSelection::has_selection(window, cx));
3985            assert_eq!(TextSelection::selected_text(window, cx), "");
3986        });
3987    }
3988
3989    #[gpui::test]
3990    fn two_windows_isolate_selection_copy_clear_and_release_ownership(cx: &mut TestAppContext) {
3991        let first = cx.add_window(|_, cx| WindowOwnedSelectionView {
3992            selection: TextSelectionHandle::new("first", cx),
3993        });
3994        let second = cx.add_window(|_, cx| WindowOwnedSelectionView {
3995            selection: TextSelectionHandle::new("second", cx),
3996        });
3997        let first_selection = cx.update(|cx| first.read(cx).unwrap().selection.clone());
3998        let second_selection = cx.update(|cx| second.read(cx).unwrap().selection.clone());
3999
4000        let first_state = cx
4001            .update_window(*first, |_, window, cx| {
4002                let _ = window.draw(cx);
4003                first_selection.set_local_selection(true, cx);
4004                assert_eq!(TextSelection::selected_text(window, cx), "first");
4005                WindowSelectionState::existing(window, cx)
4006                    .unwrap()
4007                    .downgrade()
4008            })
4009            .unwrap();
4010        cx.update_window(*second, |_, window, cx| {
4011            let _ = window.draw(cx);
4012            second_selection.set_local_selection(true, cx);
4013            assert_eq!(TextSelection::selected_text(window, cx), "second");
4014        })
4015        .unwrap();
4016
4017        cx.update_window(*first, |_, window, cx| {
4018            TextSelection::clear(window, cx);
4019            assert_eq!(TextSelection::selected_text(window, cx), "");
4020        })
4021        .unwrap();
4022        cx.update_window(*second, |_, window, cx| {
4023            assert_eq!(TextSelection::selected_text(window, cx), "second");
4024        })
4025        .unwrap();
4026
4027        cx.update_window(*first, |_, window, _| window.remove_window())
4028            .unwrap();
4029        cx.run_until_parked();
4030
4031        assert!(first_state.upgrade().is_none());
4032        cx.update_window(*second, |_, window, cx| {
4033            assert_eq!(TextSelection::selected_text(window, cx), "second");
4034        })
4035        .unwrap();
4036        cx.update(|cx| {
4037            assert_eq!(cx.global::<SelectionStateRegistry>().0.len(), 1);
4038        });
4039    }
4040
4041    #[gpui::test]
4042    fn copy_callback_can_reenter_window_and_handle_selection(cx: &mut TestAppContext) {
4043        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
4044        cx.update(|window, cx| {
4045            let _ = window.draw(cx);
4046            let state = WindowSelectionState::existing(window, cx).unwrap();
4047            let selection = TextSelectionHandle::new("fallback", cx);
4048            let state_for_copy = state.clone();
4049            let selection_for_copy = selection.clone();
4050            selection.copy_with(
4051                move |cx: &mut App| {
4052                    state_for_copy.update(cx, |state, _| {
4053                        assert!(state.snapshot().is_some());
4054                    });
4055                    assert!(selection_for_copy.snapshot(cx).is_some());
4056                    selection_for_copy.set_fallback_copy_text("reentered", cx);
4057                    "reentrant copy".to_string()
4058                },
4059                cx,
4060            );
4061            state.update(cx, |state, cx| {
4062                FakeParticipant {
4063                    selection: selection.clone(),
4064                }
4065                .register(state, 0., TextSelectionScopeId::default(), 0, cx);
4066                state.begin(point(px(1.), px(1.)), false, cx);
4067                state.update(point(px(8.), px(1.)), cx);
4068                state.end(cx);
4069            });
4070
4071            assert_eq!(TextSelection::selected_text(window, cx), "reentrant copy");
4072        });
4073    }
4074
4075    #[gpui::test]
4076    fn cross_participant_selection_excludes_participants_outside_its_document_interval(
4077        cx: &mut TestAppContext,
4078    ) {
4079        cx.update(|cx| {
4080            let mut selection_state = WindowSelectionState::default();
4081            let first = FakeParticipant::new("first", cx);
4082            let second = FakeParticipant::new("second", cx);
4083            let third = FakeParticipant::new("third", cx);
4084            first.register(
4085                &mut selection_state,
4086                0.,
4087                TextSelectionScopeId::default(),
4088                0,
4089                cx,
4090            );
4091            second.register(
4092                &mut selection_state,
4093                20.,
4094                TextSelectionScopeId::default(),
4095                1,
4096                cx,
4097            );
4098            third.register(
4099                &mut selection_state,
4100                40.,
4101                TextSelectionScopeId::default(),
4102                2,
4103                cx,
4104            );
4105
4106            selection_state.begin(point(px(1.), px(1.)), false, cx);
4107            selection_state.update(point(px(1.), px(25.)), cx);
4108            selection_state.end(cx);
4109
4110            assert_eq!(selection_state.selected_text(cx), "first\nsecond");
4111            assert!(third.selection.snapshot(cx).is_none());
4112        });
4113    }
4114
4115    #[gpui::test]
4116    fn changing_scope_clears_the_previous_scope_selection(cx: &mut TestAppContext) {
4117        cx.update(|cx| {
4118            let mut selection_state = WindowSelectionState::default();
4119            let base = FakeParticipant::new("base", cx);
4120            let modal = FakeParticipant::new("modal", cx);
4121            base.register(
4122                &mut selection_state,
4123                0.,
4124                TextSelectionScopeId::default(),
4125                0,
4126                cx,
4127            );
4128            modal.register(
4129                &mut selection_state,
4130                20.,
4131                TextSelectionScopeId::from_raw(1),
4132                1,
4133                cx,
4134            );
4135
4136            selection_state.begin(point(px(1.), px(1.)), false, cx);
4137            selection_state.update(point(px(8.), px(1.)), cx);
4138            selection_state.end(cx);
4139            selection_state.set_active_scope(TextSelectionScopeId::from_raw(1), cx);
4140
4141            assert!(!selection_state.has_selection(cx));
4142            assert!(base.selection.snapshot(cx).is_none());
4143        });
4144    }
4145
4146    #[gpui::test]
4147    fn blank_only_drag_never_publishes_or_copies_selection(cx: &mut TestAppContext) {
4148        cx.update(|cx| {
4149            let mut selection_state = WindowSelectionState::default();
4150            let participant = FakeParticipant::new("participant", cx);
4151            participant.register(
4152                &mut selection_state,
4153                0.,
4154                TextSelectionScopeId::default(),
4155                0,
4156                cx,
4157            );
4158
4159            selection_state.begin(point(px(200.), px(1.)), false, cx);
4160            selection_state.update(point(px(200.), px(8.)), cx);
4161            selection_state.end(cx);
4162
4163            assert!(!selection_state.has_selection(cx));
4164            assert_eq!(selection_state.selected_text(cx), "");
4165            assert!(participant.selection.snapshot(cx).is_none());
4166        });
4167    }
4168
4169    #[gpui::test]
4170    fn unmounted_selection_does_not_revive_when_the_participant_returns(cx: &mut TestAppContext) {
4171        cx.update(|cx| {
4172            let mut selection_state = WindowSelectionState::default();
4173            let participant = FakeParticipant::new("stale", cx);
4174            participant.register(
4175                &mut selection_state,
4176                0.,
4177                TextSelectionScopeId::default(),
4178                0,
4179                cx,
4180            );
4181            selection_state.begin(point(px(1.), px(1.)), false, cx);
4182            selection_state.update(point(px(8.), px(1.)), cx);
4183            selection_state.end(cx);
4184            assert_eq!(selection_state.selected_text(cx), "stale");
4185
4186            selection_state.finish_frame(cx);
4187            selection_state.finish_frame(cx);
4188            assert_eq!(selection_state.selected_text(cx), "");
4189            assert!(participant.selection.snapshot(cx).is_none());
4190            participant.register(
4191                &mut selection_state,
4192                0.,
4193                TextSelectionScopeId::default(),
4194                0,
4195                cx,
4196            );
4197            assert_eq!(selection_state.selected_text(cx), "");
4198            assert!(participant.selection.snapshot(cx).is_none());
4199
4200            selection_state.begin(point(px(1.), px(1.)), false, cx);
4201            selection_state.update(point(px(8.), px(1.)), cx);
4202            selection_state.end(cx);
4203            assert_eq!(selection_state.selected_text(cx), "stale");
4204        });
4205    }
4206
4207    #[gpui::test]
4208    fn removing_an_unselected_participant_preserves_the_current_selection(cx: &mut TestAppContext) {
4209        cx.update(|cx| {
4210            let mut selection_state = WindowSelectionState::default();
4211            let selected = FakeParticipant::new("selected", cx);
4212            let unrelated = FakeParticipant::new("unrelated", cx);
4213            let scope = TextSelectionScopeId::default();
4214            selected.register(&mut selection_state, 0., scope, 0, cx);
4215            unrelated.register(&mut selection_state, 20., scope, 1, cx);
4216            selection_state.begin(point(px(1.), px(1.)), false, cx);
4217            selection_state.update(point(px(8.), px(1.)), cx);
4218            selection_state.end(cx);
4219            assert_eq!(selection_state.selected_text(cx), "selected");
4220
4221            selection_state.finish_frame(cx);
4222            selected.register(&mut selection_state, 0., scope, 0, cx);
4223            selection_state.finish_frame(cx);
4224            assert_eq!(selection_state.selected_text(cx), "selected");
4225            unrelated.register(&mut selection_state, 20., scope, 1, cx);
4226            assert_eq!(selection_state.selected_text(cx), "selected");
4227        });
4228    }
4229
4230    #[gpui::test]
4231    fn removing_either_range_endpoint_discards_the_shared_selection(cx: &mut TestAppContext) {
4232        for remove_anchor in [true, false] {
4233            cx.update(|cx| {
4234                let mut selection_state = WindowSelectionState::default();
4235                let anchor = FakeParticipant::new("anchor", cx);
4236                let cursor = FakeParticipant::new("cursor", cx);
4237                let scope = TextSelectionScopeId::default();
4238                anchor.register(&mut selection_state, 0., scope, 0, cx);
4239                cursor.register(&mut selection_state, 20., scope, 1, cx);
4240                selection_state.begin(point(px(1.), px(1.)), false, cx);
4241                selection_state.update(point(px(8.), px(21.)), cx);
4242                selection_state.end(cx);
4243                assert_eq!(selection_state.selected_text(cx), "anchor\ncursor");
4244                selection_state.finish_frame(cx);
4245
4246                if remove_anchor {
4247                    cursor.register(&mut selection_state, 20., scope, 1, cx);
4248                } else {
4249                    anchor.register(&mut selection_state, 0., scope, 0, cx);
4250                }
4251                selection_state.finish_frame(cx);
4252                assert_eq!(selection_state.selected_text(cx), "");
4253                anchor.register(&mut selection_state, 0., scope, 0, cx);
4254                cursor.register(&mut selection_state, 20., scope, 1, cx);
4255                assert_eq!(selection_state.selected_text(cx), "");
4256            });
4257        }
4258    }
4259
4260    #[gpui::test]
4261    fn clear_or_unmount_stops_auto_scroll_before_discarding_the_anchor(cx: &mut TestAppContext) {
4262        for unmount in [false, true] {
4263            let commands = Rc::new(RefCell::new(Vec::new()));
4264            let observed = commands.clone();
4265            let (mut selection_state, participant) = cx.update(|cx| {
4266                let selection_state = WindowSelectionState::default();
4267                let participant = FakeParticipant::new("scroll", cx);
4268                participant
4269                    .selection
4270                    .subscribe(
4271                        move |event, _| {
4272                            if let TextSelectionEvent::AutoScroll(delta) = event {
4273                                observed.borrow_mut().push(*delta);
4274                            }
4275                        },
4276                        cx,
4277                    )
4278                    .detach();
4279                (selection_state, participant)
4280            });
4281            cx.run_until_parked();
4282            cx.update(|cx| {
4283                participant.register(
4284                    &mut selection_state,
4285                    0.,
4286                    TextSelectionScopeId::default(),
4287                    0,
4288                    cx,
4289                );
4290
4291                selection_state.begin(point(px(1.), px(1.)), false, cx);
4292                selection_state.update(point(px(1.), px(25.)), cx);
4293                if unmount {
4294                    selection_state.finish_frame(cx);
4295                    selection_state.finish_frame(cx);
4296                } else {
4297                    selection_state.clear(cx);
4298                }
4299            });
4300            cx.run_until_parked();
4301            assert!(commands.borrow().iter().any(Option::is_some));
4302            assert_eq!(commands.borrow().last(), Some(&None));
4303        }
4304    }
4305
4306    #[gpui::test]
4307    fn drag_auto_scroll_stops_when_the_content_mask_collapses(cx: &mut TestAppContext) {
4308        let window = cx.add_window(|_, cx| WindowSelectionView {
4309            selection: TextSelectionHandle::new("unused", cx),
4310        });
4311        window
4312            .update(cx, |_, window, cx| {
4313                let state = cx.new(|_| WindowSelectionState::default());
4314                let participant = FakeParticipant::new("participant", cx);
4315                state.update(cx, |state, cx| {
4316                    participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
4317                    state.begin(point(px(1.), px(1.)), false, cx);
4318                });
4319                // The scrollable ancestor got clipped away mid-drag, so the
4320                // refreshed registration carries a collapsed content mask.
4321                let collapsed = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(0.)));
4322                state.update(cx, |state, cx| {
4323                    state.register_participant(
4324                        participant.selection.clone(),
4325                        TextSelectionRegistration::new(
4326                            Hitbox {
4327                                id: HitboxId::placeholder(),
4328                                bounds: collapsed,
4329                                content_mask: ContentMask { bounds: collapsed },
4330                                behavior: HitboxBehavior::Normal,
4331                            },
4332                            collapsed,
4333                        )
4334                        .with_text_bounds(vec![collapsed]),
4335                        cx,
4336                    );
4337                    state.update_in_window(point(px(1.), px(50.)), window, cx);
4338                    assert!(!state.auto_scroll.is_active());
4339                    assert!(state.auto_scroll.last_drag_position.is_none());
4340                });
4341            })
4342            .unwrap();
4343    }
4344
4345    #[gpui::test]
4346    fn pointer_moves_after_a_click_do_not_auto_scroll(cx: &mut TestAppContext) {
4347        let window = cx.add_window(|_, cx| WindowSelectionView {
4348            selection: TextSelectionHandle::new("unused", cx),
4349        });
4350        window
4351            .update(cx, |_, window, cx| {
4352                let state = cx.new(|_| WindowSelectionState::default());
4353                let participant = FakeParticipant::new("participant", cx);
4354                state.update(cx, |state, cx| {
4355                    participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
4356                    // A click on text keeps its anchor so shift-click can extend it.
4357                    state.begin(point(px(1.), px(1.)), false, cx);
4358                    state.end(cx);
4359                    assert!(state.anchor.is_some());
4360
4361                    state.update_in_window(point(px(1.), px(50.)), window, cx);
4362                    assert!(!state.auto_scroll.is_active());
4363                });
4364            })
4365            .unwrap();
4366    }
4367
4368    #[gpui::test]
4369    fn proxy_endpoints_break_equal_position_ties_by_document_order(cx: &mut TestAppContext) {
4370        cx.update(|cx| {
4371            let mut selection_state = WindowSelectionState::default();
4372            let later = FakeParticipant::new("later", cx);
4373            let earlier = FakeParticipant::new("earlier", cx);
4374            later.register(
4375                &mut selection_state,
4376                0.,
4377                TextSelectionScopeId::default(),
4378                2,
4379                cx,
4380            );
4381            earlier.register(
4382                &mut selection_state,
4383                0.,
4384                TextSelectionScopeId::default(),
4385                1,
4386                cx,
4387            );
4388
4389            selection_state.begin(point(px(1.), px(1.)), false, cx);
4390            selection_state.update(point(px(200.), px(25.)), cx);
4391            let endpoint = selection_state.snapshot().unwrap().cursor();
4392
4393            assert_eq!(endpoint.entity_id(), Some(earlier.selection.entity_id()));
4394        });
4395    }
4396
4397    #[gpui::test]
4398    fn equal_area_hovered_participants_break_ties_by_document_order(cx: &mut TestAppContext) {
4399        cx.update(|cx| {
4400            for _ in 0..64 {
4401                let mut selection_state = WindowSelectionState::default();
4402                let later = FakeParticipant::new("later", cx);
4403                let earliest = FakeParticipant::new("earliest", cx);
4404                let middle = FakeParticipant::new("middle", cx);
4405                later.register(
4406                    &mut selection_state,
4407                    0.,
4408                    TextSelectionScopeId::default(),
4409                    30,
4410                    cx,
4411                );
4412                earliest.register(
4413                    &mut selection_state,
4414                    0.,
4415                    TextSelectionScopeId::default(),
4416                    10,
4417                    cx,
4418                );
4419                middle.register(
4420                    &mut selection_state,
4421                    0.,
4422                    TextSelectionScopeId::default(),
4423                    20,
4424                    cx,
4425                );
4426
4427                selection_state.begin(point(px(1.), px(1.)), false, cx);
4428                selection_state.update(point(px(8.), px(1.)), cx);
4429
4430                assert_eq!(
4431                    selection_state.snapshot().unwrap().anchor().entity_id(),
4432                    Some(earliest.selection.entity_id())
4433                );
4434            }
4435        });
4436    }
4437
4438    #[gpui::test]
4439    fn text_selection_namespace_is_a_safe_no_op_until_the_element_is_rendered(
4440        cx: &mut TestAppContext,
4441    ) {
4442        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
4443            selection: TextSelectionHandle::new("not enabled", cx),
4444        });
4445        cx.update(|window, cx| {
4446            assert!(!TextSelection::has_selection(window, cx));
4447            assert_eq!(TextSelection::selected_text(window, cx), "");
4448            TextSelection::clear(window, cx);
4449            TextSelection::end(window, cx);
4450            assert!(!TextSelection::has_selection(window, cx));
4451        });
4452    }
4453
4454    #[gpui::test]
4455    fn unit_selection_element_supports_scope_and_registration_on_the_first_frame(
4456        cx: &mut TestAppContext,
4457    ) {
4458        let (view, cx) = cx.add_window_view(|_, cx| FirstFrameScopedSelectionView {
4459            selection: TextSelectionHandle::new("first frame", cx),
4460        });
4461        let selection = cx.update(|_, cx| view.read(cx).selection.clone());
4462
4463        cx.update(|window, cx| {
4464            let _ = window.draw(cx);
4465            let state = WindowSelectionState::existing(window, cx).unwrap();
4466            assert_eq!(
4467                state.read(cx).active_scope,
4468                TextSelectionScopeId::from_raw(23)
4469            );
4470            assert!(
4471                state
4472                    .read(cx)
4473                    .participants
4474                    .borrow()
4475                    .contains_key(&selection.entity_id())
4476            );
4477        });
4478    }
4479
4480    #[gpui::test]
4481    fn lazy_registration_does_not_enable_queries_without_the_element(cx: &mut TestAppContext) {
4482        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
4483            selection: TextSelectionHandle::new("registered", cx),
4484        });
4485        cx.update(|window, cx| {
4486            let selection = TextSelectionHandle::new("registered", cx);
4487            selection.set_local_selection(true, cx);
4488            let bounds = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(20.)));
4489            let hitbox = Hitbox {
4490                id: HitboxId::placeholder(),
4491                bounds,
4492                content_mask: ContentMask { bounds },
4493                behavior: HitboxBehavior::Normal,
4494            };
4495            selection.register(
4496                TextSelectionRegistration::new(hitbox, bounds).with_text_bounds(vec![bounds]),
4497                window,
4498                cx,
4499            );
4500            assert_eq!(TextSelection::selected_text(window, cx), "");
4501            assert!(!TextSelection::has_selection(window, cx));
4502            TextSelection::clear(window, cx);
4503            assert_eq!(TextSelection::selected_text(window, cx), "");
4504        });
4505    }
4506
4507    /// A view reading the window's selection state, beside selectable text,
4508    /// counting its renders.
4509    #[cfg(feature = "gpui-fast")]
4510    struct SelectionStateReader {
4511        renders: Rc<Cell<usize>>,
4512    }
4513
4514    #[cfg(feature = "gpui-fast")]
4515    impl Render for SelectionStateReader {
4516        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4517            self.renders.set(self.renders.get() + 1);
4518            if let Some(state) = WindowSelectionState::existing(window, cx) {
4519                let _ = state.read(cx).participants.borrow().len();
4520            }
4521            div().size_full()
4522        }
4523    }
4524
4525    /// The selectable text of a [`ToggleSelectionElementView`] beside a
4526    /// [`SelectionStateReader`].
4527    #[cfg(feature = "gpui-fast")]
4528    struct ReaderBesideSelectionView {
4529        text: gpui::Entity<ToggleSelectionElementView>,
4530        reader: gpui::Entity<SelectionStateReader>,
4531    }
4532
4533    #[cfg(feature = "gpui-fast")]
4534    impl Render for ReaderBesideSelectionView {
4535        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4536            div()
4537                .size_full()
4538                .child(self.reader.clone())
4539                .child(self.text.clone())
4540        }
4541    }
4542
4543    /// Selectable text drawn again, nothing about its selection changed,
4544    /// writes neither the window's selection state nor its own: a view
4545    /// reading them is retained, as it would not be had they changed.
4546    #[cfg(feature = "gpui-fast")]
4547    #[gpui::test]
4548    fn redrawing_selectable_text_leaves_its_readers_retained(cx: &mut TestAppContext) {
4549        let renders = Rc::new(Cell::new(0));
4550        let (view, cx) = cx.add_window_view(|_, cx| ReaderBesideSelectionView {
4551            text: cx.new(|cx| ToggleSelectionElementView {
4552                enabled: true,
4553                selection: TextSelectionHandle::new("text", cx),
4554            }),
4555            reader: cx.new(|_| SelectionStateReader {
4556                renders: renders.clone(),
4557            }),
4558        });
4559        let text = cx.update(|_, cx| view.read(cx).text.clone());
4560        let redraw = |cx: &mut gpui::VisualTestContext| {
4561            text.update(cx, |_, cx| cx.notify());
4562            cx.update(|window, cx| {
4563                let _ = window.draw(cx);
4564                window.simulate_next_frame(cx);
4565            });
4566            cx.run_until_parked();
4567        };
4568        redraw(cx);
4569        redraw(cx);
4570        let before = renders.get();
4571        for _ in 0..4 {
4572            redraw(cx);
4573        }
4574        assert_eq!(renders.get(), before, "the reader rendered again");
4575    }
4576
4577    #[gpui::test]
4578    fn retained_selection_state_releases_and_does_not_resurrect_selection(cx: &mut TestAppContext) {
4579        let (view, cx) = cx.add_window_view(|_, cx| ToggleSelectionElementView {
4580            enabled: true,
4581            selection: TextSelectionHandle::new("local", cx),
4582        });
4583        let selection = cx.update(|_, cx| view.read(cx).selection.clone());
4584        cx.update(|window, cx| {
4585            let _ = window.draw(cx);
4586            selection.set_local_selection(true, cx);
4587            assert!(TextSelection::has_selection(window, cx));
4588
4589            window.simulate_next_frame(cx);
4590            assert!(TextSelection::has_selection(window, cx));
4591            let _ = window.draw(cx);
4592            assert!(TextSelection::has_selection(window, cx));
4593        });
4594        view.update(cx, |view, cx| {
4595            view.enabled = false;
4596            cx.notify();
4597        });
4598        cx.update(|window, cx| {
4599            let _ = window.draw(cx);
4600        });
4601        cx.update(|window, cx| {
4602            window.simulate_next_frame(cx);
4603        });
4604        cx.update(|window, cx| {
4605            window.simulate_next_frame(cx);
4606        });
4607        cx.run_until_parked();
4608        cx.update(|window, cx| {
4609            assert!(!TextSelection::has_selection(window, cx));
4610            assert_eq!(TextSelection::selected_text(window, cx), "");
4611            assert!(!selection.has_local_selection(cx));
4612        });
4613
4614        view.update(cx, |view, cx| {
4615            view.enabled = true;
4616            cx.notify();
4617        });
4618        cx.update(|window, cx| {
4619            let _ = window.draw(cx);
4620            assert!(!TextSelection::has_selection(window, cx));
4621            assert_eq!(TextSelection::selected_text(window, cx), "");
4622        });
4623    }
4624
4625    #[gpui::test]
4626    fn mounted_selection_element_does_not_keep_an_idle_frame_queue_alive(cx: &mut TestAppContext) {
4627        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
4628        cx.update(|window, cx| {
4629            let _ = window.draw(cx);
4630            assert_eq!(window.simulate_next_frame(cx), 0);
4631            assert_eq!(window.simulate_next_frame(cx), 0);
4632            assert!(live_text_selection_state(window, cx).is_some());
4633        });
4634    }
4635
4636    #[gpui::test]
4637    fn selection_element_initializes_suppression_and_respects_bubble_suppression(
4638        cx: &mut TestAppContext,
4639    ) {
4640        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
4641        cx.update(|window, cx| {
4642            let _ = window.draw(cx);
4643        });
4644        cx.simulate_mouse_down(
4645            point(px(1.), px(1.)),
4646            MouseButton::Left,
4647            gpui::Modifiers::default(),
4648        );
4649        cx.simulate_mouse_up(
4650            point(px(1.), px(1.)),
4651            MouseButton::Left,
4652            gpui::Modifiers::default(),
4653        );
4654        cx.update(|window, cx| {
4655            assert!(GlobalState::is_text_selection_suppressed(cx));
4656            assert!(!TextSelection::has_selection(window, cx));
4657        });
4658    }
4659
4660    #[gpui::test]
4661    fn frame_sweep_keeps_a_participant_registered_before_the_selection_element_paints(
4662        cx: &mut TestAppContext,
4663    ) {
4664        cx.update(|cx| {
4665            let mut selection_state = WindowSelectionState::default();
4666            let participant = FakeParticipant::new("painted first", cx);
4667            participant.register(
4668                &mut selection_state,
4669                0.,
4670                TextSelectionScopeId::default(),
4671                0,
4672                cx,
4673            );
4674            selection_state.begin(point(px(1.), px(1.)), false, cx);
4675            selection_state.update(point(px(8.), px(1.)), cx);
4676            selection_state.end(cx);
4677
4678            selection_state.finish_frame(cx);
4679
4680            assert_eq!(selection_state.selected_text(cx), "painted first");
4681            assert!(participant.selection.snapshot(cx).is_some());
4682        });
4683    }
4684
4685    #[gpui::test]
4686    fn two_selection_elements_schedule_only_one_post_frame_sweep(cx: &mut TestAppContext) {
4687        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
4688            selection: TextSelectionHandle::new("once", cx),
4689        });
4690        cx.update(|window, cx| {
4691            let selection_state = WindowSelectionState::ensure(window, cx);
4692            let selection = view.read(cx).selection.clone();
4693            selection_state.update(cx, |selection_state, cx| {
4694                FakeParticipant { selection }.register(
4695                    selection_state,
4696                    0.,
4697                    TextSelectionScopeId::default(),
4698                    0,
4699                    cx,
4700                );
4701                selection_state.begin(point(px(1.), px(1.)), false, cx);
4702                selection_state.update(point(px(8.), px(1.)), cx);
4703                selection_state.end(cx);
4704            });
4705
4706            let _ = window.draw(cx);
4707            window.simulate_next_frame(cx);
4708
4709            let items = selection_state.read(cx).copy_items(cx);
4710            assert_eq!(resolve_copy_items(items, cx), "once");
4711        });
4712    }
4713
4714    #[gpui::test]
4715    fn duplicate_selection_elements_gate_real_pointer_gestures_and_reentrant_clear(
4716        cx: &mut TestAppContext,
4717    ) {
4718        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
4719            selection: TextSelectionHandle::new("once", cx),
4720        });
4721        let clear_count = Rc::new(Cell::new(0));
4722        cx.update(|window, cx| {
4723            let state = WindowSelectionState::ensure(window, cx);
4724            let state_for_clear = state.clone();
4725            let count = clear_count.clone();
4726            let selection = view.read(cx).selection.clone();
4727            selection
4728                .subscribe(
4729                    move |event, cx| {
4730                        if matches!(event, TextSelectionEvent::Cleared) {
4731                            count.set(count.get() + 1);
4732                            let _ = state_for_clear.read(cx).snapshot();
4733                        }
4734                    },
4735                    cx,
4736                )
4737                .detach();
4738            let _ = window.draw(cx);
4739        });
4740
4741        cx.simulate_mouse_down(
4742            point(px(10.), px(10.)),
4743            MouseButton::Left,
4744            gpui::Modifiers::default(),
4745        );
4746        cx.simulate_mouse_up(
4747            point(px(10.), px(10.)),
4748            MouseButton::Left,
4749            gpui::Modifiers::default(),
4750        );
4751        cx.simulate_mouse_down(
4752            point(px(70.), px(10.)),
4753            MouseButton::Left,
4754            gpui::Modifiers {
4755                shift: true,
4756                ..Default::default()
4757            },
4758        );
4759        cx.simulate_mouse_up(
4760            point(px(70.), px(10.)),
4761            MouseButton::Left,
4762            gpui::Modifiers::default(),
4763        );
4764        cx.update(|window, cx| assert!(TextSelection::has_selection(window, cx)));
4765
4766        cx.simulate_mouse_down(
4767            point(px(15.), px(10.)),
4768            MouseButton::Left,
4769            gpui::Modifiers::default(),
4770        );
4771        cx.simulate_mouse_move(
4772            point(px(85.), px(10.)),
4773            Some(MouseButton::Left),
4774            gpui::Modifiers::default(),
4775        );
4776        cx.simulate_mouse_up(
4777            point(px(85.), px(10.)),
4778            MouseButton::Left,
4779            gpui::Modifiers::default(),
4780        );
4781        cx.update(|window, cx| assert!(TextSelection::has_selection(window, cx)));
4782        assert_eq!(clear_count.get(), 3);
4783    }
4784
4785    #[gpui::test]
4786    fn selection_layer_handles_real_double_and_triple_click_events(cx: &mut TestAppContext) {
4787        let (text, layout) = laid_out_runs(&["alpha beta"], cx).pop().unwrap();
4788        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
4789            selection: TextSelectionHandle::new("", cx),
4790        });
4791        cx.update(|window, cx| {
4792            let _ = window.draw(cx);
4793            let selection = view.read(cx).selection.clone();
4794            selection.resolve_content_key_with(|_, _| Some(TextSelectionContentKey::new(17)), cx);
4795            selection.update_runs(&[text_run(0, text.clone(), layout.clone())], cx);
4796        });
4797
4798        let position = layout.position_for_index(7).unwrap();
4799        cx.simulate_event(MouseDownEvent {
4800            position,
4801            modifiers: gpui::Modifiers::default(),
4802            button: MouseButton::Left,
4803            click_count: 2,
4804            first_mouse: false,
4805        });
4806        cx.simulate_event(MouseUpEvent {
4807            position,
4808            modifiers: gpui::Modifiers::default(),
4809            button: MouseButton::Left,
4810            click_count: 2,
4811        });
4812        cx.update(|window, cx| {
4813            let selection = view.read(cx).selection.clone();
4814            selection.update_runs(&[text_run(0, text.clone(), layout.clone())], cx);
4815            assert_eq!(TextSelection::selected_text(window, cx), "beta");
4816            let snapshot = selection.snapshot(cx).unwrap();
4817            assert_eq!(
4818                snapshot.anchor().content_key(),
4819                Some(TextSelectionContentKey::new(17))
4820            );
4821            assert_eq!(
4822                snapshot.cursor().content_key(),
4823                Some(TextSelectionContentKey::new(17))
4824            );
4825        });
4826
4827        cx.simulate_event(MouseDownEvent {
4828            position,
4829            modifiers: gpui::Modifiers::default(),
4830            button: MouseButton::Left,
4831            click_count: 3,
4832            first_mouse: false,
4833        });
4834        cx.simulate_event(MouseUpEvent {
4835            position,
4836            modifiers: gpui::Modifiers::default(),
4837            button: MouseButton::Left,
4838            click_count: 3,
4839        });
4840        cx.update(|window, cx| {
4841            let selection = view.read(cx).selection.clone();
4842            selection.update_runs(&[text_run(0, text, layout)], cx);
4843            assert_eq!(TextSelection::selected_text(window, cx), "alpha beta");
4844        });
4845    }
4846}