Detailed changes
@@ -297,7 +297,7 @@ impl DiagnosticBlock {
return;
};
- for (excerpt_id, range) in multibuffer.excerpts_for_buffer(buffer_id, cx) {
+ for (excerpt_id, _, range) in multibuffer.excerpts_for_buffer(buffer_id, cx) {
if range.context.overlaps(&diagnostic.range, &snapshot) {
Self::jump_to(
editor,
@@ -583,7 +583,7 @@ impl ProjectDiagnosticsEditor {
RetainExcerpts::All | RetainExcerpts::Dirty => multi_buffer
.excerpts_for_buffer(buffer_id, cx)
.into_iter()
- .map(|(_, range)| range)
+ .map(|(_, _, range)| range)
.sorted_by(|a, b| cmp_excerpts(&buffer_snapshot, a, b))
.collect(),
}
@@ -107,7 +107,7 @@ use project::{InlayId, lsp_store::LspFoldingRange, lsp_store::TokenType};
use serde::Deserialize;
use smallvec::SmallVec;
use sum_tree::{Bias, TreeMap};
-use text::{BufferId, LineIndent, Patch, ToOffset as _};
+use text::{BufferId, LineIndent, Patch};
use ui::{SharedString, px};
use unicode_segmentation::UnicodeSegmentation;
use ztracing::instrument;
@@ -1977,57 +1977,11 @@ impl DisplaySnapshot {
/// Returned ranges are 0-based relative to `buffer_range.start`.
pub(super) fn combined_highlights(
&self,
- buffer_id: BufferId,
- buffer_range: Range<usize>,
+ multibuffer_range: Range<MultiBufferOffset>,
syntax_theme: &theme::SyntaxTheme,
) -> Vec<(Range<usize>, HighlightStyle)> {
let multibuffer = self.buffer_snapshot();
- let multibuffer_range = multibuffer
- .excerpts()
- .find_map(|(excerpt_id, buffer, range)| {
- if buffer.remote_id() != buffer_id {
- return None;
- }
- let context_start = range.context.start.to_offset(buffer);
- let context_end = range.context.end.to_offset(buffer);
- if buffer_range.start < context_start || buffer_range.end > context_end {
- return None;
- }
- let start_anchor = buffer.anchor_before(buffer_range.start);
- let end_anchor = buffer.anchor_after(buffer_range.end);
- let mb_range =
- multibuffer.anchor_range_in_excerpt(excerpt_id, start_anchor..end_anchor)?;
- Some(mb_range.start.to_offset(multibuffer)..mb_range.end.to_offset(multibuffer))
- });
-
- let Some(multibuffer_range) = multibuffer_range else {
- // Range is outside all excerpts (e.g. symbol name not in a
- // multi-buffer excerpt). Fall back to buffer-level syntax highlights.
- let buffer_snapshot = multibuffer.excerpts().find_map(|(_, buffer, _)| {
- (buffer.remote_id() == buffer_id).then(|| buffer.clone())
- });
- let Some(buffer_snapshot) = buffer_snapshot else {
- return Vec::new();
- };
- let mut highlights = Vec::new();
- let mut offset = 0usize;
- for chunk in buffer_snapshot.chunks(buffer_range, true) {
- let chunk_len = chunk.text.len();
- if chunk_len == 0 {
- continue;
- }
- if let Some(style) = chunk
- .syntax_highlight_id
- .and_then(|id| id.style(syntax_theme))
- {
- highlights.push((offset..offset + chunk_len, style));
- }
- offset += chunk_len;
- }
- return highlights;
- };
-
let chunks = custom_highlights::CustomHighlightsChunks::new(
multibuffer_range,
true,
@@ -1,4 +1,4 @@
-use std::{cmp, ops::Range};
+use std::ops::Range;
use collections::HashMap;
use futures::FutureExt;
@@ -6,10 +6,15 @@ use futures::future::join_all;
use gpui::{App, Context, HighlightStyle, Task};
use itertools::Itertools as _;
use language::language_settings::language_settings;
-use language::{Buffer, BufferSnapshot, OutlineItem};
-use multi_buffer::{Anchor, MultiBufferSnapshot};
-use text::{Bias, BufferId, OffsetRangeExt as _, ToOffset as _};
+use language::{Buffer, OutlineItem};
+use multi_buffer::{
+ Anchor, AnchorRangeExt as _, MultiBufferOffset, MultiBufferRow, MultiBufferSnapshot,
+ ToOffset as _,
+};
+use text::BufferId;
use theme::{ActiveTheme as _, SyntaxTheme};
+use unicode_segmentation::UnicodeSegmentation as _;
+use util::maybe;
use crate::display_map::DisplaySnapshot;
use crate::{Editor, LSP_REQUEST_DEBOUNCE_TIMEOUT};
@@ -215,16 +220,13 @@ impl Editor {
let display_snapshot =
editor.display_map.update(cx, |map, cx| map.snapshot(cx));
let mut highlighted_results = results;
- for (buffer_id, items) in &mut highlighted_results {
- if let Some(buffer) = editor.buffer.read(cx).buffer(*buffer_id) {
- let snapshot = buffer.read(cx).snapshot();
- apply_highlights(
- items,
- *buffer_id,
- &snapshot,
- &display_snapshot,
- &syntax,
- );
+ for items in highlighted_results.values_mut() {
+ for item in items {
+ if let Some(highlights) =
+ highlights_from_buffer(&display_snapshot, &item, &syntax)
+ {
+ item.highlight_ranges = highlights;
+ }
}
}
editor.lsp_document_symbols.extend(highlighted_results);
@@ -242,34 +244,6 @@ fn lsp_symbols_enabled(buffer: &Buffer, cx: &App) -> bool {
.lsp_enabled()
}
-/// Applies combined syntax + semantic token highlights to LSP document symbol
-/// outline items that were built without highlights by the project layer.
-fn apply_highlights(
- items: &mut [OutlineItem<text::Anchor>],
- buffer_id: BufferId,
- buffer_snapshot: &BufferSnapshot,
- display_snapshot: &DisplaySnapshot,
- syntax_theme: &SyntaxTheme,
-) {
- for item in items {
- let symbol_range = item.range.to_offset(buffer_snapshot);
- let selection_start = item.source_range_for_text.start.to_offset(buffer_snapshot);
-
- if let Some(highlights) = highlights_from_buffer(
- &item.text,
- 0,
- buffer_id,
- buffer_snapshot,
- display_snapshot,
- symbol_range,
- selection_start,
- syntax_theme,
- ) {
- item.highlight_ranges = highlights;
- }
- }
-}
-
/// Finds where the symbol name appears in the buffer and returns combined
/// (tree-sitter + semantic token) highlights for those positions.
///
@@ -278,117 +252,78 @@ fn apply_highlights(
/// to word-by-word matching for cases like `impl<T> Trait<T> for Type`
/// where the LSP name doesn't appear verbatim in the buffer.
fn highlights_from_buffer(
- name: &str,
- name_offset_in_text: usize,
- buffer_id: BufferId,
- buffer_snapshot: &BufferSnapshot,
display_snapshot: &DisplaySnapshot,
- symbol_range: Range<usize>,
- selection_start_offset: usize,
+ item: &OutlineItem<text::Anchor>,
syntax_theme: &SyntaxTheme,
) -> Option<Vec<(Range<usize>, HighlightStyle)>> {
- if name.is_empty() {
+ let outline_text = &item.text;
+ if outline_text.is_empty() {
return None;
}
- let range_start_offset = symbol_range.start;
- let range_end_offset = symbol_range.end;
-
- // Try to find the name verbatim in the buffer near the selection range.
- let search_start = buffer_snapshot.clip_offset(
- selection_start_offset
- .saturating_sub(name.len())
- .max(range_start_offset),
- Bias::Right,
- );
- let search_end = buffer_snapshot.clip_offset(
- cmp::min(selection_start_offset + name.len() * 2, range_end_offset),
- Bias::Left,
- );
-
- if search_start < search_end {
- let buffer_text: String = buffer_snapshot
- .text_for_range(search_start..search_end)
- .collect();
- if let Some(found_at) = buffer_text.find(name) {
- let name_start_offset = search_start + found_at;
- let name_end_offset = name_start_offset + name.len();
- let result = highlights_for_buffer_range(
- name_offset_in_text,
- name_start_offset..name_end_offset,
- buffer_id,
- display_snapshot,
- syntax_theme,
+ let multi_buffer_snapshot = display_snapshot.buffer();
+ let multi_buffer_source_range_anchors =
+ multi_buffer_snapshot.text_anchors_to_visible_anchors([
+ item.source_range_for_text.start,
+ item.source_range_for_text.end,
+ ]);
+ let Some(anchor_range) = maybe!({
+ Some(
+ (*multi_buffer_source_range_anchors.get(0)?)?
+ ..(*multi_buffer_source_range_anchors.get(1)?)?,
+ )
+ }) else {
+ return None;
+ };
+
+ let selection_point_range = anchor_range.to_point(multi_buffer_snapshot);
+ let mut search_start = selection_point_range.start;
+ search_start.column = 0;
+ let search_start_offset = search_start.to_offset(&multi_buffer_snapshot);
+ let mut search_end = selection_point_range.end;
+ search_end.column = multi_buffer_snapshot.line_len(MultiBufferRow(search_end.row));
+
+ let search_text = multi_buffer_snapshot
+ .text_for_range(search_start..search_end)
+ .collect::<String>();
+
+ let mut outline_text_highlights = Vec::new();
+ match search_text.find(outline_text) {
+ Some(start_index) => {
+ let multibuffer_start = search_start_offset + MultiBufferOffset(start_index);
+ let multibuffer_end = multibuffer_start + MultiBufferOffset(outline_text.len());
+ outline_text_highlights.extend(
+ display_snapshot
+ .combined_highlights(multibuffer_start..multibuffer_end, syntax_theme),
);
- if result.is_some() {
- return result;
- }
}
- }
-
- // Fallback: match word-by-word. Split the name on whitespace and find
- // each word sequentially in the buffer's symbol range.
- let range_start_offset = buffer_snapshot.clip_offset(range_start_offset, Bias::Right);
- let range_end_offset = buffer_snapshot.clip_offset(range_end_offset, Bias::Left);
-
- let mut highlights = Vec::new();
- let mut got_any = false;
- let buffer_text: String = buffer_snapshot
- .text_for_range(range_start_offset..range_end_offset)
- .collect();
- let mut buf_search_from = 0usize;
- let mut name_search_from = 0usize;
- for word in name.split_whitespace() {
- let name_word_start = name[name_search_from..]
- .find(word)
- .map(|pos| name_search_from + pos)
- .unwrap_or(name_search_from);
- if let Some(found_in_buf) = buffer_text[buf_search_from..].find(word) {
- let buf_word_start = range_start_offset + buf_search_from + found_in_buf;
- let buf_word_end = buf_word_start + word.len();
- let text_cursor = name_offset_in_text + name_word_start;
- if let Some(mut word_highlights) = highlights_for_buffer_range(
- text_cursor,
- buf_word_start..buf_word_end,
- buffer_id,
- display_snapshot,
- syntax_theme,
- ) {
- got_any = true;
- highlights.append(&mut word_highlights);
+ None => {
+ for (outline_text_word_start, outline_word) in outline_text.split_word_bound_indices() {
+ if let Some(start_index) = search_text.find(outline_word) {
+ let multibuffer_start = search_start_offset + MultiBufferOffset(start_index);
+ let multibuffer_end = multibuffer_start + MultiBufferOffset(outline_word.len());
+ outline_text_highlights.extend(
+ display_snapshot
+ .combined_highlights(multibuffer_start..multibuffer_end, syntax_theme)
+ .into_iter()
+ .map(|(range_in_word, style)| {
+ (
+ outline_text_word_start + range_in_word.start
+ ..outline_text_word_start + range_in_word.end,
+ style,
+ )
+ }),
+ );
+ }
}
- buf_search_from = buf_search_from + found_in_buf + word.len();
}
- name_search_from = name_word_start + word.len();
}
- got_any.then_some(highlights)
-}
-
-/// Gets combined (tree-sitter + semantic token) highlights for a buffer byte
-/// range via the editor's display snapshot, then shifts the returned ranges
-/// so they start at `text_cursor_start` (the position in the outline item text).
-fn highlights_for_buffer_range(
- text_cursor_start: usize,
- buffer_range: Range<usize>,
- buffer_id: BufferId,
- display_snapshot: &DisplaySnapshot,
- syntax_theme: &SyntaxTheme,
-) -> Option<Vec<(Range<usize>, HighlightStyle)>> {
- let raw = display_snapshot.combined_highlights(buffer_id, buffer_range, syntax_theme);
- if raw.is_empty() {
- return None;
+ if outline_text_highlights.is_empty() {
+ None
+ } else {
+ Some(outline_text_highlights)
}
- Some(
- raw.into_iter()
- .map(|(range, style)| {
- (
- range.start + text_cursor_start..range.end + text_cursor_start,
- style,
- )
- })
- .collect(),
- )
}
#[cfg(test)]
@@ -7500,7 +7500,8 @@ impl Editor {
let mut read_ranges = Vec::new();
for highlight in highlights {
let buffer_id = cursor_buffer.read(cx).remote_id();
- for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
+ for (excerpt_id, _, excerpt_range) in
+ buffer.excerpts_for_buffer(buffer_id, cx)
{
let start = highlight
.range
@@ -20539,7 +20540,7 @@ impl Editor {
let mut all_folded_excerpt_ids = Vec::new();
for buffer_id in &ids_to_fold {
let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(*buffer_id, cx);
- all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _)| id));
+ all_folded_excerpt_ids.extend(folded_excerpts.into_iter().map(|(id, _, _)| id));
}
self.display_map.update(cx, |display_map, cx| {
@@ -20569,7 +20570,7 @@ impl Editor {
display_map.unfold_buffers([buffer_id], cx);
});
cx.emit(EditorEvent::BufferFoldToggled {
- ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
+ ids: unfolded_excerpts.iter().map(|&(id, _, _)| id).collect(),
folded: false,
});
cx.notify();
@@ -22941,7 +22942,7 @@ impl Editor {
.snapshot();
let mut handled = false;
- for (id, ExcerptRange { context, .. }) in
+ for (id, _, ExcerptRange { context, .. }) in
self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
{
if context.start.cmp(&position, &snapshot).is_ge()
@@ -1165,8 +1165,8 @@ impl SplittableEditor {
let lhs_ranges: Vec<ExcerptRange<Point>> = rhs_multibuffer
.excerpts_for_buffer(main_buffer_snapshot.remote_id(), cx)
.into_iter()
- .filter(|(id, _)| rhs_excerpt_ids.contains(id))
- .map(|(_, excerpt_range)| {
+ .filter(|(id, _, _)| rhs_excerpt_ids.contains(id))
+ .map(|(_, _, excerpt_range)| {
let to_base_text = |range: Range<Point>| {
let start = diff_snapshot
.buffer_point_to_base_text_range(
@@ -182,7 +182,7 @@ fn conflicts_updated(
let excerpts = multibuffer.excerpts_for_buffer(buffer_id, cx);
let Some(buffer_snapshot) = excerpts
.first()
- .and_then(|(excerpt_id, _)| snapshot.buffer_for_excerpt(*excerpt_id))
+ .and_then(|(excerpt_id, _, _)| snapshot.buffer_for_excerpt(*excerpt_id))
else {
return;
};
@@ -221,7 +221,7 @@ fn conflicts_updated(
let mut removed_highlighted_ranges = Vec::new();
let mut removed_block_ids = HashSet::default();
for (conflict_range, block_id) in old_conflicts {
- let Some((excerpt_id, _)) = excerpts.iter().find(|(_, range)| {
+ let Some((excerpt_id, _, _)) = excerpts.iter().find(|(_, _, range)| {
let precedes_start = range
.context
.start
@@ -263,7 +263,7 @@ fn conflicts_updated(
let new_conflicts = &conflict_set.conflicts[event.new_range.clone()];
let mut blocks = Vec::new();
for conflict in new_conflicts {
- let Some((excerpt_id, _)) = excerpts.iter().find(|(_, range)| {
+ let Some((excerpt_id, _, _)) = excerpts.iter().find(|(_, _, range)| {
let precedes_start = range
.context
.start
@@ -94,7 +94,9 @@ impl GoToLine {
.read(cx)
.excerpts_for_buffer(snapshot.remote_id(), cx)
.into_iter()
- .map(move |(_, range)| text::ToPoint::to_point(&range.context.end, &snapshot).row)
+ .map(move |(_, _, range)| {
+ text::ToPoint::to_point(&range.context.end, &snapshot).row
+ })
.max()
.unwrap_or(0);
@@ -246,7 +246,12 @@ impl StyledText {
pub fn with_runs(mut self, runs: Vec<TextRun>) -> Self {
let mut text = &**self.text;
for run in &runs {
- text = text.get(run.len..).expect("invalid text run");
+ text = text.get(run.len..).unwrap_or_else(|| {
+ #[cfg(debug_assertions)]
+ panic!("invalid text run. Text: '{text}', run: {run:?}");
+ #[cfg(not(debug_assertions))]
+ panic!("invalid text run");
+ });
}
assert!(text.is_empty(), "invalid text run");
self.runs = Some(runs);
@@ -1987,7 +1987,7 @@ impl MultiBuffer {
&self,
buffer_id: BufferId,
cx: &App,
- ) -> Vec<(ExcerptId, ExcerptRange<text::Anchor>)> {
+ ) -> Vec<(ExcerptId, Arc<BufferSnapshot>, ExcerptRange<text::Anchor>)> {
let mut excerpts = Vec::new();
let snapshot = self.read(cx);
let mut cursor = snapshot.excerpts.cursor::<Option<&Locator>>(());
@@ -1997,7 +1997,7 @@ impl MultiBuffer {
if let Some(excerpt) = cursor.item()
&& excerpt.locator == *locator
{
- excerpts.push((excerpt.id, excerpt.range.clone()));
+ excerpts.push((excerpt.id, excerpt.buffer.clone(), excerpt.range.clone()));
}
}
}
@@ -2128,7 +2128,7 @@ impl MultiBuffer {
) -> Option<Anchor> {
let mut found = None;
let snapshot = buffer.read(cx).snapshot();
- for (excerpt_id, range) in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
+ for (excerpt_id, _, range) in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
let start = range.context.start.to_point(&snapshot);
let end = range.context.end.to_point(&snapshot);
if start <= point && point < end {
@@ -2157,7 +2157,7 @@ impl MultiBuffer {
cx: &App,
) -> Option<Anchor> {
let snapshot = buffer.read(cx).snapshot();
- for (excerpt_id, range) in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
+ for (excerpt_id, _, range) in self.excerpts_for_buffer(snapshot.remote_id(), cx) {
if range.context.start.cmp(&anchor, &snapshot).is_le()
&& range.context.end.cmp(&anchor, &snapshot).is_ge()
{
@@ -1285,7 +1285,7 @@ fn test_resolving_anchors_after_replacing_their_excerpts(cx: &mut App) {
let mut ids = multibuffer
.excerpts_for_buffer(buffer_2.read(cx).remote_id(), cx)
.into_iter()
- .map(|(id, _)| id);
+ .map(|(id, _, _)| id);
(ids.next().unwrap(), ids.next().unwrap())
});
let snapshot_2 = multibuffer.read(cx).snapshot(cx);
@@ -1143,7 +1143,7 @@ impl OutlinePanel {
.excerpts_for_buffer(buffer.read(cx).remote_id(), cx)
})
.and_then(|excerpts| {
- let (excerpt_id, excerpt_range) = excerpts.first()?;
+ let (excerpt_id, _, excerpt_range) = excerpts.first()?;
multi_buffer_snapshot
.anchor_in_excerpt(*excerpt_id, excerpt_range.context.start)
})