Skip to main content

gpui_base/
text_selection.rs

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