linked_editing_ranges.rs

  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}