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