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