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