1use collections::HashMap;
2use gpui::{AppContext, Context, Window};
3use itertools::Itertools;
4use std::{ops::Range, time::Duration};
5use text::{AnchorRangeExt, BufferId, ToPoint};
6use util::ResultExt;
7
8use crate::Editor;
9
10#[derive(Clone, Default)]
11pub(super) struct LinkedEditingRanges(
12 /// Ranges are non-overlapping and sorted by .0 (thus, [x + 1].start > [x].end must hold)
13 pub HashMap<BufferId, Vec<(Range<text::Anchor>, Vec<Range<text::Anchor>>)>>,
14);
15
16impl LinkedEditingRanges {
17 pub(super) fn get(
18 &self,
19 id: BufferId,
20 anchor: Range<text::Anchor>,
21 snapshot: &text::BufferSnapshot,
22 ) -> Option<&(Range<text::Anchor>, Vec<Range<text::Anchor>>)> {
23 let ranges_for_buffer = self.0.get(&id)?;
24 let lower_bound = ranges_for_buffer
25 .partition_point(|(range, _)| range.start.cmp(&anchor.start, snapshot).is_le());
26 if lower_bound == 0 {
27 // None of the linked ranges contains `anchor`.
28 return None;
29 }
30 ranges_for_buffer
31 .get(lower_bound - 1)
32 .filter(|(range, _)| range.end.cmp(&anchor.end, snapshot).is_ge())
33 }
34 pub(super) fn is_empty(&self) -> bool {
35 self.0.is_empty()
36 }
37
38 pub(super) fn clear(&mut self) {
39 self.0.clear();
40 }
41}
42
43const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
44
45// TODO do not refresh anything at all, if the settings/capabilities do not have it enabled.
46pub(super) fn refresh_linked_ranges(
47 editor: &mut Editor,
48 window: &mut Window,
49 cx: &mut Context<Editor>,
50) -> Option<()> {
51 if editor.ignore_lsp_data() || editor.pending_rename.is_some() {
52 return None;
53 }
54 let project = editor.project()?.downgrade();
55
56 editor.linked_editing_range_task = Some(cx.spawn_in(window, async move |editor, cx| {
57 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
58
59 let mut applicable_selections = Vec::new();
60 editor
61 .update(cx, |editor, cx| {
62 let display_snapshot = editor.display_snapshot(cx);
63 let selections = editor.selections.all::<usize>(&display_snapshot);
64 let snapshot = display_snapshot.buffer_snapshot();
65 let buffer = editor.buffer.read(cx);
66 for selection in selections {
67 let cursor_position = selection.head();
68 let start_position = snapshot.anchor_before(cursor_position);
69 let end_position = snapshot.anchor_after(selection.tail());
70 if start_position.buffer_id != end_position.buffer_id
71 || end_position.buffer_id.is_none()
72 {
73 // Throw away selections spanning multiple buffers.
74 continue;
75 }
76 if let Some(buffer) = buffer.buffer_for_anchor(end_position, cx) {
77 applicable_selections.push((
78 buffer,
79 start_position.text_anchor,
80 end_position.text_anchor,
81 ));
82 }
83 }
84 })
85 .ok()?;
86
87 if applicable_selections.is_empty() {
88 return None;
89 }
90
91 let highlights = project
92 .update(cx, |project, cx| {
93 let mut linked_edits_tasks = vec![];
94 for (buffer, start, end) in &applicable_selections {
95 let linked_edits_task = project.linked_edits(buffer, *start, cx);
96 let cx = cx.to_async();
97 let highlights = async move {
98 let edits = linked_edits_task.await.log_err()?;
99 let snapshot = cx
100 .read_entity(&buffer, |buffer, _| buffer.snapshot())
101 .ok()?;
102 let buffer_id = snapshot.remote_id();
103
104 // Find the range containing our current selection.
105 // We might not find one, because the selection contains both the start and end of the contained range
106 // (think of selecting <`html>foo`</html> - even though there's a matching closing tag, the selection goes beyond the range of the opening tag)
107 // or the language server may not have returned any ranges.
108
109 let start_point = start.to_point(&snapshot);
110 let end_point = end.to_point(&snapshot);
111 let _current_selection_contains_range = edits.iter().find(|range| {
112 range.start.to_point(&snapshot) <= start_point
113 && range.end.to_point(&snapshot) >= end_point
114 });
115 _current_selection_contains_range?;
116 // Now link every range as each-others sibling.
117 let mut siblings: HashMap<Range<text::Anchor>, Vec<_>> = Default::default();
118 let mut insert_sorted_anchor =
119 |key: &Range<text::Anchor>, value: &Range<text::Anchor>| {
120 siblings.entry(key.clone()).or_default().push(value.clone());
121 };
122 for items in edits.into_iter().combinations(2) {
123 let Ok([first, second]): Result<[_; 2], _> = items.try_into() else {
124 unreachable!()
125 };
126
127 insert_sorted_anchor(&first, &second);
128 insert_sorted_anchor(&second, &first);
129 }
130 let mut siblings: Vec<(_, _)> = siblings.into_iter().collect();
131 siblings.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
132 Some((buffer_id, siblings))
133 };
134 linked_edits_tasks.push(highlights);
135 }
136 linked_edits_tasks
137 })
138 .ok()?;
139
140 let highlights = futures::future::join_all(highlights).await;
141
142 editor
143 .update(cx, |this, cx| {
144 this.linked_edit_ranges.0.clear();
145 if this.pending_rename.is_some() {
146 return;
147 }
148 for (buffer_id, ranges) in highlights.into_iter().flatten() {
149 this.linked_edit_ranges
150 .0
151 .entry(buffer_id)
152 .or_default()
153 .extend(ranges);
154 }
155 for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
156 let Some(snapshot) = this
157 .buffer
158 .read(cx)
159 .buffer(*buffer_id)
160 .map(|buffer| buffer.read(cx).snapshot())
161 else {
162 continue;
163 };
164 values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
165 }
166
167 cx.notify();
168 })
169 .ok()?;
170
171 Some(())
172 }));
173 None
174}