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, ScrollEvent, SharedString, Style,
14    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, caret_line_box,
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    paint_order: bool,
224    text_bounds: Vec<Bounds<Pixels>>,
225    self_scroll: bool,
226    selection_edges: Option<(Bounds<Pixels>, Bounds<Pixels>)>,
227    rendered: Option<WeakEntity<RenderedMarker>>,
228}
229
230impl TextSelectionRegistration {
231    /// Creates a registration with default scope, order, and scroll offset.
232    pub fn new(hitbox: Hitbox, bounds: Bounds<Pixels>) -> Self {
233        Self {
234            hitbox,
235            bounds,
236            scroll_offset: Point::default(),
237            scope: TextSelectionScopeId::default(),
238            document_order: 0,
239            paint_order: false,
240            text_bounds: Vec::new(),
241            self_scroll: false,
242            selection_edges: None,
243            rendered: None,
244        }
245    }
246
247    /// Ties this registration to the retained state of the element reporting
248    /// it, so that a frame replayed from that element's cached view keeps it.
249    ///
250    /// Call this from the reporting element's `prepaint` or `paint`. A
251    /// registration made outside a drawing element cannot be tied to one and
252    /// is swept the first frame it misses, as every registration used to be.
253    pub fn with_rendered_element(
254        mut self,
255        participant: &TextSelectionHandle,
256        window: &mut Window,
257        cx: &mut App,
258    ) -> Self {
259        self.rendered = Some(
260            window
261                .use_keyed_state(
262                    ElementId::NamedInteger(
263                        "text-selection-participant".into(),
264                        participant.entity_id().as_u64(),
265                    ),
266                    cx,
267                    |_, _| RenderedMarker,
268                )
269                .downgrade(),
270        );
271        self
272    }
273
274    /// Whether the element that reported this registration is still part of
275    /// the window, even if it did not paint the frame that just finished.
276    fn is_rendered(&self) -> bool {
277        self.rendered
278            .as_ref()
279            .is_some_and(|marker| marker.upgrade().is_some())
280    }
281
282    /// Marks a participant that scrolls its own content in response to
283    /// [`TextSelectionEvent::AutoScroll`]. Drag auto-scroll then drives it
284    /// directly, measured against its own bounds, instead of synthesizing a
285    /// wheel event for the nearest scrollable ancestor.
286    pub(crate) fn with_self_scroll(mut self, self_scroll: bool) -> Self {
287        self.self_scroll = self_scroll;
288        self
289    }
290
291    /// Sets the participant's content scroll offset.
292    pub fn with_scroll_offset(mut self, scroll_offset: Point<Pixels>) -> Self {
293        self.scroll_offset = scroll_offset;
294        self
295    }
296
297    /// Sets the opaque selection scope.
298    pub fn with_scope(mut self, scope: TextSelectionScopeId) -> Self {
299        self.scope = scope;
300        self
301    }
302
303    /// Sets the stable logical document order.
304    pub fn with_document_order(mut self, document_order: u64) -> Self {
305        self.document_order = document_order;
306        self
307    }
308
309    /// Uses the order of the TextViews in a complete painted frame. A cached
310    /// view keeps that order while only some of its siblings paint again.
311    pub(crate) fn in_paint_order(mut self) -> Self {
312        self.paint_order = true;
313        self
314    }
315
316    /// Sets the glyph-bearing bounds used to reject blank-only gestures.
317    pub fn with_text_bounds(mut self, text_bounds: Vec<Bounds<Pixels>>) -> Self {
318        self.text_bounds = text_bounds;
319        self
320    }
321
322    /// Sets where the participant painted the two ends of its selection: the
323    /// caret line box before its first selected character and the one after
324    /// its last, in window coordinates. The touch handles are drawn there.
325    pub fn with_selection_edges(mut self, start: Bounds<Pixels>, end: Bounds<Pixels>) -> Self {
326        self.selection_edges = Some((start, end));
327        self
328    }
329
330    /// Returns the participant hitbox.
331    pub fn hitbox(&self) -> &Hitbox {
332        &self.hitbox
333    }
334
335    /// Returns the participant's window-coordinate bounds.
336    pub const fn bounds(&self) -> Bounds<Pixels> {
337        self.bounds
338    }
339
340    /// Returns the participant's content scroll offset.
341    pub const fn scroll_offset(&self) -> Point<Pixels> {
342        self.scroll_offset
343    }
344
345    /// Returns the opaque selection scope.
346    pub const fn scope(&self) -> TextSelectionScopeId {
347        self.scope
348    }
349
350    /// Returns the stable logical document order.
351    pub const fn document_order(&self) -> u64 {
352        self.document_order
353    }
354
355    /// Returns the glyph-bearing bounds used to reject blank-only gestures.
356    pub fn text_bounds(&self) -> &[Bounds<Pixels>] {
357        &self.text_bounds
358    }
359
360    /// Returns the caret line boxes at the participant's selection ends.
361    pub const fn selection_edges(&self) -> Option<(Bounds<Pixels>, Bounds<Pixels>)> {
362        self.selection_edges
363    }
364}
365
366/// Laid-out text reported by a plain selection participant during paint.
367#[derive(Clone)]
368pub struct TextSelectionRun {
369    /// Logical order within the containing participant.
370    document_order: u64,
371    /// The exact text used to produce `layout`.
372    text: SharedString,
373    /// Laid-out glyph geometry in window coordinates.
374    layout: TextLayout,
375    /// The run's window-coordinate paint bounds.
376    bounds: Bounds<Pixels>,
377    /// The boundary a triple-click drag snaps to in this run.
378    triple_click: TripleClickSelection,
379}
380
381#[derive(Clone, Copy)]
382enum TripleClickSelection {
383    LogicalLine,
384    Paragraph,
385    Row(Bounds<Pixels>),
386}
387
388impl TextSelectionRun {
389    /// Creates a laid-out text run.
390    pub fn new(text: impl Into<SharedString>, layout: TextLayout, bounds: Bounds<Pixels>) -> Self {
391        Self {
392            document_order: 0,
393            text: text.into(),
394            layout,
395            bounds,
396            triple_click: TripleClickSelection::LogicalLine,
397        }
398    }
399
400    /// Sets the run's logical order within the participant.
401    pub const fn with_document_order(mut self, document_order: u64) -> Self {
402        self.document_order = document_order;
403        self
404    }
405
406    /// Uses TextView's triple-click boundary: the whole paragraph, or the
407    /// laid-out row when this run is one piece of an inline flow.
408    pub(crate) const fn with_paragraph(mut self, row: Option<Bounds<Pixels>>) -> Self {
409        self.triple_click = match row {
410            Some(row) => TripleClickSelection::Row(row),
411            None => TripleClickSelection::Paragraph,
412        };
413        self
414    }
415
416    /// Returns the run's logical order within its participant.
417    pub const fn document_order(&self) -> u64 {
418        self.document_order
419    }
420
421    /// Returns the exact text used to produce the layout.
422    pub fn text(&self) -> &SharedString {
423        &self.text
424    }
425
426    /// Returns the laid-out glyph geometry.
427    pub fn layout(&self) -> &TextLayout {
428        &self.layout
429    }
430
431    /// Returns the run's window-coordinate paint bounds.
432    pub const fn bounds(&self) -> Bounds<Pixels> {
433        self.bounds
434    }
435}
436
437/// Selection projected onto a participant's laid-out text runs.
438#[derive(Clone, Debug, Default, PartialEq, Eq)]
439pub struct TextSelectionProjection {
440    /// Selected UTF-8 byte ranges paired with the input runs.
441    ranges: Vec<Option<Range<usize>>>,
442    /// Whether the participant participates in the current selection.
443    is_active: bool,
444}
445
446impl TextSelectionProjection {
447    /// Returns selected UTF-8 byte ranges paired with the input runs.
448    pub fn ranges(&self) -> &[Option<Range<usize>>] {
449        &self.ranges
450    }
451
452    /// Returns whether the participant participates in the selection.
453    pub const fn is_active(&self) -> bool {
454        self.is_active
455    }
456}
457
458/// Projects a participant selection snapshot onto laid-out plain-text runs.
459///
460/// The returned states retain the input order so callers can pair every state
461/// with its run. The ranges are always character boundaries; `order` is used
462/// only when a participant caches selected text for copying.
463fn project_ranges(
464    snapshot: Option<TextSelectionSnapshot>,
465    runs: &[TextSelectionRun],
466) -> TextSelectionProjection {
467    let Some(snapshot) = snapshot else {
468        return TextSelectionProjection {
469            ranges: vec![None; runs.len()],
470            is_active: false,
471        };
472    };
473    let Some(window_points) = snapshot.window_points() else {
474        return TextSelectionProjection {
475            ranges: vec![None; runs.len()],
476            is_active: true,
477        };
478    };
479
480    TextSelectionProjection {
481        ranges: runs
482            .iter()
483            .map(|run| selection_range_for_run(run, window_points.anchor, window_points.cursor))
484            .collect(),
485        is_active: true,
486    }
487}
488
489fn selection_range_for_run(
490    run: &TextSelectionRun,
491    selection_start: Point<Pixels>,
492    selection_end: Point<Pixels>,
493) -> Option<Range<usize>> {
494    if run.text.len() != run.layout.len() {
495        return None;
496    }
497
498    if run.text.is_empty() {
499        return None;
500    }
501
502    let line_height = run.layout.line_height();
503    // Each character is tested with its row's top and height, so a run whose
504    // rows all miss the band, or all lie strictly inside it with no endpoint
505    // on any row, has the same answer for every character. Decide those
506    // without the walk below, which scans the layout twice per character.
507    let (rows_top, rows_bottom) = text_rows_extent(&run.layout, line_height);
508    let band_top = selection_start.y.min(selection_end.y);
509    let band_bottom = selection_start.y.max(selection_end.y);
510    if rows_bottom <= band_top || rows_top > band_bottom {
511        return None;
512    }
513    if band_top < rows_top && band_bottom >= rows_bottom {
514        return Some(0..run.text.len());
515    }
516
517    let mut range = None;
518    for (offset, character) in run.text.char_indices() {
519        let next_offset = offset + character.len_utf8();
520        let Some(position) = run.layout.position_for_index(offset) else {
521            continue;
522        };
523
524        let char_width = run
525            .layout
526            .position_for_index(next_offset)
527            .filter(|next| next.y == position.y)
528            .map_or_else(|| line_height.half(), |next| next.x - position.x);
529
530        if point_in_selection_band(
531            position,
532            char_width,
533            selection_start,
534            selection_end,
535            line_height,
536        ) {
537            range.get_or_insert(offset..offset).end = next_offset;
538        }
539    }
540    range
541}
542
543/// The top of the first laid-out row of `text_layout` and the bottom of its
544/// last one, each row `line_height` tall as a selection band test sees it.
545///
546/// Both ends are accumulated the way [`TextLayout::position_for_index`] places
547/// rows, so comparisons against them agree with a per-character walk to the
548/// bit. The last laid-out row may hold no character such a walk tests (a
549/// trailing empty line, or a row whose only character is placed at the end of
550/// the row before it); callers treat the extent as covering, never as exact.
551pub(crate) fn text_rows_extent(text_layout: &TextLayout, line_height: Pixels) -> (Pixels, Pixels) {
552    let top = text_layout.bounds().top();
553    let layout_line_height = text_layout.line_height();
554    let lines = text_layout.line_layouts();
555    let mut last_line_top = top;
556    for line in lines.iter().take(lines.len().saturating_sub(1)) {
557        last_line_top += line.size(layout_line_height).height;
558    }
559    let last_row_top = lines.last().map_or(top, |line| {
560        last_line_top + line.wrap_boundaries.len() as f32 * layout_line_height
561    });
562    (top, last_row_top + line_height)
563}
564
565/// The carets at the two ends of what `click_count` clicks at `position`
566/// select: the word under it for two, its line or paragraph for three.
567fn carets_for_multi_click(
568    runs: &[TextSelectionRun],
569    position: Point<Pixels>,
570    click_count: usize,
571) -> Option<(Bounds<Pixels>, Bounds<Pixels>)> {
572    let run = runs.iter().find(|run| run.bounds.contains(&position))?;
573    if run.text.len() != run.layout.len() {
574        return None;
575    }
576    let offset = run.layout.index_for_position(position).ok()?;
577    unit_carets(run, offset, click_count)
578}
579
580/// The carets at the two ends of the word (`click_count` 2) or the run's
581/// triple-click boundary (3 or more) at the byte `offset` of `run`.
582fn unit_carets(
583    run: &TextSelectionRun,
584    offset: usize,
585    click_count: usize,
586) -> Option<(Bounds<Pixels>, Bounds<Pixels>)> {
587    let range = match click_count {
588        2 => word_range_at(&run.text, offset)?,
589        3.. => match run.triple_click {
590            TripleClickSelection::LogicalLine => line_range_at(&run.text, offset),
591            TripleClickSelection::Paragraph => 0..run.text.len(),
592            TripleClickSelection::Row(row) => {
593                return Some((
594                    caret_line_box(row.origin, row.size.height),
595                    caret_line_box(row.top_right(), row.size.height),
596                ));
597            }
598        },
599        _ => return None,
600    };
601    if range.is_empty() {
602        return None;
603    }
604    let line_height = run.layout.line_height();
605    Some((
606        caret_line_box(run.layout.position_for_index(range.start)?, line_height),
607        caret_line_box(run.layout.position_for_index(range.end)?, line_height),
608    ))
609}
610
611fn point_in_selection_band(
612    position: Point<Pixels>,
613    char_width: Pixels,
614    selection_start: Point<Pixels>,
615    selection_end: Point<Pixels>,
616    line_height: Pixels,
617) -> bool {
618    let point_in_line =
619        |point: Point<Pixels>| point.y >= position.y && point.y < position.y + line_height;
620    let top = selection_start.y.min(selection_end.y);
621    let bottom = selection_start.y.max(selection_end.y);
622    let x = position.x + char_width.half();
623
624    if position.y + line_height <= top || position.y > bottom {
625        return false;
626    }
627
628    if point_in_line(selection_start) && point_in_line(selection_end) {
629        let left = selection_start.x.min(selection_end.x);
630        let right = selection_start.x.max(selection_end.x);
631        return x >= left && x <= right;
632    }
633
634    let (top_point, bottom_point) = if selection_start.y < selection_end.y {
635        (selection_start, selection_end)
636    } else {
637        (selection_end, selection_start)
638    };
639    if point_in_line(top_point) {
640        x >= top_point.x
641    } else if point_in_line(bottom_point) {
642        x <= bottom_point.x
643    } else {
644        true
645    }
646}
647
648type FocusCallback = Rc<dyn Fn(&mut Window, &mut App)>;
649type ClearHandler = Rc<dyn Fn(&mut App)>;
650type CopyCallback = Rc<dyn Fn(&mut App) -> String>;
651type ContentKeyResolver = Rc<dyn Fn(Point<Pixels>, &App) -> Option<TextSelectionContentKey>>;
652
653/// Notifications emitted by a text-selection participant.
654#[derive(Clone, Copy, Debug, PartialEq)]
655pub enum TextSelectionEvent {
656    /// The participant's window-selection projection changed.
657    SelectionChanged(Option<TextSelectionSnapshot>),
658    /// The active drag requests vertical auto-scroll, or `None` to stop.
659    AutoScroll(Option<Pixels>),
660    /// Window selection cleared the participant's participant-local state.
661    Cleared,
662    /// The touch selection the participant takes part in changed: it
663    /// appeared, its ends moved, a handle drag began or ended, or it went
664    /// away. The participant paints the handles and lays their hitboxes out
665    /// from the ends it painted last frame, so it renders once more.
666    TouchSelectionChanged,
667}
668
669struct CopyItem {
670    document_order: u64,
671    callback: Option<CopyCallback>,
672    fallback: String,
673}
674
675fn resolve_copy_items(mut items: Vec<CopyItem>, cx: &mut App) -> String {
676    items.sort_by_key(|item| item.document_order);
677    items
678        .into_iter()
679        .map(|item| {
680            item.callback
681                .map(|callback| callback(cx))
682                .unwrap_or(item.fallback)
683        })
684        .filter(|text| !text.trim().is_empty())
685        .collect::<Vec<_>>()
686        .join("\n")
687}
688
689fn dispatch_clear_handlers(handlers: Vec<ClearHandler>, cx: &mut App) {
690    for handler in handlers {
691        handler(cx);
692    }
693}
694
695fn clear_departed_participants(
696    participants: Vec<Entity<SelectableTextState>>,
697    cx: &mut App,
698) -> Vec<ClearHandler> {
699    participants
700        .into_iter()
701        .filter_map(|participant| {
702            participant
703                .read(cx)
704                .has_selection_to_clear()
705                .then(|| participant.update(cx, |state, cx| state.clear_state(cx)))
706                .flatten()
707        })
708        .collect()
709}
710
711struct SelectableTextState {
712    fallback_copy_text: String,
713    window_selection: RefCell<Option<WeakEntity<WindowSelectionState>>>,
714    /// The selected text as the participant last painted it. Painting
715    /// projects it, which is not a change of the participant.
716    projected_copy_text: RefCell<Option<String>>,
717    /// The participant's text as it last painted it, for hit testing.
718    runs: RefCell<Vec<TextSelectionRun>>,
719    local_selection: bool,
720    snapshot: Option<TextSelectionSnapshot>,
721    on_focus: Option<FocusCallback>,
722    clear: Option<ClearHandler>,
723    copy: Option<CopyCallback>,
724    content_key_resolver: Option<ContentKeyResolver>,
725}
726
727impl EventEmitter<TextSelectionEvent> for SelectableTextState {}
728
729impl SelectableTextState {
730    fn new(fallback_copy_text: impl Into<String>) -> Self {
731        Self {
732            fallback_copy_text: fallback_copy_text.into(),
733            window_selection: RefCell::new(None),
734            projected_copy_text: RefCell::new(None),
735            runs: RefCell::new(Vec::new()),
736            local_selection: false,
737            snapshot: None,
738            on_focus: None,
739            clear: None,
740            copy: None,
741            content_key_resolver: None,
742        }
743    }
744
745    /// The current geometry selection snapshot for this participant.
746    fn snapshot(&self) -> Option<TextSelectionSnapshot> {
747        self.snapshot
748    }
749
750    /// Sets the text copied by this participant when it participates in selection.
751    fn set_fallback_copy_text(&mut self, text: impl Into<String>) {
752        self.fallback_copy_text = text.into();
753        *self.projected_copy_text.get_mut() = None;
754    }
755
756    /// Marks participant-local selection (for example select-all) as active.
757    fn set_local_selection(&mut self, active: bool) {
758        self.local_selection = active;
759    }
760
761    /// Projects this participant's current snapshot onto plain-text runs and caches
762    /// their selected substrings for the window selection query.
763    ///
764    /// Call this once per painted run. A snapshot change or
765    /// Clearing window selection invalidates the cache immediately, so copy
766    /// never returns text from a previous projection while waiting to repaint.
767    fn update_runs(&self, runs: &[TextSelectionRun]) -> TextSelectionProjection {
768        *self.runs.borrow_mut() = runs.to_vec();
769        let states = project_ranges(self.snapshot, runs);
770        let mut selected_runs = runs
771            .iter()
772            .zip(states.ranges())
773            .enumerate()
774            .filter_map(|(index, (run, state))| {
775                state.as_ref().map(|range| {
776                    debug_assert!(run.text.is_char_boundary(range.start));
777                    debug_assert!(run.text.is_char_boundary(range.end));
778                    (
779                        run.document_order,
780                        index,
781                        run.text[range.clone()].to_string(),
782                    )
783                })
784            })
785            .collect::<Vec<_>>();
786        selected_runs.sort_by_key(|(order, index, _)| (*order, *index));
787        *self.projected_copy_text.borrow_mut() =
788            Some(selected_runs.into_iter().map(|(_, _, text)| text).collect());
789        states
790    }
791
792    /// Installs the callback which focuses the participant when a drag begins in it.
793    fn set_focus_handler(&mut self, callback: impl Fn(&mut Window, &mut App) + 'static) {
794        self.on_focus = Some(Rc::new(callback));
795    }
796
797    fn clear_with(&mut self, callback: impl Fn(&mut App) + 'static) {
798        self.clear = Some(Rc::new(callback));
799    }
800
801    /// Installs a participant-specific copy projection.
802    fn copy_with(&mut self, callback: impl Fn(&mut App) -> String + 'static) {
803        self.copy = Some(Rc::new(callback));
804    }
805
806    /// Installs a participant-specific lookup for stable virtualized content keys.
807    fn resolve_content_key_with(
808        &mut self,
809        callback: impl Fn(Point<Pixels>, &App) -> Option<TextSelectionContentKey> + 'static,
810    ) {
811        self.content_key_resolver = Some(Rc::new(callback));
812    }
813
814    fn set_snapshot_quietly(&mut self, snapshot: Option<TextSelectionSnapshot>) -> bool {
815        if self.snapshot == snapshot {
816            return false;
817        }
818        self.snapshot = snapshot;
819        *self.projected_copy_text.get_mut() = None;
820        true
821    }
822
823    fn set_snapshot(&mut self, snapshot: Option<TextSelectionSnapshot>, cx: &mut Context<Self>) {
824        if self.set_snapshot_quietly(snapshot) {
825            cx.emit(TextSelectionEvent::SelectionChanged(snapshot));
826        }
827    }
828
829    /// Whether clearing the participant would change anything: it is part
830    /// of the window selection, or selected on its own.
831    fn has_selection_to_clear(&self) -> bool {
832        self.snapshot.is_some() || self.local_selection
833    }
834
835    fn clear_state(&mut self, cx: &mut Context<Self>) -> Option<ClearHandler> {
836        self.snapshot = None;
837        *self.projected_copy_text.get_mut() = None;
838        self.local_selection = false;
839        cx.emit(TextSelectionEvent::Cleared);
840        cx.emit(TextSelectionEvent::SelectionChanged(None));
841        self.clear.clone()
842    }
843
844    fn set_auto_scroll(&self, delta: Option<Pixels>, cx: &mut Context<Self>) {
845        cx.emit(TextSelectionEvent::AutoScroll(delta));
846    }
847
848    fn focus(&self, window: &mut Window, cx: &mut App) {
849        if let Some(callback) = self.on_focus.clone() {
850            window.defer(cx, move |window, cx| callback(window, cx));
851        }
852    }
853
854    fn copy_item(&self, document_order: u64) -> Option<CopyItem> {
855        (self.snapshot.is_some() || self.local_selection).then(|| CopyItem {
856            document_order,
857            callback: self.copy.clone(),
858            fallback: self
859                .projected_copy_text
860                .borrow()
861                .clone()
862                .unwrap_or_else(|| self.fallback_copy_text.clone()),
863        })
864    }
865}
866
867/// The touch handles a participant laid out for a frame, with their hitboxes.
868#[derive(Default)]
869pub struct TouchHandleLayout {
870    hitboxes: Vec<(SelectionEdge, Hitbox)>,
871}
872
873/// A stable, participant-neutral handle for text that participates in window selection.
874#[derive(Clone)]
875pub struct TextSelectionHandle(Entity<SelectableTextState>);
876
877impl TextSelectionHandle {
878    /// Creates a selection participant handle with fallback text for copying.
879    pub fn new(fallback_copy_text: impl Into<String>, cx: &mut App) -> Self {
880        Self(cx.new(|_| SelectableTextState::new(fallback_copy_text)))
881    }
882
883    /// Returns this participant's stable identity.
884    pub fn entity_id(&self) -> EntityId {
885        self.0.entity_id()
886    }
887
888    /// Returns the current geometry selection snapshot for this participant.
889    pub fn snapshot(&self, cx: &App) -> Option<TextSelectionSnapshot> {
890        self.0.read(cx).snapshot()
891    }
892
893    /// Records the anchor cell during prepaint, before any participant paints.
894    pub(crate) fn record_table_cell(&self, bounds: Bounds<Pixels>, cx: &App) {
895        let Some(state) = self.window_selection(cx) else {
896            return;
897        };
898        let state = state.read(cx);
899        if state.touch_select_all {
900            return;
901        }
902        let Some(snapshot) = self.snapshot(cx) else {
903            return;
904        };
905        if snapshot.anchor().entity_id() == Some(self.entity_id())
906            && snapshot
907                .window_points()
908                .is_some_and(|points| bounds.contains(&points.anchor()))
909        {
910            state
911                .table_cell_anchor
912                .set(Some((snapshot.anchor(), bounds)));
913        }
914    }
915
916    /// The current gesture's anchor cell, shared only by participants in its window.
917    pub(crate) fn table_cell_selection(&self, cx: &App) -> Option<Bounds<Pixels>> {
918        let state = self.window_selection(cx)?;
919        let state = state.read(cx);
920        if state.touch_select_all {
921            return None;
922        }
923        let (anchor, bounds) = state.table_cell_anchor.get()?;
924        (self.snapshot(cx)?.anchor() == anchor).then_some(bounds)
925    }
926
927    /// Retires an anchor projection when its participant replaces its content.
928    pub(crate) fn clear_table_cell_selection(&self, cx: &App) {
929        if let Some(state) = self.window_selection(cx) {
930            let state = state.read(cx);
931            if state
932                .table_cell_anchor
933                .get()
934                .is_some_and(|(endpoint, _)| endpoint.entity_id() == Some(self.entity_id()))
935            {
936                state.table_cell_anchor.set(None);
937            }
938        }
939    }
940
941    fn window_selection(&self, cx: &App) -> Option<Entity<WindowSelectionState>> {
942        self.0
943            .read(cx)
944            .window_selection
945            .borrow()
946            .as_ref()?
947            .upgrade()
948    }
949
950    /// Sets the fallback text copied while this participant participates.
951    pub fn set_fallback_copy_text(&self, text: impl Into<String>, cx: &mut App) {
952        self.0
953            .update(cx, |state, _| state.set_fallback_copy_text(text));
954    }
955
956    /// Marks participant-local selection, such as select-all, as active.
957    pub fn set_local_selection(&self, active: bool, cx: &mut App) {
958        self.0
959            .update(cx, |state, _| state.set_local_selection(active));
960    }
961
962    /// Returns whether participant-local selection is active.
963    pub fn has_local_selection(&self, cx: &App) -> bool {
964        self.0.read(cx).local_selection
965    }
966
967    /// Registers this participant and its geometry for the current frame.
968    pub fn register(
969        &self,
970        mut registration: TextSelectionRegistration,
971        window: &mut Window,
972        cx: &mut App,
973    ) {
974        if let Some(scope) = current_text_selection_scope(window.window_handle().window_id(), cx) {
975            registration.scope = scope;
976        }
977        let Some(state) = WindowSelectionState::existing(window, cx) else {
978            return;
979        };
980        *self.0.read(cx).window_selection.borrow_mut() = Some(state.downgrade());
981        state.read(cx).register_quietly(self, registration);
982        let snapshot = state.read(cx).participant_snapshot(self.entity_id());
983        if self.snapshot(cx) != snapshot {
984            // Plain text projects its runs immediately after registering.
985            // Rich text may already have painted, so notify after the frame
986            // as well, when another draw can actually be scheduled.
987            self.0.update(cx, |participant, _| {
988                participant.set_snapshot_quietly(snapshot);
989            });
990            state
991                .read(cx)
992                .draw_changed
993                .borrow_mut()
994                .insert(self.entity_id(), self.downgrade());
995        }
996        schedule_selection_frame(&state, window, cx);
997    }
998
999    /// Projects the current snapshot onto plain-text runs and caches their copy text.
1000    pub fn update_runs(&self, runs: &[TextSelectionRun], cx: &mut App) -> TextSelectionProjection {
1001        self.0.read(cx).update_runs(runs)
1002    }
1003
1004    /// The projected text this participant adds to the window's selected
1005    /// text, or `None` when it adds none or copies through its own callback.
1006    pub(crate) fn projected_copy_text(&self, cx: &App) -> Option<String> {
1007        self.0
1008            .read(cx)
1009            .copy_item(0)
1010            .filter(|item| item.callback.is_none())
1011            .map(|item| item.fallback)
1012    }
1013
1014    // Rich text renders its own selection; retain geometry only for word hit testing.
1015    pub(crate) fn set_hit_test_runs(&self, runs: &[TextSelectionRun], cx: &mut App) {
1016        *self.0.read(cx).runs.borrow_mut() = runs.to_vec();
1017    }
1018
1019    /// Lets a drag from a double or triple click that this participant
1020    /// selected for itself, from the caret `start` to the caret `end` in
1021    /// window coordinates, extend the selection by the same unit through the
1022    /// window selection, as a shift-click after it does.
1023    pub(crate) fn hold_multi_click(
1024        &self,
1025        start: Bounds<Pixels>,
1026        end: Bounds<Pixels>,
1027        click_count: usize,
1028        cx: &mut App,
1029    ) {
1030        let Some(state) = self.window_selection(cx) else {
1031            return;
1032        };
1033        state.update(cx, |state, cx| {
1034            if let Some(unit) = state.unit_of(&self.0, start, end, click_count, cx) {
1035                state.hold_unit(unit, cx);
1036            }
1037        });
1038    }
1039
1040    /// Subscribes to participant selection notifications.
1041    pub fn subscribe(
1042        &self,
1043        mut callback: impl FnMut(&TextSelectionEvent, &mut App) + 'static,
1044        cx: &mut App,
1045    ) -> Subscription {
1046        cx.subscribe(&self.0, move |_, event, cx| callback(event, cx))
1047    }
1048
1049    /// Subscribes `window` to refresh whenever this participant's selection changes.
1050    #[must_use = "retain the subscription or explicitly detach it"]
1051    pub fn refresh_window_on_change(&self, window: &Window, cx: &mut App) -> Subscription {
1052        let window = window.window_handle();
1053        self.subscribe(
1054            move |event, cx| {
1055                if matches!(event, TextSelectionEvent::SelectionChanged(_)) {
1056                    _ = window.update(cx, |_, window, _| window.refresh());
1057                }
1058            },
1059            cx,
1060        )
1061    }
1062
1063    /// Sets the callback which focuses the participant when a drag begins in it.
1064    pub fn focus_with(&self, callback: impl Fn(&mut Window, &mut App) + 'static, cx: &mut App) {
1065        self.0
1066            .update(cx, |state, _| state.set_focus_handler(callback));
1067    }
1068
1069    /// Sets the synchronous participant cleanup command used by window clear.
1070    pub fn clear_with(&self, callback: impl Fn(&mut App) + 'static, cx: &mut App) {
1071        self.0.update(cx, |state, _| state.clear_with(callback));
1072    }
1073
1074    /// Sets a participant-specific copy projection.
1075    pub fn copy_with(&self, callback: impl Fn(&mut App) -> String + 'static, cx: &mut App) {
1076        self.0.update(cx, |state, _| state.copy_with(callback));
1077    }
1078
1079    /// Lays out the touch handles at the ends of this participant's
1080    /// selection, as they were painted last frame, and gives each a hitbox
1081    /// where the finger takes it. Call during the participant's prepaint;
1082    /// hand the result to [`Self::paint_touch_handles`].
1083    pub fn prepaint_touch_handles(&self, window: &mut Window, cx: &App) -> TouchHandleLayout {
1084        let Some(state) = WindowSelectionState::existing(window, cx) else {
1085            return TouchHandleLayout::default();
1086        };
1087        let hitboxes = state
1088            .read(cx)
1089            .touch_handles_of(self.entity_id())
1090            .into_iter()
1091            .map(|(edge, caret)| {
1092                let hitbox = window.insert_hitbox(
1093                    TouchHandle::hit_bounds(edge, caret),
1094                    HitboxBehavior::BlockMouse,
1095                );
1096                (edge, hitbox)
1097            })
1098            .collect();
1099        TouchHandleLayout { hitboxes }
1100    }
1101
1102    /// Paints the touch handles at the ends of this participant's selection,
1103    /// in `color`, where the participant is in the paint order — so whatever
1104    /// is drawn over the text is drawn over its handles too. Call at the end
1105    /// of the participant's paint, after [`Self::register`] for the frame.
1106    ///
1107    /// The handles take the finger's drag from there; Base moves the
1108    /// selection with it.
1109    pub fn paint_touch_handles(
1110        &self,
1111        layout: &TouchHandleLayout,
1112        color: Hsla,
1113        window: &mut Window,
1114        cx: &mut App,
1115    ) {
1116        let Some(state) = WindowSelectionState::existing(window, cx) else {
1117            return;
1118        };
1119        for (edge, caret) in state.read(cx).touch_handles_of(self.entity_id()) {
1120            TouchHandle::paint(edge, caret, color, window);
1121        }
1122        let bounds: Vec<Bounds<Pixels>> = layout
1123            .hitboxes
1124            .iter()
1125            .map(|(_, hitbox)| hitbox.bounds)
1126            .collect();
1127        if !state.read(cx).handles_painted_at(self.entity_id(), &bounds) {
1128            state.update(cx, |state, _| {
1129                state.register_handles(self.entity_id(), bounds)
1130            });
1131        }
1132        for (edge, hitbox) in &layout.hitboxes {
1133            let edge = *edge;
1134            // Touch: the drag is offered on the first touch, before it can
1135            // become a tap, a long press or a pan.
1136            window.on_mouse_event({
1137                let hitbox = hitbox.clone();
1138                let state = state.downgrade();
1139                move |event: &TouchDragEvent, phase, window, cx| {
1140                    if !phase.bubble()
1141                        || event.phase != TouchPhase::Started
1142                        || window.default_prevented()
1143                        || !hitbox.is_hovered(window)
1144                    {
1145                        return;
1146                    }
1147                    let Some(state) = state.upgrade() else {
1148                        return;
1149                    };
1150                    window.prevent_default();
1151                    cx.stop_propagation();
1152                    state.update(cx, |state, cx| {
1153                        state.begin_edge_drag(edge, event.position, window, cx)
1154                    });
1155                    WindowSelectionState::resolve_content_keys(&state, cx);
1156                }
1157            });
1158            // Mouse: the same drag for a pointer.
1159            window.on_mouse_event({
1160                let hitbox = hitbox.clone();
1161                let state = state.downgrade();
1162                move |event: &MouseDownEvent, phase, window, cx| {
1163                    if !phase.bubble()
1164                        || event.button != MouseButton::Left
1165                        || !hitbox.is_hovered(window)
1166                    {
1167                        return;
1168                    }
1169                    let Some(state) = state.upgrade() else {
1170                        return;
1171                    };
1172                    cx.stop_propagation();
1173                    state.update(cx, |state, cx| {
1174                        state.begin_edge_drag(edge, event.position, window, cx)
1175                    });
1176                    WindowSelectionState::resolve_content_keys(&state, cx);
1177                }
1178            });
1179        }
1180    }
1181
1182    /// Sets a participant-specific lookup for stable virtualized content keys.
1183    pub fn resolve_content_key_with(
1184        &self,
1185        callback: impl Fn(Point<Pixels>, &App) -> Option<TextSelectionContentKey> + 'static,
1186        cx: &mut App,
1187    ) {
1188        self.0
1189            .update(cx, |state, _| state.resolve_content_key_with(callback));
1190    }
1191
1192    fn downgrade(&self) -> WeakEntity<SelectableTextState> {
1193        self.0.downgrade()
1194    }
1195}
1196
1197#[derive(Clone)]
1198struct ParticipantRegistration {
1199    participant: WeakEntity<SelectableTextState>,
1200    registration: Rc<TextSelectionRegistration>,
1201    document_order: u64,
1202    generation: u64,
1203}
1204
1205#[derive(Clone)]
1206struct SelectionEndpoint {
1207    participant: Option<WeakEntity<SelectableTextState>>,
1208    point: Point<Pixels>,
1209    inside: bool,
1210    inside_text: bool,
1211    content_key: Option<TextSelectionContentKey>,
1212    content_key_resolver: Option<(ContentKeyResolver, Point<Pixels>)>,
1213}
1214
1215impl SelectionEndpoint {
1216    fn snapshot(&self) -> TextSelectionEndpoint {
1217        let snapshot = TextSelectionEndpoint::new(self.entity_id(), self.point);
1218        if let Some(content_key) = self.content_key {
1219            snapshot.with_content_key(content_key)
1220        } else {
1221            snapshot
1222        }
1223    }
1224
1225    fn resolve(
1226        &self,
1227        participants: &HashMap<EntityId, ParticipantRegistration>,
1228    ) -> Option<Point<Pixels>> {
1229        let participant = self.participant.as_ref()?;
1230        let registration = participants.get(&participant.entity_id())?;
1231        participant.upgrade()?;
1232        Some(
1233            self.point
1234                + registration.registration.scroll_offset
1235                + registration.registration.bounds.origin,
1236        )
1237    }
1238
1239    fn entity_id(&self) -> Option<EntityId> {
1240        self.participant
1241            .as_ref()
1242            .map(|participant| participant.entity_id())
1243    }
1244
1245    /// This endpoint moved by `delta` within its participant, its content
1246    /// key to be resolved again.
1247    fn moved(&self, delta: Point<Pixels>) -> Self {
1248        let point = self.point + delta;
1249        Self {
1250            point,
1251            content_key: None,
1252            content_key_resolver: self
1253                .content_key_resolver
1254                .clone()
1255                .map(|(resolver, _)| (resolver, point)),
1256            ..self.clone()
1257        }
1258    }
1259}
1260
1261/// The word or line a double or triple click selected. A drag from the
1262/// click, and a shift-click after it, extend the selection by the same unit,
1263/// and it stays selected whichever way they go.
1264#[derive(Clone)]
1265struct SelectionUnit {
1266    click_count: usize,
1267    start: SelectionEndpoint,
1268    end: SelectionEndpoint,
1269    /// The heights of the rows the unit starts and ends on.
1270    row_heights: (Pixels, Pixels),
1271}
1272
1273/// What a long press left behind: the handles and the edit menu.
1274///
1275/// The presentation layer draws both and reports their bounds each frame, so
1276/// that a press on a handle or a menu item is not taken for a press on the
1277/// text underneath, which would clear the very selection they belong to.
1278#[derive(Default)]
1279struct TouchSelection {
1280    /// The current selection was made by touch and carries handles.
1281    active: bool,
1282    menu_open: bool,
1283    drag: Option<EdgeDrag>,
1284    /// Where the menu was painted this frame, and the frame before: a press
1285    /// arrives between frames, and the surfaces may have moved in the one
1286    /// that has not painted yet.
1287    ui_bounds: Vec<Bounds<Pixels>>,
1288    previous_ui_bounds: Vec<Bounds<Pixels>>,
1289    /// Where each participant last painted its handles. A participant drawn
1290    /// from an earlier frame, which does not paint again, still shows its
1291    /// handles where it painted them; one that paints again replaces them.
1292    handle_bounds: HashMap<EntityId, Vec<Bounds<Pixels>>>,
1293    /// The ends as last laid out: `(start, end, start_visible, end_visible)`.
1294    /// A frame in which no participant paints a selection — the cursor sits
1295    /// between two characters mid-drag — would otherwise lose the handle the
1296    /// finger holds, and with it the rest of the drag.
1297    last_edges: std::cell::Cell<Option<(Bounds<Pixels>, Bounds<Pixels>, bool, bool)>>,
1298}
1299
1300impl TouchSelection {
1301    fn covers(&self, position: Point<Pixels>) -> bool {
1302        self.active
1303            && self
1304                .ui_bounds
1305                .iter()
1306                .chain(&self.previous_ui_bounds)
1307                .chain(self.handle_bounds.values().flatten())
1308                .any(|bounds| bounds.contains(&position))
1309    }
1310
1311    /// Whether the handles or the menu were painted in either of the last
1312    /// two frames, which [`Self::begin_frame`] has to shift.
1313    fn painted_any(&self) -> bool {
1314        !self.ui_bounds.is_empty() || !self.previous_ui_bounds.is_empty()
1315    }
1316
1317    /// A new frame begins: what was painted last frame is kept one frame more.
1318    fn begin_frame(&mut self) {
1319        self.previous_ui_bounds = std::mem::take(&mut self.ui_bounds);
1320    }
1321
1322    /// Drops the handles and the menu; returns whether there were any.
1323    fn reset(&mut self) -> bool {
1324        let had = self.active;
1325        self.handle_bounds.clear();
1326        self.active = false;
1327        self.menu_open = false;
1328        self.drag = None;
1329        self.last_edges.set(None);
1330        had
1331    }
1332}
1333
1334/// Automatic order is committed only after all live TextViews have painted.
1335/// A partial frame is a subsequence of that order, not a new document.
1336#[derive(Default)]
1337struct ParticipantPaintOrder {
1338    next: u64,
1339    painted: Vec<EntityId>,
1340    last: Option<u64>,
1341    dirty: bool,
1342    refresh_requested: bool,
1343}
1344
1345/// Window-local generic text-selection state.
1346#[derive(Default)]
1347struct WindowSelectionState {
1348    /// What each participant painted this frame, or last painted. Painting
1349    /// reports it without changing the selection: see
1350    /// [`WindowSelectionState::register_quietly`].
1351    participants: RefCell<HashMap<EntityId, ParticipantRegistration>>,
1352    active_scope: TextSelectionScopeId,
1353    /// A prepaint projection owned by this window and reset with its selection.
1354    table_cell_anchor: Cell<Option<(TextSelectionEndpoint, Bounds<Pixels>)>>,
1355    touch_select_all: bool,
1356    anchor: Option<SelectionEndpoint>,
1357    cursor: Option<SelectionEndpoint>,
1358    pending_extension_anchor: Option<SelectionEndpoint>,
1359    unit: Option<SelectionUnit>,
1360    is_selecting: bool,
1361    did_hit_text: bool,
1362    frame_generation: Cell<u64>,
1363    finish_frame_scheduled: Cell<bool>,
1364    layer_painted: Cell<bool>,
1365    paint_order: RefCell<ParticipantPaintOrder>,
1366    /// Participants given an updated snapshot while drawing. Their observers
1367    /// run after the frame, including adapters which registered after paint.
1368    draw_changed: RefCell<HashMap<EntityId, WeakEntity<SelectableTextState>>>,
1369    refresh_held_cursor: Cell<bool>,
1370    touch_edges_moved: Cell<bool>,
1371    #[cfg(test)]
1372    snapshot_visits: Cell<usize>,
1373    mouse_down_prepared: bool,
1374    auto_scroll: AutoScroll,
1375    /// Where the anchor's text was when the last synthetic wheel went out,
1376    /// and how many went out without moving it. A container at its end
1377    /// cannot scroll further; pushing it on would only make one that
1378    /// bounces stretch and snap back on every tick.
1379    auto_scroll_stall: (Option<(Bounds<Pixels>, Point<Pixels>)>, u8),
1380    touch: TouchSelection,
1381    /// This entity, so that touch changes can notify observers from paths that
1382    /// only hold an [`App`].
1383    entity_id: Option<EntityId>,
1384}
1385
1386impl WindowSelectionState {
1387    fn resolve_content_keys(state: &Entity<Self>, cx: &mut App) {
1388        let pending = state.read_with(cx, |state, _| {
1389            [
1390                state
1391                    .anchor
1392                    .as_ref()
1393                    .and_then(|endpoint| endpoint.content_key_resolver.clone()),
1394                state
1395                    .cursor
1396                    .as_ref()
1397                    .and_then(|endpoint| endpoint.content_key_resolver.clone()),
1398            ]
1399        });
1400        // Nothing to resolve and no selection to publish: an event that
1401        // changed nothing does not change the state either.
1402        if pending.iter().all(Option::is_none) && !state.read(cx).is_active() {
1403            return;
1404        }
1405        let resolved =
1406            pending.map(|pending| pending.and_then(|(callback, point)| callback(point, cx)));
1407        state.update(cx, |state, cx| {
1408            if let (Some(endpoint), Some(key)) = (state.anchor.as_mut(), resolved[0]) {
1409                endpoint.content_key = Some(key);
1410                endpoint.content_key_resolver = None;
1411            }
1412            if let (Some(endpoint), Some(key)) = (state.cursor.as_mut(), resolved[1]) {
1413                endpoint.content_key = Some(key);
1414                endpoint.content_key_resolver = None;
1415            }
1416            state.publish_snapshots(cx);
1417        });
1418    }
1419    fn acquire(window_id: gpui::WindowId, cx: &mut App) -> Entity<Self> {
1420        if !cx.has_global::<SelectionStateRegistry>() {
1421            cx.set_global(SelectionStateRegistry::default());
1422        }
1423        if let Some(state) = cx
1424            .global::<SelectionStateRegistry>()
1425            .0
1426            .get(&window_id)
1427            .and_then(WeakEntity::upgrade)
1428        {
1429            return state;
1430        }
1431
1432        let active_scope = if cx.has_global::<PendingTextSelectionScopes>() {
1433            cx.global_mut::<PendingTextSelectionScopes>()
1434                .0
1435                .remove(&window_id)
1436                .unwrap_or_default()
1437        } else {
1438            TextSelectionScopeId::default()
1439        };
1440
1441        let state = cx.new(move |cx| {
1442            let entity_id = cx.entity_id();
1443            cx.on_release(move |state: &mut WindowSelectionState, cx| {
1444                let handlers = state.clear_state(cx);
1445                if cx.has_global::<SelectionStateRegistry>() {
1446                    let registry = &mut cx.global_mut::<SelectionStateRegistry>().0;
1447                    if registry
1448                        .get(&window_id)
1449                        .is_some_and(|state| state.entity_id() == entity_id)
1450                    {
1451                        registry.remove(&window_id);
1452                    }
1453                }
1454                if !handlers.is_empty() {
1455                    cx.defer(move |cx| dispatch_clear_handlers(handlers, cx));
1456                }
1457            })
1458            .detach();
1459            Self {
1460                active_scope,
1461                entity_id: Some(entity_id),
1462                ..Self::default()
1463            }
1464        });
1465        cx.global_mut::<SelectionStateRegistry>()
1466            .0
1467            .insert(window_id, state.downgrade());
1468        state
1469    }
1470
1471    #[cfg(test)]
1472    fn ensure(window: &Window, cx: &mut App) -> Entity<Self> {
1473        Self::acquire(window.window_handle().window_id(), cx)
1474    }
1475
1476    fn existing(window: &Window, cx: &App) -> Option<Entity<Self>> {
1477        if !cx.has_global::<SelectionStateRegistry>() {
1478            return None;
1479        }
1480        cx.global::<SelectionStateRegistry>()
1481            .0
1482            .get(&window.window_handle().window_id())
1483            .and_then(WeakEntity::upgrade)
1484    }
1485
1486    /// Updates the active scope. Participants from other scopes cannot participate.
1487    #[cfg(test)]
1488    fn set_active_scope(&mut self, scope: TextSelectionScopeId, cx: &mut App) {
1489        let handlers = self.set_active_scope_state(scope, cx);
1490        dispatch_clear_handlers(handlers, cx);
1491    }
1492
1493    fn set_active_scope_state(
1494        &mut self,
1495        scope: TextSelectionScopeId,
1496        cx: &mut App,
1497    ) -> Vec<ClearHandler> {
1498        if self.active_scope == scope {
1499            return Vec::new();
1500        }
1501        let handlers = self.clear_state(cx);
1502        self.active_scope = scope;
1503        self.publish_snapshots(cx);
1504        handlers
1505    }
1506
1507    /// Sweeps participants after a rendered frame has completed.
1508    ///
1509    /// Registrations are stamped with the current generation while any sibling
1510    /// is painting. Sweeping only after paint makes registration independent of
1511    /// whether a participant or the lifecycle element paints first.
1512    ///
1513    /// A missed generation alone does not mean the participant left the window:
1514    /// a cached view replays its recorded frame, painting the same text at the
1515    /// same place without running any of its elements. Such a participant keeps
1516    /// the registration it last reported, which still describes what is on
1517    /// screen; only one whose element GPUI has dropped is swept.
1518    ///
1519    /// Removing an endpoint also retires the window's gesture. Clearing only
1520    /// the participant snapshot would restore that selection if its retained
1521    /// entity were registered again.
1522    #[cfg(test)]
1523    fn finish_frame(&mut self, cx: &mut App) -> Vec<ClearHandler> {
1524        let stale = self.stale_participants(true);
1525        let mut handlers = if self.endpoint_departed(&stale) {
1526            self.clear_state(cx)
1527        } else {
1528            Vec::new()
1529        };
1530        let departed = self.remove_stale_participants(stale);
1531        handlers.extend(clear_departed_participants(departed, cx));
1532        self.finish_registrations();
1533        self.publish_snapshots(cx);
1534        handlers
1535    }
1536
1537    fn stale_participants(
1538        &self,
1539        full_frame: bool,
1540    ) -> Vec<(EntityId, WeakEntity<SelectableTextState>)> {
1541        let generation = self.frame_generation.get();
1542        self.participants
1543            .borrow()
1544            .iter()
1545            .filter_map(|(id, participant)| {
1546                let registration = &participant.registration;
1547                let missed_frame = participant.generation != generation
1548                    && !registration.is_rendered()
1549                    && (full_frame || registration.rendered.is_some());
1550                (missed_frame || participant.participant.upgrade().is_none())
1551                    .then(|| (*id, participant.participant.clone()))
1552            })
1553            .collect()
1554    }
1555
1556    fn endpoint_departed(&self, stale: &[(EntityId, WeakEntity<SelectableTextState>)]) -> bool {
1557        stale.iter().any(|(id, _)| {
1558            [
1559                self.anchor.as_ref(),
1560                self.cursor.as_ref(),
1561                self.pending_extension_anchor.as_ref(),
1562            ]
1563            .into_iter()
1564            .flatten()
1565            .chain(self.unit.iter().flat_map(|unit| [&unit.start, &unit.end]))
1566            .any(|endpoint| endpoint.entity_id() == Some(*id))
1567        })
1568    }
1569
1570    fn remove_stale_participants(
1571        &self,
1572        stale: Vec<(EntityId, WeakEntity<SelectableTextState>)>,
1573    ) -> Vec<Entity<SelectableTextState>> {
1574        let mut participants = self.participants.borrow_mut();
1575        stale
1576            .into_iter()
1577            .filter_map(|(id, participant)| {
1578                participants.remove(&id);
1579                participant.upgrade()
1580            })
1581            .collect()
1582    }
1583
1584    /// Finishes bookkeeping through a read of the window entity. Updating it
1585    /// would invalidate every retained view that registered a participant.
1586    /// Returns whether one complete frame is needed to recover paint order.
1587    fn finish_registrations(&self) -> bool {
1588        let generation = self.frame_generation.get();
1589        let mut participants = self.participants.borrow_mut();
1590        let mut order = self.paint_order.borrow_mut();
1591        let complete = participants.values().all(|participant| {
1592            !participant.registration.paint_order || participant.generation == generation
1593        });
1594        let refresh = if complete {
1595            let mut next = 0;
1596            for id in order.painted.drain(..) {
1597                if let Some(participant) = participants.get_mut(&id)
1598                    && participant.registration.paint_order
1599                {
1600                    next += 1;
1601                    participant.document_order = next;
1602                }
1603            }
1604            order.next = next;
1605            order.dirty = false;
1606            order.refresh_requested = false;
1607            false
1608        } else {
1609            let refresh = order.dirty && !order.refresh_requested;
1610            order.refresh_requested |= refresh;
1611            order.painted.clear();
1612            refresh
1613        };
1614        order.last = None;
1615        self.finish_frame_scheduled.set(false);
1616        self.layer_painted.set(false);
1617        self.frame_generation.set(generation.wrapping_add(1));
1618        refresh
1619    }
1620
1621    fn schedule_finish_frame(&self) -> bool {
1622        !self.finish_frame_scheduled.replace(true)
1623    }
1624
1625    /// Whether there is a selection, or a gesture or touch selection that
1626    /// may make one: what painting the participants reports can change it.
1627    fn is_active(&self) -> bool {
1628        self.anchor.is_some()
1629            || self.cursor.is_some()
1630            || self.pending_extension_anchor.is_some()
1631            || self.is_selecting
1632            || self.refresh_held_cursor.get()
1633            || self.touch.active
1634    }
1635
1636    /// Records one participant in constant time, even during a selection.
1637    /// Full publication belongs to input handling or the end of the frame,
1638    /// never to each participant's draw callback.
1639    fn register_quietly(
1640        &self,
1641        selection: &TextSelectionHandle,
1642        registration: TextSelectionRegistration,
1643    ) {
1644        let mut participants = self.participants.borrow_mut();
1645        let previous = participants.get(&selection.entity_id());
1646        if self.is_selecting
1647            && registration.self_scroll
1648            && self.anchor.as_ref().and_then(SelectionEndpoint::entity_id)
1649                == Some(selection.entity_id())
1650            && previous.is_some_and(|previous| {
1651                previous.registration.scroll_offset != registration.scroll_offset
1652                    || previous.registration.bounds != registration.bounds
1653            })
1654        {
1655            self.refresh_held_cursor.set(true);
1656        }
1657        // The handles sit on the painted ends; when those moved — a scroll, a
1658        // reflow, a select-all — whoever draws the handles needs to know.
1659        if self.touch.active
1660            && previous.is_none_or(|previous| {
1661                previous.registration.selection_edges != registration.selection_edges
1662            })
1663        {
1664            self.touch_edges_moved.set(true);
1665        }
1666        let generation = self.frame_generation.get();
1667        let document_order = if registration.paint_order {
1668            let mut order = self.paint_order.borrow_mut();
1669            let previous = previous.filter(|previous| previous.registration.paint_order);
1670            let number = previous.map_or_else(
1671                || {
1672                    order.next += 1;
1673                    order.dirty = true;
1674                    order.next
1675                },
1676                |previous| previous.document_order,
1677            );
1678            // Reparenting an element changes its keyed marker and may move it
1679            // among retained siblings. Geometry alone is not a topology change:
1680            // scrolling an ancestor moves its text without changing its order.
1681            if previous.is_some_and(|previous| {
1682                previous
1683                    .registration
1684                    .rendered
1685                    .as_ref()
1686                    .map(|marker| marker.entity_id())
1687                    != registration
1688                        .rendered
1689                        .as_ref()
1690                        .map(|marker| marker.entity_id())
1691            }) {
1692                order.dirty = true;
1693            }
1694            let inverted = order.last.is_some_and(|last| last > number);
1695            order.dirty |= inverted;
1696            order.last = Some(number);
1697            // As with geometry, a handle painted twice takes its last position.
1698            order.painted.push(selection.entity_id());
1699            number
1700        } else {
1701            registration.document_order
1702        };
1703        participants.insert(
1704            selection.entity_id(),
1705            ParticipantRegistration {
1706                participant: selection.downgrade(),
1707                registration: Rc::new(registration),
1708                document_order,
1709                generation,
1710            },
1711        );
1712    }
1713
1714    #[cfg(test)]
1715    fn register_participant(
1716        &mut self,
1717        selection: TextSelectionHandle,
1718        registration: TextSelectionRegistration,
1719        cx: &mut App,
1720    ) {
1721        self.register_quietly(&selection, registration);
1722        self.publish_snapshots(cx);
1723    }
1724
1725    /// Starts a selection gesture using bounds hit testing (useful to adapters/tests).
1726    #[cfg(test)]
1727    fn begin(&mut self, position: Point<Pixels>, extend: bool, cx: &mut App) {
1728        self.begin_impl(position, extend, false, None, cx);
1729    }
1730
1731    /// Updates the current gesture using bounds hit testing.
1732    #[cfg(test)]
1733    fn update(&mut self, position: Point<Pixels>, cx: &mut App) {
1734        self.update_impl(position, None, cx);
1735    }
1736
1737    /// Ends the current gesture and keeps its selection visible.
1738    pub fn end(&mut self, cx: &mut App) {
1739        self.pending_extension_anchor = None;
1740        if !self.is_selecting {
1741            return;
1742        }
1743        self.is_selecting = false;
1744        if !self.did_hit_text {
1745            self.anchor = None;
1746            self.cursor = None;
1747        }
1748        self.stop_anchor_auto_scroll(cx);
1749        self.publish_snapshots(cx);
1750    }
1751
1752    /// Clears both window selection and every participant's local selection.
1753    pub fn clear(&mut self, cx: &mut App) {
1754        let handlers = self.clear_state(cx);
1755        dispatch_clear_handlers(handlers, cx);
1756    }
1757
1758    fn clear_state(&mut self, cx: &mut App) -> Vec<ClearHandler> {
1759        self.draw_changed.get_mut().clear();
1760        self.table_cell_anchor.set(None);
1761        self.touch_select_all = false;
1762        self.stop_anchor_auto_scroll(cx);
1763        self.anchor = None;
1764        self.cursor = None;
1765        self.pending_extension_anchor = None;
1766        self.unit = None;
1767        self.is_selecting = false;
1768        self.did_hit_text = false;
1769        if self.touch.reset() {
1770            self.touch_changed(cx);
1771        }
1772        self.prune_dead_participants();
1773        self.participants
1774            .borrow()
1775            .values()
1776            .filter_map(|registration| registration.participant.upgrade())
1777            .filter_map(|participant| participant.update(cx, |state, cx| state.clear_state(cx)))
1778            .collect()
1779    }
1780
1781    /// Tells whoever draws the handles and the menu that they changed.
1782    ///
1783    /// Deferred, because the change may come from a participant painting its
1784    /// selection ends: a notification raised inside a draw marks the view
1785    /// dirty but starts no frame, and the handles would sit where they were
1786    /// until something else redrew the window.
1787    ///
1788    /// The participants in the selection hear it too: they paint the
1789    /// handles, and lay the handles' hitboxes out from the ends they painted
1790    /// last frame, so they render once more. A participant under a cached
1791    /// view would otherwise not, and the next frame would replay this one,
1792    /// with no hitbox where a handle is.
1793    fn touch_changed(&self, cx: &mut App) {
1794        let participants = self
1795            .participants
1796            .borrow()
1797            .values()
1798            .filter_map(|registration| registration.participant.upgrade())
1799            .filter(|participant| participant.read(cx).snapshot.is_some())
1800            .collect::<Vec<_>>();
1801        let entity_id = self.entity_id;
1802        cx.defer(move |cx| {
1803            for participant in participants {
1804                participant.update(cx, |_, cx| {
1805                    cx.emit(TextSelectionEvent::TouchSelectionChanged)
1806                });
1807            }
1808            if let Some(entity_id) = entity_id {
1809                cx.notify(entity_id);
1810            }
1811        });
1812    }
1813
1814    fn copy_items(&self, cx: &App) -> Vec<CopyItem> {
1815        self.participants
1816            .borrow()
1817            .values()
1818            .filter_map(|registration| {
1819                let participant = registration.participant.upgrade()?;
1820                participant.read(cx).copy_item(registration.document_order)
1821            })
1822            .collect()
1823    }
1824
1825    #[cfg(test)]
1826    fn selected_text(&self, cx: &mut App) -> String {
1827        resolve_copy_items(self.copy_items(cx), cx)
1828    }
1829
1830    /// Whether the current endpoints take in at least one character of some
1831    /// participant's painted text, as opposed to two points with nothing
1832    /// between them.
1833    fn selects_text(&self, cx: &App) -> bool {
1834        self.snapshot().is_some()
1835            && self.participants.borrow().values().any(|registration| {
1836                registration
1837                    .participant
1838                    .upgrade()
1839                    .is_some_and(|participant| {
1840                        let participant = participant.read(cx);
1841                        project_ranges(participant.snapshot, &participant.runs.borrow())
1842                            .ranges()
1843                            .iter()
1844                            .any(|range| range.as_ref().is_some_and(|range| !range.is_empty()))
1845                    })
1846            })
1847    }
1848
1849    /// Returns whether a drag or a participant-local selection is active.
1850    pub fn has_selection(&self, cx: &App) -> bool {
1851        self.snapshot().is_some()
1852            || self.participants.borrow().values().any(|registration| {
1853                registration
1854                    .participant
1855                    .upgrade()
1856                    .is_some_and(|participant| participant.read(cx).local_selection)
1857            })
1858    }
1859
1860    /// Returns the current resolved selection endpoints.
1861    pub fn snapshot(&self) -> Option<TextSelectionSnapshot> {
1862        if !self.did_hit_text {
1863            return None;
1864        }
1865        let anchor_endpoint = self.anchor.as_ref()?;
1866        let cursor_endpoint = self.cursor.as_ref()?;
1867        let anchor = anchor_endpoint.resolve(&self.participants.borrow())?;
1868        let cursor = cursor_endpoint.resolve(&self.participants.borrow())?;
1869        (anchor != cursor).then(|| {
1870            TextSelectionSnapshot::new(anchor_endpoint.snapshot(), cursor_endpoint.snapshot())
1871                .with_selecting(self.is_selecting)
1872                .with_window_points(Some(TextSelectionWindowPoints { anchor, cursor }))
1873        })
1874    }
1875
1876    /// Returns whether a drag is currently in progress.
1877    #[cfg(test)]
1878    fn is_selecting(&self) -> bool {
1879        self.is_selecting
1880    }
1881
1882    fn prepare_for_mouse_down(&mut self, extend: bool, cx: &mut App) -> Vec<ClearHandler> {
1883        let pending_extension_anchor = extend.then(|| self.anchor.clone()).flatten();
1884        let unit = extend.then(|| self.unit.take()).flatten();
1885        if !extend {
1886            self.table_cell_anchor.set(None);
1887            self.touch_select_all = false;
1888        }
1889        self.stop_anchor_auto_scroll(cx);
1890        self.anchor = None;
1891        self.cursor = None;
1892        self.pending_extension_anchor = None;
1893        self.unit = unit;
1894        self.is_selecting = false;
1895        self.did_hit_text = false;
1896        if self.touch.reset() {
1897            self.touch_changed(cx);
1898        }
1899        self.prune_dead_participants();
1900        let handlers = self
1901            .participants
1902            .borrow()
1903            .values()
1904            .filter_map(|registration| registration.participant.upgrade())
1905            .filter_map(|participant| participant.update(cx, |state, cx| state.clear_state(cx)))
1906            .collect();
1907        self.pending_extension_anchor = pending_extension_anchor;
1908        handlers
1909    }
1910
1911    /// The touch selection laid out for its handles and edit menu.
1912    ///
1913    /// The ends come from the participants: the first selected character of
1914    /// the earliest participant in document order and the last of the latest.
1915    /// A participant whose selection is entirely scrolled away paints no ends,
1916    /// so the handles disappear with the text they mark.
1917    fn touch_selection(&self) -> Option<TouchSelectionSnapshot> {
1918        if !self.touch.active {
1919            return None;
1920        }
1921        // Each end with its owner's viewport: an end scrolled out of its
1922        // participant gets no handle.
1923        let mut start: Option<(u64, Bounds<Pixels>, bool)> = None;
1924        let mut end: Option<(u64, Bounds<Pixels>, bool)> = None;
1925        for registration in self.participants.borrow().values() {
1926            let geometry = &registration.registration;
1927            if geometry.scope != self.active_scope || registration.participant.upgrade().is_none() {
1928                continue;
1929            }
1930            let Some((edge_start, edge_end)) = geometry.selection_edges else {
1931                continue;
1932            };
1933            let order = registration.document_order;
1934            let viewport = geometry.hitbox.bounds;
1935            if start.is_none_or(|(best, ..)| order < best) {
1936                start = Some((order, edge_start, caret_in_view(edge_start, viewport)));
1937            }
1938            if end.is_none_or(|(best, ..)| order >= best) {
1939                end = Some((order, edge_end, caret_in_view(edge_end, viewport)));
1940            }
1941        }
1942        let edges = match (start, end) {
1943            (Some((_, start, start_visible)), Some((_, end, end_visible))) => {
1944                let edges = (start, end, start_visible, end_visible);
1945                self.touch.last_edges.set(Some(edges));
1946                edges
1947            }
1948            _ if self.touch.drag.is_some() => self.touch.last_edges.get()?,
1949            _ => return None,
1950        };
1951        let (start, end, start_visible, end_visible) = edges;
1952        Some(
1953            TouchSelectionSnapshot::new(start, end)
1954                .with_edge_visible(SelectionEdge::Start, start_visible)
1955                .with_edge_visible(SelectionEdge::End, end_visible)
1956                .with_menu_open(self.touch.menu_open)
1957                .with_dragging(self.touch.drag.map(|drag| drag.edge())),
1958        )
1959    }
1960
1961    /// The handles `participant` paints: the start when no participant
1962    /// earlier in document order has a selection end, the end when none
1963    /// later has. Participants paint in document order, so a later one that
1964    /// takes the end over has registered its ends by the time it asks.
1965    fn touch_handles_of(&self, participant: EntityId) -> Vec<(SelectionEdge, Bounds<Pixels>)> {
1966        if !self.touch.active {
1967            return Vec::new();
1968        }
1969        let Some(own) = self.participants.borrow().get(&participant).cloned() else {
1970            return Vec::new();
1971        };
1972        let Some((start, end)) = own.registration.selection_edges else {
1973            return Vec::new();
1974        };
1975        if start == end {
1976            return Vec::new();
1977        }
1978        let order = own.document_order;
1979        let participants = self.participants.borrow();
1980        let others = participants.iter().filter(|(id, registration)| {
1981            **id != participant
1982                && registration.registration.scope == self.active_scope
1983                && registration.registration.selection_edges.is_some()
1984                && registration.participant.upgrade().is_some()
1985        });
1986        let (mut earlier, mut later) = (false, false);
1987        for (_, registration) in others {
1988            let other = registration.document_order;
1989            earlier |= other < order;
1990            later |= other > order;
1991        }
1992        let viewport = own.registration.hitbox.bounds;
1993        let mut handles = Vec::with_capacity(2);
1994        if !earlier && caret_in_view(start, viewport) {
1995            handles.push((SelectionEdge::Start, start));
1996        }
1997        if !later && caret_in_view(end, viewport) {
1998            handles.push((SelectionEdge::End, end));
1999        }
2000        handles
2001    }
2002
2003    /// Keeps the selection a long press made, and opens the edit menu over it.
2004    fn keep_touch_selection(&mut self, cx: &mut App) {
2005        self.touch.active = self.snapshot().is_some();
2006        self.touch.menu_open = self.touch.active;
2007        self.touch.drag = None;
2008        self.touch_changed(cx);
2009    }
2010
2011    /// Records where the menu is painted this frame.
2012    fn register_touch_ui(&mut self, bounds: Bounds<Pixels>) {
2013        self.touch.ui_bounds.push(bounds);
2014    }
2015
2016    /// Whether `bounds` are where `participant` painted its handles last.
2017    fn handles_painted_at(&self, participant: EntityId, bounds: &[Bounds<Pixels>]) -> bool {
2018        self.touch
2019            .handle_bounds
2020            .get(&participant)
2021            .map_or(bounds.is_empty(), |painted| painted == bounds)
2022    }
2023
2024    /// Records where `participant` painted its handles, which replace those
2025    /// it painted before.
2026    fn register_handles(&mut self, participant: EntityId, bounds: Vec<Bounds<Pixels>>) {
2027        self.touch.handle_bounds.insert(participant, bounds);
2028    }
2029
2030    fn close_edit_menu(&mut self, cx: &mut App) {
2031        if !self.touch.menu_open {
2032            return;
2033        }
2034        self.touch.menu_open = false;
2035        self.touch_changed(cx);
2036    }
2037
2038    /// The handles follow the text, but the menu would sit over whatever
2039    /// scrolls underneath it: it steps aside while a finger scrolls and comes
2040    /// back over the handles once the finger lifts.
2041    fn edit_menu_on_scroll(&mut self, phase: TouchPhase, cx: &mut App) {
2042        if !self.touch.active || self.touch.drag.is_some() {
2043            return;
2044        }
2045        match phase {
2046            TouchPhase::Ended | TouchPhase::Cancelled => {
2047                if !self.touch.menu_open {
2048                    self.touch.menu_open = true;
2049                    self.touch_changed(cx);
2050                }
2051            }
2052            _ => self.close_edit_menu(cx),
2053        }
2054    }
2055
2056    /// Selects all of the participant the touch selection started in.
2057    ///
2058    /// This stays a point selection — anchored on the participant's first
2059    /// line of text, ending on its last — rather than switching the
2060    /// participant to a local select-all: one selection, painted, reported
2061    /// and copied the one way, with handles that drag on from its ends.
2062    fn select_all_touched(&mut self, cx: &mut App) {
2063        if !self.touch.active {
2064            return;
2065        }
2066        let Some((participant, registration)) = self.anchor_registration() else {
2067            return;
2068        };
2069        // From the participant's text runs, not its painted line bounds:
2070        // those are clipped to the viewport, and a message taller than the
2071        // screen would only select what is on it.
2072        let (anchor, cursor) = {
2073            let runs = &participant.read(cx).runs.borrow();
2074            let (Some(first), Some(last)) = (
2075                runs.iter().min_by_key(|run| run.document_order),
2076                runs.iter().max_by_key(|run| run.document_order),
2077            ) else {
2078                return;
2079            };
2080            let (Some(start), Some(end)) = (
2081                first.layout.position_for_index(0),
2082                last.layout.position_for_index(last.text.len()),
2083            ) else {
2084                return;
2085            };
2086            // Just inside the first and the last glyph, mid-line, so both
2087            // land on text.
2088            let inset = px(1.);
2089            (
2090                point(start.x + inset, start.y + first.layout.line_height() / 2.),
2091                point(end.x - inset, end.y + last.layout.line_height() / 2.),
2092            )
2093        };
2094        let content_key_resolver = participant.read(cx).content_key_resolver.clone();
2095        let to_endpoint = |window_point: Point<Pixels>| {
2096            let content_point =
2097                window_point - registration.bounds.origin - registration.scroll_offset;
2098            SelectionEndpoint {
2099                participant: Some(participant.downgrade()),
2100                point: content_point,
2101                inside: true,
2102                inside_text: true,
2103                content_key: None,
2104                content_key_resolver: content_key_resolver
2105                    .clone()
2106                    .map(|resolver| (resolver, content_point)),
2107            }
2108        };
2109        self.table_cell_anchor.set(None);
2110        self.touch_select_all = true;
2111        self.anchor = Some(to_endpoint(anchor));
2112        self.cursor = Some(to_endpoint(cursor));
2113        self.did_hit_text = true;
2114        self.is_selecting = false;
2115        self.touch.menu_open = true;
2116        self.publish_snapshots(cx);
2117        self.touch_changed(cx);
2118    }
2119
2120    /// Starts dragging one end of the touch selection from `finger`.
2121    ///
2122    /// The selection is rebuilt from the painted ends, anchored at the end that
2123    /// stays: a select-all or a word selection becomes an ordinary point
2124    /// selection which the drag then extends.
2125    fn begin_edge_drag(
2126        &mut self,
2127        edge: SelectionEdge,
2128        finger: Point<Pixels>,
2129        window: &mut Window,
2130        cx: &mut App,
2131    ) {
2132        let Some(snapshot) = self.touch_selection() else {
2133            return;
2134        };
2135        let handlers = self.prepare_for_mouse_down(false, cx);
2136        dispatch_clear_handlers(handlers, cx);
2137        let held = snapshot.edge(edge.opposite());
2138        // Just inside the held end, so the anchor lands on the character it
2139        // marks rather than on the boundary between two participants.
2140        let nudge = px(1.);
2141        let anchor_point = match edge {
2142            SelectionEdge::Start => point(held.left() - nudge, held.center().y),
2143            SelectionEdge::End => point(held.left() + nudge, held.center().y),
2144        };
2145        let anchor = self.endpoint(anchor_point, None, cx);
2146        let drag = EdgeDrag::begin(edge, snapshot.edge(edge), finger);
2147        let cursor = self.endpoint(drag.text_position(finger), Some(window), cx);
2148        self.did_hit_text = anchor.inside_text || cursor.inside_text;
2149        self.anchor = Some(anchor);
2150        self.cursor = Some(cursor);
2151        self.is_selecting = true;
2152        self.touch.active = true;
2153        self.touch.menu_open = false;
2154        self.touch.drag = Some(drag);
2155        self.publish_snapshots(cx);
2156        self.touch_changed(cx);
2157    }
2158
2159    fn update_edge_drag(&mut self, finger: Point<Pixels>, window: &Window, cx: &mut Context<Self>) {
2160        let Some(drag) = self.touch.drag else {
2161            return;
2162        };
2163        let before = self.cursor.clone();
2164        self.update_in_window(drag.text_position(finger), window, cx);
2165        // A handle never collapses the selection: at the other end it stops,
2166        // and the finger has to pass that end, onto text, to swap the two.
2167        if !self.selects_text(cx) {
2168            self.cursor = before;
2169            self.publish_snapshots(cx);
2170            return;
2171        }
2172        // Dragging one end past the other swaps them: the cursor now lies
2173        // before the anchor, so the finger holds what became the start.
2174        if let Some(points) = self
2175            .snapshot()
2176            .and_then(|snapshot| snapshot.window_points())
2177        {
2178            let (anchor, cursor) = (points.anchor(), points.cursor());
2179            let cursor_first = cursor.y < anchor.y || (cursor.y == anchor.y && cursor.x < anchor.x);
2180            let edge = if cursor_first {
2181                SelectionEdge::Start
2182            } else {
2183                SelectionEdge::End
2184            };
2185            if let Some(drag) = self.touch.drag.as_mut() {
2186                drag.set_edge(edge);
2187            }
2188        }
2189        self.touch_changed(cx);
2190    }
2191
2192    fn end_edge_drag(&mut self, cx: &mut App) {
2193        if self.touch.drag.take().is_none() {
2194            return;
2195        }
2196        self.end(cx);
2197        self.keep_touch_selection(cx);
2198    }
2199
2200    fn begin_in_window(
2201        &mut self,
2202        position: Point<Pixels>,
2203        extend: bool,
2204        window: &mut Window,
2205        cx: &mut App,
2206    ) {
2207        self.begin_impl(position, extend, true, Some(window), cx);
2208    }
2209
2210    fn update_in_window(
2211        &mut self,
2212        position: Point<Pixels>,
2213        window: &Window,
2214        cx: &mut Context<Self>,
2215    ) {
2216        if !cx.has_active_drag() {
2217            self.update_impl(position, Some(window), cx);
2218            self.update_auto_scroll(position, window, cx);
2219        }
2220    }
2221
2222    /// Selects the word or line under `position`, and returns it.
2223    fn select_at(
2224        &mut self,
2225        position: Point<Pixels>,
2226        click_count: usize,
2227        window: &mut Window,
2228        cx: &mut App,
2229    ) -> Option<SelectionUnit> {
2230        GlobalState::init(cx);
2231        if GlobalState::is_text_selection_suppressed(cx) {
2232            return None;
2233        }
2234        let hit = self.endpoint(position, Some(window), cx);
2235        if !hit.inside_text {
2236            return None;
2237        }
2238        let participant = hit.participant?.upgrade()?;
2239        let (start, end) =
2240            carets_for_multi_click(&participant.read(cx).runs.borrow(), position, click_count)?;
2241        let unit = self.unit_of(&participant, start, end, click_count, cx)?;
2242        self.anchor = Some(unit.start.clone());
2243        self.cursor = Some(unit.end.clone());
2244        self.did_hit_text = true;
2245        self.is_selecting = false;
2246        participant.update(cx, |state, cx| state.focus(window, cx));
2247        self.publish_snapshots(cx);
2248        Some(unit)
2249    }
2250
2251    /// The word or line of `participant` from the caret `start` to the caret
2252    /// `end`, both in window coordinates, that `click_count` clicks selected.
2253    fn unit_of(
2254        &self,
2255        participant: &Entity<SelectableTextState>,
2256        start: Bounds<Pixels>,
2257        end: Bounds<Pixels>,
2258        click_count: usize,
2259        cx: &App,
2260    ) -> Option<SelectionUnit> {
2261        let registration = self
2262            .participants
2263            .borrow()
2264            .get(&participant.entity_id())?
2265            .registration
2266            .clone();
2267        let content_key_resolver = participant.read(cx).content_key_resolver.clone();
2268        let to_endpoint = |point: Point<Pixels>| {
2269            let content_point = point - registration.bounds.origin - registration.scroll_offset;
2270            SelectionEndpoint {
2271                participant: Some(participant.downgrade()),
2272                point: content_point,
2273                inside: true,
2274                inside_text: true,
2275                content_key: None,
2276                content_key_resolver: content_key_resolver
2277                    .clone()
2278                    .map(|resolver| (resolver, content_point)),
2279            }
2280        };
2281        Some(SelectionUnit {
2282            click_count,
2283            start: to_endpoint(start.origin),
2284            end: to_endpoint(end.origin),
2285            row_heights: (start.size.height, end.size.height),
2286        })
2287    }
2288
2289    /// Keeps the gesture of the click that selected `unit` going: a drag
2290    /// from it, until the button is released, and a shift-click after it
2291    /// extend the selection by the same unit.
2292    fn hold_unit(&mut self, unit: SelectionUnit, cx: &mut App) {
2293        self.unit = Some(unit);
2294        self.is_selecting = true;
2295        self.publish_snapshots(cx);
2296    }
2297
2298    /// Moves the cursor to the edge of the unit at `position`, which
2299    /// `endpoint` hit, and the anchor to the far edge of `unit`. A pointer
2300    /// over no text moves the cursor to itself, as a plain drag does.
2301    fn extend_unit(
2302        &mut self,
2303        unit: &SelectionUnit,
2304        position: Point<Pixels>,
2305        endpoint: SelectionEndpoint,
2306        cx: &App,
2307    ) {
2308        let (start, end) = {
2309            let participants = self.participants.borrow();
2310            (
2311                unit.start.resolve(&participants),
2312                unit.end.resolve(&participants),
2313            )
2314        };
2315        let (Some(start), Some(end)) = (start, end) else {
2316            self.did_hit_text |= endpoint.inside_text;
2317            self.cursor = Some(endpoint);
2318            return;
2319        };
2320        let (start_height, end_height) = unit.row_heights;
2321        let before =
2322            position.y < start.y || (position.y < start.y + start_height && position.x < start.x);
2323        let after = position.y >= end.y + end_height || (position.y >= end.y && position.x > end.x);
2324        if !before && !after {
2325            // A participant's own multi-click selection stands until the
2326            // pointer leaves it.
2327            if self.anchor.is_some() {
2328                self.anchor = Some(unit.start.clone());
2329                self.cursor = Some(unit.end.clone());
2330                self.did_hit_text = true;
2331            }
2332            return;
2333        }
2334        let (edge, anchor) = if before {
2335            (SelectionEdge::Start, &unit.end)
2336        } else {
2337            (SelectionEdge::End, &unit.start)
2338        };
2339        let cursor = Self::unit_edge_at(&endpoint, position, unit.click_count, edge, cx);
2340        self.anchor = Some(anchor.clone());
2341        self.cursor = Some(cursor.unwrap_or(endpoint));
2342        self.did_hit_text = true;
2343    }
2344
2345    /// `endpoint`, hit at `position`, moved to the `edge` of the word or line
2346    /// under the pointer.
2347    fn unit_edge_at(
2348        endpoint: &SelectionEndpoint,
2349        position: Point<Pixels>,
2350        click_count: usize,
2351        edge: SelectionEdge,
2352        cx: &App,
2353    ) -> Option<SelectionEndpoint> {
2354        if !endpoint.inside {
2355            return None;
2356        }
2357        let participant = endpoint.participant.as_ref()?.upgrade()?;
2358        let runs = participant.read(cx).runs.borrow();
2359        let run = runs.iter().find(|run| run.bounds.contains(&position))?;
2360        if run.text.len() != run.layout.len() {
2361            return None;
2362        }
2363        // Past the end of a row, the pointer takes the character closest to it.
2364        let offset = run
2365            .layout
2366            .index_for_position(position)
2367            .unwrap_or_else(|closest| closest);
2368        let (start, end) = unit_carets(run, offset, click_count)?;
2369        let caret = match edge {
2370            SelectionEdge::Start => start,
2371            SelectionEdge::End => end,
2372        };
2373        Some(endpoint.moved(caret.origin - position))
2374    }
2375
2376    #[cfg(test)]
2377    fn update_in_window_with_active_drag(
2378        &mut self,
2379        position: Point<Pixels>,
2380        active_drag: bool,
2381        window: &Window,
2382        cx: &mut App,
2383    ) {
2384        if !active_drag {
2385            self.update_impl(position, Some(window), cx);
2386        }
2387    }
2388
2389    fn begin_impl(
2390        &mut self,
2391        position: Point<Pixels>,
2392        extend: bool,
2393        already_prepared: bool,
2394        window: Option<&mut Window>,
2395        cx: &mut App,
2396    ) {
2397        GlobalState::init(cx);
2398        if GlobalState::is_text_selection_suppressed(cx) {
2399            self.pending_extension_anchor = None;
2400            return;
2401        }
2402        let previous_anchor = extend
2403            .then(|| {
2404                self.pending_extension_anchor
2405                    .take()
2406                    .or_else(|| self.anchor.clone())
2407            })
2408            .flatten()
2409            .filter(|anchor| anchor.resolve(&self.participants.borrow()).is_some());
2410        if !extend && !already_prepared {
2411            self.clear(cx);
2412        }
2413        let endpoint = self.endpoint(position, window.as_deref(), cx);
2414        let focus_participant = endpoint
2415            .inside
2416            .then(|| endpoint.participant.clone())
2417            .flatten();
2418        let anchor = previous_anchor.unwrap_or_else(|| endpoint.clone());
2419        self.anchor = Some(anchor.clone());
2420        self.cursor = Some(endpoint.clone());
2421        self.did_hit_text = anchor.inside_text || endpoint.inside_text;
2422        self.is_selecting = true;
2423        // A shift-click after a double or triple click extends by its unit.
2424        if let Some(unit) = extend.then(|| self.unit.clone()).flatten() {
2425            self.extend_unit(&unit, position, endpoint, cx);
2426        }
2427        if let Some(participant) = focus_participant.and_then(|participant| participant.upgrade()) {
2428            if let Some(window) = window {
2429                participant.update(cx, |state, cx| state.focus(window, cx));
2430            }
2431        }
2432        self.publish_snapshots(cx);
2433    }
2434
2435    fn update_impl(&mut self, position: Point<Pixels>, window: Option<&Window>, cx: &mut App) {
2436        if !self.is_selecting {
2437            return;
2438        }
2439        let endpoint = self.endpoint(position, window, cx);
2440        match self.unit.clone() {
2441            Some(unit) => self.extend_unit(&unit, position, endpoint, cx),
2442            None => {
2443                self.did_hit_text |= endpoint.inside_text;
2444                self.cursor = Some(endpoint);
2445            }
2446        }
2447        if window.is_none() {
2448            self.update_participant_auto_scroll(position, cx);
2449        }
2450        self.publish_snapshots(cx);
2451    }
2452
2453    fn endpoint(
2454        &mut self,
2455        position: Point<Pixels>,
2456        window: Option<&Window>,
2457        cx: &App,
2458    ) -> SelectionEndpoint {
2459        self.prune_dead_participants();
2460        let mut hit: Option<(ParticipantRegistration, f32)> = None;
2461        let mut predecessor: Option<ParticipantRegistration> = None;
2462        let mut first: Option<ParticipantRegistration> = None;
2463
2464        for registration in self.participants.borrow().values() {
2465            if registration.registration.scope != self.active_scope
2466                || registration.participant.upgrade().is_none()
2467            {
2468                continue;
2469            }
2470            let participant_geometry = &registration.registration;
2471            let hovered = window.map_or_else(
2472                || participant_geometry.bounds.contains(&position),
2473                |window| participant_geometry.hitbox.is_hovered(window),
2474            );
2475            if hovered {
2476                let area = f32::from(participant_geometry.bounds.size.width)
2477                    * f32::from(participant_geometry.bounds.size.height);
2478                if hit.as_ref().is_none_or(|(best, best_area)| {
2479                    area < *best_area
2480                        || (area == *best_area && registration.document_order < best.document_order)
2481                }) {
2482                    hit = Some((registration.clone(), area));
2483                }
2484            }
2485            if participant_geometry.bounds.top() <= position.y
2486                && predecessor.as_ref().is_none_or(|best| {
2487                    participant_geometry.bounds.top() > best.registration.bounds.top()
2488                        || (participant_geometry.bounds.top() == best.registration.bounds.top()
2489                            && registration.document_order < best.document_order)
2490                })
2491            {
2492                predecessor = Some(registration.clone());
2493            }
2494            if first.as_ref().is_none_or(|best| {
2495                participant_geometry.bounds.top() < best.registration.bounds.top()
2496                    || (participant_geometry.bounds.top() == best.registration.bounds.top()
2497                        && registration.document_order < best.document_order)
2498            }) {
2499                first = Some(registration.clone());
2500            }
2501        }
2502
2503        let selection = hit.map(|(participant, _)| (participant, true)).or_else(|| {
2504            predecessor
2505                .or(first)
2506                .map(|participant| (participant, false))
2507        });
2508        match selection {
2509            Some((participant, inside)) => {
2510                let registration = participant.registration;
2511                let participant = participant.participant;
2512                let point = position - registration.bounds.origin - registration.scroll_offset;
2513                let content_key_resolver = participant.upgrade().and_then(|participant| {
2514                    participant
2515                        .read(cx)
2516                        .content_key_resolver
2517                        .clone()
2518                        .map(|callback| (callback, point))
2519                });
2520                SelectionEndpoint {
2521                    point,
2522                    participant: Some(participant),
2523                    inside,
2524                    inside_text: inside
2525                        && registration
2526                            .text_bounds
2527                            .iter()
2528                            .any(|bounds| bounds.contains(&position)),
2529                    content_key: None,
2530                    content_key_resolver,
2531                }
2532            }
2533            None => SelectionEndpoint {
2534                participant: None,
2535                point: position,
2536                inside: false,
2537                inside_text: false,
2538                content_key: None,
2539                content_key_resolver: None,
2540            },
2541        }
2542    }
2543
2544    fn publish_snapshots(&mut self, cx: &mut App) {
2545        self.prune_dead_participants();
2546        for (participant, snapshot) in self.changed_snapshots(cx) {
2547            self.draw_changed.get_mut().remove(&participant.entity_id());
2548            participant.update(cx, |state, cx| state.set_snapshot(snapshot, cx));
2549        }
2550    }
2551
2552    fn participant_snapshot(&self, id: EntityId) -> Option<TextSelectionSnapshot> {
2553        #[cfg(test)]
2554        self.snapshot_visits.set(self.snapshot_visits.get() + 1);
2555
2556        let participants = self.participants.borrow();
2557        let registration = participants.get(&id)?;
2558        (registration.registration.scope == self.active_scope
2559            && self.participates(id, registration)
2560            && self.single_participant().is_none_or(|single| single == id))
2561        .then(|| self.snapshot())
2562        .flatten()
2563        .map(|mut snapshot| {
2564            snapshot.coverage = self.coverage_for(id);
2565            snapshot
2566        })
2567    }
2568
2569    fn changed_snapshots(
2570        &self,
2571        cx: &App,
2572    ) -> Vec<(Entity<SelectableTextState>, Option<TextSelectionSnapshot>)> {
2573        self.participants
2574            .borrow()
2575            .iter()
2576            .filter_map(|(id, registration)| {
2577                let participant = registration.participant.upgrade()?;
2578                let snapshot = self.participant_snapshot(*id);
2579                (participant.read(cx).snapshot != snapshot).then_some((participant, snapshot))
2580            })
2581            .collect()
2582    }
2583
2584    fn coverage_for(&self, id: EntityId) -> TextSelectionCoverage {
2585        let Some(anchor) = self.anchor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2586            return TextSelectionCoverage::Bounded;
2587        };
2588        let Some(cursor) = self.cursor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2589            return TextSelectionCoverage::Bounded;
2590        };
2591        if anchor == cursor {
2592            return TextSelectionCoverage::Bounded;
2593        }
2594        let anchor_order = self.participants.borrow()[&anchor].document_order;
2595        let cursor_order = self.participants.borrow()[&cursor].document_order;
2596        if id != anchor && id != cursor {
2597            TextSelectionCoverage::Full
2598        } else if (id == anchor) == (anchor_order < cursor_order) {
2599            TextSelectionCoverage::ToEnd
2600        } else {
2601            TextSelectionCoverage::FromStart
2602        }
2603    }
2604
2605    fn single_participant(&self) -> Option<EntityId> {
2606        let anchor = self.anchor.as_ref()?.entity_id()?;
2607        let cursor = self.cursor.as_ref()?.entity_id()?;
2608        (anchor == cursor).then_some(anchor)
2609    }
2610
2611    fn participates(&self, id: EntityId, registration: &ParticipantRegistration) -> bool {
2612        let Some(anchor) = self.anchor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2613            return false;
2614        };
2615        let Some(cursor) = self.cursor.as_ref().and_then(SelectionEndpoint::entity_id) else {
2616            return false;
2617        };
2618        let Some(anchor_registration) = self.participants.borrow().get(&anchor).cloned() else {
2619            return false;
2620        };
2621        let Some(cursor_registration) = self.participants.borrow().get(&cursor).cloned() else {
2622            return false;
2623        };
2624        let start = anchor_registration
2625            .document_order
2626            .min(cursor_registration.document_order);
2627        let end = anchor_registration
2628            .document_order
2629            .max(cursor_registration.document_order);
2630        (start..=end).contains(&registration.document_order) || id == anchor || id == cursor
2631    }
2632
2633    fn update_auto_scroll(
2634        &mut self,
2635        position: Point<Pixels>,
2636        window: &Window,
2637        cx: &mut Context<Self>,
2638    ) {
2639        // A finished gesture keeps its anchor for shift-click extension; only
2640        // a live drag may scroll.
2641        if !self.is_selecting {
2642            return;
2643        }
2644        let Some((_, registration)) = self.anchor_registration() else {
2645            return;
2646        };
2647        // Exactly one writer per drag: a participant that scrolls its own
2648        // content is notified directly; anything else gets a synthetic wheel.
2649        if registration.self_scroll {
2650            self.auto_scroll.stop();
2651            self.update_participant_auto_scroll(position, cx);
2652            return;
2653        }
2654        // The content mask is the nearest clipping viewport established by a
2655        // scrollable ancestor. It remains stable as the participant itself
2656        // moves, so selection keeps scrolling the same related region even
2657        // after the anchor text has moved out of view.
2658        let visible_bounds = registration.hitbox.content_mask.bounds;
2659        // Keeps the synthesized wheel event hit-testing inside the mask.
2660        const HIT_TEST_INSET: Pixels = px(1.);
2661        // A collapsed mask leaves an empty clamp range below — stop.
2662        if visible_bounds.size.width < HIT_TEST_INSET * 2.
2663            || visible_bounds.size.height < HIT_TEST_INSET * 2.
2664        {
2665            self.stop_anchor_auto_scroll(cx);
2666            return;
2667        }
2668        let delta = AutoScroll::compute_delta(position.y, visible_bounds);
2669        let event_position = point(
2670            position.x.clamp(
2671                visible_bounds.left() + HIT_TEST_INSET,
2672                visible_bounds.right() - HIT_TEST_INSET,
2673            ),
2674            position.y.clamp(
2675                visible_bounds.top() + HIT_TEST_INSET,
2676                visible_bounds.bottom() - HIT_TEST_INSET,
2677            ),
2678        );
2679        self.auto_scroll.last_drag_position = Some(event_position);
2680        self.auto_scroll_stall = (None, 0);
2681        let window = window.window_handle();
2682        self.auto_scroll.set(delta, cx, move |delta, state, cx| {
2683            let Some(position) = state.auto_scroll.last_drag_position else {
2684                return;
2685            };
2686            // Hold off once the container has shown, over a few ticks, that
2687            // it has nowhere left to go; the next move of the finger asks
2688            // again.
2689            const STALLED_TICKS: u8 = 3;
2690            let geometry = state
2691                .anchor_registration()
2692                .map(|(_, registration)| (registration.bounds, registration.scroll_offset));
2693            let (last, stalled) = &mut state.auto_scroll_stall;
2694            if *last == geometry {
2695                *stalled = stalled.saturating_add(1);
2696                if *stalled >= STALLED_TICKS {
2697                    return;
2698                }
2699            } else {
2700                *last = geometry;
2701                *stalled = 0;
2702            }
2703            let window = window;
2704            cx.defer(move |cx| {
2705                _ = window.update(cx, |_, window, cx| {
2706                    window.dispatch_event(
2707                        ScrollEvent {
2708                            position,
2709                            delta: ScrollDelta::Pixels(point(px(0.), -delta)),
2710                            ..Default::default()
2711                        }
2712                        .to_platform_input(),
2713                        cx,
2714                    );
2715                });
2716            });
2717        });
2718    }
2719
2720    /// Drives the anchor participant's own scrolling, measured against the
2721    /// visible portion of the participant's element bounds.
2722    fn update_participant_auto_scroll(&self, position: Point<Pixels>, cx: &mut App) {
2723        let Some((participant, registration)) = self.anchor_registration() else {
2724            return;
2725        };
2726        let visible_bounds = registration
2727            .bounds
2728            .intersect(&registration.hitbox.content_mask.bounds);
2729        let delta = if visible_bounds.size.width > px(0.) && visible_bounds.size.height > px(0.) {
2730            AutoScroll::compute_delta(position.y, visible_bounds)
2731        } else {
2732            None
2733        };
2734        participant.update(cx, |state, cx| state.set_auto_scroll(delta, cx));
2735    }
2736
2737    fn stop_anchor_auto_scroll(&mut self, cx: &mut App) {
2738        self.auto_scroll.stop();
2739        let Some(participant) = self.anchor_participant() else {
2740            return;
2741        };
2742        participant.update(cx, |state, cx| state.set_auto_scroll(None, cx));
2743    }
2744
2745    /// The live participant owning the anchor of the current gesture.
2746    fn anchor_participant(&self) -> Option<Entity<SelectableTextState>> {
2747        self.anchor
2748            .as_ref()
2749            .filter(|anchor| anchor.inside)?
2750            .participant
2751            .as_ref()?
2752            .upgrade()
2753    }
2754
2755    /// The anchor participant together with its current frame registration.
2756    fn anchor_registration(
2757        &self,
2758    ) -> Option<(Entity<SelectableTextState>, Rc<TextSelectionRegistration>)> {
2759        let participant = self.anchor_participant()?;
2760        let registration = self
2761            .participants
2762            .borrow()
2763            .get(&participant.entity_id())?
2764            .clone();
2765        Some((participant, registration.registration.clone()))
2766    }
2767
2768    fn prune_dead_participants(&mut self) {
2769        self.participants
2770            .get_mut()
2771            .retain(|_, registration| registration.participant.upgrade().is_some());
2772    }
2773}
2774
2775#[derive(Default)]
2776/// Non-owning window locator; retained [`TextSelection`] element state owns
2777/// each live selection entity.
2778struct SelectionStateRegistry(HashMap<gpui::WindowId, WeakEntity<WindowSelectionState>>);
2779
2780impl Global for SelectionStateRegistry {}
2781
2782#[derive(Default)]
2783struct PendingTextSelectionScopes(HashMap<gpui::WindowId, TextSelectionScopeId>);
2784
2785impl Global for PendingTextSelectionScopes {}
2786
2787#[derive(Default)]
2788struct TextSelectionScopeStacks(HashMap<gpui::WindowId, Vec<TextSelectionScopeId>>);
2789
2790impl Global for TextSelectionScopeStacks {}
2791
2792fn push_text_selection_scope(window_id: gpui::WindowId, scope: TextSelectionScopeId, cx: &mut App) {
2793    if !cx.has_global::<TextSelectionScopeStacks>() {
2794        cx.set_global(TextSelectionScopeStacks::default());
2795    }
2796    cx.global_mut::<TextSelectionScopeStacks>()
2797        .0
2798        .entry(window_id)
2799        .or_default()
2800        .push(scope);
2801}
2802
2803fn pop_text_selection_scope(window_id: gpui::WindowId, cx: &mut App) {
2804    let stacks = &mut cx.global_mut::<TextSelectionScopeStacks>().0;
2805    let remove_stack = stacks.get_mut(&window_id).is_some_and(|stack| {
2806        stack.pop();
2807        stack.is_empty()
2808    });
2809    if remove_stack {
2810        stacks.remove(&window_id);
2811    }
2812}
2813
2814fn current_text_selection_scope(
2815    window_id: gpui::WindowId,
2816    cx: &App,
2817) -> Option<TextSelectionScopeId> {
2818    cx.has_global::<TextSelectionScopeStacks>()
2819        .then(|| {
2820            cx.global::<TextSelectionScopeStacks>()
2821                .0
2822                .get(&window_id)
2823                .and_then(|stack| stack.last().copied())
2824        })
2825        .flatten()
2826}
2827
2828fn with_text_selection_scope<T>(
2829    window_id: gpui::WindowId,
2830    scope: TextSelectionScopeId,
2831    cx: &mut App,
2832    callback: impl FnOnce(&mut App) -> T,
2833) -> T {
2834    push_text_selection_scope(window_id, scope, cx);
2835    let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| callback(cx)));
2836    pop_text_selection_scope(window_id, cx);
2837    match result {
2838        Ok(result) => result,
2839        Err(payload) => std::panic::resume_unwind(payload),
2840    }
2841}
2842
2843/// Window-level operations for text selection.
2844pub struct TextSelection;
2845
2846impl TextSelection {
2847    /// Returns whether `position` hits selectable text in the active scope.
2848    ///
2849    /// Uses the current frame's registered text geometry and hitboxes without
2850    /// starting or changing a selection. Blank space and text outside the active
2851    /// selection scope do not match. A containing control can yield a long press
2852    /// to the window's text selection before claiming the gesture itself.
2853    /// Call during pointer dispatch with the event's position, so the hitboxes
2854    /// reflect the point being queried. Touch-selection controls do not match.
2855    pub fn is_selectable_at(position: Point<Pixels>, window: &Window, cx: &mut App) -> bool {
2856        live_text_selection_state(window, cx).is_some_and(|state| {
2857            state.update(cx, |state, cx| {
2858                !state.touch.covers(position)
2859                    && state.endpoint(position, Some(window), cx).inside_text
2860            })
2861        })
2862    }
2863
2864    /// Returns the currently selected text in logical document order.
2865    pub fn selected_text(window: &mut Window, cx: &mut App) -> String {
2866        let Some(state) = live_text_selection_state(window, cx) else {
2867            return String::new();
2868        };
2869        let items = state.read(cx).copy_items(cx);
2870        resolve_copy_items(items, cx)
2871    }
2872
2873    /// Returns whether the window has a geometry selection or any participant
2874    /// has an active participant-local selection such as select-all.
2875    pub fn has_selection(window: &mut Window, cx: &mut App) -> bool {
2876        live_text_selection_state(window, cx).is_some_and(|state| state.read(cx).has_selection(cx))
2877    }
2878
2879    /// Clears window selection and every participant's local selection.
2880    pub fn clear(window: &mut Window, cx: &mut App) {
2881        if let Some(state) = live_text_selection_state(window, cx) {
2882            let handlers = state.update(cx, |state, cx| state.clear_state(cx));
2883            dispatch_clear_handlers(handlers, cx);
2884        }
2885    }
2886
2887    /// Clears selection for a known window identifier.
2888    ///
2889    /// Prefer [`Self::clear`] when a window reference is available. This
2890    /// narrow entry point supports hosts retiring deprecated window wrappers.
2891    pub fn clear_for_window(window_id: gpui::WindowId, cx: &mut App) {
2892        clear_window_text_selection(window_id, cx);
2893    }
2894
2895    /// Ends the current drag while leaving its selection visible.
2896    pub fn end(window: &mut Window, cx: &mut App) {
2897        if let Some(state) = live_text_selection_state(window, cx) {
2898            state.update(cx, |state, cx| state.end(cx));
2899        }
2900    }
2901
2902    /// Calls `callback` whenever the touch selection changes: it appears, its
2903    /// handles move, its menu opens or closes, or it goes away. Whoever draws
2904    /// the handles and the menu re-renders from here.
2905    pub fn observe_touch_selection(
2906        window: &Window,
2907        cx: &mut App,
2908        callback: impl Fn(&mut App) + 'static,
2909    ) -> Subscription {
2910        let state = WindowSelectionState::acquire(window.window_handle().window_id(), cx);
2911        cx.observe(&state, move |_, cx| callback(cx))
2912    }
2913
2914    /// Returns the selection a long press made, laid out for its handles and
2915    /// edit menu, or `None` when the selection was made with a pointer.
2916    pub fn touch_selection(window: &Window, cx: &App) -> Option<TouchSelectionSnapshot> {
2917        WindowSelectionState::existing(window, cx)?
2918            .read(cx)
2919            .touch_selection()
2920    }
2921
2922    /// Records where a touch handle or the edit menu is painted this frame, so
2923    /// that pressing it does not clear the selection it belongs to. Call from
2924    /// paint, every frame the surface is shown.
2925    pub fn register_touch_ui(bounds: Bounds<Pixels>, window: &Window, cx: &mut App) {
2926        if let Some(state) = WindowSelectionState::existing(window, cx) {
2927            state.update(cx, |state, _| state.register_touch_ui(bounds));
2928        }
2929    }
2930
2931    /// Closes the edit menu and keeps the selection with its handles.
2932    pub fn close_edit_menu(window: &mut Window, cx: &mut App) {
2933        if let Some(state) = live_text_selection_state(window, cx) {
2934            state.update(cx, |state, cx| state.close_edit_menu(cx));
2935        }
2936    }
2937
2938    /// Selects all of the text the touch selection started in, keeping the
2939    /// handles and the edit menu over the result.
2940    pub fn select_all(window: &mut Window, cx: &mut App) {
2941        if let Some(state) = live_text_selection_state(window, cx) {
2942            state.update(cx, |state, cx| state.select_all_touched(cx));
2943        }
2944    }
2945
2946    /// Starts dragging one end of the touch selection from `finger`. The other
2947    /// end stays; the menu closes until [`Self::end_edge_drag`].
2948    pub fn begin_edge_drag(
2949        edge: SelectionEdge,
2950        finger: Point<Pixels>,
2951        window: &mut Window,
2952        cx: &mut App,
2953    ) {
2954        if let Some(state) = live_text_selection_state(window, cx) {
2955            state.update(cx, |state, cx| {
2956                state.begin_edge_drag(edge, finger, window, cx)
2957            });
2958            WindowSelectionState::resolve_content_keys(&state, cx);
2959        }
2960    }
2961
2962    /// Moves the dragged end to the text under `finger`.
2963    pub fn update_edge_drag(finger: Point<Pixels>, window: &mut Window, cx: &mut App) {
2964        if let Some(state) = live_text_selection_state(window, cx) {
2965            state.update(cx, |state, cx| state.update_edge_drag(finger, window, cx));
2966            WindowSelectionState::resolve_content_keys(&state, cx);
2967        }
2968    }
2969
2970    /// Ends the handle drag and reopens the edit menu over the result.
2971    pub fn end_edge_drag(window: &mut Window, cx: &mut App) {
2972        if let Some(state) = live_text_selection_state(window, cx) {
2973            state.update(cx, |state, cx| state.end_edge_drag(cx));
2974        }
2975    }
2976
2977    /// Activates the opaque selection scope for this window.
2978    pub fn activate_scope(scope: TextSelectionScopeId, window: &mut Window, cx: &mut App) {
2979        let Some(state) = WindowSelectionState::existing(window, cx) else {
2980            if !cx.has_global::<PendingTextSelectionScopes>() {
2981                cx.set_global(PendingTextSelectionScopes::default());
2982            }
2983            cx.global_mut::<PendingTextSelectionScopes>()
2984                .0
2985                .insert(window.window_handle().window_id(), scope);
2986            return;
2987        };
2988        if state.read(cx).active_scope == scope {
2989            return;
2990        }
2991        let handlers = state.update(cx, |state, cx| state.set_active_scope_state(scope, cx));
2992        dispatch_clear_handlers(handlers, cx);
2993    }
2994}
2995
2996/// A zero-sized root layer which enables text selection for a window.
2997///
2998/// Mount one as the root's first child. Its stable `"window-text-selection"`
2999/// element identity retains the window-local selection entity across frames.
3000pub struct TextSelectionLayer;
3001
3002pub(crate) fn text_selection_scope(
3003    scope: TextSelectionScopeId,
3004    element: impl IntoElement,
3005) -> impl IntoElement {
3006    TextSelectionScopeMarker {
3007        scope,
3008        element: element.into_element(),
3009    }
3010}
3011
3012struct TextSelectionScopeMarker<E> {
3013    scope: TextSelectionScopeId,
3014    element: E,
3015}
3016
3017impl<E: Element> IntoElement for TextSelectionScopeMarker<E> {
3018    type Element = Self;
3019
3020    fn into_element(self) -> Self::Element {
3021        self
3022    }
3023}
3024
3025impl<E: Element> Element for TextSelectionScopeMarker<E> {
3026    type RequestLayoutState = E::RequestLayoutState;
3027    type PrepaintState = E::PrepaintState;
3028
3029    fn id(&self) -> Option<ElementId> {
3030        self.element.id()
3031    }
3032
3033    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
3034        self.element.source_location()
3035    }
3036
3037    fn request_layout(
3038        &mut self,
3039        id: Option<&GlobalElementId>,
3040        inspector_id: Option<&InspectorElementId>,
3041        window: &mut Window,
3042        cx: &mut App,
3043    ) -> (LayoutId, Self::RequestLayoutState) {
3044        let window_id = window.window_handle().window_id();
3045        with_text_selection_scope(window_id, self.scope, cx, |cx| {
3046            self.element.request_layout(id, inspector_id, window, cx)
3047        })
3048    }
3049
3050    fn prepaint(
3051        &mut self,
3052        id: Option<&GlobalElementId>,
3053        inspector_id: Option<&InspectorElementId>,
3054        bounds: Bounds<Pixels>,
3055        request_layout: &mut Self::RequestLayoutState,
3056        window: &mut Window,
3057        cx: &mut App,
3058    ) -> Self::PrepaintState {
3059        let window_id = window.window_handle().window_id();
3060        with_text_selection_scope(window_id, self.scope, cx, |cx| {
3061            self.element
3062                .prepaint(id, inspector_id, bounds, request_layout, window, cx)
3063        })
3064    }
3065
3066    fn paint(
3067        &mut self,
3068        id: Option<&GlobalElementId>,
3069        inspector_id: Option<&InspectorElementId>,
3070        bounds: Bounds<Pixels>,
3071        request_layout: &mut Self::RequestLayoutState,
3072        prepaint: &mut Self::PrepaintState,
3073        window: &mut Window,
3074        cx: &mut App,
3075    ) {
3076        let window_id = window.window_handle().window_id();
3077        with_text_selection_scope(window_id, self.scope, cx, |cx| {
3078            self.element.paint(
3079                id,
3080                inspector_id,
3081                bounds,
3082                request_layout,
3083                prepaint,
3084                window,
3085                cx,
3086            );
3087        });
3088    }
3089}
3090
3091#[doc(hidden)]
3092pub struct TextSelectionLayerPrepaintState(Entity<WindowSelectionState>);
3093
3094impl IntoElement for TextSelectionLayer {
3095    type Element = Self;
3096
3097    fn into_element(self) -> Self::Element {
3098        self
3099    }
3100}
3101
3102impl Element for TextSelectionLayer {
3103    type RequestLayoutState = ();
3104    type PrepaintState = TextSelectionLayerPrepaintState;
3105
3106    fn id(&self) -> Option<ElementId> {
3107        Some("window-text-selection".into())
3108    }
3109
3110    fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
3111        None
3112    }
3113
3114    fn request_layout(
3115        &mut self,
3116        _: Option<&GlobalElementId>,
3117        _: Option<&InspectorElementId>,
3118        window: &mut Window,
3119        cx: &mut App,
3120    ) -> (LayoutId, Self::RequestLayoutState) {
3121        (window.request_layout(Style::default(), [], cx), ())
3122    }
3123
3124    fn prepaint(
3125        &mut self,
3126        global_id: Option<&GlobalElementId>,
3127        _: Option<&InspectorElementId>,
3128        _: Bounds<Pixels>,
3129        _: &mut Self::RequestLayoutState,
3130        window: &mut Window,
3131        cx: &mut App,
3132    ) -> Self::PrepaintState {
3133        GlobalState::init(cx);
3134        let state = retain_text_selection_state(global_id, window, cx);
3135        state.read(cx).layer_painted.set(true);
3136        // The handles and the menu register again as they paint this frame.
3137        if state.read(cx).touch.painted_any() {
3138            state.update(cx, |state, _| state.touch.begin_frame());
3139        }
3140        TextSelectionLayerPrepaintState(state)
3141    }
3142
3143    fn paint(
3144        &mut self,
3145        _: Option<&GlobalElementId>,
3146        _: Option<&InspectorElementId>,
3147        _: Bounds<Pixels>,
3148        _: &mut Self::RequestLayoutState,
3149        state: &mut Self::PrepaintState,
3150        window: &mut Window,
3151        cx: &mut App,
3152    ) {
3153        paint_text_selection(&state.0, window, cx);
3154    }
3155}
3156
3157fn retain_text_selection_state(
3158    global_id: Option<&GlobalElementId>,
3159    window: &mut Window,
3160    cx: &mut App,
3161) -> Entity<WindowSelectionState> {
3162    let window_id = window.window_handle().window_id();
3163    let state = window.with_element_state::<Entity<WindowSelectionState>, _>(
3164        global_id.expect("TextSelection has a stable element id"),
3165        |retained, _| {
3166            let state = retained.unwrap_or_else(|| WindowSelectionState::acquire(window_id, cx));
3167            (state.clone(), state)
3168        },
3169    );
3170    // Registered once: writing the global again every frame would change it,
3171    // and every view that reads it, on every frame.
3172    let registered = cx.has_global::<SelectionStateRegistry>()
3173        && cx
3174            .global::<SelectionStateRegistry>()
3175            .0
3176            .get(&window_id)
3177            .is_some_and(|registered| registered.entity_id() == state.entity_id());
3178    if !registered {
3179        if !cx.has_global::<SelectionStateRegistry>() {
3180            cx.set_global(SelectionStateRegistry::default());
3181        }
3182        cx.global_mut::<SelectionStateRegistry>()
3183            .0
3184            .insert(window_id, state.downgrade());
3185    }
3186    state
3187}
3188
3189fn schedule_selection_frame(
3190    state: &Entity<WindowSelectionState>,
3191    window: &mut Window,
3192    cx: &mut App,
3193) {
3194    if state.read(cx).schedule_finish_frame() {
3195        let state = state.downgrade();
3196        window.defer(cx, move |window, cx| {
3197            let Some(state) = state.upgrade() else {
3198                return;
3199            };
3200            let stale = state
3201                .read(cx)
3202                .stale_participants(state.read(cx).layer_painted.get());
3203            // Retiring an endpoint is a real selection change. Ordinary
3204            // frame publication does not write the shared window entity.
3205            let mut handlers = if state.read(cx).endpoint_departed(&stale) {
3206                state.update(cx, |state, cx| state.clear_state(cx))
3207            } else {
3208                Vec::new()
3209            };
3210            let departed = state.read(cx).remove_stale_participants(stale);
3211            handlers.extend(clear_departed_participants(departed, cx));
3212            let refresh_order = state.read(cx).finish_registrations();
3213            let mut changes = state
3214                .read(cx)
3215                .changed_snapshots(cx)
3216                .into_iter()
3217                .map(|(participant, snapshot)| (participant.entity_id(), (participant, snapshot)))
3218                .collect::<HashMap<_, _>>();
3219            let drawn = std::mem::take(&mut *state.read(cx).draw_changed.borrow_mut());
3220            for (id, participant) in drawn {
3221                if let Some(participant) = participant.upgrade() {
3222                    changes.entry(id).or_insert_with(|| {
3223                        let snapshot = participant.read(cx).snapshot;
3224                        (participant, snapshot)
3225                    });
3226                }
3227            }
3228            for (_, (participant, snapshot)) in changes {
3229                participant.update(cx, |participant, cx| {
3230                    participant.set_snapshot_quietly(snapshot);
3231                    cx.emit(TextSelectionEvent::SelectionChanged(snapshot));
3232                });
3233            }
3234            dispatch_clear_handlers(handlers, cx);
3235            if refresh_order {
3236                // GPUI does not expose the position of a replayed subtree in
3237                // paint order. Recover it once after an incomplete topology
3238                // change, not on ordinary partial selection redraws.
3239                window.refresh();
3240            }
3241            if state.read(cx).touch_edges_moved.replace(false) {
3242                state.update(cx, |state, cx| state.touch_changed(cx));
3243            }
3244            // Direct participant scrolling produces no wheel event. Refresh
3245            // the held cursor after paint registers the new scroll geometry.
3246            if state.read(cx).refresh_held_cursor.replace(false) && state.read(cx).is_selecting {
3247                state.update(cx, |state, cx| {
3248                    // A handle drag holds the finger off the text; keep the
3249                    // same offset, or the cursor would hop between the two.
3250                    let position = window.mouse_position();
3251                    let position = state
3252                        .touch
3253                        .drag
3254                        .map_or(position, |drag| drag.text_position(position));
3255                    state.update_in_window(position, window, cx);
3256                });
3257                WindowSelectionState::resolve_content_keys(&state, cx);
3258            }
3259        });
3260    }
3261}
3262
3263fn paint_text_selection(state: &Entity<WindowSelectionState>, window: &mut Window, cx: &mut App) {
3264    schedule_selection_frame(state, window, cx);
3265
3266    let mouse_down_state = state.downgrade();
3267    window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
3268        if event.button != MouseButton::Left {
3269            return;
3270        }
3271        let Some(state) = mouse_down_state.upgrade() else {
3272            return;
3273        };
3274        // A press on a handle or on the edit menu acts on the selection; it
3275        // must not clear it.
3276        if state.read(cx).touch.covers(event.position) {
3277            return;
3278        }
3279        if phase.capture() {
3280            GlobalState::init(cx);
3281            GlobalState::reset_text_selection_suppression(cx);
3282            let handlers = state.update(cx, |state, cx| {
3283                if state.mouse_down_prepared {
3284                    return Vec::new();
3285                }
3286                state.mouse_down_prepared = true;
3287                state.prepare_for_mouse_down(event.click_count == 1 && event.modifiers.shift, cx)
3288            });
3289            dispatch_clear_handlers(handlers, cx);
3290        } else if event.click_count == 1 {
3291            if GlobalState::is_text_selection_suppressed(cx) {
3292                state.update(cx, |state, _| state.pending_extension_anchor = None);
3293                return;
3294            }
3295            state.update(cx, |state, cx| {
3296                if !state.is_selecting {
3297                    state.begin_in_window(event.position, event.modifiers.shift, window, cx)
3298                }
3299            });
3300            WindowSelectionState::resolve_content_keys(&state, cx);
3301        } else if event.click_count >= 2 {
3302            if GlobalState::is_text_selection_suppressed(cx) {
3303                return;
3304            }
3305            let touch = GlobalState::is_touch_press(cx);
3306            state.update(cx, |state, cx| {
3307                let unit = state.select_at(event.position, event.click_count, window, cx);
3308                // A double tap is touch's other way to select a word, and it
3309                // gets the handles and the menu like a long press does.
3310                if touch {
3311                    state.keep_touch_selection(cx);
3312                } else if let Some(unit) = unit {
3313                    state.hold_unit(unit, cx);
3314                }
3315            });
3316            WindowSelectionState::resolve_content_keys(&state, cx);
3317        }
3318    });
3319
3320    // Every touch is offered as a drag first; that is how a tap's mouse
3321    // events are later told apart from a mouse's.
3322    window.on_mouse_event(move |event: &TouchDragEvent, phase, _, cx| {
3323        if phase.capture() && event.phase == TouchPhase::Started {
3324            GlobalState::note_touch(cx);
3325        }
3326    });
3327
3328    // Touch panning remains scrolling until a long press actually hits text.
3329    // Claiming the gesture keeps subsequent moves out of the pan recognizer.
3330    let long_press_state = state.downgrade();
3331    window.on_mouse_event(move |event: &LongPressEvent, phase, window, cx| {
3332        if !phase.bubble() {
3333            return;
3334        }
3335        let Some(state) = long_press_state.upgrade() else {
3336            return;
3337        };
3338        if event.phase == TouchPhase::Started {
3339            if window.default_prevented()
3340                || state.read(cx).touch.covers(event.start_position)
3341                || !state.update(cx, |state, cx| {
3342                    state
3343                        .endpoint(event.start_position, Some(window), cx)
3344                        .inside_text
3345                })
3346            {
3347                return;
3348            }
3349            GlobalState::init(cx);
3350            GlobalState::reset_text_selection_suppression(cx);
3351            let handlers = state.update(cx, |state, cx| state.prepare_for_mouse_down(false, cx));
3352            dispatch_clear_handlers(handlers, cx);
3353            let selected = state.update(cx, |state, cx| {
3354                state.select_at(event.start_position, 2, window, cx);
3355                state.anchor.is_some()
3356            });
3357            if !selected {
3358                return;
3359            }
3360            window.capture_long_press(&state);
3361        } else if !window.has_long_press_capture(&state) {
3362            return;
3363        } else {
3364            state.update(cx, |state, cx| match event.phase {
3365                TouchPhase::Moved => {
3366                    state.is_selecting = true;
3367                    state.update_in_window(event.position, window, cx);
3368                }
3369                TouchPhase::Ended | TouchPhase::Cancelled => {
3370                    state.end(cx);
3371                    // The finger is up; the selection it made gets its
3372                    // handles and the edit menu.
3373                    state.keep_touch_selection(cx);
3374                }
3375                _ => {}
3376            });
3377        }
3378        window.prevent_default();
3379        cx.stop_propagation();
3380        WindowSelectionState::resolve_content_keys(&state, cx);
3381    });
3382
3383    // A handle drag in progress follows the finger or the pointer wherever
3384    // it goes, whether or not the handle it took is laid out this frame.
3385    let drag_state = state.downgrade();
3386    window.on_mouse_event(move |event: &TouchDragEvent, phase, window, cx| {
3387        if !phase.bubble() || event.phase == TouchPhase::Started {
3388            return;
3389        }
3390        let Some(state) = drag_state.upgrade() else {
3391            return;
3392        };
3393        if state.read(cx).touch.drag.is_none() {
3394            return;
3395        }
3396        cx.stop_propagation();
3397        state.update(cx, |state, cx| match event.phase {
3398            TouchPhase::Moved => state.update_edge_drag(event.position, window, cx),
3399            _ => state.end_edge_drag(cx),
3400        });
3401        WindowSelectionState::resolve_content_keys(&state, cx);
3402    });
3403    let drag_state = state.downgrade();
3404    window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
3405        if phase.bubble()
3406            && event.pressed_button == Some(MouseButton::Left)
3407            && let Some(state) = drag_state.upgrade()
3408            && state.read(cx).touch.drag.is_some()
3409        {
3410            state.update(cx, |state, cx| {
3411                state.update_edge_drag(event.position, window, cx)
3412            });
3413            WindowSelectionState::resolve_content_keys(&state, cx);
3414        }
3415    });
3416    let drag_state = state.downgrade();
3417    window.on_mouse_event(move |event: &MouseUpEvent, phase, _, cx| {
3418        if phase.bubble()
3419            && event.button == MouseButton::Left
3420            && let Some(state) = drag_state.upgrade()
3421        {
3422            state.update(cx, |state, cx| state.end_edge_drag(cx));
3423        }
3424    });
3425
3426    let mouse_move_state = state.downgrade();
3427    window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
3428        if phase.bubble()
3429            && let Some(state) = mouse_move_state.upgrade()
3430            // Moving the pointer only moves a selection being made.
3431            && state.read(cx).is_selecting
3432        {
3433            state.update(cx, |state, cx| {
3434                // A handle drag maps the pointer through the handle's offset;
3435                // the raw pointer must not fight it.
3436                if state.touch.drag.is_some() {
3437                    return;
3438                }
3439                state.update_in_window(event.position, window, cx)
3440            });
3441            WindowSelectionState::resolve_content_keys(&state, cx);
3442        }
3443    });
3444
3445    let mouse_up_state = state.downgrade();
3446    window.on_mouse_event(move |_: &MouseUpEvent, phase, _, cx| {
3447        if phase.bubble()
3448            && let Some(state) = mouse_up_state.upgrade()
3449        {
3450            state.update(cx, |state, cx| {
3451                state.mouse_down_prepared = false;
3452                state.end(cx)
3453            });
3454        }
3455    });
3456
3457    let scroll_state = state.downgrade();
3458    window.on_mouse_event(move |event: &ScrollEvent, phase, window, cx| {
3459        let Some(state) = scroll_state.upgrade() else {
3460            return;
3461        };
3462        // On capture, before a scroll container can stop the event: one
3463        // stretched past its end swallows the whole stream, the lift
3464        // included, and the menu would never come back.
3465        if phase.capture() {
3466            if state.read(cx).touch.active {
3467                state.update(cx, |state, cx| {
3468                    state.edit_menu_on_scroll(event.touch_phase, cx)
3469                });
3470            }
3471            return;
3472        }
3473        // A scroll only moves a selection being made under the pointer.
3474        if phase.bubble() && state.read(cx).is_selecting {
3475            let position = window.mouse_position();
3476            state.update(cx, |state, cx| {
3477                // A handle drag holds the finger off the text; keep its offset.
3478                let position = state
3479                    .touch
3480                    .drag
3481                    .map_or(position, |drag| drag.text_position(position));
3482                state.update_in_window(position, window, cx)
3483            });
3484            WindowSelectionState::resolve_content_keys(&state, cx);
3485        }
3486    });
3487}
3488
3489fn live_text_selection_state(
3490    window: &Window,
3491    cx: &mut App,
3492) -> Option<Entity<WindowSelectionState>> {
3493    WindowSelectionState::existing(window, cx)
3494}
3495
3496pub(crate) fn clear_window_text_selection(window_id: gpui::WindowId, cx: &mut App) {
3497    if !cx.has_global::<SelectionStateRegistry>() {
3498        return;
3499    }
3500    let Some(state) = cx
3501        .global::<SelectionStateRegistry>()
3502        .0
3503        .get(&window_id)
3504        .and_then(WeakEntity::upgrade)
3505    else {
3506        return;
3507    };
3508    let handlers = state.update(cx, |state, cx| state.clear_state(cx));
3509    dispatch_clear_handlers(handlers, cx);
3510}
3511
3512#[cfg(test)]
3513mod tests {
3514    use super::*;
3515    use crate::ElementExt as _;
3516    #[cfg(feature = "gpui-fast")]
3517    use gpui::StatefulInteractiveElement as _;
3518    use gpui::{
3519        Bounds, ContentMask, Context, Hitbox, HitboxBehavior, HitboxId, InteractiveElement as _,
3520        IntoElement, ParentElement as _, Render, SharedString, Styled as _, StyledText,
3521        TestAppContext, TextLayout, Window, div, point, prelude::FluentBuilder as _, px, size,
3522    };
3523    use std::{
3524        cell::{Cell, RefCell},
3525        rc::Rc,
3526    };
3527
3528    struct FakeParticipant {
3529        selection: TextSelectionHandle,
3530    }
3531
3532    struct WindowSelectionView {
3533        selection: TextSelectionHandle,
3534    }
3535
3536    struct SelectionElementOnlyView;
3537    struct ToggleSelectionElementView {
3538        enabled: bool,
3539        selection: TextSelectionHandle,
3540    }
3541
3542    struct DoubleSelectionElementView {
3543        selection: TextSelectionHandle,
3544    }
3545
3546    struct WindowOwnedSelectionView {
3547        selection: TextSelectionHandle,
3548    }
3549
3550    struct FirstFrameScopedSelectionView {
3551        selection: TextSelectionHandle,
3552    }
3553
3554    struct PlainRunLayoutView {
3555        texts: Vec<SharedString>,
3556        layouts: Vec<TextLayout>,
3557    }
3558
3559    impl Render for WindowSelectionView {
3560        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3561            div()
3562        }
3563    }
3564
3565    impl Render for SelectionElementOnlyView {
3566        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3567            div()
3568                .size_full()
3569                .child(TextSelectionLayer)
3570                .child(
3571                    div()
3572                        .size_full()
3573                        .on_mouse_down(MouseButton::Left, |_, _, cx| {
3574                            GlobalState::suppress_text_selection(cx);
3575                        }),
3576                )
3577        }
3578    }
3579
3580    impl Render for ToggleSelectionElementView {
3581        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3582            let selection = self.selection.clone();
3583            div().when(self.enabled, |this| {
3584                this.child(TextSelectionLayer)
3585                    .child(div().size_full().on_prepaint(move |bounds, window, cx| {
3586                        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3587                        selection.register(
3588                            TextSelectionRegistration::new(hitbox, bounds)
3589                                .with_text_bounds(vec![bounds]),
3590                            window,
3591                            cx,
3592                        );
3593                    }))
3594            })
3595        }
3596    }
3597
3598    impl Render for DoubleSelectionElementView {
3599        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3600            let selection = self.selection.clone();
3601            div()
3602                .size_full()
3603                .child(TextSelectionLayer)
3604                .child(TextSelectionLayer)
3605                .on_prepaint(move |bounds, window, cx| {
3606                    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3607                    selection.register(
3608                        TextSelectionRegistration::new(hitbox, bounds)
3609                            .with_text_bounds(vec![bounds]),
3610                        window,
3611                        cx,
3612                    );
3613                })
3614        }
3615    }
3616
3617    impl Render for WindowOwnedSelectionView {
3618        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3619            let selection = self.selection.clone();
3620            div()
3621                .size_full()
3622                .child(TextSelectionLayer)
3623                .child(div().size_full().on_prepaint(move |bounds, window, cx| {
3624                    let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3625                    selection.register(
3626                        TextSelectionRegistration::new(hitbox, bounds)
3627                            .with_text_bounds(vec![bounds]),
3628                        window,
3629                        cx,
3630                    );
3631                }))
3632        }
3633    }
3634
3635    impl Render for FirstFrameScopedSelectionView {
3636        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3637            let scope = TextSelectionScopeId::from_raw(23);
3638            TextSelection::activate_scope(scope, window, cx);
3639            let selection = self.selection.clone();
3640
3641            div().child(TextSelectionLayer).child(
3642                div()
3643                    .size_full()
3644                    .on_prepaint(move |bounds, window, cx| {
3645                        let hitbox = window.insert_hitbox(bounds, HitboxBehavior::Normal);
3646                        selection.register(
3647                            TextSelectionRegistration::new(hitbox, bounds)
3648                                .with_text_bounds(vec![bounds]),
3649                            window,
3650                            cx,
3651                        );
3652                    })
3653                    .text_selection_scope(scope),
3654            )
3655        }
3656    }
3657
3658    impl Render for PlainRunLayoutView {
3659        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
3660            self.layouts.clear();
3661            let children = self
3662                .texts
3663                .iter()
3664                .enumerate()
3665                .map(|(index, text)| {
3666                    let text = StyledText::new(text.clone());
3667                    self.layouts.push(text.layout().clone());
3668                    div().absolute().top(px(index as f32 * 40.)).child(text)
3669                })
3670                .collect::<Vec<_>>();
3671            div().size_full().children(children)
3672        }
3673    }
3674
3675    impl FakeParticipant {
3676        fn new(text: &str, cx: &mut gpui::App) -> Self {
3677            let selection = TextSelectionHandle::new(text, cx);
3678            Self { selection }
3679        }
3680
3681        fn register(
3682            &self,
3683            selection_state: &mut WindowSelectionState,
3684            y: f32,
3685            scope: TextSelectionScopeId,
3686            document_order: u64,
3687            cx: &mut gpui::App,
3688        ) {
3689            let bounds = Bounds::new(point(px(0.), px(y)), size(px(100.), px(10.)));
3690            selection_state.register_participant(
3691                self.selection.clone(),
3692                TextSelectionRegistration::new(
3693                    Hitbox {
3694                        id: HitboxId::placeholder(),
3695                        bounds,
3696                        content_mask: ContentMask { bounds },
3697                        behavior: HitboxBehavior::Normal,
3698                    },
3699                    bounds,
3700                )
3701                .with_scope(scope)
3702                .with_document_order(document_order)
3703                .with_text_bounds(vec![bounds]),
3704                cx,
3705            );
3706        }
3707    }
3708
3709    fn laid_out_runs(texts: &[&str], cx: &mut TestAppContext) -> Vec<(SharedString, TextLayout)> {
3710        let texts = texts
3711            .iter()
3712            .map(|text| SharedString::from(*text))
3713            .collect::<Vec<_>>();
3714        let view = cx.add_window({
3715            let texts = texts.clone();
3716            move |_, _| PlainRunLayoutView {
3717                texts,
3718                layouts: Vec::new(),
3719            }
3720        });
3721        cx.update_window(*view, |_, window, cx| {
3722            let _ = window.draw(cx);
3723        })
3724        .unwrap();
3725        let layouts = cx.update(|cx| view.read(cx).unwrap().layouts.clone());
3726        texts.into_iter().zip(layouts).collect()
3727    }
3728
3729    fn plain_snapshot(anchor: Point<Pixels>, cursor: Point<Pixels>) -> TextSelectionSnapshot {
3730        TextSelectionSnapshot::new(
3731            TextSelectionEndpoint::new(None, anchor),
3732            TextSelectionEndpoint::new(None, cursor),
3733        )
3734        .with_window_points(Some(TextSelectionWindowPoints { anchor, cursor }))
3735    }
3736
3737    #[gpui::test]
3738    fn scope_stack_is_cleaned_after_panicking_subtree(cx: &mut TestAppContext) {
3739        let window_id = {
3740            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3741            window_cx.update(|window, _| window.window_handle().window_id())
3742        };
3743        let scope = TextSelectionScopeId::from_raw(41);
3744
3745        cx.update(|cx| {
3746            let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
3747                with_text_selection_scope(window_id, scope, cx, |_| panic!("subtree failed"));
3748            }));
3749
3750            assert!(result.is_err());
3751            assert_eq!(current_text_selection_scope(window_id, cx), None);
3752        });
3753    }
3754
3755    #[gpui::test]
3756    fn reentrant_scope_from_one_window_does_not_pollute_another(cx: &mut TestAppContext) {
3757        let first_window_id = {
3758            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3759            window_cx.update(|window, _| window.window_handle().window_id())
3760        };
3761        let second_window_id = {
3762            let (_, window_cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
3763            window_cx.update(|window, _| window.window_handle().window_id())
3764        };
3765        let scope = TextSelectionScopeId::from_raw(42);
3766
3767        cx.update(|cx| {
3768            with_text_selection_scope(first_window_id, scope, cx, |cx| {
3769                assert_eq!(current_text_selection_scope(second_window_id, cx), None);
3770                assert_eq!(
3771                    current_text_selection_scope(first_window_id, cx),
3772                    Some(scope)
3773                );
3774            });
3775        });
3776    }
3777
3778    #[gpui::test]
3779    fn selection_callback_can_reenter_its_selection_state(cx: &mut TestAppContext) {
3780        let called = Rc::new(Cell::new(false));
3781        let called_from_callback = called.clone();
3782        let (selection_state, participant) = cx.update(|cx| {
3783            let selection_state = cx.new(|_| WindowSelectionState::default());
3784            let selection_state_for_callback = selection_state.clone();
3785            let participant = FakeParticipant::new("participant", cx);
3786            participant
3787                .selection
3788                .subscribe(
3789                    move |event, cx| {
3790                        if matches!(event, TextSelectionEvent::SelectionChanged(Some(_))) {
3791                            selection_state_for_callback
3792                                .update(cx, |_, _| called_from_callback.set(true));
3793                        }
3794                    },
3795                    cx,
3796                )
3797                .detach();
3798            (selection_state, participant)
3799        });
3800        cx.run_until_parked();
3801        cx.update(|cx| {
3802            selection_state.update(cx, |selection_state, cx| {
3803                participant.register(selection_state, 0., TextSelectionScopeId::default(), 0, cx);
3804                selection_state.begin(point(px(1.), px(1.)), false, cx);
3805                selection_state.update(point(px(20.), px(1.)), cx);
3806            });
3807        });
3808        cx.run_until_parked();
3809        assert!(called.get());
3810    }
3811
3812    #[gpui::test]
3813    fn selection_events_preserve_snapshot_then_clear_order(cx: &mut TestAppContext) {
3814        let observed = Rc::new(RefCell::new(Vec::new()));
3815        let observed_for_callback = observed.clone();
3816        let selection = cx.update(|cx| {
3817            let selection = TextSelectionHandle::new("selection", cx);
3818            selection
3819                .subscribe(
3820                    move |event, _| {
3821                        if let TextSelectionEvent::SelectionChanged(snapshot) = event {
3822                            observed_for_callback.borrow_mut().push(snapshot.is_some());
3823                        }
3824                    },
3825                    cx,
3826                )
3827                .detach();
3828            selection
3829        });
3830        cx.run_until_parked();
3831        cx.update(|cx| {
3832            selection.0.update(cx, |state, cx| {
3833                state.set_snapshot(
3834                    Some(plain_snapshot(point(px(1.), px(1.)), point(px(8.), px(1.)))),
3835                    cx,
3836                );
3837                state.clear_state(cx);
3838            });
3839        });
3840        cx.run_until_parked();
3841        assert_eq!(&*observed.borrow(), &[true, false]);
3842    }
3843
3844    fn text_run(order: u64, text: SharedString, layout: TextLayout) -> TextSelectionRun {
3845        let bounds = layout.bounds();
3846        TextSelectionRun::new(text, layout, bounds).with_document_order(order)
3847    }
3848
3849    #[gpui::test]
3850    fn public_selection_data_uses_builders_and_readers(cx: &mut TestAppContext) {
3851        let bounds = Bounds::new(point(px(1.), px(2.)), size(px(30.), px(10.)));
3852        let hitbox = Hitbox {
3853            id: HitboxId::placeholder(),
3854            bounds,
3855            content_mask: ContentMask { bounds },
3856            behavior: HitboxBehavior::Normal,
3857        };
3858        let scope = TextSelectionScopeId::from_raw(7);
3859        let endpoint = TextSelectionEndpoint::new(None, bounds.origin)
3860            .with_content_key(TextSelectionContentKey::new(11));
3861        let snapshot = TextSelectionSnapshot::new(endpoint, endpoint)
3862            .with_selecting(true)
3863            .with_window_points(Some(TextSelectionWindowPoints {
3864                anchor: bounds.origin,
3865                cursor: bounds.bottom_right(),
3866            }))
3867            .with_coverage(TextSelectionCoverage::Full);
3868        let registration = TextSelectionRegistration::new(hitbox, bounds)
3869            .with_scroll_offset(point(px(3.), px(4.)))
3870            .with_scope(scope)
3871            .with_document_order(9)
3872            .with_text_bounds(vec![bounds]);
3873
3874        assert_eq!(endpoint.entity_id(), None);
3875        assert_eq!(endpoint.content_point(), bounds.origin);
3876        assert_eq!(
3877            endpoint.content_key(),
3878            Some(TextSelectionContentKey::new(11))
3879        );
3880        assert_eq!(snapshot.anchor(), endpoint);
3881        assert_eq!(snapshot.cursor(), endpoint);
3882        assert!(snapshot.is_selecting());
3883        assert_eq!(snapshot.coverage(), TextSelectionCoverage::Full);
3884        assert_eq!(
3885            snapshot.window_points(),
3886            Some(TextSelectionWindowPoints {
3887                anchor: bounds.origin,
3888                cursor: bounds.bottom_right(),
3889            })
3890        );
3891        assert_eq!(registration.bounds(), bounds);
3892        assert_eq!(registration.scroll_offset(), point(px(3.), px(4.)));
3893        assert_eq!(registration.scope(), scope);
3894        assert_eq!(registration.document_order(), 9);
3895        assert_eq!(registration.text_bounds(), &[bounds]);
3896
3897        let (text, layout) = laid_out_runs(&["aé"], cx).pop().unwrap();
3898        let text_run = TextSelectionRun::new(text.clone(), layout.clone(), layout.bounds())
3899            .with_document_order(3);
3900        assert_eq!(text_run.document_order(), 3);
3901        assert_eq!(text_run.text(), &text);
3902        assert_eq!(text_run.layout().len(), layout.len());
3903        assert_eq!(text_run.bounds(), layout.bounds());
3904
3905        let projection = TextSelectionProjection {
3906            ranges: vec![Some(1..3)],
3907            is_active: true,
3908        };
3909        assert_eq!(projection.ranges(), &[Some(1..3)]);
3910        assert!(projection.is_active());
3911    }
3912
3913    #[gpui::test]
3914    fn selection_handle_is_the_public_adapter_seam(cx: &mut TestAppContext) {
3915        let selected = Rc::new(Cell::new(false));
3916        let selected_from_callback = selected.clone();
3917        cx.update(|cx| {
3918            let selection = TextSelectionHandle::new("initial", cx);
3919            let entity_id = selection.entity_id();
3920            selection.set_fallback_copy_text("updated", cx);
3921            selection.set_local_selection(true, cx);
3922            selection
3923                .subscribe(
3924                    move |event, _| {
3925                        if let TextSelectionEvent::SelectionChanged(snapshot) = event {
3926                            selected_from_callback.set(snapshot.is_some());
3927                        }
3928                    },
3929                    cx,
3930                )
3931                .detach();
3932            selection.focus_with(|_, _| {}, cx);
3933            selection.copy_with(|_| "copied".to_string(), cx);
3934            selection.resolve_content_key_with(|_, _| Some(TextSelectionContentKey::new(3)), cx);
3935
3936            assert_eq!(selection.entity_id(), entity_id);
3937            assert_eq!(selection.snapshot(cx), None);
3938            assert_eq!(
3939                selection.update_runs(&[], cx),
3940                TextSelectionProjection::default()
3941            );
3942        });
3943        assert!(!selected.get());
3944    }
3945
3946    #[gpui::test]
3947    fn selection_handle_can_subscribe_its_window_to_refresh(cx: &mut TestAppContext) {
3948        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
3949            selection: TextSelectionHandle::new("refresh", cx),
3950        });
3951        cx.update(|window, cx| {
3952            let selection = TextSelectionHandle::new("refresh", cx);
3953            selection.refresh_window_on_change(window, cx).detach();
3954        });
3955    }
3956
3957    #[gpui::test]
3958    fn plain_projection_preserves_forward_reversed_and_unicode_ranges(cx: &mut TestAppContext) {
3959        let (text, layout) = laid_out_runs(&["aé🙂z"], cx).pop().unwrap();
3960        let run = text_run(0, text, layout.clone());
3961        let start = layout.position_for_index(1).unwrap();
3962        let end = layout.position_for_index(7).unwrap();
3963
3964        let forward = project_ranges(Some(plain_snapshot(start, end)), std::slice::from_ref(&run));
3965        let reversed = project_ranges(Some(plain_snapshot(end, start)), &[run]);
3966
3967        assert_eq!(forward.ranges(), &[Some(1..7)]);
3968        assert_eq!(reversed.ranges(), &[Some(1..7)]);
3969        assert!(forward.is_active());
3970        assert!(reversed.is_active());
3971    }
3972
3973    #[gpui::test]
3974    fn double_click_expands_a_plain_run_to_the_input_word_boundary(cx: &mut TestAppContext) {
3975        let (text, layout) = laid_out_runs(&["one café, three"], cx).pop().unwrap();
3976        let run = text_run(0, text, layout.clone());
3977        let click = layout.position_for_index(6).unwrap();
3978
3979        let (anchor, cursor) =
3980            carets_for_multi_click(std::slice::from_ref(&run), click, 2).unwrap();
3981        let states = project_ranges(Some(plain_snapshot(anchor.origin, cursor.origin)), &[run]);
3982
3983        assert_eq!(states.ranges(), &[Some(4..9)]);
3984    }
3985
3986    #[gpui::test]
3987    fn multi_click_uses_text_layout_window_coordinates_at_a_nonzero_origin(
3988        cx: &mut TestAppContext,
3989    ) {
3990        let mut runs = laid_out_runs(&["above", "alpha beta"], cx);
3991        let (text, layout) = runs.pop().unwrap();
3992        assert!(layout.bounds().origin.y > px(0.));
3993        let run = text_run(0, text, layout.clone());
3994        let click = layout.position_for_index(7).unwrap();
3995
3996        let (anchor, cursor) =
3997            carets_for_multi_click(std::slice::from_ref(&run), click, 2).unwrap();
3998        let projection = project_ranges(Some(plain_snapshot(anchor.origin, cursor.origin)), &[run]);
3999
4000        assert_eq!(projection.ranges(), &[Some(6..10)]);
4001    }
4002
4003    #[gpui::test]
4004    fn triple_click_expands_to_the_input_logical_line_not_the_visual_row(cx: &mut TestAppContext) {
4005        let (text, layout) = laid_out_runs(&["second line"], cx).pop().unwrap();
4006        let run = text_run(0, text, layout.clone());
4007        let click = layout.position_for_index(4).unwrap();
4008
4009        let (anchor, cursor) =
4010            carets_for_multi_click(std::slice::from_ref(&run), click, 4).unwrap();
4011        let states = project_ranges(Some(plain_snapshot(anchor.origin, cursor.origin)), &[run]);
4012
4013        assert_eq!(states.ranges(), &[Some(0..11)]);
4014        assert_eq!(line_range_at("first line\nsecond line\nthird", 15), 11..22);
4015    }
4016
4017    #[gpui::test]
4018    fn plain_projection_spans_multiple_runs_and_leaves_empty_gutters_unselected(
4019        cx: &mut TestAppContext,
4020    ) {
4021        let mut runs = laid_out_runs(&["first", "", "second"], cx);
4022        let (first_text, first_layout) = runs.remove(0);
4023        let (gutter_text, gutter_layout) = runs.remove(0);
4024        let (second_text, second_layout) = runs.remove(0);
4025        let start = first_layout.position_for_index(2).unwrap();
4026        let end = second_layout.position_for_index(3).unwrap();
4027        let states = project_ranges(
4028            Some(plain_snapshot(start, end)),
4029            &[
4030                text_run(2, second_text, second_layout),
4031                text_run(1, gutter_text, gutter_layout),
4032                text_run(0, first_text, first_layout),
4033            ],
4034        );
4035
4036        assert_eq!(states.ranges(), &[Some(0..3), None, Some(2..5)]);
4037        assert!(states.is_active());
4038    }
4039
4040    #[gpui::test]
4041    fn plain_projection_caches_multiple_participant_copies_in_document_order(
4042        cx: &mut TestAppContext,
4043    ) {
4044        let mut runs = laid_out_runs(&["one", "two"], cx);
4045        let (first_text, first_layout) = runs.remove(0);
4046        let (second_text, second_layout) = runs.remove(0);
4047        let snapshot = plain_snapshot(
4048            first_layout.position_for_index(1).unwrap(),
4049            second_layout.position_for_index(2).unwrap(),
4050        );
4051        cx.update(|cx| {
4052            let mut selection_state = WindowSelectionState::default();
4053            let first = FakeParticipant::new("", cx);
4054            let second = FakeParticipant::new("", cx);
4055            first.register(
4056                &mut selection_state,
4057                0.,
4058                TextSelectionScopeId::default(),
4059                1,
4060                cx,
4061            );
4062            second.register(
4063                &mut selection_state,
4064                20.,
4065                TextSelectionScopeId::default(),
4066                0,
4067                cx,
4068            );
4069
4070            first
4071                .selection
4072                .0
4073                .update(cx, |state, cx| state.set_snapshot(Some(snapshot), cx));
4074            let projection = first
4075                .selection
4076                .update_runs(&[text_run(0, first_text, first_layout)], cx);
4077            assert_eq!(projection.ranges(), &[Some(1..3)]);
4078            assert!(projection.is_active());
4079            second
4080                .selection
4081                .0
4082                .update(cx, |state, cx| state.set_snapshot(Some(snapshot), cx));
4083            let projection = second
4084                .selection
4085                .update_runs(&[text_run(0, second_text, second_layout)], cx);
4086            assert_eq!(projection.ranges(), &[Some(0..2)]);
4087            assert!(projection.is_active());
4088
4089            assert_eq!(selection_state.selected_text(cx), "tw\nne");
4090        });
4091    }
4092
4093    #[gpui::test]
4094    fn plain_projection_invalidates_cached_copy_when_the_snapshot_changes(cx: &mut TestAppContext) {
4095        let (text, layout) = laid_out_runs(&["first"], cx).pop().unwrap();
4096        let first_snapshot = plain_snapshot(
4097            layout.position_for_index(1).unwrap(),
4098            layout.position_for_index(3).unwrap(),
4099        );
4100        let changed_snapshot = plain_snapshot(
4101            layout.position_for_index(3).unwrap(),
4102            layout.position_for_index(5).unwrap(),
4103        );
4104        let run = text_run(0, text, layout);
4105        cx.update(|cx| {
4106            let mut selection_state = WindowSelectionState::default();
4107            let participant = FakeParticipant::new("", cx);
4108            participant.register(
4109                &mut selection_state,
4110                0.,
4111                TextSelectionScopeId::default(),
4112                0,
4113                cx,
4114            );
4115            participant.selection.0.update(cx, |state, cx| {
4116                state.set_snapshot(Some(first_snapshot), cx);
4117                state.update_runs(std::slice::from_ref(&run));
4118            });
4119            assert_eq!(selection_state.selected_text(cx), "ir");
4120
4121            participant.selection.0.update(cx, |state, cx| {
4122                state.set_snapshot(Some(changed_snapshot), cx);
4123            });
4124            assert_eq!(selection_state.selected_text(cx), "");
4125
4126            participant.selection.update_runs(&[run], cx);
4127            assert_eq!(selection_state.selected_text(cx), "st");
4128            selection_state.clear(cx);
4129            participant.selection.set_local_selection(true, cx);
4130            assert_eq!(selection_state.selected_text(cx), "");
4131        });
4132    }
4133
4134    #[gpui::test]
4135    fn plain_projection_orders_cached_runs_by_frame_order_not_input_order(cx: &mut TestAppContext) {
4136        let mut runs = laid_out_runs(&["one", "two"], cx);
4137        let (first_text, first_layout) = runs.remove(0);
4138        let (second_text, second_layout) = runs.remove(0);
4139        let snapshot = plain_snapshot(
4140            first_layout.position_for_index(1).unwrap(),
4141            second_layout.position_for_index(2).unwrap(),
4142        );
4143        cx.update(|cx| {
4144            let mut selection_state = WindowSelectionState::default();
4145            let participant = FakeParticipant::new("", cx);
4146            participant.register(
4147                &mut selection_state,
4148                0.,
4149                TextSelectionScopeId::default(),
4150                0,
4151                cx,
4152            );
4153            participant.selection.0.update(cx, |state, cx| {
4154                state.set_snapshot(Some(snapshot), cx);
4155                state.update_runs(&[
4156                    text_run(1, first_text, first_layout),
4157                    text_run(0, second_text, second_layout),
4158                ]);
4159            });
4160
4161            assert_eq!(selection_state.selected_text(cx), "twne");
4162        });
4163    }
4164
4165    #[gpui::test]
4166    fn plain_projection_safely_rejects_a_text_layout_length_mismatch(cx: &mut TestAppContext) {
4167        let (_, layout) = laid_out_runs(&["short"], cx).pop().unwrap();
4168        let start = layout.position_for_index(0).unwrap();
4169        let end = layout.position_for_index(5).unwrap();
4170        let states = project_ranges(
4171            Some(plain_snapshot(start, end)),
4172            &[text_run(0, SharedString::from("longer"), layout)],
4173        );
4174
4175        assert_eq!(states.ranges(), &[None]);
4176        assert!(states.is_active());
4177    }
4178
4179    #[gpui::test]
4180    fn begin_update_and_end_publish_a_cross_participant_selection(cx: &mut TestAppContext) {
4181        cx.update(|cx| {
4182            let mut selection_state = WindowSelectionState::default();
4183            let first = FakeParticipant::new("first", cx);
4184            let second = FakeParticipant::new("second", cx);
4185            first.register(
4186                &mut selection_state,
4187                0.,
4188                TextSelectionScopeId::default(),
4189                0,
4190                cx,
4191            );
4192            second.register(
4193                &mut selection_state,
4194                20.,
4195                TextSelectionScopeId::default(),
4196                1,
4197                cx,
4198            );
4199
4200            selection_state.begin(point(px(1.), px(1.)), false, cx);
4201            selection_state.update(point(px(1.), px(25.)), cx);
4202            assert!(selection_state.has_selection(cx));
4203            assert_eq!(selection_state.selected_text(cx), "first\nsecond");
4204
4205            selection_state.end(cx);
4206            assert!(!selection_state.is_selecting());
4207        });
4208    }
4209
4210    #[gpui::test]
4211    fn shift_extension_keeps_its_original_anchor_when_reversed(cx: &mut TestAppContext) {
4212        cx.update(|cx| {
4213            let mut selection_state = WindowSelectionState::default();
4214            let participant = FakeParticipant::new("participant", cx);
4215            participant.register(
4216                &mut selection_state,
4217                0.,
4218                TextSelectionScopeId::default(),
4219                0,
4220                cx,
4221            );
4222
4223            selection_state.begin(point(px(2.), px(2.)), false, cx);
4224            selection_state.end(cx);
4225            selection_state.begin(point(px(8.), px(2.)), true, cx);
4226            selection_state.end(cx);
4227            let first_anchor = selection_state.snapshot().unwrap().anchor();
4228
4229            selection_state.begin(point(px(0.), px(2.)), true, cx);
4230            selection_state.end(cx);
4231            let reversed = selection_state.snapshot().unwrap();
4232            assert_eq!(reversed.anchor(), first_anchor);
4233            assert!(reversed.cursor().content_point().x < reversed.anchor().content_point().x);
4234        });
4235    }
4236
4237    #[gpui::test]
4238    fn content_key_resolver_runs_outside_the_window_state_lease(cx: &mut TestAppContext) {
4239        cx.update(|cx| {
4240            let state = cx.new(|_| WindowSelectionState::default());
4241            let participant = FakeParticipant::new("virtual", cx);
4242            let state_for_callback = state.clone();
4243            participant.selection.resolve_content_key_with(
4244                move |_, cx| {
4245                    let _ = state_for_callback.read(cx).snapshot();
4246                    Some(TextSelectionContentKey::new(7))
4247                },
4248                cx,
4249            );
4250            state.update(cx, |state, cx| {
4251                participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
4252                state.begin(point(px(1.), px(1.)), false, cx);
4253                state.update(point(px(8.), px(1.)), cx);
4254            });
4255
4256            WindowSelectionState::resolve_content_keys(&state, cx);
4257
4258            assert_eq!(
4259                state.read(cx).snapshot().unwrap().cursor().content_key(),
4260                Some(TextSelectionContentKey::new(7))
4261            );
4262        });
4263    }
4264
4265    #[gpui::test]
4266    fn active_dnd_does_not_move_a_text_selection_cursor(cx: &mut TestAppContext) {
4267        let window = cx.add_window(|_, cx| WindowSelectionView {
4268            selection: TextSelectionHandle::new("unused", cx),
4269        });
4270        window
4271            .update(cx, |_, window, cx| {
4272                let mut state = WindowSelectionState::default();
4273                let participant = FakeParticipant::new("participant", cx);
4274                participant.register(&mut state, 0., TextSelectionScopeId::default(), 0, cx);
4275                state.begin(point(px(1.), px(1.)), false, cx);
4276                let before = state.cursor.as_ref().unwrap().point;
4277                state.update_in_window_with_active_drag(point(px(80.), px(1.)), true, window, cx);
4278                assert_eq!(state.cursor.as_ref().unwrap().point, before);
4279            })
4280            .unwrap();
4281    }
4282
4283    #[gpui::test]
4284    fn shift_extension_falls_back_when_the_anchor_participant_was_swept(cx: &mut TestAppContext) {
4285        cx.update(|cx| {
4286            let mut selection_state = WindowSelectionState::default();
4287            let first = FakeParticipant::new("first", cx);
4288            let second = FakeParticipant::new("second", cx);
4289            first.register(
4290                &mut selection_state,
4291                0.,
4292                TextSelectionScopeId::default(),
4293                0,
4294                cx,
4295            );
4296            selection_state.begin(point(px(1.), px(1.)), false, cx);
4297            selection_state.update(point(px(8.), px(1.)), cx);
4298            selection_state.end(cx);
4299
4300            selection_state.finish_frame(cx);
4301            selection_state.finish_frame(cx);
4302            second.register(
4303                &mut selection_state,
4304                20.,
4305                TextSelectionScopeId::default(),
4306                1,
4307                cx,
4308            );
4309            selection_state.begin(point(px(1.), px(21.)), true, cx);
4310            selection_state.update(point(px(8.), px(21.)), cx);
4311            selection_state.end(cx);
4312
4313            assert_eq!(selection_state.selected_text(cx), "second");
4314        });
4315    }
4316
4317    #[gpui::test]
4318    fn scope_and_suppression_prevent_unrelated_participants_from_participating(
4319        cx: &mut TestAppContext,
4320    ) {
4321        cx.update(|cx| {
4322            let mut selection_state = WindowSelectionState::default();
4323            let base = FakeParticipant::new("base", cx);
4324            let modal = FakeParticipant::new("modal", cx);
4325            base.register(
4326                &mut selection_state,
4327                0.,
4328                TextSelectionScopeId::default(),
4329                0,
4330                cx,
4331            );
4332            modal.register(&mut selection_state, 20., TextSelectionScopeId(1), 1, cx);
4333
4334            selection_state.set_active_scope(TextSelectionScopeId(1), cx);
4335            selection_state.begin(point(px(1.), px(21.)), false, cx);
4336            selection_state.update(point(px(8.), px(21.)), cx);
4337            selection_state.end(cx);
4338            assert_eq!(selection_state.selected_text(cx), "modal");
4339
4340            selection_state.clear(cx);
4341            GlobalState::init(cx);
4342            GlobalState::suppress_text_selection(cx);
4343            selection_state.begin(point(px(1.), px(21.)), false, cx);
4344            selection_state.update(point(px(8.), px(21.)), cx);
4345            assert!(!selection_state.has_selection(cx));
4346        });
4347    }
4348
4349    #[gpui::test]
4350    fn dead_participants_are_pruned_and_empty_selection_falls_back_safely(cx: &mut TestAppContext) {
4351        let selection_state = cx.update(|cx| {
4352            let selection_state = cx.new(|_| WindowSelectionState::default());
4353            let participant = FakeParticipant::new("gone", cx);
4354            selection_state.update(cx, |selection_state, cx| {
4355                participant.register(selection_state, 0., TextSelectionScopeId::default(), 0, cx)
4356            });
4357            selection_state
4358        });
4359        cx.update(|cx| {
4360            selection_state.update(cx, |selection_state, cx| {
4361                selection_state.begin(point(px(1.), px(1.)), false, cx);
4362                selection_state.update(point(px(8.), px(1.)), cx);
4363                selection_state.end(cx);
4364
4365                assert_eq!(selection_state.selected_text(cx), "");
4366                assert!(!selection_state.has_selection(cx));
4367            });
4368        });
4369    }
4370
4371    #[gpui::test]
4372    fn text_selection_namespace_reports_copies_ends_and_clears_selection(cx: &mut TestAppContext) {
4373        let (view, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
4374            selection: TextSelectionHandle::new("copied", cx),
4375        });
4376        cx.update(|window, cx| {
4377            let selection = view.read(cx).selection.clone();
4378            let selection_state = WindowSelectionState::ensure(window, cx);
4379            selection_state.update(cx, |selection_state, cx| {
4380                FakeParticipant { selection }.register(
4381                    selection_state,
4382                    0.,
4383                    TextSelectionScopeId::default(),
4384                    0,
4385                    cx,
4386                );
4387                selection_state.begin(point(px(1.), px(1.)), false, cx);
4388                selection_state.update(point(px(8.), px(1.)), cx);
4389            });
4390
4391            assert!(TextSelection::has_selection(window, cx));
4392            assert_eq!(TextSelection::selected_text(window, cx), "copied");
4393            TextSelection::end(window, cx);
4394            assert!(TextSelection::has_selection(window, cx));
4395            TextSelection::clear(window, cx);
4396            assert!(!TextSelection::has_selection(window, cx));
4397            assert_eq!(TextSelection::selected_text(window, cx), "");
4398        });
4399    }
4400
4401    #[gpui::test]
4402    fn two_windows_isolate_selection_copy_clear_and_release_ownership(cx: &mut TestAppContext) {
4403        let first = cx.add_window(|_, cx| WindowOwnedSelectionView {
4404            selection: TextSelectionHandle::new("first", cx),
4405        });
4406        let second = cx.add_window(|_, cx| WindowOwnedSelectionView {
4407            selection: TextSelectionHandle::new("second", cx),
4408        });
4409        let first_selection = cx.update(|cx| first.read(cx).unwrap().selection.clone());
4410        let second_selection = cx.update(|cx| second.read(cx).unwrap().selection.clone());
4411
4412        let first_state = cx
4413            .update_window(*first, |_, window, cx| {
4414                let _ = window.draw(cx);
4415                first_selection.set_local_selection(true, cx);
4416                assert_eq!(TextSelection::selected_text(window, cx), "first");
4417                WindowSelectionState::existing(window, cx)
4418                    .unwrap()
4419                    .downgrade()
4420            })
4421            .unwrap();
4422        cx.update_window(*second, |_, window, cx| {
4423            let _ = window.draw(cx);
4424            second_selection.set_local_selection(true, cx);
4425            assert_eq!(TextSelection::selected_text(window, cx), "second");
4426        })
4427        .unwrap();
4428
4429        cx.update_window(*first, |_, window, cx| {
4430            TextSelection::clear(window, cx);
4431            assert_eq!(TextSelection::selected_text(window, cx), "");
4432        })
4433        .unwrap();
4434        cx.update_window(*second, |_, window, cx| {
4435            assert_eq!(TextSelection::selected_text(window, cx), "second");
4436        })
4437        .unwrap();
4438
4439        cx.update_window(*first, |_, window, _| window.remove_window())
4440            .unwrap();
4441        cx.run_until_parked();
4442
4443        assert!(first_state.upgrade().is_none());
4444        cx.update_window(*second, |_, window, cx| {
4445            assert_eq!(TextSelection::selected_text(window, cx), "second");
4446        })
4447        .unwrap();
4448        cx.update(|cx| {
4449            assert_eq!(cx.global::<SelectionStateRegistry>().0.len(), 1);
4450        });
4451    }
4452
4453    #[gpui::test]
4454    fn copy_callback_can_reenter_window_and_handle_selection(cx: &mut TestAppContext) {
4455        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
4456        cx.update(|window, cx| {
4457            let _ = window.draw(cx);
4458            let state = WindowSelectionState::existing(window, cx).unwrap();
4459            let selection = TextSelectionHandle::new("fallback", cx);
4460            let state_for_copy = state.clone();
4461            let selection_for_copy = selection.clone();
4462            selection.copy_with(
4463                move |cx: &mut App| {
4464                    state_for_copy.update(cx, |state, _| {
4465                        assert!(state.snapshot().is_some());
4466                    });
4467                    assert!(selection_for_copy.snapshot(cx).is_some());
4468                    selection_for_copy.set_fallback_copy_text("reentered", cx);
4469                    "reentrant copy".to_string()
4470                },
4471                cx,
4472            );
4473            state.update(cx, |state, cx| {
4474                FakeParticipant {
4475                    selection: selection.clone(),
4476                }
4477                .register(state, 0., TextSelectionScopeId::default(), 0, cx);
4478                state.begin(point(px(1.), px(1.)), false, cx);
4479                state.update(point(px(8.), px(1.)), cx);
4480                state.end(cx);
4481            });
4482
4483            assert_eq!(TextSelection::selected_text(window, cx), "reentrant copy");
4484        });
4485    }
4486
4487    #[gpui::test]
4488    fn cross_participant_selection_excludes_participants_outside_its_document_interval(
4489        cx: &mut TestAppContext,
4490    ) {
4491        cx.update(|cx| {
4492            let mut selection_state = WindowSelectionState::default();
4493            let first = FakeParticipant::new("first", cx);
4494            let second = FakeParticipant::new("second", cx);
4495            let third = FakeParticipant::new("third", cx);
4496            first.register(
4497                &mut selection_state,
4498                0.,
4499                TextSelectionScopeId::default(),
4500                0,
4501                cx,
4502            );
4503            second.register(
4504                &mut selection_state,
4505                20.,
4506                TextSelectionScopeId::default(),
4507                1,
4508                cx,
4509            );
4510            third.register(
4511                &mut selection_state,
4512                40.,
4513                TextSelectionScopeId::default(),
4514                2,
4515                cx,
4516            );
4517
4518            selection_state.begin(point(px(1.), px(1.)), false, cx);
4519            selection_state.update(point(px(1.), px(25.)), cx);
4520            selection_state.end(cx);
4521
4522            assert_eq!(selection_state.selected_text(cx), "first\nsecond");
4523            assert!(third.selection.snapshot(cx).is_none());
4524        });
4525    }
4526
4527    #[gpui::test]
4528    fn changing_scope_clears_the_previous_scope_selection(cx: &mut TestAppContext) {
4529        cx.update(|cx| {
4530            let mut selection_state = WindowSelectionState::default();
4531            let base = FakeParticipant::new("base", cx);
4532            let modal = FakeParticipant::new("modal", cx);
4533            base.register(
4534                &mut selection_state,
4535                0.,
4536                TextSelectionScopeId::default(),
4537                0,
4538                cx,
4539            );
4540            modal.register(
4541                &mut selection_state,
4542                20.,
4543                TextSelectionScopeId::from_raw(1),
4544                1,
4545                cx,
4546            );
4547
4548            selection_state.begin(point(px(1.), px(1.)), false, cx);
4549            selection_state.update(point(px(8.), px(1.)), cx);
4550            selection_state.end(cx);
4551            selection_state.set_active_scope(TextSelectionScopeId::from_raw(1), cx);
4552
4553            assert!(!selection_state.has_selection(cx));
4554            assert!(base.selection.snapshot(cx).is_none());
4555        });
4556    }
4557
4558    #[gpui::test]
4559    fn blank_only_drag_never_publishes_or_copies_selection(cx: &mut TestAppContext) {
4560        cx.update(|cx| {
4561            let mut selection_state = WindowSelectionState::default();
4562            let participant = FakeParticipant::new("participant", cx);
4563            participant.register(
4564                &mut selection_state,
4565                0.,
4566                TextSelectionScopeId::default(),
4567                0,
4568                cx,
4569            );
4570
4571            selection_state.begin(point(px(200.), px(1.)), false, cx);
4572            selection_state.update(point(px(200.), px(8.)), cx);
4573            selection_state.end(cx);
4574
4575            assert!(!selection_state.has_selection(cx));
4576            assert_eq!(selection_state.selected_text(cx), "");
4577            assert!(participant.selection.snapshot(cx).is_none());
4578        });
4579    }
4580
4581    #[gpui::test]
4582    fn unmounted_selection_does_not_revive_when_the_participant_returns(cx: &mut TestAppContext) {
4583        cx.update(|cx| {
4584            let mut selection_state = WindowSelectionState::default();
4585            let participant = FakeParticipant::new("stale", cx);
4586            participant.register(
4587                &mut selection_state,
4588                0.,
4589                TextSelectionScopeId::default(),
4590                0,
4591                cx,
4592            );
4593            selection_state.begin(point(px(1.), px(1.)), false, cx);
4594            selection_state.update(point(px(8.), px(1.)), cx);
4595            selection_state.end(cx);
4596            assert_eq!(selection_state.selected_text(cx), "stale");
4597
4598            selection_state.finish_frame(cx);
4599            selection_state.finish_frame(cx);
4600            assert_eq!(selection_state.selected_text(cx), "");
4601            assert!(participant.selection.snapshot(cx).is_none());
4602            participant.register(
4603                &mut selection_state,
4604                0.,
4605                TextSelectionScopeId::default(),
4606                0,
4607                cx,
4608            );
4609            assert_eq!(selection_state.selected_text(cx), "");
4610            assert!(participant.selection.snapshot(cx).is_none());
4611
4612            selection_state.begin(point(px(1.), px(1.)), false, cx);
4613            selection_state.update(point(px(8.), px(1.)), cx);
4614            selection_state.end(cx);
4615            assert_eq!(selection_state.selected_text(cx), "stale");
4616        });
4617    }
4618
4619    #[gpui::test]
4620    fn removing_an_unselected_participant_preserves_the_current_selection(cx: &mut TestAppContext) {
4621        cx.update(|cx| {
4622            let mut selection_state = WindowSelectionState::default();
4623            let selected = FakeParticipant::new("selected", cx);
4624            let unrelated = FakeParticipant::new("unrelated", cx);
4625            let scope = TextSelectionScopeId::default();
4626            selected.register(&mut selection_state, 0., scope, 0, cx);
4627            unrelated.register(&mut selection_state, 20., scope, 1, cx);
4628            selection_state.begin(point(px(1.), px(1.)), false, cx);
4629            selection_state.update(point(px(8.), px(1.)), cx);
4630            selection_state.end(cx);
4631            assert_eq!(selection_state.selected_text(cx), "selected");
4632
4633            selection_state.finish_frame(cx);
4634            selected.register(&mut selection_state, 0., scope, 0, cx);
4635            selection_state.finish_frame(cx);
4636            assert_eq!(selection_state.selected_text(cx), "selected");
4637            unrelated.register(&mut selection_state, 20., scope, 1, cx);
4638            assert_eq!(selection_state.selected_text(cx), "selected");
4639        });
4640    }
4641
4642    #[gpui::test]
4643    fn removing_either_range_endpoint_discards_the_shared_selection(cx: &mut TestAppContext) {
4644        for remove_anchor in [true, false] {
4645            cx.update(|cx| {
4646                let mut selection_state = WindowSelectionState::default();
4647                let anchor = FakeParticipant::new("anchor", cx);
4648                let cursor = FakeParticipant::new("cursor", cx);
4649                let scope = TextSelectionScopeId::default();
4650                anchor.register(&mut selection_state, 0., scope, 0, cx);
4651                cursor.register(&mut selection_state, 20., scope, 1, cx);
4652                selection_state.begin(point(px(1.), px(1.)), false, cx);
4653                selection_state.update(point(px(8.), px(21.)), cx);
4654                selection_state.end(cx);
4655                assert_eq!(selection_state.selected_text(cx), "anchor\ncursor");
4656                selection_state.finish_frame(cx);
4657
4658                if remove_anchor {
4659                    cursor.register(&mut selection_state, 20., scope, 1, cx);
4660                } else {
4661                    anchor.register(&mut selection_state, 0., scope, 0, cx);
4662                }
4663                selection_state.finish_frame(cx);
4664                assert_eq!(selection_state.selected_text(cx), "");
4665                anchor.register(&mut selection_state, 0., scope, 0, cx);
4666                cursor.register(&mut selection_state, 20., scope, 1, cx);
4667                assert_eq!(selection_state.selected_text(cx), "");
4668            });
4669        }
4670    }
4671
4672    #[gpui::test]
4673    fn clear_or_unmount_stops_auto_scroll_before_discarding_the_anchor(cx: &mut TestAppContext) {
4674        for unmount in [false, true] {
4675            let commands = Rc::new(RefCell::new(Vec::new()));
4676            let observed = commands.clone();
4677            let (mut selection_state, participant) = cx.update(|cx| {
4678                let selection_state = WindowSelectionState::default();
4679                let participant = FakeParticipant::new("scroll", cx);
4680                participant
4681                    .selection
4682                    .subscribe(
4683                        move |event, _| {
4684                            if let TextSelectionEvent::AutoScroll(delta) = event {
4685                                observed.borrow_mut().push(*delta);
4686                            }
4687                        },
4688                        cx,
4689                    )
4690                    .detach();
4691                (selection_state, participant)
4692            });
4693            cx.run_until_parked();
4694            cx.update(|cx| {
4695                participant.register(
4696                    &mut selection_state,
4697                    0.,
4698                    TextSelectionScopeId::default(),
4699                    0,
4700                    cx,
4701                );
4702
4703                selection_state.begin(point(px(1.), px(1.)), false, cx);
4704                selection_state.update(point(px(1.), px(25.)), cx);
4705                if unmount {
4706                    selection_state.finish_frame(cx);
4707                    selection_state.finish_frame(cx);
4708                } else {
4709                    selection_state.clear(cx);
4710                }
4711            });
4712            cx.run_until_parked();
4713            assert!(commands.borrow().iter().any(Option::is_some));
4714            assert_eq!(commands.borrow().last(), Some(&None));
4715        }
4716    }
4717
4718    #[gpui::test]
4719    fn drag_auto_scroll_stops_when_the_content_mask_collapses(cx: &mut TestAppContext) {
4720        let window = cx.add_window(|_, cx| WindowSelectionView {
4721            selection: TextSelectionHandle::new("unused", cx),
4722        });
4723        window
4724            .update(cx, |_, window, cx| {
4725                let state = cx.new(|_| WindowSelectionState::default());
4726                let participant = FakeParticipant::new("participant", cx);
4727                state.update(cx, |state, cx| {
4728                    participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
4729                    state.begin(point(px(1.), px(1.)), false, cx);
4730                });
4731                // The scrollable ancestor got clipped away mid-drag, so the
4732                // refreshed registration carries a collapsed content mask.
4733                let collapsed = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(0.)));
4734                state.update(cx, |state, cx| {
4735                    state.register_participant(
4736                        participant.selection.clone(),
4737                        TextSelectionRegistration::new(
4738                            Hitbox {
4739                                id: HitboxId::placeholder(),
4740                                bounds: collapsed,
4741                                content_mask: ContentMask { bounds: collapsed },
4742                                behavior: HitboxBehavior::Normal,
4743                            },
4744                            collapsed,
4745                        )
4746                        .with_text_bounds(vec![collapsed]),
4747                        cx,
4748                    );
4749                    state.update_in_window(point(px(1.), px(50.)), window, cx);
4750                    assert!(!state.auto_scroll.is_active());
4751                    assert!(state.auto_scroll.last_drag_position.is_none());
4752                });
4753            })
4754            .unwrap();
4755    }
4756
4757    #[gpui::test]
4758    fn pointer_moves_after_a_click_do_not_auto_scroll(cx: &mut TestAppContext) {
4759        let window = cx.add_window(|_, cx| WindowSelectionView {
4760            selection: TextSelectionHandle::new("unused", cx),
4761        });
4762        window
4763            .update(cx, |_, window, cx| {
4764                let state = cx.new(|_| WindowSelectionState::default());
4765                let participant = FakeParticipant::new("participant", cx);
4766                state.update(cx, |state, cx| {
4767                    participant.register(state, 0., TextSelectionScopeId::default(), 0, cx);
4768                    // A click on text keeps its anchor so shift-click can extend it.
4769                    state.begin(point(px(1.), px(1.)), false, cx);
4770                    state.end(cx);
4771                    assert!(state.anchor.is_some());
4772
4773                    state.update_in_window(point(px(1.), px(50.)), window, cx);
4774                    assert!(!state.auto_scroll.is_active());
4775                });
4776            })
4777            .unwrap();
4778    }
4779
4780    #[gpui::test]
4781    fn proxy_endpoints_break_equal_position_ties_by_document_order(cx: &mut TestAppContext) {
4782        cx.update(|cx| {
4783            let mut selection_state = WindowSelectionState::default();
4784            let later = FakeParticipant::new("later", cx);
4785            let earlier = FakeParticipant::new("earlier", cx);
4786            later.register(
4787                &mut selection_state,
4788                0.,
4789                TextSelectionScopeId::default(),
4790                2,
4791                cx,
4792            );
4793            earlier.register(
4794                &mut selection_state,
4795                0.,
4796                TextSelectionScopeId::default(),
4797                1,
4798                cx,
4799            );
4800
4801            selection_state.begin(point(px(1.), px(1.)), false, cx);
4802            selection_state.update(point(px(200.), px(25.)), cx);
4803            let endpoint = selection_state.snapshot().unwrap().cursor();
4804
4805            assert_eq!(endpoint.entity_id(), Some(earlier.selection.entity_id()));
4806        });
4807    }
4808
4809    #[gpui::test]
4810    fn equal_area_hovered_participants_break_ties_by_document_order(cx: &mut TestAppContext) {
4811        cx.update(|cx| {
4812            for _ in 0..64 {
4813                let mut selection_state = WindowSelectionState::default();
4814                let later = FakeParticipant::new("later", cx);
4815                let earliest = FakeParticipant::new("earliest", cx);
4816                let middle = FakeParticipant::new("middle", cx);
4817                later.register(
4818                    &mut selection_state,
4819                    0.,
4820                    TextSelectionScopeId::default(),
4821                    30,
4822                    cx,
4823                );
4824                earliest.register(
4825                    &mut selection_state,
4826                    0.,
4827                    TextSelectionScopeId::default(),
4828                    10,
4829                    cx,
4830                );
4831                middle.register(
4832                    &mut selection_state,
4833                    0.,
4834                    TextSelectionScopeId::default(),
4835                    20,
4836                    cx,
4837                );
4838
4839                selection_state.begin(point(px(1.), px(1.)), false, cx);
4840                selection_state.update(point(px(8.), px(1.)), cx);
4841
4842                assert_eq!(
4843                    selection_state.snapshot().unwrap().anchor().entity_id(),
4844                    Some(earliest.selection.entity_id())
4845                );
4846            }
4847        });
4848    }
4849
4850    #[gpui::test]
4851    fn text_selection_namespace_is_a_safe_no_op_until_the_element_is_rendered(
4852        cx: &mut TestAppContext,
4853    ) {
4854        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
4855            selection: TextSelectionHandle::new("not enabled", cx),
4856        });
4857        cx.update(|window, cx| {
4858            assert!(!TextSelection::has_selection(window, cx));
4859            assert_eq!(TextSelection::selected_text(window, cx), "");
4860            TextSelection::clear(window, cx);
4861            TextSelection::end(window, cx);
4862            assert!(!TextSelection::has_selection(window, cx));
4863        });
4864    }
4865
4866    #[gpui::test]
4867    fn unit_selection_element_supports_scope_and_registration_on_the_first_frame(
4868        cx: &mut TestAppContext,
4869    ) {
4870        let (view, cx) = cx.add_window_view(|_, cx| FirstFrameScopedSelectionView {
4871            selection: TextSelectionHandle::new("first frame", cx),
4872        });
4873        let selection = cx.update(|_, cx| view.read(cx).selection.clone());
4874
4875        cx.update(|window, cx| {
4876            let _ = window.draw(cx);
4877            let state = WindowSelectionState::existing(window, cx).unwrap();
4878            assert_eq!(
4879                state.read(cx).active_scope,
4880                TextSelectionScopeId::from_raw(23)
4881            );
4882            assert!(
4883                state
4884                    .read(cx)
4885                    .participants
4886                    .borrow()
4887                    .contains_key(&selection.entity_id())
4888            );
4889        });
4890    }
4891
4892    #[gpui::test]
4893    fn lazy_registration_does_not_enable_queries_without_the_element(cx: &mut TestAppContext) {
4894        let (_, cx) = cx.add_window_view(|_, cx| WindowSelectionView {
4895            selection: TextSelectionHandle::new("registered", cx),
4896        });
4897        cx.update(|window, cx| {
4898            let selection = TextSelectionHandle::new("registered", cx);
4899            selection.set_local_selection(true, cx);
4900            let bounds = Bounds::new(point(px(0.), px(0.)), size(px(100.), px(20.)));
4901            let hitbox = Hitbox {
4902                id: HitboxId::placeholder(),
4903                bounds,
4904                content_mask: ContentMask { bounds },
4905                behavior: HitboxBehavior::Normal,
4906            };
4907            selection.register(
4908                TextSelectionRegistration::new(hitbox, bounds).with_text_bounds(vec![bounds]),
4909                window,
4910                cx,
4911            );
4912            assert_eq!(TextSelection::selected_text(window, cx), "");
4913            assert!(!TextSelection::has_selection(window, cx));
4914            TextSelection::clear(window, cx);
4915            assert_eq!(TextSelection::selected_text(window, cx), "");
4916        });
4917    }
4918
4919    struct RetainedSelectionText {
4920        text: Entity<crate::text::TextViewState>,
4921        renders: Rc<Cell<usize>>,
4922        scrollable: bool,
4923    }
4924
4925    impl RetainedSelectionText {
4926        fn new(text: &str, cx: &mut App) -> Entity<Self> {
4927            let text = cx.new(|cx| crate::text::TextViewState::markdown(text, cx));
4928            cx.new(|_| Self {
4929                text,
4930                renders: Rc::new(Cell::new(0)),
4931                scrollable: false,
4932            })
4933        }
4934    }
4935
4936    impl Render for RetainedSelectionText {
4937        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4938            self.renders.set(self.renders.get() + 1);
4939            div()
4940                .w(px(240.))
4941                .h(px(32.))
4942                .child(crate::text::TextView::new(&self.text).scrollable(self.scrollable))
4943        }
4944    }
4945
4946    struct RetainedSelectionRoot {
4947        texts: Vec<Entity<RetainedSelectionText>>,
4948    }
4949
4950    impl Render for RetainedSelectionRoot {
4951        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4952            div()
4953                .size_full()
4954                .flex()
4955                .flex_col()
4956                .child(TextSelectionLayer)
4957                .children(self.texts.iter().cloned())
4958        }
4959    }
4960
4961    #[cfg(feature = "gpui-fast")]
4962    struct RetainedScrollingSelectionRoot {
4963        text: Entity<RetainedSelectionText>,
4964        unrelated: Entity<RetainedSelectionText>,
4965        scroll: gpui::ScrollHandle,
4966    }
4967
4968    #[cfg(feature = "gpui-fast")]
4969    impl Render for RetainedScrollingSelectionRoot {
4970        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
4971            div()
4972                .size_full()
4973                .flex()
4974                .flex_col()
4975                .child(TextSelectionLayer)
4976                .child(
4977                    div()
4978                        .id("scrolling-texts")
4979                        .w(px(240.))
4980                        .h(px(64.))
4981                        .flex_none()
4982                        .overflow_y_scroll()
4983                        .track_scroll(&self.scroll)
4984                        .child(div().h(px(352.)).child(self.text.clone())),
4985                )
4986                .child(self.unrelated.clone())
4987        }
4988    }
4989
4990    fn draw_selection_frame(cx: &mut gpui::VisualTestContext) {
4991        cx.update(|window, cx| window.draw(cx).clear(cx));
4992        cx.run_until_parked();
4993    }
4994
4995    /// Dispatch the gesture and copy in the same update, before either paint
4996    /// or the TextView's selection subscription can repair its copy cache.
4997    fn select_retained_texts(
4998        last: usize,
4999        release: bool,
5000        cx: &mut gpui::VisualTestContext,
5001    ) -> String {
5002        cx.update(|window, cx| {
5003            let state = WindowSelectionState::existing(window, cx).unwrap();
5004            let mut bounds = state
5005                .read(cx)
5006                .participants
5007                .borrow()
5008                .values()
5009                .map(|participant| participant.registration.text_bounds[0])
5010                .collect::<Vec<_>>();
5011            bounds.sort_by(|a, b| a.top().partial_cmp(&b.top()).unwrap());
5012            let start = point(bounds[0].left() + px(0.1), bounds[0].center().y);
5013            let end = point(bounds[last].right() - px(0.1), bounds[last].center().y);
5014            window.dispatch_event(
5015                gpui::PlatformInput::MouseDown(MouseDownEvent {
5016                    position: start,
5017                    button: MouseButton::Left,
5018                    click_count: 1,
5019                    ..Default::default()
5020                }),
5021                cx,
5022            );
5023            window.dispatch_event(
5024                gpui::PlatformInput::MouseMove(MouseMoveEvent {
5025                    position: end,
5026                    pressed_button: Some(MouseButton::Left),
5027                    ..Default::default()
5028                }),
5029                cx,
5030            );
5031            if release {
5032                window.dispatch_event(
5033                    gpui::PlatformInput::MouseUp(MouseUpEvent {
5034                        position: end,
5035                        button: MouseButton::Left,
5036                        modifiers: gpui::Modifiers::default(),
5037                        click_count: 1,
5038                    }),
5039                    cx,
5040                );
5041            }
5042            assert!(TextSelection::has_selection(window, cx));
5043            TextSelection::selected_text(window, cx)
5044        })
5045    }
5046
5047    /// Uses only the original public TextView/selection APIs and real retained
5048    /// views, so this also runs against the implementation before this fix.
5049    #[cfg(feature = "gpui-fast")]
5050    #[gpui::test]
5051    fn active_selection_keeps_unrelated_text_views_retained(cx: &mut TestAppContext) {
5052        cx.update(crate::init);
5053        let (root, cx) = cx.add_window_view(|_, cx| RetainedSelectionRoot {
5054            texts: ["alpha", "bravo", "charlie"]
5055                .into_iter()
5056                .map(|text| RetainedSelectionText::new(text, cx))
5057                .collect(),
5058        });
5059        draw_selection_frame(cx);
5060        draw_selection_frame(cx);
5061        assert_eq!(select_retained_texts(0, false, cx).trim(), "alpha");
5062        draw_selection_frame(cx);
5063        draw_selection_frame(cx);
5064        let texts = root.read_with(cx, |root, _| root.texts.clone());
5065        let unrelated = texts[1].read_with(cx, |text, _| text.renders.clone());
5066        let before = unrelated.get();
5067        let first_line = cx.update(|window, cx| {
5068            let state = WindowSelectionState::existing(window, cx).unwrap();
5069            state
5070                .read(cx)
5071                .participants
5072                .borrow()
5073                .values()
5074                .map(|participant| participant.registration.text_bounds[0])
5075                .min_by(|a, b| a.top().partial_cmp(&b.top()).unwrap())
5076                .unwrap()
5077        });
5078        for fraction in [0.2, 0.4, 0.6, 0.8] {
5079            cx.update(|window, cx| {
5080                window.dispatch_event(
5081                    gpui::PlatformInput::MouseMove(MouseMoveEvent {
5082                        position: point(
5083                            first_line.left() + first_line.size.width * fraction,
5084                            first_line.center().y,
5085                        ),
5086                        pressed_button: Some(MouseButton::Left),
5087                        ..Default::default()
5088                    }),
5089                    cx,
5090                );
5091            });
5092            draw_selection_frame(cx);
5093        }
5094        assert_ne!(
5095            cx.update(|window, cx| TextSelection::selected_text(window, cx))
5096                .trim(),
5097            "alpha"
5098        );
5099        assert_eq!(
5100            unrelated.get(),
5101            before,
5102            "active registration redrew an unrelated view"
5103        );
5104    }
5105
5106    #[gpui::test]
5107    fn active_selection_frame_publication_visits_participants_linearly(cx: &mut TestAppContext) {
5108        for count in [4, 16] {
5109            cx.update(crate::init);
5110            let (_, cx) = cx.add_window_view(|_, cx| RetainedSelectionRoot {
5111                texts: (0..count)
5112                    .map(|_| RetainedSelectionText::new("text", cx))
5113                    .collect(),
5114            });
5115            draw_selection_frame(cx);
5116            draw_selection_frame(cx);
5117            assert_eq!(select_retained_texts(0, false, cx).trim(), "text");
5118            draw_selection_frame(cx);
5119            draw_selection_frame(cx);
5120            cx.update(|window, cx| {
5121                let state = WindowSelectionState::existing(window, cx).unwrap();
5122                state.read(cx).snapshot_visits.set(0);
5123                window.refresh();
5124                window.draw(cx).clear(cx);
5125            });
5126            cx.run_until_parked();
5127            cx.update(|window, cx| {
5128                let state = WindowSelectionState::existing(window, cx).unwrap();
5129                let visits = state.read(cx).snapshot_visits.get();
5130                assert!(
5131                    visits >= count,
5132                    "the frame must actually visit the participants"
5133                );
5134                assert!(
5135                    visits <= 3 * count,
5136                    "{count} participants required {visits} snapshot visits"
5137                );
5138            });
5139        }
5140    }
5141
5142    #[cfg(feature = "gpui-fast")]
5143    #[gpui::test]
5144    fn scrolling_selected_text_does_not_rebuild_unrelated_document_order(cx: &mut TestAppContext) {
5145        cx.update(crate::init);
5146        let (root, cx) = cx.add_window_view(|_, cx| {
5147            let scrolling = RetainedSelectionText::new("alpha\n\nbravo\n\ncharlie\n\ndelta", cx);
5148            scrolling.update(cx, |text, _| text.scrollable = true);
5149            RetainedSelectionRoot {
5150                texts: vec![scrolling, RetainedSelectionText::new("unrelated", cx)],
5151            }
5152        });
5153        draw_selection_frame(cx);
5154        draw_selection_frame(cx);
5155        assert_eq!(select_retained_texts(0, false, cx).trim(), "alpha");
5156        draw_selection_frame(cx);
5157        draw_selection_frame(cx);
5158        let unrelated = root.read_with(cx, |root, cx| root.texts[1].read(cx).renders.clone());
5159        let before = unrelated.get();
5160        let previous_offset = cx.update(|window, cx| {
5161            let state = WindowSelectionState::existing(window, cx).unwrap();
5162            let registration = state
5163                .read(cx)
5164                .participants
5165                .borrow()
5166                .values()
5167                .find(|participant| participant.registration.self_scroll)
5168                .unwrap()
5169                .registration
5170                .clone();
5171            let offset = registration.scroll_offset;
5172            window.dispatch_event(
5173                gpui::PlatformInput::Scroll(ScrollEvent {
5174                    position: registration.bounds.center(),
5175                    delta: ScrollDelta::Pixels(point(px(0.), px(-20.))),
5176                    touch_phase: TouchPhase::Moved,
5177                    ..Default::default()
5178                }),
5179                cx,
5180            );
5181            offset
5182        });
5183        for _ in 0..3 {
5184            draw_selection_frame(cx);
5185        }
5186        cx.update(|window, cx| {
5187            let state = WindowSelectionState::existing(window, cx).unwrap();
5188            let participants = state.read(cx).participants.borrow();
5189            let scrolling = participants
5190                .values()
5191                .find(|participant| participant.registration.self_scroll)
5192                .unwrap();
5193            assert_ne!(
5194                scrolling.registration.scroll_offset, previous_offset,
5195                "the content must actually scroll"
5196            );
5197            assert!(!state.read(cx).refresh_held_cursor.get());
5198        });
5199        assert_eq!(
5200            unrelated.get(),
5201            before,
5202            "a content scroll must not request a full order rebuild"
5203        );
5204    }
5205
5206    #[cfg(feature = "gpui-fast")]
5207    #[gpui::test]
5208    fn ancestor_scrolling_keeps_unrelated_text_views_retained(cx: &mut TestAppContext) {
5209        for selected in [false, true] {
5210            cx.update(crate::init);
5211            let (root, cx) = cx.add_window_view(|_, cx| RetainedScrollingSelectionRoot {
5212                text: RetainedSelectionText::new("alpha", cx),
5213                unrelated: RetainedSelectionText::new("unrelated", cx),
5214                scroll: gpui::ScrollHandle::new(),
5215            });
5216            draw_selection_frame(cx);
5217            draw_selection_frame(cx);
5218            if selected {
5219                assert_eq!(select_retained_texts(0, true, cx).trim(), "alpha");
5220                draw_selection_frame(cx);
5221                draw_selection_frame(cx);
5222            }
5223            let (scroll, renders, text) = root.read_with(cx, |root, cx| {
5224                (
5225                    root.scroll.clone(),
5226                    root.unrelated.read(cx).renders.clone(),
5227                    root.text.read(cx).text.clone(),
5228                )
5229            });
5230            let before = renders.get();
5231            let bounds_before = text.read_with(cx, |text, _| text.bounds());
5232            for _ in 0..4 {
5233                let previous_offset = scroll.offset();
5234                cx.update(|window, cx| {
5235                    window.dispatch_event(
5236                        gpui::PlatformInput::Scroll(ScrollEvent {
5237                            position: point(px(20.), px(20.)),
5238                            delta: ScrollDelta::Pixels(point(px(0.), px(-4.))),
5239                            touch_phase: TouchPhase::Moved,
5240                            ..Default::default()
5241                        }),
5242                        cx,
5243                    );
5244                });
5245                for _ in 0..3 {
5246                    draw_selection_frame(cx);
5247                }
5248                assert_ne!(scroll.offset(), previous_offset, "the ancestor must scroll");
5249                assert_eq!(
5250                    renders.get(),
5251                    before,
5252                    "ancestor scrolling rebuilt an unrelated text view (selected: {selected})"
5253                );
5254                let copied = cx.update(|window, cx| TextSelection::selected_text(window, cx));
5255                assert_eq!(copied.trim(), if selected { "alpha" } else { "" });
5256            }
5257            assert_ne!(text.read_with(cx, |text, _| text.bounds()), bounds_before);
5258        }
5259    }
5260
5261    #[gpui::test]
5262    fn retained_text_order_survives_partial_frames_insertion_and_reordering(
5263        cx: &mut TestAppContext,
5264    ) {
5265        cx.update(crate::init);
5266        let (root, cx) = cx.add_window_view(|_, cx| RetainedSelectionRoot {
5267            texts: ["alpha", "bravo", "charlie"]
5268                .into_iter()
5269                .map(|text| RetainedSelectionText::new(text, cx))
5270                .collect(),
5271        });
5272        draw_selection_frame(cx);
5273        draw_selection_frame(cx);
5274        let words = |text: String| {
5275            text.split_whitespace()
5276                .map(str::to_owned)
5277                .collect::<Vec<_>>()
5278        };
5279        assert_eq!(
5280            words(select_retained_texts(2, true, cx)),
5281            ["alpha", "bravo", "charlie"]
5282        );
5283        let texts = root.read_with(cx, |root, _| root.texts.clone());
5284        for _ in 0..3 {
5285            texts[1].update(cx, |_, cx| cx.notify());
5286            draw_selection_frame(cx);
5287            assert_eq!(
5288                words(cx.update(|window, cx| TextSelection::selected_text(window, cx))),
5289                ["alpha", "bravo", "charlie"]
5290            );
5291        }
5292        root.update(cx, |root, cx| {
5293            root.texts
5294                .insert(1, RetainedSelectionText::new("delta", cx));
5295            cx.notify();
5296        });
5297        for _ in 0..3 {
5298            draw_selection_frame(cx);
5299        }
5300        assert_eq!(
5301            words(cx.update(|window, cx| TextSelection::selected_text(window, cx))),
5302            ["alpha", "delta", "bravo", "charlie"]
5303        );
5304        root.update(cx, |root, cx| {
5305            root.texts.swap(1, 2);
5306            cx.notify();
5307        });
5308        for _ in 0..3 {
5309            draw_selection_frame(cx);
5310        }
5311        assert_eq!(
5312            words(cx.update(|window, cx| TextSelection::selected_text(window, cx))),
5313            ["alpha", "bravo", "delta", "charlie"]
5314        );
5315        #[cfg(feature = "gpui-fast")]
5316        {
5317            let renders = texts[0].read_with(cx, |text, _| text.renders.clone());
5318            let before = renders.get();
5319            for _ in 0..3 {
5320                draw_selection_frame(cx);
5321            }
5322            assert_eq!(renders.get(), before, "order recovery must settle");
5323        }
5324        // Keep the endpoint's entity alive after removing its element. A
5325        // later remount must not resurrect the retired window gesture.
5326        root.update(cx, |root, cx| {
5327            root.texts.pop();
5328            cx.notify();
5329        });
5330        draw_selection_frame(cx);
5331        draw_selection_frame(cx);
5332        assert!(
5333            cx.update(|window, cx| TextSelection::selected_text(window, cx))
5334                .is_empty()
5335        );
5336        root.update(cx, |root, cx| {
5337            root.texts.push(texts[2].clone());
5338            cx.notify();
5339        });
5340        draw_selection_frame(cx);
5341        assert!(
5342            cx.update(|window, cx| TextSelection::selected_text(window, cx))
5343                .is_empty()
5344        );
5345    }
5346
5347    /// A view reading the window's selection state, beside selectable text,
5348    /// counting its renders.
5349    #[cfg(feature = "gpui-fast")]
5350    struct SelectionStateReader {
5351        renders: Rc<Cell<usize>>,
5352    }
5353
5354    #[cfg(feature = "gpui-fast")]
5355    impl Render for SelectionStateReader {
5356        fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
5357            self.renders.set(self.renders.get() + 1);
5358            if let Some(state) = WindowSelectionState::existing(window, cx) {
5359                let _ = state.read(cx).participants.borrow().len();
5360            }
5361            div().size_full()
5362        }
5363    }
5364
5365    /// The selectable text of a [`ToggleSelectionElementView`] beside a
5366    /// [`SelectionStateReader`].
5367    #[cfg(feature = "gpui-fast")]
5368    struct ReaderBesideSelectionView {
5369        text: gpui::Entity<ToggleSelectionElementView>,
5370        reader: gpui::Entity<SelectionStateReader>,
5371    }
5372
5373    #[cfg(feature = "gpui-fast")]
5374    impl Render for ReaderBesideSelectionView {
5375        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5376            div()
5377                .size_full()
5378                .child(self.reader.clone())
5379                .child(self.text.clone())
5380        }
5381    }
5382
5383    /// Selectable text drawn again, nothing about its selection changed,
5384    /// writes neither the window's selection state nor its own: a view
5385    /// reading them is retained, as it would not be had they changed.
5386    #[cfg(feature = "gpui-fast")]
5387    #[gpui::test]
5388    fn redrawing_selectable_text_leaves_its_readers_retained(cx: &mut TestAppContext) {
5389        let renders = Rc::new(Cell::new(0));
5390        let (view, cx) = cx.add_window_view(|_, cx| ReaderBesideSelectionView {
5391            text: cx.new(|cx| ToggleSelectionElementView {
5392                enabled: true,
5393                selection: TextSelectionHandle::new("text", cx),
5394            }),
5395            reader: cx.new(|_| SelectionStateReader {
5396                renders: renders.clone(),
5397            }),
5398        });
5399        let text = cx.update(|_, cx| view.read(cx).text.clone());
5400        let redraw = |cx: &mut gpui::VisualTestContext| {
5401            text.update(cx, |_, cx| cx.notify());
5402            cx.update(|window, cx| {
5403                let _ = window.draw(cx);
5404                window.simulate_next_frame(cx);
5405            });
5406            cx.run_until_parked();
5407        };
5408        redraw(cx);
5409        redraw(cx);
5410        let before = renders.get();
5411        for _ in 0..4 {
5412            redraw(cx);
5413        }
5414        assert_eq!(renders.get(), before, "the reader rendered again");
5415    }
5416
5417    #[gpui::test]
5418    fn retained_selection_state_releases_and_does_not_resurrect_selection(cx: &mut TestAppContext) {
5419        let (view, cx) = cx.add_window_view(|_, cx| ToggleSelectionElementView {
5420            enabled: true,
5421            selection: TextSelectionHandle::new("local", cx),
5422        });
5423        let selection = cx.update(|_, cx| view.read(cx).selection.clone());
5424        cx.update(|window, cx| {
5425            let _ = window.draw(cx);
5426            selection.set_local_selection(true, cx);
5427            assert!(TextSelection::has_selection(window, cx));
5428
5429            window.simulate_next_frame(cx);
5430            assert!(TextSelection::has_selection(window, cx));
5431            let _ = window.draw(cx);
5432            assert!(TextSelection::has_selection(window, cx));
5433        });
5434        view.update(cx, |view, cx| {
5435            view.enabled = false;
5436            cx.notify();
5437        });
5438        cx.update(|window, cx| {
5439            let _ = window.draw(cx);
5440        });
5441        cx.update(|window, cx| {
5442            window.simulate_next_frame(cx);
5443        });
5444        cx.update(|window, cx| {
5445            window.simulate_next_frame(cx);
5446        });
5447        cx.run_until_parked();
5448        cx.update(|window, cx| {
5449            assert!(!TextSelection::has_selection(window, cx));
5450            assert_eq!(TextSelection::selected_text(window, cx), "");
5451            assert!(!selection.has_local_selection(cx));
5452        });
5453
5454        view.update(cx, |view, cx| {
5455            view.enabled = true;
5456            cx.notify();
5457        });
5458        cx.update(|window, cx| {
5459            let _ = window.draw(cx);
5460            assert!(!TextSelection::has_selection(window, cx));
5461            assert_eq!(TextSelection::selected_text(window, cx), "");
5462        });
5463    }
5464
5465    #[gpui::test]
5466    fn mounted_selection_element_does_not_keep_an_idle_frame_queue_alive(cx: &mut TestAppContext) {
5467        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
5468        cx.update(|window, cx| {
5469            let _ = window.draw(cx);
5470            assert_eq!(window.simulate_next_frame(cx), 0);
5471            assert_eq!(window.simulate_next_frame(cx), 0);
5472            assert!(live_text_selection_state(window, cx).is_some());
5473        });
5474    }
5475
5476    #[gpui::test]
5477    fn selection_element_initializes_suppression_and_respects_bubble_suppression(
5478        cx: &mut TestAppContext,
5479    ) {
5480        let (_, cx) = cx.add_window_view(|_, _| SelectionElementOnlyView);
5481        cx.update(|window, cx| {
5482            let _ = window.draw(cx);
5483        });
5484        cx.simulate_mouse_down(
5485            point(px(1.), px(1.)),
5486            MouseButton::Left,
5487            gpui::Modifiers::default(),
5488        );
5489        cx.simulate_mouse_up(
5490            point(px(1.), px(1.)),
5491            MouseButton::Left,
5492            gpui::Modifiers::default(),
5493        );
5494        cx.update(|window, cx| {
5495            assert!(GlobalState::is_text_selection_suppressed(cx));
5496            assert!(!TextSelection::has_selection(window, cx));
5497        });
5498    }
5499
5500    #[gpui::test]
5501    fn frame_sweep_keeps_a_participant_registered_before_the_selection_element_paints(
5502        cx: &mut TestAppContext,
5503    ) {
5504        cx.update(|cx| {
5505            let mut selection_state = WindowSelectionState::default();
5506            let participant = FakeParticipant::new("painted first", cx);
5507            participant.register(
5508                &mut selection_state,
5509                0.,
5510                TextSelectionScopeId::default(),
5511                0,
5512                cx,
5513            );
5514            selection_state.begin(point(px(1.), px(1.)), false, cx);
5515            selection_state.update(point(px(8.), px(1.)), cx);
5516            selection_state.end(cx);
5517
5518            selection_state.finish_frame(cx);
5519
5520            assert_eq!(selection_state.selected_text(cx), "painted first");
5521            assert!(participant.selection.snapshot(cx).is_some());
5522        });
5523    }
5524
5525    #[gpui::test]
5526    fn two_selection_elements_schedule_only_one_post_frame_sweep(cx: &mut TestAppContext) {
5527        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
5528            selection: TextSelectionHandle::new("once", cx),
5529        });
5530        cx.update(|window, cx| {
5531            let selection_state = WindowSelectionState::ensure(window, cx);
5532            let selection = view.read(cx).selection.clone();
5533            selection_state.update(cx, |selection_state, cx| {
5534                FakeParticipant { selection }.register(
5535                    selection_state,
5536                    0.,
5537                    TextSelectionScopeId::default(),
5538                    0,
5539                    cx,
5540                );
5541                selection_state.begin(point(px(1.), px(1.)), false, cx);
5542                selection_state.update(point(px(8.), px(1.)), cx);
5543                selection_state.end(cx);
5544            });
5545
5546            let _ = window.draw(cx);
5547            window.simulate_next_frame(cx);
5548
5549            let items = selection_state.read(cx).copy_items(cx);
5550            assert_eq!(resolve_copy_items(items, cx), "once");
5551        });
5552    }
5553
5554    #[gpui::test]
5555    fn duplicate_selection_elements_gate_real_pointer_gestures_and_reentrant_clear(
5556        cx: &mut TestAppContext,
5557    ) {
5558        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
5559            selection: TextSelectionHandle::new("once", cx),
5560        });
5561        let clear_count = Rc::new(Cell::new(0));
5562        cx.update(|window, cx| {
5563            let state = WindowSelectionState::ensure(window, cx);
5564            let state_for_clear = state.clone();
5565            let count = clear_count.clone();
5566            let selection = view.read(cx).selection.clone();
5567            selection
5568                .subscribe(
5569                    move |event, cx| {
5570                        if matches!(event, TextSelectionEvent::Cleared) {
5571                            count.set(count.get() + 1);
5572                            let _ = state_for_clear.read(cx).snapshot();
5573                        }
5574                    },
5575                    cx,
5576                )
5577                .detach();
5578            let _ = window.draw(cx);
5579        });
5580
5581        cx.simulate_mouse_down(
5582            point(px(10.), px(10.)),
5583            MouseButton::Left,
5584            gpui::Modifiers::default(),
5585        );
5586        cx.simulate_mouse_up(
5587            point(px(10.), px(10.)),
5588            MouseButton::Left,
5589            gpui::Modifiers::default(),
5590        );
5591        cx.simulate_mouse_down(
5592            point(px(70.), px(10.)),
5593            MouseButton::Left,
5594            gpui::Modifiers {
5595                shift: true,
5596                ..Default::default()
5597            },
5598        );
5599        cx.simulate_mouse_up(
5600            point(px(70.), px(10.)),
5601            MouseButton::Left,
5602            gpui::Modifiers::default(),
5603        );
5604        cx.update(|window, cx| assert!(TextSelection::has_selection(window, cx)));
5605
5606        cx.simulate_mouse_down(
5607            point(px(15.), px(10.)),
5608            MouseButton::Left,
5609            gpui::Modifiers::default(),
5610        );
5611        cx.simulate_mouse_move(
5612            point(px(85.), px(10.)),
5613            Some(MouseButton::Left),
5614            gpui::Modifiers::default(),
5615        );
5616        cx.simulate_mouse_up(
5617            point(px(85.), px(10.)),
5618            MouseButton::Left,
5619            gpui::Modifiers::default(),
5620        );
5621        cx.update(|window, cx| assert!(TextSelection::has_selection(window, cx)));
5622        assert_eq!(clear_count.get(), 3);
5623    }
5624
5625    #[gpui::test]
5626    fn selection_layer_handles_real_double_and_triple_click_events(cx: &mut TestAppContext) {
5627        let (text, layout) = laid_out_runs(&["alpha beta"], cx).pop().unwrap();
5628        let (view, cx) = cx.add_window_view(|_, cx| DoubleSelectionElementView {
5629            selection: TextSelectionHandle::new("", cx),
5630        });
5631        cx.update(|window, cx| {
5632            let _ = window.draw(cx);
5633            let selection = view.read(cx).selection.clone();
5634            selection.resolve_content_key_with(|_, _| Some(TextSelectionContentKey::new(17)), cx);
5635            selection.update_runs(&[text_run(0, text.clone(), layout.clone())], cx);
5636        });
5637
5638        let position = layout.position_for_index(7).unwrap();
5639        cx.simulate_event(MouseDownEvent {
5640            position,
5641            modifiers: gpui::Modifiers::default(),
5642            button: MouseButton::Left,
5643            click_count: 2,
5644            first_mouse: false,
5645        });
5646        cx.simulate_event(MouseUpEvent {
5647            position,
5648            modifiers: gpui::Modifiers::default(),
5649            button: MouseButton::Left,
5650            click_count: 2,
5651        });
5652        cx.update(|window, cx| {
5653            let selection = view.read(cx).selection.clone();
5654            selection.update_runs(&[text_run(0, text.clone(), layout.clone())], cx);
5655            assert_eq!(TextSelection::selected_text(window, cx), "beta");
5656            let snapshot = selection.snapshot(cx).unwrap();
5657            assert_eq!(
5658                snapshot.anchor().content_key(),
5659                Some(TextSelectionContentKey::new(17))
5660            );
5661            assert_eq!(
5662                snapshot.cursor().content_key(),
5663                Some(TextSelectionContentKey::new(17))
5664            );
5665        });
5666
5667        cx.simulate_event(MouseDownEvent {
5668            position,
5669            modifiers: gpui::Modifiers::default(),
5670            button: MouseButton::Left,
5671            click_count: 3,
5672            first_mouse: false,
5673        });
5674        cx.simulate_event(MouseUpEvent {
5675            position,
5676            modifiers: gpui::Modifiers::default(),
5677            button: MouseButton::Left,
5678            click_count: 3,
5679        });
5680        cx.update(|window, cx| {
5681            let selection = view.read(cx).selection.clone();
5682            selection.update_runs(&[text_run(0, text, layout)], cx);
5683            assert_eq!(TextSelection::selected_text(window, cx), "alpha beta");
5684        });
5685    }
5686
5687    /// Each text a [`crate::SelectableText`] of its own, one below the other.
5688    struct SelectableTextsView {
5689        texts: Vec<&'static str>,
5690    }
5691
5692    impl Render for SelectableTextsView {
5693        fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
5694            div()
5695                .size_full()
5696                .child(TextSelectionLayer)
5697                .child(
5698                    div()
5699                        .w(px(400.))
5700                        .children(self.texts.iter().enumerate().map(|(ix, text)| {
5701                            crate::SelectableText::new(("text", ix), *text)
5702                                .document_order(ix as u64)
5703                        })),
5704                )
5705        }
5706    }
5707
5708    /// The middle of the character at `index` of `text`, as it was painted.
5709    fn painted_position(text: &str, index: usize, window: &Window, cx: &App) -> Point<Pixels> {
5710        let state = WindowSelectionState::existing(window, cx).unwrap();
5711        let participants = state.read(cx).participants.borrow();
5712        participants
5713            .values()
5714            .filter_map(|registration| registration.participant.upgrade())
5715            .find_map(|participant| {
5716                let runs = participant.read(cx).runs.borrow();
5717                let run = runs.iter().find(|run| run.text.as_ref() == text)?;
5718                let position = run.layout.position_for_index(index)?;
5719                Some(point(
5720                    position.x + px(1.),
5721                    position.y + run.layout.line_height() / 2.,
5722                ))
5723            })
5724            .unwrap()
5725    }
5726
5727    /// Presses the left button `click_count` times at the character `index`
5728    /// of `text`, and returns where.
5729    fn press_at(
5730        text: &str,
5731        index: usize,
5732        click_count: usize,
5733        shift: bool,
5734        cx: &mut gpui::VisualTestContext,
5735    ) -> Point<Pixels> {
5736        let position = cx.update(|window, cx| painted_position(text, index, window, cx));
5737        cx.simulate_event(MouseDownEvent {
5738            position,
5739            modifiers: gpui::Modifiers {
5740                shift,
5741                ..Default::default()
5742            },
5743            button: MouseButton::Left,
5744            click_count,
5745            first_mouse: false,
5746        });
5747        position
5748    }
5749
5750    /// Drags the pressed button to the character `index` of `text`, and
5751    /// returns the text selected there.
5752    fn drag_to(text: &str, index: usize, cx: &mut gpui::VisualTestContext) -> String {
5753        let position = cx.update(|window, cx| painted_position(text, index, window, cx));
5754        cx.simulate_event(MouseMoveEvent {
5755            position,
5756            pressed_button: Some(MouseButton::Left),
5757            modifiers: gpui::Modifiers::default(),
5758        });
5759        painted_selection(cx)
5760    }
5761
5762    fn release(click_count: usize, cx: &mut gpui::VisualTestContext) {
5763        let position = cx.update(|window, _| window.mouse_position());
5764        cx.simulate_event(MouseUpEvent {
5765            position,
5766            modifiers: gpui::Modifiers::default(),
5767            button: MouseButton::Left,
5768            click_count,
5769        });
5770    }
5771
5772    fn painted_selection(cx: &mut gpui::VisualTestContext) -> String {
5773        cx.update(|window, cx| {
5774            let _ = window.draw(cx);
5775            TextSelection::selected_text(window, cx)
5776        })
5777    }
5778
5779    #[gpui::test]
5780    fn double_click_drag_extends_the_selection_by_words(cx: &mut TestAppContext) {
5781        let (_, cx) = cx.add_window_view(|_, _| SelectableTextsView {
5782            texts: vec!["one two three", "four five six"],
5783        });
5784        painted_selection(cx);
5785
5786        press_at("one two three", 5, 2, false, cx);
5787        assert_eq!(painted_selection(cx), "two");
5788        assert_eq!(drag_to("one two three", 9, cx), "two three");
5789        // Backward, the whole of the word the drag started from stays.
5790        assert_eq!(drag_to("one two three", 1, cx), "one two");
5791        assert_eq!(drag_to("four five six", 6, cx), "two three\nfour five");
5792        release(2, cx);
5793        assert_eq!(painted_selection(cx), "two three\nfour five");
5794    }
5795
5796    #[gpui::test]
5797    fn triple_click_drag_extends_the_selection_by_lines(cx: &mut TestAppContext) {
5798        let text = "first line\nsecond line\nthird line";
5799        let (_, cx) = cx.add_window_view(|_, _| SelectableTextsView { texts: vec![text] });
5800        painted_selection(cx);
5801
5802        press_at(text, 13, 3, false, cx);
5803        assert_eq!(painted_selection(cx), "second line");
5804        assert_eq!(drag_to(text, 25, cx), "second line\nthird line");
5805        assert_eq!(drag_to(text, 2, cx), "first line\nsecond line");
5806        release(3, cx);
5807        assert_eq!(painted_selection(cx), "first line\nsecond line");
5808    }
5809
5810    #[gpui::test]
5811    fn shift_click_after_a_double_click_extends_by_words(cx: &mut TestAppContext) {
5812        let text = "one two three";
5813        let (_, cx) = cx.add_window_view(|_, _| SelectableTextsView { texts: vec![text] });
5814        painted_selection(cx);
5815
5816        press_at(text, 5, 2, false, cx);
5817        release(2, cx);
5818        press_at(text, 9, 1, true, cx);
5819        release(1, cx);
5820        assert_eq!(painted_selection(cx), "two three");
5821        press_at(text, 1, 1, true, cx);
5822        release(1, cx);
5823        assert_eq!(painted_selection(cx), "one two");
5824
5825        // A plain click ends the word selection: a drag from it is by characters.
5826        press_at(text, 5, 1, false, cx);
5827        assert_eq!(drag_to(text, 9, cx), "wo t");
5828        release(1, cx);
5829    }
5830}