From 657186ba15bd04ac68c4d046fdfdbf9ea7fe0f13 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Tue, 3 Mar 2026 14:07:36 -0500 Subject: [PATCH] wip getting ready to try changing anchorseektarget Co-authored-by: Conrad Irwin --- crates/agent_ui/src/inline_assistant.rs | 2 +- crates/agent_ui/src/message_editor.rs | 2 +- crates/agent_ui/src/text_thread_editor.rs | 10 +- crates/editor/src/display_map.rs | 8 +- crates/editor/src/document_colors.rs | 2 +- crates/editor/src/editor.rs | 14 +- crates/editor/src/element.rs | 2 +- crates/editor/src/hover_links.rs | 6 +- crates/editor/src/hover_popover.rs | 2 +- crates/git_ui/src/conflict_view.rs | 11 +- crates/language_tools/src/syntax_tree_view.rs | 2 +- crates/multi_buffer/src/anchor.rs | 30 ++- crates/multi_buffer/src/multi_buffer.rs | 182 +++++++++----- crates/multi_buffer/src/multi_buffer_tests.rs | 4 +- crates/multi_buffer/src/path_key.rs | 226 +++++++----------- crates/multi_buffer/src/transaction.rs | 80 +++---- crates/outline_panel/src/outline_panel.rs | 2 +- 17 files changed, 291 insertions(+), 294 deletions(-) diff --git a/crates/agent_ui/src/inline_assistant.rs b/crates/agent_ui/src/inline_assistant.rs index 9ac84addcc80c806739570ad9951209f16c31bb1..980a4e9f22407d5cf490e2e37996a31e1ffb7c55 100644 --- a/crates/agent_ui/src/inline_assistant.rs +++ b/crates/agent_ui/src/inline_assistant.rs @@ -1999,7 +1999,7 @@ impl CodeActionProvider for AssistantCodeActionProvider { } let multibuffer_snapshot = multibuffer.read(cx); - multibuffer_snapshot.anchor_range_in_excerpt(excerpt_id, action.range) + multibuffer_snapshot.anchor_range_in_buffer(excerpt_id, action.range) }) }) .context("invalid range")?; diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index bf6e753c884fa2434b4fa0f95ff7530cb0ab31bd..38a30f7395fb42d3fd1fda4d02d79dedb8064365 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -1666,7 +1666,7 @@ mod tests { editor.update_in(cx, |editor, window, cx| { let snapshot = editor.buffer().read(cx).snapshot(cx); let range = snapshot - .anchor_range_in_excerpt(excerpt_id, completion.replace_range) + .anchor_range_in_buffer(excerpt_id, completion.replace_range) .unwrap(); editor.edit([(range, completion.new_text)], cx); (completion.confirm.unwrap())(CompletionIntent::Complete, window, cx); diff --git a/crates/agent_ui/src/text_thread_editor.rs b/crates/agent_ui/src/text_thread_editor.rs index 13764bd655c23176b3aa016f36eae193e16f92de..cf6a13161f8f325ddd50e6990410dabe0ba26fed 100644 --- a/crates/agent_ui/src/text_thread_editor.rs +++ b/crates/agent_ui/src/text_thread_editor.rs @@ -740,7 +740,7 @@ impl TextThreadEditor { }; let range = buffer - .anchor_range_in_excerpt(excerpt_id, command.source_range.clone()) + .anchor_range_in_buffer(excerpt_id, command.source_range.clone()) .unwrap(); Crease::inline(range, placeholder, render_toggle, render_trailer) }), @@ -813,7 +813,7 @@ impl TextThreadEditor { let (excerpt_id, _buffer_id, _buffer_snapshot) = buffer.as_singleton().unwrap(); let range = buffer - .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone()) + .anchor_range_in_buffer(excerpt_id, invoked_slash_command.range.clone()) .unwrap(); editor.remove_folds_with_type( &[range], @@ -833,7 +833,7 @@ impl TextThreadEditor { let (excerpt_id, _buffer_id, _buffer_snapshot) = buffer.as_singleton().unwrap(); let context = self.text_thread.downgrade(); let range = buffer - .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone()) + .anchor_range_in_buffer(excerpt_id, invoked_slash_command.range.clone()) .unwrap(); let crease = Crease::inline( range, @@ -875,7 +875,7 @@ impl TextThreadEditor { let mut creases = Vec::new(); for (section, status) in sections { let range = buffer - .anchor_range_in_excerpt(excerpt_id, section.range) + .anchor_range_in_buffer(excerpt_id, section.range) .unwrap(); let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row); buffer_rows_to_fold.insert(buffer_row); @@ -924,7 +924,7 @@ impl TextThreadEditor { let mut creases = Vec::new(); for section in sections { let range = buffer - .anchor_range_in_excerpt(excerpt_id, section.range) + .anchor_range_in_buffer(excerpt_id, section.range) .unwrap(); let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row); buffer_rows_to_fold.insert(buffer_row); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 888a3729ca769551954712dc2e8c3fb197367551..9dc83bc6ff390551a27fdc1d8a83fddeda4f09e6 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1054,9 +1054,9 @@ impl DisplayMap { let base_placeholder = self.fold_placeholder.clone(); let creases = ranges.into_iter().filter_map(|folding_range| { - let mb_range = excerpt_ids.iter().find_map(|&id| { - snapshot.anchor_range_in_excerpt(id, folding_range.range.clone()) - })?; + let mb_range = excerpt_ids + .iter() + .find_map(|&id| snapshot.anchor_range_in_buffer(id, folding_range.range.clone()))?; let placeholder = if let Some(collapsed_text) = folding_range.collapsed_text { FoldPlaceholder { render: Arc::new({ @@ -1989,7 +1989,7 @@ impl DisplaySnapshot { 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)?; + multibuffer.anchor_range_in_buffer(excerpt_id, start_anchor..end_anchor)?; Some(mb_range.start.to_offset(multibuffer)..mb_range.end.to_offset(multibuffer)) }); diff --git a/crates/editor/src/document_colors.rs b/crates/editor/src/document_colors.rs index 579414c7f91c6b2770951a2439599abc4000b27c..c782d2eb656d478fec75fc3b0354259e44a05b63 100644 --- a/crates/editor/src/document_colors.rs +++ b/crates/editor/src/document_colors.rs @@ -254,7 +254,7 @@ impl Editor { buffer_snapshot.clip_point_utf16(color_end, Bias::Right), ); let Some(range) = multi_buffer_snapshot - .anchor_range_in_excerpt(*excerpt_id, start..end) + .anchor_range_in_buffer(*excerpt_id, start..end) else { continue; }; diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 2fe2c838988a9b3d1bce49fad8f92345f20b2bb9..494dc8c3ef2f6860a1ab642f4268f066c2b57c5c 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -8620,7 +8620,7 @@ impl Editor { .into_iter() .flat_map(|(range, new_text)| { Some(( - multibuffer.anchor_range_in_excerpt(excerpt_id, range)?, + multibuffer.anchor_range_in_buffer(excerpt_id, range)?, new_text, )) }) @@ -24114,14 +24114,14 @@ impl Editor { excerpts: excerpts.clone(), }); } - multi_buffer::Event::BuffersRemoved { - ids, - removed_buffer_ids, - } => { + multi_buffer::Event::BuffersRemoved { removed_buffer_ids } => { if let Some(inlay_hints) = &mut self.inlay_hints { inlay_hints.remove_inlay_chunk_data(removed_buffer_ids); } - self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx); + self.refresh_inlay_hints( + InlayHintRefreshReason::ExcerptsRemoved(removed_buffer_ids.clone()), + cx, + ); for buffer_id in removed_buffer_ids { self.registered_buffers.remove(buffer_id); self.tasks @@ -24687,7 +24687,7 @@ impl Editor { let range = buffer_snapshot.anchor_before(range.start) ..buffer_snapshot.anchor_after(range.end); multibuffer_snapshot - .anchor_range_in_excerpt(excerpt_id, range) + .anchor_range_in_buffer(excerpt_id, range) .unwrap() })); }, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index ade457ae058c5a73c5e599026a9fd6cedf0375e2..39f994156070d229ae3457c53442079d6fbdc9bf 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -7888,7 +7888,7 @@ impl EditorElement { let clipped_start = range.start.max(&buffer_range.start, buffer); let clipped_end = range.end.min(&buffer_range.end, buffer); let range = buffer_snapshot - .anchor_range_in_excerpt(excerpt_id, *clipped_start..*clipped_end)?; + .anchor_range_in_buffer(excerpt_id, *clipped_start..*clipped_end)?; let start = range.start.to_display_point(display_snapshot); let end = range.end.to_display_point(display_snapshot); let selection_layout = SelectionLayout { diff --git a/crates/editor/src/hover_links.rs b/crates/editor/src/hover_links.rs index a2f56f625d9553e81c9de4abbe21451982cfd17e..e99e4630fe384f3e3af079b887bcce2cc3638dd6 100644 --- a/crates/editor/src/hover_links.rs +++ b/crates/editor/src/hover_links.rs @@ -365,7 +365,7 @@ pub fn show_link_definition( this.read_with(cx, |_, _| { let range = maybe!({ let range = - snapshot.anchor_range_in_excerpt(excerpt_id, url_range)?; + snapshot.anchor_range_in_buffer(excerpt_id, url_range)?; Some(RangeInEditor::Text(range)) }); (range, vec![HoverLink::Url(url)]) @@ -376,7 +376,7 @@ pub fn show_link_definition( { let range = maybe!({ let range = - snapshot.anchor_range_in_excerpt(excerpt_id, filename_range)?; + snapshot.anchor_range_in_buffer(excerpt_id, filename_range)?; Some(RangeInEditor::Text(range)) }); @@ -390,7 +390,7 @@ pub fn show_link_definition( ( definition_result.iter().find_map(|link| { link.origin.as_ref().and_then(|origin| { - let range = snapshot.anchor_range_in_excerpt( + let range = snapshot.anchor_range_in_buffer( excerpt_id, origin.range.clone(), )?; diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index f5d5e6d5ab69d690bd5f3aee29bf9aa493cf0059..27c092b4c0b204c3bfae0db01201f340fcb1bd30 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -477,7 +477,7 @@ fn show_hover( .and_then(|range| { let range = snapshot .buffer_snapshot() - .anchor_range_in_excerpt(excerpt_id, range)?; + .anchor_range_in_buffer(excerpt_id, range)?; Some(range) }) .or_else(|| { diff --git a/crates/git_ui/src/conflict_view.rs b/crates/git_ui/src/conflict_view.rs index 82571b541e692141f843a4c3ef6e082c72e55e48..16ea6f5c6cf037a495b2e6271544722b4fc03eeb 100644 --- a/crates/git_ui/src/conflict_view.rs +++ b/crates/git_ui/src/conflict_view.rs @@ -237,7 +237,7 @@ fn conflicts_updated( continue; }; let excerpt_id = *excerpt_id; - let Some(range) = snapshot.anchor_range_in_excerpt(excerpt_id, conflict_range) else { + let Some(range) = snapshot.anchor_range_in_buffer(excerpt_id, conflict_range) else { continue; }; removed_highlighted_ranges.push(range.clone()); @@ -324,9 +324,9 @@ fn update_conflict_highlighting( ) -> Option<()> { log::debug!("update conflict highlighting for {conflict:?}"); - let outer = buffer.anchor_range_in_excerpt(excerpt_id, conflict.range.clone())?; - let ours = buffer.anchor_range_in_excerpt(excerpt_id, conflict.ours.clone())?; - let theirs = buffer.anchor_range_in_excerpt(excerpt_id, conflict.theirs.clone())?; + let outer = buffer.anchor_range_in_buffer(excerpt_id, conflict.range.clone())?; + let ours = buffer.anchor_range_in_buffer(excerpt_id, conflict.ours.clone())?; + let theirs = buffer.anchor_range_in_buffer(excerpt_id, conflict.theirs.clone())?; let ours_background = cx.theme().colors().version_control_conflict_marker_ours; let theirs_background = cx.theme().colors().version_control_conflict_marker_theirs; @@ -470,8 +470,7 @@ pub(crate) fn resolve_conflict( }) .ok()?; let &(_, block_id) = &state.block_ids[ix]; - let range = - snapshot.anchor_range_in_excerpt(excerpt_id, resolved_conflict.range)?; + let range = snapshot.anchor_range_in_buffer(excerpt_id, resolved_conflict.range)?; editor.remove_gutter_highlights::(vec![range.clone()], cx); diff --git a/crates/language_tools/src/syntax_tree_view.rs b/crates/language_tools/src/syntax_tree_view.rs index b44d2e05d90733469a5385c2695b3fda3ff47c5e..6a41b26617cf1030d0b57aae1956476fd441bb4a 100644 --- a/crates/language_tools/src/syntax_tree_view.rs +++ b/crates/language_tools/src/syntax_tree_view.rs @@ -361,7 +361,7 @@ impl SyntaxTreeView { let multibuffer = editor_state.editor.read(cx).buffer(); let multibuffer = multibuffer.read(cx).snapshot(cx); let excerpt_id = buffer_state.excerpt_id; - let range = multibuffer.anchor_range_in_excerpt(excerpt_id, range)?; + let range = multibuffer.anchor_range_in_buffer(excerpt_id, range)?; let key = cx.entity_id().as_u64() as usize; // Update the editor with the anchor range. diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index ea622705130615fb5d1a58d9d3fb6b2ac6db1a87..90a4557c8669e2c498de3f6fbda6b55cd6567653 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -95,7 +95,7 @@ impl ExcerptAnchor { self } - fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering { + pub(crate) fn cmp(&self, other: &Self, snapshot: &MultiBufferSnapshot) -> Ordering { let Some(self_path_key) = snapshot.path_keys_by_index.get(&self.path) else { panic!("anchor's path was never added to multibuffer") }; @@ -206,7 +206,7 @@ impl ExcerptAnchor { } fn is_valid(&self, snapshot: &MultiBufferSnapshot) -> bool { - let Some(target) = snapshot.try_anchor_seek_target(*self) else { + let Some(target) = self.try_seek_target(snapshot) else { return false; }; let mut cursor = snapshot.excerpts.cursor::(()); @@ -230,13 +230,21 @@ impl ExcerptAnchor { } pub(crate) fn seek_target(&self, snapshot: &MultiBufferSnapshot) -> AnchorSeekTarget { - let path_key = snapshot.path_for_anchor(*self); + self.try_seek_target(snapshot) + .expect("anchor is from different multi-buffer") + } + + pub(crate) fn try_seek_target( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Option { + let path_key = snapshot.try_path_for_anchor(*self)?; let buffer = snapshot.buffer_for_path(&path_key).cloned(); - AnchorSeekTarget::Excerpt { + Some(AnchorSeekTarget::Excerpt { path_key, anchor: *self, snapshot: buffer, - } + }) } } @@ -338,12 +346,14 @@ impl Anchor { } } - pub(crate) fn try_seek_target(&self, arg: &MultiBufferSnapshot) -> Option { - todo!() + pub(crate) fn try_seek_target( + &self, + snapshot: &MultiBufferSnapshot, + ) -> Option { match self { - Anchor::Min => AnchorSeekTarget::Min, - Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.seek_target(snapshot), - Anchor::Max => AnchorSeekTarget::Max, + Anchor::Min => Some(AnchorSeekTarget::Min), + Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.try_seek_target(snapshot), + Anchor::Max => Some(AnchorSeekTarget::Max), } } } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index f57777d73cb30f3f1c9c0ad1b317412d49ff4a3a..cf9baed833b40754a5b00b04ebe329f8d5618587 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -60,7 +60,7 @@ use text::{ use theme::SyntaxTheme; use ztracing::instrument; -pub use self::path_key::{PathExcerptInsertResult, PathKey}; +pub use self::path_key::PathKey; pub static EXCERPT_CONTEXT_LINES: OnceLock u32> = OnceLock::new(); @@ -96,7 +96,7 @@ pub struct MultiBuffer { pub struct PathKeyIndex(u64); pub struct ExcerptInfo { - path_key: PathKey, + path_key_index: PathKeyIndex, buffer_id: BufferId, range: ExcerptRange, } @@ -168,7 +168,9 @@ impl MultiBufferDiffHunk { } pub type MultiBufferPoint = Point; +/// ExcerptOffset is offset into the non-deleted text of the multibuffer type ExcerptOffset = ExcerptDimension; +/// ExcerptOffset is based on the non-deleted text of the multibuffer type ExcerptPoint = ExcerptDimension; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq, Hash, serde::Deserialize)] @@ -789,7 +791,7 @@ impl ExcerptRange { #[derive(Clone, Debug)] pub struct ExcerptSummary { path_key: PathKey, - max_anchor: Anchor, + max_anchor: text::Anchor, widest_line_number: u32, text: MBTextSummary, count: usize, @@ -799,7 +801,7 @@ impl ExcerptSummary { pub fn min() -> Self { ExcerptSummary { path_key: PathKey::min(), - max_anchor: Anchor::Min, + max_anchor: text::Anchor::MIN, widest_line_number: 0, text: MBTextSummary::default(), count: 0, @@ -1647,13 +1649,21 @@ impl MultiBuffer { } let start = text::Anchor::max( &excerpt.range.context.start, - &selection.start.text_anchor().unwrap_or(text::Anchor::MIN), + &selection + .start + .excerpt_anchor() + .map(|excerpt_anchor| excerpt_anchor.text_anchor()) + .unwrap_or(text::Anchor::MIN), &excerpt.buffer, ) .clone(); let end = text::Anchor::min( &excerpt.range.context.end, - &selection.end.text_anchor().unwrap_or(text::Anchor::MAX), + &selection + .end + .excerpt_anchor() + .map(|excerpt_anchor| excerpt_anchor.text_anchor()) + .unwrap_or(text::Anchor::MAX), &excerpt.buffer, ) .clone(); @@ -1771,11 +1781,7 @@ impl MultiBuffer { } #[ztracing::instrument(skip_all)] - pub fn excerpts_for_buffer( - &self, - buffer_id: BufferId, - cx: &App, - ) -> Vec> { + pub fn excerpts_for_buffer(&self, buffer_id: BufferId, cx: &App) -> Vec { let mut excerpts = Vec::new(); let snapshot = self.read(cx); let Some(path_key) = snapshot.path_keys_by_buffer.get(&buffer_id).cloned() else { @@ -1785,10 +1791,10 @@ impl MultiBuffer { let mut cursor = snapshot.excerpts.cursor::(()); cursor.seek_forward(&path_key, Bias::Left); - while let Some(item) = cursor.item() - && item.path_key == path_key + while let Some(excerpt) = cursor.item() + && excerpt.path_key == path_key { - excerpts.push(item.range.clone()); + excerpts.push(excerpt.info()); cursor.next() } excerpts @@ -1845,18 +1851,14 @@ impl MultiBuffer { let mut cursor = snapshot.cursor::(); cursor.seek(&offset); let excerpt = cursor.excerpt().or(snapshot.excerpts.last())?; - Some(ExcerptInfo { - path_key: excerpt.path_key.clone(), - buffer_id: excerpt.buffer.remote_id(), - range: excerpt.range.clone(), - }) + Some(excerpt.info()) } pub fn buffer_for_anchor(&self, anchor: Anchor, cx: &App) -> Option> { let buffer_id = match anchor { Anchor::Min => self.snapshot(cx).excerpts.first()?.buffer.remote_id(), Anchor::Max => self.snapshot(cx).excerpts.last()?.buffer.remote_id(), - Anchor::Text { buffer_id, .. } => buffer_id, + Anchor::Excerpt(excerpt_anchor) => excerpt_anchor.buffer_id, }; self.buffer(buffer_id) } @@ -1895,22 +1897,32 @@ impl MultiBuffer { ) -> Option { let mut found = None; let snapshot = buffer.read(cx).snapshot(); - for 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 { - found = Some(snapshot.clip_point(point, Bias::Left)); + let text_anchor = snapshot.anchor_after(&point); + for excerpt in self.excerpts_for_buffer(snapshot.remote_id(), cx) { + if excerpt.range.contains(&text_anchor, &snapshot) { + found = Some(Anchor::in_buffer(excerpt.path_key_index, text_anchor)); break; } - if point < start { - found = Some(start); - } - if point > end { - found = Some(end); + if excerpt + .range + .context + .start + .cmp(&text_anchor, &snapshot) + .is_gt() + { + found = Some(Anchor::in_buffer( + excerpt.path_key_index, + excerpt.range.context.start, + )); + break; } + found = Some(Anchor::in_buffer( + excerpt.path_key_index, + excerpt.range.context.end, + )); } - Some(Anchor::text(snapshot.anchor_after(found?))) + found } pub fn buffer_anchor_to_anchor( @@ -1921,9 +1933,9 @@ impl MultiBuffer { cx: &App, ) -> Option { let snapshot = buffer.read(cx).snapshot(); - for range in self.excerpts_for_buffer(snapshot.remote_id(), cx) { - if range.contains(&anchor, &snapshot) { - return Some(Anchor::text(anchor)); + for excerpt in self.excerpts_for_buffer(snapshot.remote_id(), cx) { + if excerpt.range.contains(&anchor, &snapshot) { + return Some(Anchor::in_buffer(excerpt.path_key_index, anchor)); } } @@ -5377,12 +5389,15 @@ impl MultiBufferSnapshot { /// Returns an anchor for the given excerpt and text anchor, /// Returns [`None`] if the excerpt_id is no longer valid or the text anchor range is out of excerpt's bounds. - pub fn anchor_range_in_excerpt( + pub fn anchor_range_in_buffer( &self, - excerpt_id: ExcerptId, + excerpt_id: BufferId, text_anchor: Range, ) -> Option> { - let excerpt = self.excerpt(self.latest_excerpt_id(excerpt_id))?; + let path_key_index = self.first_excerpt_for_buffer(buffer_id)?.path_key_index; + + let target = self.anchor_see + self.excerpts.cursor.find(&snapshot, (path_key_index, anchor)) Some( Self::anchor_in_excerpt_(excerpt, text_anchor.start)? @@ -6293,17 +6308,17 @@ impl MultiBufferSnapshot { .flat_map(|item| { Some(OutlineItem { depth: item.depth, - range: self.anchor_range_in_excerpt(excerpt_id, item.range)?, + range: self.anchor_range_in_buffer(excerpt_id, item.range)?, source_range_for_text: self - .anchor_range_in_excerpt(excerpt_id, item.source_range_for_text)?, + .anchor_range_in_buffer(excerpt_id, item.source_range_for_text)?, text: item.text, highlight_ranges: item.highlight_ranges, name_ranges: item.name_ranges, body_range: item.body_range.and_then(|body_range| { - self.anchor_range_in_excerpt(excerpt_id, body_range) + self.anchor_range_in_buffer(excerpt_id, body_range) }), annotation_range: item.annotation_range.and_then(|annotation_range| { - self.anchor_range_in_excerpt(excerpt_id, annotation_range) + self.anchor_range_in_buffer(excerpt_id, annotation_range) }), }) }) @@ -6371,6 +6386,14 @@ impl MultiBufferSnapshot { self.path_keys_by_buffer.get(&buffer_id) } + pub fn first_excerpt_for_buffer(&self, buffer_id: BufferId) -> Option<&Excerpt> { + let path_key = self.path_keys_by_buffer.get(&buffer_id)?; + let (_, _, first_excerpt) = + self.excerpts + .find::((), path_key, Bias::Left); + first_excerpt + } + pub fn buffer_for_id(&self, id: BufferId) -> Option<&BufferSnapshot> { self.buffer_for_path(self.path_keys_by_buffer.get(&id)?) } @@ -6609,11 +6632,15 @@ impl MultiBufferSnapshot { excerpt_edits } - fn excerpt_ranges_for_path( - &self, - path_key: &PathKey, - ) -> impl Iterator> { - todo!() + fn excerpts_for_path<'a>( + &'a self, + path_key: &'a PathKey, + ) -> impl Iterator + 'a { + let mut cursor = self.excerpts.cursor::(()); + cursor.seek(path_key, Bias::Left); + cursor + .take_while(move |item| &item.path_key == path_key) + .map(|excerpt| excerpt.info()) } } @@ -6632,27 +6659,46 @@ impl MultiBufferSnapshot { #[cfg(any(test, feature = "test-support"))] fn check_invariants(&self) { let excerpts = self.excerpts.items(()); - let excerpt_ids = self.excerpt_ids.items(()); - for (ix, excerpt) in excerpts.iter().enumerate() { - if ix == 0 { - if excerpt.locator <= Locator::min() { - panic!("invalid first excerpt locator {:?}", excerpt.locator); - } - } else if excerpt.locator <= excerpts[ix - 1].locator { - panic!("excerpts are out-of-order: {:?}", excerpts); - } - } + let all_buffer_path_keys = HashSet::from_iter(self.path_keys_by_buffer.values().cloned()); + let all_excerpt_path_keys = HashSet::from_iter(excerpts.iter().map(|e| e.path_key.clone())); - for (ix, entry) in excerpt_ids.iter().enumerate() { - if ix == 0 { - if entry.id.cmp(&ExcerptId::min(), self).is_le() { - panic!("invalid first excerpt id {:?}", entry.id); + for (ix, excerpt) in excerpts.iter().enumerate() { + if ix > 0 { + let prev = &excerpts[ix - 1]; + + if excerpt.path_key < prev.path_key { + panic!("excerpt path_keys are out-of-order: {:?}", excerpts); + } else if excerpt.path_key == prev.path_key { + if excerpt + .start_anchor() + .cmp(&prev.end_anchor(), &self) + .is_le() + { + panic!("excerpt anchors are out-of-order: {:?}", excerpts); + } + if excerpt + .start_anchor() + .cmp(&excerpt.end_anchor(), &self) + .is_ge() + { + panic!("excerpt with backward range: {:?}", excerpts); + } } - } else if entry.id <= excerpt_ids[ix - 1].id { - panic!("excerpt ids are out-of-order: {:?}", excerpt_ids); } + assert!( + all_buffer_path_keys.contains(&excerpt.path_key), + "excerpt path key not found in active path keys: {:?}", + excerpt.path_key + ); + assert_eq!( + self.path_keys_by_index.get(&excerpt.path_key_index), + Some(&excerpt.path_key), + "excerpt path key index does not match path key: {:?}", + excerpt.path_key, + ); } + assert_eq!(all_buffer_path_keys, all_excerpt_path_keys); if self.diff_transforms.summary().input != self.excerpts.summary().text { panic!( @@ -7009,12 +7055,14 @@ where impl Excerpt { fn new( path_key: PathKey, + path_key_index: PathKeyIndex, buffer: Arc, range: ExcerptRange, has_trailing_newline: bool, ) -> Self { Excerpt { path_key, + path_key_index, max_buffer_row: range.context.end.to_point(&buffer).row, text_summary: buffer .text_summary_for_range::(range.context.to_offset(&buffer)), @@ -7024,6 +7072,14 @@ impl Excerpt { } } + fn info(&self) -> ExcerptInfo { + ExcerptInfo { + path_key_index: self.path_key_index, + buffer_id: self.buffer.remote_id(), + range: self.range.clone(), + } + } + fn chunks_in_range(&self, range: Range, language_aware: bool) -> ExcerptChunks<'_> { let content_start = self.range.context.start.to_offset(&self.buffer); let chunks_start = content_start + range.start; @@ -7247,7 +7303,7 @@ impl sum_tree::Item for Excerpt { text += TextSummary::from("\n"); } ExcerptSummary { - path_key: self.path_key, + path_key: self.path_key.clone(), max_anchor: self.range.context.end, widest_line_number: self.max_buffer_row, text: text.into(), diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index ee7fdec006d52ad3712cb27405ffad000433c822..7b60f562d8be20cd7755d63f0aee3498c128026b 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -3533,8 +3533,8 @@ fn test_history(cx: &mut App) { assert_eq!( multibuffer.edited_ranges_for_transaction(transaction_1, cx), &[ - Point::new(0, 0)..Point::new(0, 2), - Point::new(1, 0)..Point::new(1, 2) + MultiBufferOffset(0)..MultiBufferOffset(2), + MultiBufferOffset(7)..MultiBufferOffset(9), ] ); diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 7088eebbecf48b0e3c4a19337d623d48a2cfb6e6..1f7ba38fc3d17db634fa20b9cf096410a0e396c3 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -5,20 +5,17 @@ use std::{ sync::Arc, }; -use collections::HashSet; use gpui::{App, AppContext, Context, Entity}; -use itertools::Itertools; use language::{Buffer, BufferSnapshot}; use rope::Point; use sum_tree::{Dimensions, SumTree}; -use text::{Bias, BufferId, Edit, OffsetRangeExt, Patch}; +use text::{Bias, Edit, OffsetRangeExt, Patch}; use util::rel_path::RelPath; use ztracing::instrument; use crate::{ Anchor, BufferState, DiffChangeKind, Event, Excerpt, ExcerptOffset, ExcerptRange, - ExcerptSummary, ExpandExcerptDirection, MultiBuffer, MultiBufferOffset, PathKeyIndex, - build_excerpt_ranges, + ExcerptSummary, ExpandExcerptDirection, MultiBuffer, PathKeyIndex, build_excerpt_ranges, }; #[derive(PartialEq, Eq, Ord, PartialOrd, Clone, Hash, Debug)] @@ -64,31 +61,19 @@ impl PathKey { } impl MultiBuffer { - pub fn paths(&self) -> impl Iterator + '_ { - self.excerpts_by_path.keys() - } - - pub fn excerpts_for_path(&self, path: &PathKey) -> impl '_ + Iterator { - self.excerpts_by_path - .get(path) - .map(|excerpts| excerpts.as_slice()) - .unwrap_or_default() - .iter() - .copied() - } - pub fn buffer_for_path(&self, path: &PathKey, cx: &App) -> Option> { - let excerpt_id = self.excerpts_by_path.get(path)?.first()?; - let snapshot = self.read(cx); - let excerpt = snapshot.excerpt(*excerpt_id)?; + let snapshot = self.snapshot(cx); + let excerpt = snapshot.excerpts_for_path(path).next()?; self.buffer(excerpt.buffer_id) } pub fn location_for_path(&self, path: &PathKey, cx: &App) -> Option { - let excerpt_id = self.excerpts_by_path.get(path)?.first()?; - let snapshot = self.read(cx); - let excerpt = snapshot.excerpt(*excerpt_id)?; - Some(Anchor::text(excerpt.id, excerpt.range.context.start)) + let snapshot = self.snapshot(cx); + let excerpt = snapshot.excerpts_for_path(path).next()?; + Some(Anchor::in_buffer( + excerpt.path_key_index, + excerpt.range.context.start, + )) } pub fn set_excerpts_for_buffer( @@ -118,12 +103,14 @@ impl MultiBuffer { build_excerpt_ranges(ranges.clone(), context_line_count, &buffer_snapshot); let (new, _) = Self::merge_excerpt_ranges(&excerpt_ranges); - let inserted = + let (inserted, path_key_index) = self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, new, cx); // todo!() move this into the callers that care let anchors = ranges .into_iter() - .map(|range| Anchor::range_in_buffer(buffer_snapshot.anchor_range_around(range))) + .map(|range| { + Anchor::range_in_buffer(path_key_index, buffer_snapshot.anchor_range_around(range)) + }) .collect::>(); (anchors, inserted) } @@ -137,13 +124,16 @@ impl MultiBuffer { cx: &mut Context, ) -> (Vec>, bool) { let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges); - let inserted = + let (inserted, path_key_index) = self.set_merged_excerpt_ranges_for_path(path, buffer, buffer_snapshot, new, cx); // todo!() move this into the callers that care let anchors = excerpt_ranges .into_iter() .map(|range| { - Anchor::range_in_buffer(buffer_snapshot.anchor_range_around(range.primary)) + Anchor::range_in_buffer( + path_key_index, + buffer_snapshot.anchor_range_around(range.primary), + ) }) .collect::>(); (anchors, inserted) @@ -174,7 +164,7 @@ impl MultiBuffer { multi_buffer .update(&mut app, move |multi_buffer, cx| { - multi_buffer.set_merged_excerpt_ranges_for_path( + let (_, path_key_index) = multi_buffer.set_merged_excerpt_ranges_for_path( path_key, buffer, &buffer_snapshot, @@ -183,7 +173,7 @@ impl MultiBuffer { ); ranges .into_iter() - .map(|range| Anchor::range_in_buffer(range)) + .map(|range| Anchor::range_in_buffer(path_key_index, range)) .collect() }) .ok() @@ -199,7 +189,10 @@ impl MultiBuffer { cx: &mut Context, ) { let snapshot = self.snapshot(cx); - let mut sorted_anchors = anchors.into_iter().collect::>(); + let mut sorted_anchors = anchors + .into_iter() + .filter_map(|anchor| anchor.excerpt_anchor()) + .collect::>(); sorted_anchors.sort_by(|a, b| a.cmp(b, &snapshot)); let mut cursor = snapshot.excerpts.cursor::(()); let mut sorted_anchors = sorted_anchors.into_iter().peekable(); @@ -217,9 +210,7 @@ impl MultiBuffer { && snapshot.path_for_anchor(anchor) == path { sorted_anchors.next(); - let Some(target) = snapshot.anchor_seek_target(anchor) else { - continue; - }; + let target = anchor.seek_target(&snapshot); // Move to the next excerpt to be expanded, and push unchanged ranges for intervening excerpts expanded_ranges.extend( cursor @@ -295,7 +286,7 @@ impl MultiBuffer { buffer_snapshot: &BufferSnapshot, new: Vec>, cx: &mut Context, - ) -> bool { + ) -> (bool, PathKeyIndex) { let anchor_ranges = new .into_iter() .map(|r| ExcerptRange { @@ -306,6 +297,29 @@ impl MultiBuffer { self.update_path_excerpts(path, buffer, buffer_snapshot, &anchor_ranges, cx) } + fn get_or_create_path_key_index(&mut self, path_key: &PathKey) -> PathKeyIndex { + let mut snapshot = self.snapshot.borrow_mut(); + let existing = snapshot + .path_keys_by_index + .iter() + // todo!() perf? (but ExcerptIdMapping was doing this) + .find(|(_, existing_path)| existing_path == &path_key) + .map(|(index, _)| *index); + + if let Some(existing) = existing { + return existing; + } + + let index = snapshot + .path_keys_by_index + .last() + .map(|(index, _)| PathKeyIndex(index.0 + 1)) + .unwrap_or(PathKeyIndex(0)); + snapshot.path_keys_by_index.insert(index, path_key.clone()); + index + } + + // todo!() re-instate nonshrinking version for project diff / diagnostics pub fn update_path_excerpts<'a>( &mut self, path_key: PathKey, @@ -313,25 +327,27 @@ impl MultiBuffer { buffer_snapshot: &BufferSnapshot, to_insert: &Vec>, cx: &mut Context, - ) -> bool { + ) -> (bool, PathKeyIndex) { + let path_key_index = self.get_or_create_path_key_index(&path_key); + if let Some(old_path_key) = self + .snapshot(cx) + .path_for_buffer(buffer_snapshot.remote_id()) + && old_path_key != &path_key + { + self.remove_excerpts_for_path(old_path_key.clone(), cx); + } + if to_insert.len() == 0 { self.remove_excerpts_for_path(path_key.clone(), cx); - if let Some(old_path_key) = self - .snapshot(cx) - .path_for_buffer(buffer_snapshot.remote_id()) - && old_path_key != &path_key - { - self.remove_excerpts_for_path(old_path_key.clone(), cx); - } - return false; + return (false, path_key_index); } assert_eq!(self.history.transaction_depth(), 0); self.sync_mut(cx); let buffer_snapshot = buffer.read(cx).snapshot(); let buffer_id = buffer_snapshot.remote_id(); - let buffer_state = self.buffers.entry(buffer_id).or_insert_with(|| { + self.buffers.entry(buffer_id).or_insert_with(|| { self.buffer_changed_since_sync.replace(true); buffer.update(cx, |buffer, _| { buffer.record_changes(Rc::downgrade(&self.buffer_changed_since_sync)); @@ -359,39 +375,6 @@ impl MultiBuffer { let mut patch = Patch::empty(); let mut added_new_excerpt = false; - let path_key_index = snapshot - .path_keys_by_index - .iter() - // todo!() perf? (but ExcerptIdMapping was doing this) - .find(|(_, existing_path)| existing_path == &&path_key) - .map(|(index, _)| *index); - let path_key_index = path_key_index.unwrap_or_else(|| { - let index = snapshot - .path_keys_by_index - .last() - .map(|(index, _)| PathKeyIndex(index.0 + 1)) - .unwrap_or(PathKeyIndex(0)); - snapshot.path_keys_by_index.insert(index, path_key.clone()); - index - }); - let old_path_key = snapshot - .path_keys_by_buffer - .insert_or_replace(buffer_id, path_key.clone()); - // handle the case where the buffer's path key has changed by - // removing any old excerpts for the buffer - if let Some(old_path_key) = &old_path_key - && old_path_key < &path_key - { - new_excerpts.append(cursor.slice(old_path_key, Bias::Left), ()); - let before = cursor.position.1; - cursor.seek_forward(old_path_key, Bias::Right); - let after = cursor.position.1; - patch.push(Edit { - old: before..after, - new: new_excerpts.summary().len()..new_excerpts.summary().len(), - }); - } - new_excerpts.append(cursor.slice(&path_key, Bias::Left), ()); // handle the case where the path key used to be associated @@ -417,7 +400,7 @@ impl MultiBuffer { let Some(next_excerpt) = to_insert.peek() else { break; }; - if &&excerpt.range == next_excerpt { + if &excerpt.range == *next_excerpt { new_excerpts.push(excerpt.clone(), ()); to_insert.next(); cursor.next(); @@ -447,6 +430,7 @@ impl MultiBuffer { new_excerpts.push( Excerpt::new( path_key.clone(), + path_key_index, buffer_snapshot.clone(), next_excerpt.clone(), to_insert.peek().is_some(), @@ -470,28 +454,9 @@ impl MultiBuffer { new: new_excerpts.summary().len()..new_excerpts.summary().len(), }); - // handle the case where the buffer's path key has changed by - // removing any old excerpts for the buffer - if let Some(old_path_key) = &old_path_key - && old_path_key > &path_key - { - new_excerpts.append(cursor.slice(old_path_key, Bias::Left), ()); - let before = cursor.position.1; - cursor.seek_forward(old_path_key, Bias::Right); - let after = cursor.position.1; - patch.push(Edit { - old: before..after, - new: new_excerpts.summary().len()..new_excerpts.summary().len(), - }); - } - let suffix = cursor.suffix(); let changed_trailing_excerpt = suffix.is_empty(); new_excerpts.append(suffix, ()); - let new_ranges = snapshot - .excerpt_ranges_for_path(&path_key) - .map(|range| range.context) - .collect(); drop(cursor); snapshot.excerpts = new_excerpts; if changed_trailing_excerpt { @@ -513,61 +478,56 @@ impl MultiBuffer { cx.emit(Event::BufferUpdated { buffer, path_key: path_key.clone(), - ranges: new_ranges, + ranges: to_insert.map(|range| range.context.clone()).collect(), }); cx.notify(); - added_new_excerpt + (added_new_excerpt, path_key_index) } pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context) { - let mut patch = Patch::empty(); + assert_eq!(self.history.transaction_depth(), 0); + self.sync_mut(cx); let mut snapshot = self.snapshot.get_mut(); let mut cursor = snapshot .excerpts .cursor::>(()); let mut new_excerpts = SumTree::new(()); - - if let Some(old_path_key) = old_path_key - && old_path_key < path_key + new_excerpts.append(cursor.slice(&path, Bias::Left), ()); + let edit_start = cursor.position.1; + let mut buffer_id = None; + if let Some(excerpt) = cursor.item() + && excerpt.path_key == path { - new_excerpts.append(cursor.slice(&old_path_key, Bias::Left), ()); - let before = cursor.position.1; - cursor.seek_forward(&old_path_key, Bias::Right); - let after = cursor.position.1; - patch.push(Edit { - old: before..after, - new: new_excerpts.summary().len()..new_excerpts.summary().len(), - }); + buffer_id = Some(excerpt.buffer.remote_id()); } - + cursor.seek(&path, Bias::Right); + let edit_end = cursor.position.1; let suffix = cursor.suffix(); let changed_trailing_excerpt = suffix.is_empty(); new_excerpts.append(suffix, ()); - for buffer_id in removed_excerpts_for_buffers { - match self.buffers.get(&buffer_id) { - Some(buffer_state) => { - snapshot - .buffer_locators - .insert(buffer_id, buffer_state.excerpts.iter().cloned().collect()); - } - None => { - snapshot.buffer_locators.remove(&buffer_id); - } - } + let edit = Edit { + old: edit_start..edit_end, + new: edit_start..edit_start, + }; + + if let Some(buffer_id) = buffer_id { + snapshot.path_keys_by_buffer.remove(&buffer_id); + self.buffers.remove(&buffer_id); + cx.emit(Event::BuffersRemoved { + removed_buffer_ids: vec![buffer_id], + }) } + drop(cursor); snapshot.excerpts = new_excerpts; if changed_trailing_excerpt { snapshot.trailing_excerpt_update_count += 1; } - let edits = Self::sync_diff_transforms( - &mut snapshot, - patch.into_inner(), - DiffChangeKind::BufferEdited, - ); + let edits = + Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited); if !edits.is_empty() { self.subscriptions.publish(edits); } @@ -575,12 +535,6 @@ impl MultiBuffer { cx.emit(Event::Edited { edited_buffer: None, }); - // todo!() is this right? different event? - cx.emit(Event::BufferUpdated { - buffer, - path_key: path.clone(), - ranges: Vec::new(), - }); cx.notify(); } } diff --git a/crates/multi_buffer/src/transaction.rs b/crates/multi_buffer/src/transaction.rs index a65e394c8f1834a95ccbc70532aa03d2a3e6e34c..e95bc4630b92c8b1b90137c4bda2a2a1555c3ca3 100644 --- a/crates/multi_buffer/src/transaction.rs +++ b/crates/multi_buffer/src/transaction.rs @@ -2,15 +2,15 @@ use gpui::{App, Context, Entity}; use language::{self, Buffer, TransactionId}; use std::{ collections::HashMap, - ops::{AddAssign, Range, Sub}, + ops::Range, time::{Duration, Instant}, }; use sum_tree::Bias; use text::BufferId; -use crate::{BufferState, MultiBufferDimension}; +use crate::{Anchor, BufferState, MultiBufferOffset}; -use super::{Event, ExcerptSummary, MultiBuffer}; +use super::{Event, MultiBuffer}; #[derive(Clone)] pub(super) struct History { @@ -314,71 +314,49 @@ impl MultiBuffer { } } - pub fn edited_ranges_for_transaction( + pub fn edited_ranges_for_transaction( &self, transaction_id: TransactionId, cx: &App, - ) -> Vec> - where - D: MultiBufferDimension - + Ord - + Sub - + AddAssign, - D::TextDimension: PartialOrd + Sub, - { + ) -> Vec> { let Some(transaction) = self.history.transaction(transaction_id) else { return Vec::new(); }; - let mut ranges = Vec::new(); let snapshot = self.read(cx); - let mut cursor = snapshot.excerpts.cursor::(()); + let mut buffer_anchors = Vec::new(); for (buffer_id, buffer_transaction) in &transaction.buffer_transactions { - let Some(buffer_state) = self.buffers.get(buffer_id) else { + let Some(buffer) = self.buffer(*buffer_id) else { + continue; + }; + let Some(excerpt) = snapshot.first_excerpt_for_buffer(*buffer_id) else { continue; }; - let buffer = buffer_state.buffer.read(cx); - for range in - buffer.edited_ranges_for_transaction_id::(*buffer_transaction) + for range in buffer + .read(cx) + .edited_ranges_for_transaction_id::(*buffer_transaction) { - for excerpt_id in &buffer_state.excerpts { - cursor.seek(excerpt_id, Bias::Left); - if let Some(excerpt) = cursor.item() - && excerpt.locator == *excerpt_id - { - let excerpt_buffer_start = excerpt - .range - .context - .start - .summary::(buffer); - let excerpt_buffer_end = excerpt - .range - .context - .end - .summary::(buffer); - let excerpt_range = excerpt_buffer_start..excerpt_buffer_end; - if excerpt_range.contains(&range.start) - && excerpt_range.contains(&range.end) - { - let excerpt_start = D::from_summary(&cursor.start().text); - - let mut start = excerpt_start; - start += range.start - excerpt_buffer_start; - let mut end = excerpt_start; - end += range.end - excerpt_buffer_start; - - ranges.push(start..end); - break; - } - } - } + buffer_anchors.push(Anchor::in_buffer( + excerpt.path_key_index, + excerpt.buffer.anchor_at(range.start, Bias::Left), + )); + buffer_anchors.push(Anchor::in_buffer( + excerpt.path_key_index, + excerpt.buffer.anchor_at(range.end, Bias::Right), + )); } } + buffer_anchors.sort_unstable_by(|a, b| a.cmp(b, &snapshot)); - ranges.sort_by_key(|range| range.start); - ranges + snapshot + .summaries_for_anchors(buffer_anchors.iter()) + .as_chunks::<2>() + .0 + .iter() + .map(|&[s, e]| s..e) + .collect::>() } pub fn merge_transactions( diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 91b0f2e6d45d87170c5bb8ecda47df3e1a64626e..d2457471280e9d2fc02bb51a7fa67b11f1e5b01c 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -3332,7 +3332,7 @@ impl OutlinePanel { .flat_map(|excerpt| excerpt.iter_outlines()) .flat_map(|outline| { let range = multi_buffer_snapshot - .anchor_range_in_excerpt(excerpt_id, outline.range.clone())?; + .anchor_range_in_buffer(excerpt_id, outline.range.clone())?; Some(( range.start.to_display_point(&editor_snapshot) ..range.end.to_display_point(&editor_snapshot),