Support diagnostic navigation in multibuffers (#22620)

cc @nathansobo 

Release Notes:

- Support diagnostic navigation in multibuffers
This commit is contained in:
Cole Miller 2025-01-03 13:07:56 -05:00 committed by GitHub
parent 39af06085a
commit 11ec25aedb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 209 additions and 150 deletions

View file

@ -797,10 +797,11 @@ impl InlineAssistant {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
let language_name = assist.editor.upgrade().and_then(|editor| { let language_name = assist.editor.upgrade().and_then(|editor| {
let multibuffer = editor.read(cx).buffer().read(cx); 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 ranges
.first() .first()
.and_then(|(buffer, _, _)| buffer.read(cx).language()) .and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name()) .map(|language| language.name())
}); });
report_assistant_event( report_assistant_event(
@ -2615,26 +2616,29 @@ impl EventEmitter<CodegenEvent> for CodegenAlternative {}
impl CodegenAlternative { impl CodegenAlternative {
pub fn new( pub fn new(
buffer: Model<MultiBuffer>, multi_buffer: Model<MultiBuffer>,
range: Range<Anchor>, range: Range<Anchor>,
active: bool, active: bool,
telemetry: Option<Arc<Telemetry>>, telemetry: Option<Arc<Telemetry>>,
builder: Arc<PromptBuilder>, builder: Arc<PromptBuilder>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) -> Self { ) -> Self {
let snapshot = buffer.read(cx).snapshot(cx); let snapshot = multi_buffer.read(cx).snapshot(cx);
let (old_buffer, _, _) = buffer let (old_excerpt, _) = snapshot
.read(cx) .range_to_buffer_ranges(range.clone())
.range_to_buffer_ranges(range.clone(), cx)
.pop() .pop()
.unwrap(); .unwrap();
let old_buffer = cx.new_model(|cx| { let old_buffer = cx.new_model(|cx| {
let old_buffer = old_buffer.read(cx); let text = old_excerpt.buffer().as_rope().clone();
let text = old_buffer.as_rope().clone(); let line_ending = old_excerpt.buffer().line_ending();
let line_ending = old_buffer.line_ending(); let language = old_excerpt.buffer().language().cloned();
let language = old_buffer.language().cloned(); let language_registry = multi_buffer
let language_registry = old_buffer.language_registry(); .read(cx)
.buffer(old_excerpt.buffer_id())
.unwrap()
.read(cx)
.language_registry();
let mut buffer = Buffer::local_normalized(text, line_ending, cx); let mut buffer = Buffer::local_normalized(text, line_ending, cx);
buffer.set_language(language, cx); buffer.set_language(language, cx);
@ -2645,7 +2649,7 @@ impl CodegenAlternative {
}); });
Self { Self {
buffer: buffer.clone(), buffer: multi_buffer.clone(),
old_buffer, old_buffer,
edit_position: None, edit_position: None,
message_id: None, message_id: None,
@ -2656,7 +2660,7 @@ impl CodegenAlternative {
generation: Task::ready(()), generation: Task::ready(()),
diff: Diff::default(), diff: Diff::default(),
telemetry, telemetry,
_subscription: cx.subscribe(&buffer, Self::handle_buffer_event), _subscription: cx.subscribe(&multi_buffer, Self::handle_buffer_event),
builder, builder,
active, active,
edits: Vec::new(), edits: Vec::new(),
@ -2867,10 +2871,11 @@ impl CodegenAlternative {
let telemetry = self.telemetry.clone(); let telemetry = self.telemetry.clone();
let language_name = { let language_name = {
let multibuffer = self.buffer.read(cx); 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 ranges
.first() .first()
.and_then(|(buffer, _, _)| buffer.read(cx).language()) .and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name()) .map(|language| language.name())
}; };

View file

@ -257,17 +257,20 @@ impl CodegenAlternative {
) -> Self { ) -> Self {
let snapshot = buffer.read(cx).snapshot(cx); let snapshot = buffer.read(cx).snapshot(cx);
let (old_buffer, _, _) = buffer let (old_excerpt, _) = snapshot
.read(cx) .range_to_buffer_ranges(range.clone())
.range_to_buffer_ranges(range.clone(), cx)
.pop() .pop()
.unwrap(); .unwrap();
let old_buffer = cx.new_model(|cx| { let old_buffer = cx.new_model(|cx| {
let old_buffer = old_buffer.read(cx); let text = old_excerpt.buffer().as_rope().clone();
let text = old_buffer.as_rope().clone(); let line_ending = old_excerpt.buffer().line_ending();
let line_ending = old_buffer.line_ending(); let language = old_excerpt.buffer().language().cloned();
let language = old_buffer.language().cloned(); let language_registry = buffer
let language_registry = old_buffer.language_registry(); .read(cx)
.buffer(old_excerpt.buffer_id())
.unwrap()
.read(cx)
.language_registry();
let mut buffer = Buffer::local_normalized(text, line_ending, cx); let mut buffer = Buffer::local_normalized(text, line_ending, cx);
buffer.set_language(language, cx); buffer.set_language(language, cx);
@ -471,10 +474,11 @@ impl CodegenAlternative {
let telemetry = self.telemetry.clone(); let telemetry = self.telemetry.clone();
let language_name = { let language_name = {
let multibuffer = self.buffer.read(cx); 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 ranges
.first() .first()
.and_then(|(buffer, _, _)| buffer.read(cx).language()) .and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name()) .map(|language| language.name())
}; };

View file

@ -871,10 +871,11 @@ impl InlineAssistant {
if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() { if let Some(model) = LanguageModelRegistry::read_global(cx).active_model() {
let language_name = assist.editor.upgrade().and_then(|editor| { let language_name = assist.editor.upgrade().and_then(|editor| {
let multibuffer = editor.read(cx).buffer().read(cx); 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 ranges
.first() .first()
.and_then(|(buffer, _, _)| buffer.read(cx).language()) .and_then(|(excerpt, _)| excerpt.buffer().language())
.map(|language| language.name()) .map(|language| language.name())
}); });
report_assistant_event( report_assistant_event(

View file

@ -1,11 +1,11 @@
use std::time::Duration; use std::time::Duration;
use editor::Editor; use editor::{AnchorRangeExt, Editor};
use gpui::{ use gpui::{
EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View, EventEmitter, IntoElement, ParentElement, Render, Styled, Subscription, Task, View,
ViewContext, WeakView, ViewContext, WeakView,
}; };
use language::Diagnostic; use language::{Diagnostic, DiagnosticEntry};
use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip}; use ui::{h_flex, prelude::*, Button, ButtonLike, Color, Icon, IconName, Label, Tooltip};
use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace}; use workspace::{item::ItemHandle, StatusItemView, ToolbarItemEvent, Workspace};
@ -148,7 +148,11 @@ impl DiagnosticIndicator {
(buffer, cursor_position) (buffer, cursor_position)
}); });
let new_diagnostic = buffer 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()) .filter(|entry| !entry.range.is_empty())
.min_by_key(|entry| (entry.diagnostic.severity, entry.range.len())) .min_by_key(|entry| (entry.diagnostic.severity, entry.range.len()))
.map(|entry| entry.diagnostic); .map(|entry| entry.diagnostic);

View file

@ -99,8 +99,8 @@ use itertools::Itertools;
use language::{ use language::{
language_settings::{self, all_language_settings, language_settings, InlayHintSettings}, language_settings::{self, all_language_settings, language_settings, InlayHintSettings},
markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel,
CursorShape, Diagnostic, Documentation, IndentKind, IndentSize, Language, OffsetRangeExt, CursorShape, Diagnostic, DiagnosticEntry, Documentation, IndentKind, IndentSize, Language,
Point, Selection, SelectionGoal, TransactionId, OffsetRangeExt, Point, Selection, SelectionGoal, TransactionId,
}; };
use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange};
use linked_editing_ranges::refresh_linked_ranges; use linked_editing_ranges::refresh_linked_ranges;
@ -3549,13 +3549,12 @@ impl Editor {
Bias::Left, Bias::Left,
); );
let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end; let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
multi_buffer multi_buffer_snapshot
.range_to_buffer_ranges(multi_buffer_visible_range, cx) .range_to_buffer_ranges(multi_buffer_visible_range)
.into_iter() .into_iter()
.filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty()) .filter(|(_, excerpt_visible_range)| !excerpt_visible_range.is_empty())
.filter_map(|(buffer_handle, excerpt_visible_range, excerpt_id)| { .filter_map(|(excerpt, excerpt_visible_range)| {
let buffer = buffer_handle.read(cx); let buffer_file = project::File::from_dyn(excerpt.buffer().file())?;
let buffer_file = project::File::from_dyn(buffer.file())?;
let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?; let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
let worktree_entry = buffer_worktree let worktree_entry = buffer_worktree
.read(cx) .read(cx)
@ -3564,17 +3563,17 @@ impl Editor {
return None; return None;
} }
let language = buffer.language()?; let language = excerpt.buffer().language()?;
if let Some(restrict_to_languages) = restrict_to_languages { if let Some(restrict_to_languages) = restrict_to_languages {
if !restrict_to_languages.contains(language) { if !restrict_to_languages.contains(language) {
return None; return None;
} }
} }
Some(( Some((
excerpt_id, excerpt.id(),
( (
buffer_handle, multi_buffer.buffer(excerpt.buffer_id()).unwrap(),
buffer.version().clone(), excerpt.buffer().version().clone(),
excerpt_visible_range, excerpt_visible_range,
), ),
)) ))
@ -9179,10 +9178,23 @@ impl Editor {
let snapshot = self.snapshot(cx); let snapshot = self.snapshot(cx);
loop { loop {
let diagnostics = if direction == Direction::Prev { 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::<Vec<_>>()
} else { } 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::<Vec<_>>()
} }
.into_iter()
.filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start)); .filter(|diagnostic| !snapshot.intersects_fold(diagnostic.range.start));
let group = diagnostics let group = diagnostics
// relies on diagnostics_in_range to return diagnostics with the same starting range to // 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 buffer = self.buffer.read(cx).snapshot(cx);
let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer); let primary_range_start = active_diagnostics.primary_range.start.to_offset(&buffer);
let is_valid = 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| { .any(|entry| {
let range = entry.range.to_offset(&buffer);
entry.diagnostic.is_primary entry.diagnostic.is_primary
&& !entry.range.is_empty() && !range.is_empty()
&& entry.range.start == primary_range_start && range.start == primary_range_start
&& entry.diagnostic.message == active_diagnostics.primary_message && 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() { let (buffer, selection) = if let Some(buffer) = self.buffer().read(cx).as_singleton() {
(buffer, selection_range.start.row..selection_range.end.row) (buffer, selection_range.start.row..selection_range.end.row)
} else { } else {
let buffer_ranges = self let multi_buffer = self.buffer().read(cx);
.buffer() let multi_buffer_snapshot = multi_buffer.snapshot(cx);
.read(cx) let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
.range_to_buffer_ranges(selection_range, cx);
let (buffer, range, _) = if selection.reversed { let (excerpt, range) = if selection.reversed {
buffer_ranges.first() buffer_ranges.first()
} else { } else {
buffer_ranges.last() buffer_ranges.last()
}?; }?;
let snapshot = buffer.read(cx).snapshot(); let snapshot = excerpt.buffer();
let selection = text::ToPoint::to_point(&range.start, &snapshot).row let selection = text::ToPoint::to_point(&range.start, &snapshot).row
..text::ToPoint::to_point(&range.end, &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)) Some((buffer, selection))
@ -12399,17 +12414,18 @@ impl Editor {
}; };
let selections = self.selections.all::<usize>(cx); let selections = self.selections.all::<usize>(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(); let mut new_selections_by_buffer = HashMap::default();
for selection in selections { for selection in selections {
for (buffer, range, _) in for (excerpt, range) in
buffer.range_to_buffer_ranges(selection.start..selection.end, cx) 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.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 new_selections_by_buffer
.entry(buffer) .entry(multi_buffer.buffer(excerpt.buffer_id()).unwrap())
.or_insert(Vec::new()) .or_insert(Vec::new())
.push(range) .push(range)
} }
@ -12508,13 +12524,15 @@ impl Editor {
} }
None => { None => {
let selections = self.selections.all::<usize>(cx); let selections = self.selections.all::<usize>(cx);
let buffer = self.buffer.read(cx); let multi_buffer = self.buffer.read(cx);
for selection in selections { for selection in selections {
for (mut buffer_handle, mut range, _) in for (excerpt, mut range) in multi_buffer
buffer.range_to_buffer_ranges(selection.range(), cx) .snapshot(cx)
.range_to_buffer_ranges(selection.range())
{ {
// When editing branch buffers, jump to the corresponding location // When editing branch buffers, jump to the corresponding location
// in their base buffer. // in their base buffer.
let mut buffer_handle = multi_buffer.buffer(excerpt.buffer_id()).unwrap();
let buffer = buffer_handle.read(cx); let buffer = buffer_handle.read(cx);
if let Some(base_buffer) = buffer.base_buffer() { if let Some(base_buffer) = buffer.base_buffer() {
range = buffer.range_to_version(range, &base_buffer.read(cx).version()); range = buffer.range_to_version(range, &base_buffer.read(cx).version());

View file

@ -45,7 +45,7 @@ use language::{
IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings, IndentGuideBackgroundColoring, IndentGuideColoring, IndentGuideSettings,
ShowWhitespaceSetting, ShowWhitespaceSetting,
}, },
ChunkRendererContext, ChunkRendererContext, DiagnosticEntry,
}; };
use lsp::DiagnosticSeverity; use lsp::DiagnosticSeverity;
use multi_buffer::{ use multi_buffer::{
@ -4741,10 +4741,11 @@ impl EditorElement {
if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None { if scrollbar_settings.diagnostics != ScrollbarDiagnostics::None {
let diagnostics = snapshot let diagnostics = snapshot
.buffer_snapshot .buffer_snapshot
.diagnostics_in_range::<_, Point>( .diagnostics_in_range(Point::zero()..max_point, false)
Point::zero()..max_point, .map(|DiagnosticEntry { diagnostic, range }| DiagnosticEntry {
false, diagnostic,
) range: range.to_point(&snapshot.buffer_snapshot),
})
// Don't show diagnostics the user doesn't care about // Don't show diagnostics the user doesn't care about
.filter(|diagnostic| { .filter(|diagnostic| {
match ( match (

View file

@ -266,12 +266,11 @@ fn show_hover(
// If there's a diagnostic, assign it on the hover state and notify // If there's a diagnostic, assign it on the hover state and notify
let mut local_diagnostic = snapshot let mut local_diagnostic = snapshot
.buffer_snapshot .buffer_snapshot
.diagnostics_in_range::<_, usize>(anchor..anchor, false) .diagnostics_in_range(anchor..anchor, false)
// Find the entry with the most specific range // Find the entry with the most specific range
.min_by_key(|entry| entry.range.end - entry.range.start) .min_by_key(|entry| {
.map(|entry| DiagnosticEntry { let range = entry.range.to_offset(&snapshot.buffer_snapshot);
diagnostic: entry.diagnostic, range.end - range.start
range: entry.range.to_anchors(&snapshot.buffer_snapshot),
}); });
// Pull the primary diagnostic out so we can jump to it if the popover is clicked // Pull the primary diagnostic out so we can jump to it if the popover is clicked

View file

@ -456,14 +456,17 @@ impl Editor {
range: Range<Anchor>, range: Range<Anchor>,
cx: &mut ViewContext<Editor>, cx: &mut ViewContext<Editor>,
) -> Option<()> { ) -> Option<()> {
let (buffer, range, _) = self let multi_buffer = self.buffer.read(cx);
.buffer let multi_buffer_snapshot = multi_buffer.snapshot(cx);
.read(cx) let (excerpt, range) = multi_buffer_snapshot
.range_to_buffer_ranges(range, cx) .range_to_buffer_ranges(range)
.into_iter() .into_iter()
.next()?; .next()?;
buffer.update(cx, |branch_buffer, cx| { multi_buffer
.buffer(excerpt.buffer_id())
.unwrap()
.update(cx, |branch_buffer, cx| {
branch_buffer.merge_into_base(vec![range], cx); branch_buffer.merge_into_base(vec![range], cx);
}); });

View file

@ -3943,14 +3943,14 @@ impl BufferSnapshot {
) -> impl 'a + Iterator<Item = DiagnosticEntry<O>> ) -> impl 'a + Iterator<Item = DiagnosticEntry<O>>
where where
T: 'a + Clone + ToOffset, T: 'a + Clone + ToOffset,
O: 'a + FromAnchor + Ord, O: 'a + FromAnchor,
{ {
let mut iterators: Vec<_> = self let mut iterators: Vec<_> = self
.diagnostics .diagnostics
.iter() .iter()
.map(|(_, collection)| { .map(|(_, collection)| {
collection collection
.range::<T, O>(search_range.clone(), self, true, reversed) .range::<T, text::Anchor>(search_range.clone(), self, true, reversed)
.peekable() .peekable()
}) })
.collect(); .collect();
@ -3964,7 +3964,7 @@ impl BufferSnapshot {
let cmp = a let cmp = a
.range .range
.start .start
.cmp(&b.range.start) .cmp(&b.range.start, self)
// when range is equal, sort by diagnostic severity // when range is equal, sort by diagnostic severity
.then(a.diagnostic.severity.cmp(&b.diagnostic.severity)) .then(a.diagnostic.severity.cmp(&b.diagnostic.severity))
// and stabilize order with group_id // and stabilize order with group_id
@ -3975,7 +3975,13 @@ impl BufferSnapshot {
cmp 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),
})
}) })
} }

View file

@ -128,13 +128,18 @@ impl SyntaxTreeView {
fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> { fn editor_updated(&mut self, did_reparse: bool, cx: &mut ViewContext<Self>) -> Option<()> {
// Find which excerpt the cursor is in, and the position within that excerpted buffer. // Find which excerpt the cursor is in, and the position within that excerpted buffer.
let editor_state = self.editor.as_mut()?; 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::<usize>(cx).range(); let selection_range = editor.selections.last::<usize>(cx).range();
editor let multi_buffer = editor.buffer().read(cx);
.buffer() let (excerpt, range) = snapshot
.read(cx) .buffer_snapshot
.range_to_buffer_ranges(selection_range, cx) .range_to_buffer_ranges(selection_range)
.pop() .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 // If the cursor has moved into a different excerpt, retrieve a new syntax layer
@ -143,16 +148,16 @@ impl SyntaxTreeView {
.active_buffer .active_buffer
.get_or_insert_with(|| BufferState { .get_or_insert_with(|| BufferState {
buffer: buffer.clone(), buffer: buffer.clone(),
excerpt_id, excerpt_id: excerpt.id(),
active_layer: None, active_layer: None,
}); });
let mut prev_layer = None; let mut prev_layer = None;
if did_reparse { if did_reparse {
prev_layer = buffer_state.active_layer.take(); 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.buffer = buffer.clone();
buffer_state.excerpt_id = excerpt_id; buffer_state.excerpt_id = excerpt.id();
buffer_state.active_layer = None; buffer_state.active_layer = None;
} }

View file

@ -1667,42 +1667,6 @@ impl MultiBuffer {
}) })
} }
pub fn range_to_buffer_ranges<T: ToOffset>(
&self,
range: Range<T>,
cx: &AppContext,
) -> Vec<(Model<Buffer>, Range<usize>, 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::<usize>(&());
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( pub fn remove_excerpts(
&mut self, &mut self,
excerpt_ids: impl IntoIterator<Item = ExcerptId>, excerpt_ids: impl IntoIterator<Item = ExcerptId>,
@ -3914,26 +3878,30 @@ impl MultiBufferSnapshot {
where where
O: text::FromAnchor + 'a, O: text::FromAnchor + 'a,
{ {
self.as_singleton() self.all_excerpts()
.into_iter() .flat_map(move |excerpt| excerpt.buffer().diagnostic_group(group_id))
.flat_map(move |(_, _, buffer)| buffer.diagnostic_group(group_id))
} }
pub fn diagnostics_in_range<'a, T, O>( pub fn diagnostics_in_range<'a, T>(
&'a self, &'a self,
range: Range<T>, range: Range<T>,
reversed: bool, reversed: bool,
) -> impl Iterator<Item = DiagnosticEntry<O>> + 'a ) -> impl Iterator<Item = DiagnosticEntry<Anchor>> + 'a
where where
T: 'a + ToOffset, T: 'a + ToOffset,
O: 'a + text::FromAnchor + Ord,
{ {
self.as_singleton() let mut ranges = self.range_to_buffer_ranges(range);
.into_iter() if reversed {
.flat_map(move |(_, _, buffer)| { ranges.reverse();
buffer.diagnostics_in_range( }
range.start.to_offset(self)..range.end.to_offset(self), ranges.into_iter().flat_map(move |(excerpt, range)| {
reversed, 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(),
},
) )
}) })
} }
@ -4185,6 +4153,42 @@ impl MultiBufferSnapshot {
}) })
} }
pub fn range_to_buffer_ranges<T: ToOffset>(
&self,
range: Range<T>,
) -> Vec<(MultiBufferExcerpt<'_>, Range<usize>)> {
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 /// 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. /// 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 self.excerpt.id
} }
pub fn buffer_id(&self) -> BufferId {
self.excerpt.buffer_id
}
pub fn start_anchor(&self) -> Anchor { pub fn start_anchor(&self) -> Anchor {
Anchor { Anchor {
buffer_id: Some(self.excerpt.buffer_id), buffer_id: Some(self.excerpt.buffer_id),

View file

@ -1234,14 +1234,13 @@ fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) {
start_ix..end_ix start_ix..end_ix
); );
let excerpted_buffer_ranges = multibuffer let snapshot = multibuffer.read(cx).snapshot(cx);
.read(cx) let excerpted_buffer_ranges = snapshot.range_to_buffer_ranges(start_ix..end_ix);
.range_to_buffer_ranges(start_ix..end_ix, cx);
let excerpted_buffers_text = excerpted_buffer_ranges let excerpted_buffers_text = excerpted_buffer_ranges
.iter() .iter()
.map(|(buffer, buffer_range, _)| { .map(|(excerpt, buffer_range)| {
buffer excerpt
.read(cx) .buffer()
.text_for_range(buffer_range.clone()) .text_for_range(buffer_range.clone())
.collect::<String>() .collect::<String>()
}) })

View file

@ -3047,6 +3047,12 @@ pub trait FromAnchor {
fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; 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 { impl FromAnchor for Point {
fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self {
snapshot.summary_for_anchor(anchor) snapshot.summary_for_anchor(anchor)