1use collections::HashMap;
  2use gpui::{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 selections = editor.selections.all::<usize>(&editor.display_snapshot(cx));
 63                let snapshot = editor.buffer.read(cx).snapshot(cx);
 64                let buffer = editor.buffer.read(cx);
 65                for selection in selections {
 66                    let cursor_position = selection.head();
 67                    let start_position = snapshot.anchor_before(cursor_position);
 68                    let end_position = snapshot.anchor_after(selection.tail());
 69                    if start_position.buffer_id != end_position.buffer_id
 70                        || end_position.buffer_id.is_none()
 71                    {
 72                        // Throw away selections spanning multiple buffers.
 73                        continue;
 74                    }
 75                    if let Some(buffer) = buffer.buffer_for_anchor(end_position, cx) {
 76                        applicable_selections.push((
 77                            buffer,
 78                            start_position.text_anchor,
 79                            end_position.text_anchor,
 80                        ));
 81                    }
 82                }
 83            })
 84            .ok()?;
 85
 86        if applicable_selections.is_empty() {
 87            return None;
 88        }
 89
 90        let highlights = project
 91            .update(cx, |project, cx| {
 92                let mut linked_edits_tasks = vec![];
 93
 94                for (buffer, start, end) in &applicable_selections {
 95                    let snapshot = buffer.read(cx).snapshot();
 96                    let buffer_id = buffer.read(cx).remote_id();
 97
 98                    let linked_edits_task = project.linked_edits(buffer, *start, cx);
 99                    let highlights = move || async move {
100                        let edits = linked_edits_task.await.log_err()?;
101                        // Find the range containing our current selection.
102                        // We might not find one, because the selection contains both the start and end of the contained range
103                        // (think of selecting <`html>foo`</html> - even though there's a matching closing tag, the selection goes beyond the range of the opening tag)
104                        // or the language server may not have returned any ranges.
105
106                        let start_point = start.to_point(&snapshot);
107                        let end_point = end.to_point(&snapshot);
108                        let _current_selection_contains_range = edits.iter().find(|range| {
109                            range.start.to_point(&snapshot) <= start_point
110                                && range.end.to_point(&snapshot) >= end_point
111                        });
112                        _current_selection_contains_range?;
113                        // Now link every range as each-others sibling.
114                        let mut siblings: HashMap<Range<text::Anchor>, Vec<_>> = Default::default();
115                        let mut insert_sorted_anchor =
116                            |key: &Range<text::Anchor>, value: &Range<text::Anchor>| {
117                                siblings.entry(key.clone()).or_default().push(value.clone());
118                            };
119                        for items in edits.into_iter().combinations(2) {
120                            let Ok([first, second]): Result<[_; 2], _> = items.try_into() else {
121                                unreachable!()
122                            };
123
124                            insert_sorted_anchor(&first, &second);
125                            insert_sorted_anchor(&second, &first);
126                        }
127                        let mut siblings: Vec<(_, _)> = siblings.into_iter().collect();
128                        siblings.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
129                        Some((buffer_id, siblings))
130                    };
131                    linked_edits_tasks.push(highlights());
132                }
133                linked_edits_tasks
134            })
135            .ok()?;
136
137        let highlights = futures::future::join_all(highlights).await;
138
139        editor
140            .update(cx, |this, cx| {
141                this.linked_edit_ranges.0.clear();
142                if this.pending_rename.is_some() {
143                    return;
144                }
145                for (buffer_id, ranges) in highlights.into_iter().flatten() {
146                    this.linked_edit_ranges
147                        .0
148                        .entry(buffer_id)
149                        .or_default()
150                        .extend(ranges);
151                }
152                for (buffer_id, values) in this.linked_edit_ranges.0.iter_mut() {
153                    let Some(snapshot) = this
154                        .buffer
155                        .read(cx)
156                        .buffer(*buffer_id)
157                        .map(|buffer| buffer.read(cx).snapshot())
158                    else {
159                        continue;
160                    };
161                    values.sort_by(|lhs, rhs| lhs.0.cmp(&rhs.0, &snapshot));
162                }
163
164                cx.notify();
165            })
166            .ok()?;
167
168        Some(())
169    }));
170    None
171}