1use std::ops::Range;
2
3use collections::HashMap;
4use itertools::Itertools;
5use text::{AnchorRangeExt, BufferId, ToPoint};
6use ui::ViewContext;
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}
39pub(super) fn refresh_linked_ranges(this: &mut Editor, cx: &mut ViewContext<Editor>) -> Option<()> {
40 if this.pending_rename.is_some() {
41 return None;
42 }
43 let project = this.project.clone()?;
44 let buffer = this.buffer.read(cx);
45 let mut applicable_selections = vec![];
46 let selections = this.selections.all::<usize>(cx);
47 let snapshot = buffer.snapshot(cx);
48 for selection in selections {
49 let cursor_position = selection.head();
50 let start_position = snapshot.anchor_before(cursor_position);
51 let end_position = snapshot.anchor_after(selection.tail());
52 if start_position.buffer_id != end_position.buffer_id || end_position.buffer_id.is_none() {
53 // Throw away selections spanning multiple buffers.
54 continue;
55 }
56 if let Some(buffer) = end_position.buffer_id.and_then(|id| buffer.buffer(id)) {
57 applicable_selections.push((
58 buffer,
59 start_position.text_anchor,
60 end_position.text_anchor,
61 ));
62 }
63 }
64 if applicable_selections.is_empty() {
65 return None;
66 }
67 this.linked_editing_range_task = Some(cx.spawn(|this, mut cx| async move {
68 let highlights = project
69 .update(&mut cx, |project, cx| {
70 let mut linked_edits_tasks = vec![];
71
72 for (buffer, start, end) in &applicable_selections {
73 let snapshot = buffer.read(cx).snapshot();
74 let buffer_id = buffer.read(cx).remote_id();
75
76 let linked_edits_task = project.linked_edit(&buffer, *start, cx);
77 let highlights = move || async move {
78 let edits = linked_edits_task.await.log_err()?;
79 // Find the range containing our current selection.
80 // We might not find one, because the selection contains both the start and end of the contained range
81 // (think of selecting <`html>foo`</html> - even though there's a matching closing tag, the selection goes beyond the range of the opening tag)
82 // or the language server may not have returned any ranges.
83
84 let start_point = start.to_point(&snapshot);
85 let end_point = end.to_point(&snapshot);
86 let _current_selection_contains_range = edits.iter().find(|range| {
87 range.start.to_point(&snapshot) <= start_point
88 && range.end.to_point(&snapshot) >= end_point
89 });
90 if _current_selection_contains_range.is_none() {
91 return None;
92 }
93 // Now link every range as each-others sibling.
94 let mut siblings: HashMap<Range<text::Anchor>, Vec<_>> = Default::default();
95 let mut insert_sorted_anchor =
96 |key: &Range<text::Anchor>, value: &Range<text::Anchor>| {
97 siblings.entry(key.clone()).or_default().push(value.clone());
98 };
99 for items in edits.into_iter().combinations(2) {
100 let Ok([first, second]): Result<[_; 2], _> = items.try_into() else {
101 unreachable!()
102 };
103
104 insert_sorted_anchor(&first, &second);
105 insert_sorted_anchor(&second, &first);
106 }
107 let mut siblings: Vec<(_, _)> = siblings.into_iter().collect();
108 siblings.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
109 Some((buffer_id, siblings))
110 };
111 linked_edits_tasks.push(highlights());
112 }
113 linked_edits_tasks
114 })
115 .log_err()?;
116
117 let highlights = futures::future::join_all(highlights).await;
118
119 this.update(&mut cx, |this, cx| {
120 this.linked_edit_ranges.0.clear();
121 if this.pending_rename.is_some() {
122 return;
123 }
124 for (buffer_id, ranges) in highlights.into_iter().flatten() {
125 this.linked_edit_ranges
126 .0
127 .entry(buffer_id)
128 .or_default()
129 .extend(ranges);
130 }
131 for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
132 let Some(snapshot) = this
133 .buffer
134 .read(cx)
135 .buffer(*buffer_id)
136 .map(|buffer| buffer.read(cx).snapshot())
137 else {
138 continue;
139 };
140 values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
141 }
142
143 cx.notify();
144 })
145 .log_err();
146
147 Some(())
148 }));
149 None
150}