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