From 11ec25aedbdff5c6bc1e6f68ee5382439710a146 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 3 Jan 2025 13:07:56 -0500 Subject: [PATCH] Support diagnostic navigation in multibuffers (#22620) cc @nathansobo Release Notes: - Support diagnostic navigation in multibuffers --- crates/assistant/src/inline_assistant.rs | 37 +++--- crates/assistant2/src/buffer_codegen.rs | 24 ++-- crates/assistant2/src/inline_assistant.rs | 5 +- crates/diagnostics/src/items.rs | 10 +- crates/editor/src/editor.rs | 84 ++++++++------ crates/editor/src/element.rs | 11 +- crates/editor/src/hover_popover.rs | 9 +- crates/editor/src/hunk_diff.rs | 17 +-- crates/language/src/buffer.rs | 14 ++- crates/language_tools/src/syntax_tree_view.rs | 23 ++-- crates/multi_buffer/src/multi_buffer.rs | 108 ++++++++++-------- crates/multi_buffer/src/multi_buffer_tests.rs | 11 +- crates/text/src/text.rs | 6 + 13 files changed, 209 insertions(+), 150 deletions(-) diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 640fea5987..27cd20dcb9 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -797,10 +797,11 @@ impl InlineAssistant { if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { let language_name = assist.editor.upgrade().and_then(|editor| { let multibuffer = editor.read(cx).buffer().read(cx); - let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx); + let multibuffer_snapshot = multibuffer.snapshot(cx); + let ranges = multibuffer_snapshot.range_to_buffer_ranges(assist.range.clone()); ranges .first() - .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .and_then(|(excerpt, _)| excerpt.buffer().language()) .map(|language| language.name()) }); report_assistant_event( @@ -2615,26 +2616,29 @@ impl EventEmitter for CodegenAlternative {} impl CodegenAlternative { pub fn new( - buffer: Model, + multi_buffer: Model, range: Range, active: bool, telemetry: Option>, builder: Arc, cx: &mut ModelContext, ) -> Self { - let snapshot = buffer.read(cx).snapshot(cx); + let snapshot = multi_buffer.read(cx).snapshot(cx); - let (old_buffer, _, _) = buffer - .read(cx) - .range_to_buffer_ranges(range.clone(), cx) + let (old_excerpt, _) = snapshot + .range_to_buffer_ranges(range.clone()) .pop() .unwrap(); let old_buffer = cx.new_model(|cx| { - let old_buffer = old_buffer.read(cx); - let text = old_buffer.as_rope().clone(); - let line_ending = old_buffer.line_ending(); - let language = old_buffer.language().cloned(); - let language_registry = old_buffer.language_registry(); + let text = old_excerpt.buffer().as_rope().clone(); + let line_ending = old_excerpt.buffer().line_ending(); + let language = old_excerpt.buffer().language().cloned(); + let language_registry = multi_buffer + .read(cx) + .buffer(old_excerpt.buffer_id()) + .unwrap() + .read(cx) + .language_registry(); let mut buffer = Buffer::local_normalized(text, line_ending, cx); buffer.set_language(language, cx); @@ -2645,7 +2649,7 @@ impl CodegenAlternative { }); Self { - buffer: buffer.clone(), + buffer: multi_buffer.clone(), old_buffer, edit_position: None, message_id: None, @@ -2656,7 +2660,7 @@ impl CodegenAlternative { generation: Task::ready(()), diff: Diff::default(), telemetry, - _subscription: cx.subscribe(&buffer, Self::handle_buffer_event), + _subscription: cx.subscribe(&multi_buffer, Self::handle_buffer_event), builder, active, edits: Vec::new(), @@ -2867,10 +2871,11 @@ impl CodegenAlternative { let telemetry = self.telemetry.clone(); let language_name = { let multibuffer = self.buffer.read(cx); - let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx); + let snapshot = multibuffer.snapshot(cx); + let ranges = snapshot.range_to_buffer_ranges(self.range.clone()); ranges .first() - .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .and_then(|(excerpt, _)| excerpt.buffer().language()) .map(|language| language.name()) }; diff --git a/crates/assistant2/src/buffer_codegen.rs b/crates/assistant2/src/buffer_codegen.rs index 54a8aa0383..aacbcccddf 100644 --- a/crates/assistant2/src/buffer_codegen.rs +++ b/crates/assistant2/src/buffer_codegen.rs @@ -257,17 +257,20 @@ impl CodegenAlternative { ) -> Self { let snapshot = buffer.read(cx).snapshot(cx); - let (old_buffer, _, _) = buffer - .read(cx) - .range_to_buffer_ranges(range.clone(), cx) + let (old_excerpt, _) = snapshot + .range_to_buffer_ranges(range.clone()) .pop() .unwrap(); let old_buffer = cx.new_model(|cx| { - let old_buffer = old_buffer.read(cx); - let text = old_buffer.as_rope().clone(); - let line_ending = old_buffer.line_ending(); - let language = old_buffer.language().cloned(); - let language_registry = old_buffer.language_registry(); + let text = old_excerpt.buffer().as_rope().clone(); + let line_ending = old_excerpt.buffer().line_ending(); + let language = old_excerpt.buffer().language().cloned(); + let language_registry = buffer + .read(cx) + .buffer(old_excerpt.buffer_id()) + .unwrap() + .read(cx) + .language_registry(); let mut buffer = Buffer::local_normalized(text, line_ending, cx); buffer.set_language(language, cx); @@ -471,10 +474,11 @@ impl CodegenAlternative { let telemetry = self.telemetry.clone(); let language_name = { let multibuffer = self.buffer.read(cx); - let ranges = multibuffer.range_to_buffer_ranges(self.range.clone(), cx); + let snapshot = multibuffer.snapshot(cx); + let ranges = snapshot.range_to_buffer_ranges(self.range.clone()); ranges .first() - .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .and_then(|(excerpt, _)| excerpt.buffer().language()) .map(|language| language.name()) }; diff --git a/crates/assistant2/src/inline_assistant.rs b/crates/assistant2/src/inline_assistant.rs index 9535531895..8db73315b2 100644 --- a/crates/assistant2/src/inline_assistant.rs +++ b/crates/assistant2/src/inline_assistant.rs @@ -871,10 +871,11 @@ impl InlineAssistant { if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { let language_name = assist.editor.upgrade().and_then(|editor| { let multibuffer = editor.read(cx).buffer().read(cx); - let ranges = multibuffer.range_to_buffer_ranges(assist.range.clone(), cx); + let snapshot = multibuffer.snapshot(cx); + let ranges = snapshot.range_to_buffer_ranges(assist.range.clone()); ranges .first() - .and_then(|(buffer, _, _)| buffer.read(cx).language()) + .and_then(|(excerpt, _)| excerpt.buffer().language()) .map(|language| language.name()) }); report_assistant_event( diff --git a/crates/diagnostics/src/items.rs b/crates/diagnostics/src/items.rs index f102be37fd..715cccc02a 100644 --- a/crates/diagnostics/src/items.rs +++ b/crates/diagnostics/src/items.rs @@ -1,11 +1,11 @@ use std::time::Duration; -use editor::Editor; +use editor::{AnchorRangeExt, Editor}; use gpui::{ EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View, ViewContext, WeakView, }; -use language::Diagnostic; +use language::{Diagnostic, DiagnosticEntry}; use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; @@ -148,7 +148,11 @@ impl DiagnosticIndicator { (buffer, cursor_position) }); let new_diagnostic = buffer - .diagnostics_in_range::<_, usize>(cursor_position..cursor_position, false) + .diagnostics_in_range(cursor_position..cursor_position, false) + .map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry { + diagnostic, + range: range.to_offset(&buffer), + }) .filter(|entry| !entry.range.is_empty()) .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len())) .map(|entry| entry.diagnostic); diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 63e081ec5a..88297f72e4 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -99,8 +99,8 @@ use itertools::Itertools; use language::{ language_settings::{self, all_language_settings, language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, - CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt, - Point, Selection, SelectionGoal, TransactionId, + CursorShape, Diagnostic, DiagnosticEntry, Documentation, IndentKind, IndentSize, Language, + OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId, }; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; @@ -3549,13 +3549,12 @@ impl Editor { Bias::Left, ); let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; - multi_buffer - .range_to_buffer_ranges(multi_buffer_visible_range, cx) + multi_buffer_snapshot + .range_to_buffer_ranges(multi_buffer_visible_range) .into_iter() - .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) - .filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| { - let buffer = buffer_handle.read(cx); - let buffer_file = project::File::from_dyn(buffer.file())?; + .filter(|(_, excerpt_visible_range)| !excerpt_visible_range.is_empty()) + .filter_map(|(excerpt, excerpt_visible_range)| { + let buffer_file = project::File::from_dyn(excerpt.buffer().file())?; let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?; let worktree_entry = buffer_worktree .read(cx) @@ -3564,17 +3563,17 @@ impl Editor { return None; } - let language = buffer.language()?; + let language = excerpt.buffer().language()?; if let Some(restrict_to_languages) = restrict_to_languages { if !restrict_to_languages.contains(language) { return None; } } Some(( - excerpt_id, + excerpt.id(), ( - buffer_handle, - buffer.version().clone(), + multi_buffer.buffer(excerpt.buffer_id()).unwrap(), + excerpt.buffer().version().clone(), excerpt_visible_range, ), )) @@ -9179,10 +9178,23 @@ impl Editor { let snapshot = self.snapshot(cx); loop { let diagnostics = if direction == Direction::Prev { - buffer.diagnostics_in_range::<_, usize>(0..search_start, true) + buffer + .diagnostics_in_range(0..search_start, true) + .map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry { + diagnostic, + range: range.to_offset(&buffer), + }) + .collect::>() } else { - buffer.diagnostics_in_range::<_, usize>(search_start..buffer.len(), false) + buffer + .diagnostics_in_range(search_start..buffer.len(), false) + .map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry { + diagnostic, + range: range.to_offset(&buffer), + }) + .collect::>() } + .into_iter() .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start)); let group = diagnostics // relies on diagnostics_in_range to return diagnostics with the same starting range to @@ -10289,11 +10301,12 @@ impl Editor { let buffer = self.buffer.read(cx).snapshot(cx); let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer); let is_valid = buffer - .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone(), false) + .diagnostics_in_range(active_diagnostics.primary_range.clone(), false) .any(|entry| { + let range = entry.range.to_offset(&buffer); entry.diagnostic.is_primary - && !entry.range.is_empty() - && entry.range.start == primary_range_start + && !range.is_empty() + && range.start == primary_range_start && entry.diagnostic.message == active_diagnostics.primary_message }); @@ -11493,21 +11506,23 @@ impl Editor { let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() { (buffer, selection_range.start.row..selection_range.end.row) } else { - let buffer_ranges = self - .buffer() - .read(cx) - .range_to_buffer_ranges(selection_range, cx); + let multi_buffer = self.buffer().read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range); - let (buffer, range, _) = if selection.reversed { + let (excerpt, range) = if selection.reversed { buffer_ranges.first() } else { buffer_ranges.last() }?; - let snapshot = buffer.read(cx).snapshot(); + let snapshot = excerpt.buffer(); let selection = text::ToPoint::to_point(&range.start, &snapshot).row ..text::ToPoint::to_point(&range.end, &snapshot).row; - (buffer.clone(), selection) + ( + multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone(), + selection, + ) }; Some((buffer, selection)) @@ -12399,17 +12414,18 @@ impl Editor { }; let selections = self.selections.all::(cx); - let buffer = self.buffer.read(cx); + let multi_buffer = self.buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); let mut new_selections_by_buffer = HashMap::default(); for selection in selections { - for (buffer, range, _) in - buffer.range_to_buffer_ranges(selection.start..selection.end, cx) + for (excerpt, range) in + multi_buffer_snapshot.range_to_buffer_ranges(selection.start..selection.end) { - let mut range = range.to_point(buffer.read(cx)); + let mut range = range.to_point(excerpt.buffer()); range.start.column = 0; - range.end.column = buffer.read(cx).line_len(range.end.row); + range.end.column = excerpt.buffer().line_len(range.end.row); new_selections_by_buffer - .entry(buffer) + .entry(multi_buffer.buffer(excerpt.buffer_id()).unwrap()) .or_insert(Vec::new()) .push(range) } @@ -12508,13 +12524,15 @@ impl Editor { } None => { let selections = self.selections.all::(cx); - let buffer = self.buffer.read(cx); + let multi_buffer = self.buffer.read(cx); for selection in selections { - for (mut buffer_handle, mut range, _) in - buffer.range_to_buffer_ranges(selection.range(), cx) + for (excerpt, mut range) in multi_buffer + .snapshot(cx) + .range_to_buffer_ranges(selection.range()) { // When editing branch buffers, jump to the corresponding location // in their base buffer. + let mut buffer_handle = multi_buffer.buffer(excerpt.buffer_id()).unwrap(); let buffer = buffer_handle.read(cx); if let Some(base_buffer) = buffer.base_buffer() { range = buffer.range_to_version(range, &base_buffer.read(cx).version()); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 79aebc07d4..06fc87b908 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -45,7 +45,7 @@ use language::{ IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, ShowWhitespaceSetting, }, - ChunkRendererContext, + ChunkRendererContext, DiagnosticEntry, }; use lsp::DiagnosticSeverity; use multi_buffer::{ @@ -4741,10 +4741,11 @@ impl EditorElement { if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None { let diagnostics = snapshot .buffer_snapshot - .diagnostics_in_range::<_, Point>( - Point::zero()..max_point, - false, - ) + .diagnostics_in_range(Point::zero()..max_point, false) + .map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry { + diagnostic, + range: range.to_point(&snapshot.buffer_snapshot), + }) // Don't show diagnostics the user doesn't care about .filter(|diagnostic| { match ( diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index fe3668b2fe..ce0532b71c 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -266,12 +266,11 @@ fn show_hover( // If there's a diagnostic, assign it on the hover state and notify let mut local_diagnostic = snapshot .buffer_snapshot - .diagnostics_in_range::<_, usize>(anchor..anchor, false) + .diagnostics_in_range(anchor..anchor, false) // Find the entry with the most specific range - .min_by_key(|entry| entry.range.end - entry.range.start) - .map(|entry| DiagnosticEntry { - diagnostic: entry.diagnostic, - range: entry.range.to_anchors(&snapshot.buffer_snapshot), + .min_by_key(|entry| { + let range = entry.range.to_offset(&snapshot.buffer_snapshot); + range.end - range.start }); // Pull the primary diagnostic out so we can jump to it if the popover is clicked diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 13dde12773..85bd964699 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -456,16 +456,19 @@ impl Editor { range: Range, cx: &mut ViewContext, ) -> Option<()> { - let (buffer, range, _) = self - .buffer - .read(cx) - .range_to_buffer_ranges(range, cx) + let multi_buffer = self.buffer.read(cx); + let multi_buffer_snapshot = multi_buffer.snapshot(cx); + let (excerpt, range) = multi_buffer_snapshot + .range_to_buffer_ranges(range) .into_iter() .next()?; - buffer.update(cx, |branch_buffer, cx| { - branch_buffer.merge_into_base(vec![range], cx); - }); + multi_buffer + .buffer(excerpt.buffer_id()) + .unwrap() + .update(cx, |branch_buffer, cx| { + branch_buffer.merge_into_base(vec![range], cx); + }); if let Some(project) = self.project.clone() { self.save(true, project, cx).detach_and_log_err(cx); diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index a55396e1e3..6a7d53de4d 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -3943,14 +3943,14 @@ impl BufferSnapshot { ) -> impl 'a + Iterator> where T: 'a + Clone + ToOffset, - O: 'a + FromAnchor + Ord, + O: 'a + FromAnchor, { let mut iterators: Vec<_> = self .diagnostics .iter() .map(|(_, collection)| { collection - .range::(search_range.clone(), self, true, reversed) + .range::(search_range.clone(), self, true, reversed) .peekable() }) .collect(); @@ -3964,7 +3964,7 @@ impl BufferSnapshot { let cmp = a .range .start - .cmp(&b.range.start) + .cmp(&b.range.start, self) // when range is equal, sort by diagnostic severity .then(a.diagnostic.severity.cmp(&b.diagnostic.severity)) // and stabilize order with group_id @@ -3975,7 +3975,13 @@ impl BufferSnapshot { cmp } })?; - iterators[next_ix].next() + iterators[next_ix] + .next() + .map(|DiagnosticEntry { range, diagnostic }| DiagnosticEntry { + diagnostic, + range: FromAnchor::from_anchor(&range.start, self) + ..FromAnchor::from_anchor(&range.end, self), + }) }) } diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index ba40d55761..97c29b8615 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -128,13 +128,18 @@ impl SyntaxTreeView { fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext) -> Option<()> { // Find which excerpt the cursor is in, and the position within that excerpted buffer. let editor_state = self.editor.as_mut()?; - let (buffer, range, excerpt_id) = editor_state.editor.update(cx, |editor, cx| { + let snapshot = editor_state + .editor + .update(cx, |editor, cx| editor.snapshot(cx)); + let (excerpt, buffer, range) = editor_state.editor.update(cx, |editor, cx| { let selection_range = editor.selections.last::(cx).range(); - editor - .buffer() - .read(cx) - .range_to_buffer_ranges(selection_range, cx) - .pop() + let multi_buffer = editor.buffer().read(cx); + let (excerpt, range) = snapshot + .buffer_snapshot + .range_to_buffer_ranges(selection_range) + .pop()?; + let buffer = multi_buffer.buffer(excerpt.buffer_id()).unwrap().clone(); + Some((excerpt, buffer, range)) })?; // If the cursor has moved into a different excerpt, retrieve a new syntax layer @@ -143,16 +148,16 @@ impl SyntaxTreeView { .active_buffer .get_or_insert_with(|| BufferState { buffer: buffer.clone(), - excerpt_id, + excerpt_id: excerpt.id(), active_layer: None, }); let mut prev_layer = None; if did_reparse { prev_layer = buffer_state.active_layer.take(); } - if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt_id { + if buffer_state.buffer != buffer || buffer_state.excerpt_id != excerpt.id() { buffer_state.buffer = buffer.clone(); - buffer_state.excerpt_id = excerpt_id; + buffer_state.excerpt_id = excerpt.id(); buffer_state.active_layer = None; } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 108b70eb29..fcdd70a317 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1667,42 +1667,6 @@ impl MultiBuffer { }) } - pub fn range_to_buffer_ranges( - &self, - range: Range, - cx: &AppContext, - ) -> Vec<(Model, Range, ExcerptId)> { - let snapshot = self.read(cx); - let start = range.start.to_offset(&snapshot); - let end = range.end.to_offset(&snapshot); - - let mut result = Vec::new(); - let mut cursor = snapshot.excerpts.cursor::(&()); - cursor.seek(&start, Bias::Right, &()); - if cursor.item().is_none() { - cursor.prev(&()); - } - - while let Some(excerpt) = cursor.item() { - if *cursor.start() > end { - break; - } - - let mut end_before_newline = cursor.end(&()); - if excerpt.has_trailing_newline { - end_before_newline -= 1; - } - let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); - let start = excerpt_start + (cmp::max(start, *cursor.start()) - *cursor.start()); - let end = excerpt_start + (cmp::min(end, end_before_newline) - *cursor.start()); - let buffer = self.buffers.borrow()[&excerpt.buffer_id].buffer.clone(); - result.push((buffer, start..end, excerpt.id)); - cursor.next(&()); - } - - result - } - pub fn remove_excerpts( &mut self, excerpt_ids: impl IntoIterator, @@ -3914,28 +3878,32 @@ impl MultiBufferSnapshot { where O: text::FromAnchor + 'a, { - self.as_singleton() - .into_iter() - .flat_map(move |(_, _, buffer)| buffer.diagnostic_group(group_id)) + self.all_excerpts() + .flat_map(move |excerpt| excerpt.buffer().diagnostic_group(group_id)) } - pub fn diagnostics_in_range<'a, T, O>( + pub fn diagnostics_in_range<'a, T>( &'a self, range: Range, reversed: bool, - ) -> impl Iterator> + 'a + ) -> impl Iterator> + 'a where T: 'a + ToOffset, - O: 'a + text::FromAnchor + Ord, { - self.as_singleton() - .into_iter() - .flat_map(move |(_, _, buffer)| { - buffer.diagnostics_in_range( - range.start.to_offset(self)..range.end.to_offset(self), - reversed, - ) - }) + let mut ranges = self.range_to_buffer_ranges(range); + if reversed { + ranges.reverse(); + } + ranges.into_iter().flat_map(move |(excerpt, range)| { + let excerpt_id = excerpt.id(); + excerpt.buffer().diagnostics_in_range(range, reversed).map( + move |DiagnosticEntry { diagnostic, range }| DiagnosticEntry { + diagnostic, + range: self.anchor_in_excerpt(excerpt_id, range.start).unwrap() + ..self.anchor_in_excerpt(excerpt_id, range.end).unwrap(), + }, + ) + }) } pub fn syntax_ancestor( @@ -4185,6 +4153,42 @@ impl MultiBufferSnapshot { }) } + pub fn range_to_buffer_ranges( + &self, + range: Range, + ) -> Vec<(MultiBufferExcerpt<'_>, Range)> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + + let mut result = Vec::new(); + let mut cursor = self.excerpts.cursor::<(usize, Point)>(&()); + cursor.seek(&start, Bias::Right, &()); + if cursor.item().is_none() { + cursor.prev(&()); + } + + while let Some(excerpt) = cursor.item() { + if cursor.start().0 > end { + break; + } + + let mut end_before_newline = cursor.end(&()).0; + if excerpt.has_trailing_newline { + end_before_newline -= 1; + } + let excerpt_start = excerpt.range.context.start.to_offset(&excerpt.buffer); + let start = excerpt_start + (cmp::max(start, cursor.start().0) - cursor.start().0); + let end = excerpt_start + (cmp::min(end, end_before_newline) - cursor.start().0); + result.push(( + MultiBufferExcerpt::new(&excerpt, *cursor.start()), + start..end, + )); + cursor.next(&()); + } + + result + } + /// Returns excerpts overlapping the given ranges. If range spans multiple excerpts returns one range for each excerpt /// /// The ranges are specified in the coordinate space of the multibuffer, not the individual excerpted buffers. @@ -4664,6 +4668,10 @@ impl<'a> MultiBufferExcerpt<'a> { self.excerpt.id } + pub fn buffer_id(&self) -> BufferId { + self.excerpt.buffer_id + } + pub fn start_anchor(&self) -> Anchor { Anchor { buffer_id: Some(self.excerpt.buffer_id), diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index fb2237bb42..059b279b78 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -1234,14 +1234,13 @@ fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) { start_ix..end_ix ); - let excerpted_buffer_ranges = multibuffer - .read(cx) - .range_to_buffer_ranges(start_ix..end_ix, cx); + let snapshot = multibuffer.read(cx).snapshot(cx); + let excerpted_buffer_ranges = snapshot.range_to_buffer_ranges(start_ix..end_ix); let excerpted_buffers_text = excerpted_buffer_ranges .iter() - .map(|(buffer, buffer_range, _)| { - buffer - .read(cx) + .map(|(excerpt, buffer_range)| { + excerpt + .buffer() .text_for_range(buffer_range.clone()) .collect::() }) diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index b037327f7e..d869791864 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -3047,6 +3047,12 @@ pub trait FromAnchor { fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; } +impl FromAnchor for Anchor { + fn from_anchor(anchor: &Anchor, _snapshot: &BufferSnapshot) -> Self { + *anchor + } +} + impl FromAnchor for Point { fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { snapshot.summary_for_anchor(anchor)