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