From 68e02d892cda4d5ba9637bcddebedf369e2d26d8 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 4 Mar 2026 17:39:35 -0500 Subject: [PATCH] wip Co-authored-by: Conrad Irwin --- crates/action_log/src/action_log.rs | 10 ++-- crates/agent_ui/src/message_editor.rs | 2 +- crates/buffer_diff/src/buffer_diff.rs | 6 +-- crates/edit_prediction/src/capture_example.rs | 2 +- crates/edit_prediction/src/edit_prediction.rs | 10 +++- crates/edit_prediction/src/udiff.rs | 3 +- crates/editor/src/code_completion_tests.rs | 4 +- crates/editor/src/editor_tests.rs | 2 +- crates/git_ui/src/commit_view.rs | 10 +++- crates/git_ui/src/project_diff.rs | 4 +- crates/language/src/diagnostic_set.rs | 22 +++----- crates/language/src/proto.rs | 4 +- crates/language/src/syntax_map.rs | 30 +++++------ crates/multi_buffer/src/anchor.rs | 53 ++++++------------- crates/multi_buffer/src/multi_buffer.rs | 51 +++++++++--------- crates/multi_buffer/src/multi_buffer_tests.rs | 8 +++ crates/multi_buffer/src/path_key.rs | 3 +- .../tests/integration/project_tests.rs | 33 +++++++----- crates/text/src/anchor.rs | 31 ++--------- crates/text/src/text.rs | 23 ++++---- 20 files changed, 147 insertions(+), 164 deletions(-) diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs index 1157d8d6f881ecb33df8104dd4be04bd9d846b5e..5cf7aaa2a17b0ab3a95019b3a52da9010edc576c 100644 --- a/crates/action_log/src/action_log.rs +++ b/crates/action_log/src/action_log.rs @@ -624,6 +624,7 @@ impl ActionLog { let task = if let Some(existing_file_content) = existing_file_content { // Capture the agent's content before restoring existing file content let agent_content = buffer.read(cx).text(); + let buffer_id = buffer.read(cx).remote_id(); buffer.update(cx, |buffer, cx| { buffer.start_transaction(); @@ -636,7 +637,10 @@ impl ActionLog { undo_info = Some(PerBufferUndo { buffer: buffer.downgrade(), - edits_to_restore: vec![(Anchor::MIN..Anchor::MAX, agent_content)], + edits_to_restore: vec![( + Anchor::min_for_buffer(buffer_id)..Anchor::max_for_buffer(buffer_id), + agent_content, + )], status: UndoBufferStatus::Created { had_existing_content: true, }, @@ -876,8 +880,8 @@ impl ActionLog { let mut valid_edits = Vec::new(); for (anchor_range, text_to_restore) in per_buffer_undo.edits_to_restore { - if anchor_range.start.buffer_id == Some(buffer.remote_id()) - && anchor_range.end.buffer_id == Some(buffer.remote_id()) + if anchor_range.start.buffer_id == buffer.remote_id() + && anchor_range.end.buffer_id == buffer.remote_id() { valid_edits.push((anchor_range, text_to_restore)); } diff --git a/crates/agent_ui/src/message_editor.rs b/crates/agent_ui/src/message_editor.rs index 38a30f7395fb42d3fd1fda4d02d79dedb8064365..0e2ceb461ee0c32c958163e1a2c1ab2363722a44 100644 --- a/crates/agent_ui/src/message_editor.rs +++ b/crates/agent_ui/src/message_editor.rs @@ -1645,7 +1645,7 @@ mod tests { completion_provider.completions( excerpt_id, &buffer, - text::Anchor::MAX, + text::Anchor::max_for_buffer(buffer.read(cx).remote_id()), CompletionContext { trigger_kind: CompletionTriggerKind::TRIGGER_CHARACTER, trigger_character: Some("@".into()), diff --git a/crates/buffer_diff/src/buffer_diff.rs b/crates/buffer_diff/src/buffer_diff.rs index f68bebdff15693c27718e3d49c09bf6cdfc8b056..d5de7f759c3368f956ce2a156bd44cc3305545ff 100644 --- a/crates/buffer_diff/src/buffer_diff.rs +++ b/crates/buffer_diff/src/buffer_diff.rs @@ -158,9 +158,9 @@ impl sum_tree::Item for PendingHunk { impl sum_tree::Summary for DiffHunkSummary { type Context<'a> = &'a text::BufferSnapshot; - fn zero(_cx: Self::Context<'_>) -> Self { + fn zero(buffer: &text::BufferSnapshot) -> Self { DiffHunkSummary { - buffer_range: Anchor::MIN..Anchor::MIN, + buffer_range: Anchor::min_min_range_for_buffer(buffer.remote_id()), diff_base_byte_range: 0..0, } } @@ -1614,7 +1614,7 @@ impl BufferDiff { ) { let hunks = self .snapshot(cx) - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer) + .hunks_intersecting_range(Anchor::min_max_range_for_buffer(buffer.remote_id()), buffer) .collect::>(); let Some(secondary) = self.secondary_diff.clone() else { return; diff --git a/crates/edit_prediction/src/capture_example.rs b/crates/edit_prediction/src/capture_example.rs index 0fbece7478068d26c0c1a8accf7e93aba8c83b9c..b20696c06d1341624979627206dbf4f992ed5de9 100644 --- a/crates/edit_prediction/src/capture_example.rs +++ b/crates/edit_prediction/src/capture_example.rs @@ -400,7 +400,7 @@ mod tests { capture_example( project.clone(), buffer.clone(), - Anchor::MIN, + Anchor::min_for_buffer(buffer.read(cx).remote_id()), events, true, cx, diff --git a/crates/edit_prediction/src/edit_prediction.rs b/crates/edit_prediction/src/edit_prediction.rs index 78f42db2120b45f04dbf83c5e706a42163ee8067..221a3c2dd16a64588e0cf1622770fe062a167637 100644 --- a/crates/edit_prediction/src/edit_prediction.rs +++ b/crates/edit_prediction/src/edit_prediction.rs @@ -1995,7 +1995,10 @@ impl EditPredictionStore { cx: &mut AsyncApp, ) -> Result, language::Anchor)>> { let collaborator_cursor_rows: Vec = active_buffer_snapshot - .selections_in_range(Anchor::MIN..Anchor::MAX, false) + .selections_in_range( + Anchor::min_max_range_for_buffer(active_buffer_snapshot.remote_id()), + false, + ) .flat_map(|(_, _, _, selections)| { selections.map(|s| s.head().to_point(active_buffer_snapshot).row) }) @@ -2071,7 +2074,10 @@ impl EditPredictionStore { candidate_buffer.read_with(cx, |buffer, _cx| { let snapshot = buffer.snapshot(); let has_collaborators = snapshot - .selections_in_range(Anchor::MIN..Anchor::MAX, false) + .selections_in_range( + Anchor::max_max_range_for_buffer(snapshot.remote_id()), + false, + ) .next() .is_some(); let position = buffer diff --git a/crates/edit_prediction/src/udiff.rs b/crates/edit_prediction/src/udiff.rs index e1e475c85526befb5549571cf7b7a2e1ef10c3d8..7f770bd8162b1d8756d6ffba7e0c9e5a819c8001 100644 --- a/crates/edit_prediction/src/udiff.rs +++ b/crates/edit_prediction/src/udiff.rs @@ -54,7 +54,6 @@ pub async fn apply_diff( let mut included_files: HashMap> = HashMap::default(); - let ranges = [Anchor::MIN..Anchor::MAX]; let mut diff = DiffParser::new(diff_str); let mut current_file = None; let mut edits: Vec<(std::ops::Range, Arc)> = vec![]; @@ -115,7 +114,7 @@ pub async fn apply_diff( edits.extend(resolve_hunk_edits_in_buffer( hunk, buffer, - ranges.as_slice(), + &[Anchor::min_max_range_for_buffer(buffer.remote_id())], status, )?); anyhow::Ok(()) diff --git a/crates/editor/src/code_completion_tests.rs b/crates/editor/src/code_completion_tests.rs index 4602824486ebb88f78ed529abb91ddcc1c34646f..a127306975daaeb9f477a1b55c413bffa794b98e 100644 --- a/crates/editor/src/code_completion_tests.rs +++ b/crates/editor/src/code_completion_tests.rs @@ -7,7 +7,7 @@ use project::{Completion, CompletionSource}; use settings::SnippetSortOrder; use std::sync::Arc; use std::sync::atomic::AtomicBool; -use text::Anchor; +use text::{Anchor, BufferId}; #[gpui::test] async fn test_sort_kind(cx: &mut TestAppContext) { @@ -393,7 +393,7 @@ impl CompletionBuilder { kind: Option, ) -> Completion { Completion { - replace_range: Anchor::MIN..Anchor::MAX, + replace_range: Anchor::min_max_range_for_buffer(BufferId::new(1)), new_text: label.to_string(), label: CodeLabel::plain(label.to_string(), filter_text), documentation: None, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 9a0033306a1032786535a188a9ea830cb44c3ca3..330ff08067cfc3ef392433ae2ca7b9c49be4aa70 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -23412,7 +23412,7 @@ async fn test_display_diff_hunks(cx: &mut TestAppContext) { multibuffer.set_excerpts_for_path( PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()), buffer.clone(), - vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)], + vec![Point::zero()..snapshot.max_point()], 2, cx, ); diff --git a/crates/git_ui/src/commit_view.rs b/crates/git_ui/src/commit_view.rs index 8f2a019fddf0513c100a53956c81012d11c2ca30..688717005325a96c0d82bd73aa481a7e49075342 100644 --- a/crates/git_ui/src/commit_view.rs +++ b/crates/git_ui/src/commit_view.rs @@ -223,7 +223,11 @@ impl CommitView { editor .buffer() .read(cx) - .buffer_anchor_to_anchor(&message_buffer, Anchor::MAX, cx) + .buffer_anchor_to_anchor( + &message_buffer, + Anchor::max_for_buffer(&message_buffer.read(cx).remote_id()), + cx, + ) .map(|anchor| BlockProperties { placement: BlockPlacement::Below(anchor), height: Some(1), @@ -431,7 +435,9 @@ impl CommitView { let base_text = diff.base_text(); - for hunk in diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer) { + for hunk in + diff.hunks_intersecting_range(Anchor::min_max_range_for_buffer(buffer_id), buffer) + { let added_rows = hunk.range.end.row.saturating_sub(hunk.range.start.row); total_additions += added_rows; diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index f62b08e4c0d99db7d2e60e6aac730a69b139cca3..77b15a8195ec73af565ec1a73dbae43ebc1dec84 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -559,7 +559,9 @@ impl ProjectDiff { let base_text = diff.base_text(); - for hunk in diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, buffer) { + for hunk in + diff.hunks_intersecting_range(Anchor::min_max_range_for_buffer(buffer_id), buffer) + { let added_rows = hunk.range.end.row.saturating_sub(hunk.range.start.row); total_additions += added_rows; diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index fa3263df48ff773b32332980e7341fa8a453ba4f..04564ecd6575f9470315e0571a60126c69d81d2b 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -326,23 +326,17 @@ impl DiagnosticEntry { } } -impl Default for Summary { - fn default() -> Self { - Self { - start: Anchor::MIN, - end: Anchor::MAX, - min_start: Anchor::MAX, - max_end: Anchor::MIN, - count: 0, - } - } -} - impl sum_tree::Summary for Summary { type Context<'a> = &'a text::BufferSnapshot; - fn zero(_cx: Self::Context<'_>) -> Self { - Default::default() + fn zero(buffer: &text::BufferSnapshot) -> Self { + Self { + start: Anchor::min_for_buffer(buffer.remote_id()), + end: Anchor::max_for_buffer(buffer.remote_id()), + min_start: Anchor::max_for_buffer(buffer.remote_id()), + max_end: Anchor::min_for_buffer(buffer.remote_id()), + count: 0, + } } fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 89c44513067f6d2309d68a9f38984988358d8877..4d26aff022be1392b0ddc6cd653a33b9eaefd950 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -260,7 +260,7 @@ pub fn serialize_anchor(anchor: &Anchor) -> proto::Anchor { Bias::Left => proto::Bias::Left as i32, Bias::Right => proto::Bias::Right as i32, }, - buffer_id: anchor.buffer_id.map(Into::into), + buffer_id: Some(anchor.buffer_id.into()), } } @@ -498,7 +498,7 @@ pub fn deserialize_anchor(anchor: proto::Anchor) -> Option { timestamp, anchor.offset as u32, bias, - buffer_id, + buffer_id.expect("FIXME rework anchor ser/de"), )) } diff --git a/crates/language/src/syntax_map.rs b/crates/language/src/syntax_map.rs index bd24424679f3e6cb02303c91e0d86db335cd0a26..ba2b6357ffb4df263ff1018bdb5288a109d8baab 100644 --- a/crates/language/src/syntax_map.rs +++ b/crates/language/src/syntax_map.rs @@ -18,7 +18,7 @@ use std::{ }; use streaming_iterator::StreamingIterator; use sum_tree::{Bias, Dimensions, SeekTarget, SumTree}; -use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint}; +use text::{Anchor, BufferId, BufferSnapshot, OffsetRangeExt, Point, Rope, ToOffset, ToPoint}; use tree_sitter::{ Node, Query, QueryCapture, QueryCaptures, QueryCursor, QueryMatch, QueryMatches, QueryPredicateArg, @@ -567,7 +567,7 @@ impl SyntaxSnapshot { let bounded_position = SyntaxLayerPositionBeforeChange { position: position.clone(), - change: changed_regions.start_position(), + change: changed_regions.start_position(text.remote_id()), }; if bounded_position.cmp(cursor.start(), text).is_gt() { let slice = cursor.slice(&bounded_position, Bias::Left); @@ -1925,11 +1925,11 @@ impl ChangedRegion { } impl ChangeRegionSet { - fn start_position(&self) -> ChangeStartPosition { + fn start_position(&self, buffer_id: BufferId) -> ChangeStartPosition { self.0.first().map_or( ChangeStartPosition { depth: usize::MAX, - position: Anchor::MAX, + position: Anchor::max_for_buffer(buffer_id), }, |region| ChangeStartPosition { depth: region.depth, @@ -1978,32 +1978,28 @@ impl ChangeRegionSet { } } -impl Default for SyntaxLayerSummary { - fn default() -> Self { +impl sum_tree::Summary for SyntaxLayerSummary { + type Context<'a> = &'a BufferSnapshot; + + fn zero(buffer: &BufferSnapshot) -> Self { Self { max_depth: 0, min_depth: 0, - range: Anchor::MAX..Anchor::MIN, - last_layer_range: Anchor::MIN..Anchor::MAX, + range: Anchor::max_for_buffer(buffer.remote_id()) + ..Anchor::min_for_buffer(buffer.remote_id()), + last_layer_range: Anchor::min_for_buffer(buffer.remote_id()) + ..Anchor::max_for_buffer(buffer.remote_id()), last_layer_language: None, contains_unknown_injections: false, } } -} - -impl sum_tree::Summary for SyntaxLayerSummary { - type Context<'a> = &'a BufferSnapshot; - - fn zero(_cx: &BufferSnapshot) -> Self { - Default::default() - } fn add_summary(&mut self, other: &Self, buffer: Self::Context<'_>) { if other.max_depth > self.max_depth { self.max_depth = other.max_depth; self.range = other.range.clone(); } else { - if self.range == (Anchor::MAX..Anchor::MAX) { + if self.range.start.is_min() && self.range.end.is_max() { self.range.start = other.range.start; } if other.range.end.cmp(&self.range.end, buffer).is_gt() { diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index ecb71344d1fbd055d099bb2d1faae48b571a151f..1767d9b92a112d571b5723729f8fca5d3d4e290b 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -10,29 +10,12 @@ use std::{ ops::{Add, AddAssign, Range, Sub}, }; use sum_tree::Bias; -use text::BufferId; /// A multibuffer anchor derived from an anchor into a specific excerpted buffer. #[derive(Clone, Copy, Eq, PartialEq, Hash)] pub struct ExcerptAnchor { - /// The position within the excerpt's underlying buffer. This is a stable - /// reference that remains valid as the buffer text is edited. - pub(crate) timestamp: clock::Lamport, - - /// The byte offset into the text inserted in the operation - /// at `timestamp`. - pub(crate) offset: u32, - /// Whether this anchor stays attached to the character *before* or *after* - /// the offset. - pub(crate) bias: Bias, - pub(crate) buffer_id: BufferId, - /// Refers to the path key that the buffer had when this anchor was created, - /// so that ordering is stable when the path key for a buffer changes + pub(crate) text_anchor: text::Anchor, pub(crate) path: PathKeyIndex, - /// When present, indicates this anchor points into deleted text within an - /// expanded diff hunk. The anchor references a position in the diff base - /// (original) text rather than the current buffer text. This is used when - /// displaying inline diffs where deleted lines are shown. pub(crate) diff_base_anchor: Option, } @@ -50,7 +33,7 @@ pub enum Anchor { impl std::fmt::Debug for ExcerptAnchor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Anchor") - .field("text_anchor", &self.text_anchor()) + .field("text_anchor", &self.text_anchor) .field("diff_base_anchor", &self.diff_base_anchor) .finish() } @@ -86,7 +69,7 @@ impl From for Anchor { impl ExcerptAnchor { pub(crate) fn text_anchor(&self) -> text::Anchor { - text::Anchor::new(self.timestamp, self.offset, self.bias, Some(self.buffer_id)) + self.text_anchor } pub(crate) fn with_diff_base_anchor(mut self, diff_base_anchor: text::Anchor) -> Self { @@ -109,8 +92,8 @@ impl ExcerptAnchor { // in the case that you removed the buffer containing self, // and added the buffer containing other with the same path key // (ordering is arbitrary but consistent) - if self.buffer_id != other.buffer_id { - return self.buffer_id.cmp(&other.buffer_id); + if self.text_anchor.buffer_id != other.text_anchor.buffer_id { + return self.text_anchor.buffer_id.cmp(&other.text_anchor.buffer_id); } let Some(buffer) = snapshot.buffer_for_path(&self_path_key) else { @@ -124,7 +107,7 @@ impl ExcerptAnchor { if (self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some()) && let Some(base_text) = snapshot .diffs - .get(&self.buffer_id) + .get(&self.text_anchor.buffer_id) .map(|diff| diff.base_text()) { let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text)); @@ -147,16 +130,16 @@ impl ExcerptAnchor { } fn bias_left(&self, snapshot: &MultiBufferSnapshot) -> Self { - if self.bias == Bias::Left { + if self.text_anchor.bias == Bias::Left { return *self; } - let Some(buffer) = snapshot.buffer_for_id(self.buffer_id) else { + let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else { return *self; }; let text_anchor = self.text_anchor().bias_left(&buffer); let ret = Self::in_buffer(self.path, text_anchor); if let Some(diff_base_anchor) = self.diff_base_anchor { - if let Some(diff) = snapshot.diffs.get(&self.buffer_id) + if let Some(diff) = snapshot.diffs.get(&self.text_anchor.buffer_id) && diff_base_anchor.is_valid(&diff.base_text()) { ret.with_diff_base_anchor(diff_base_anchor.bias_left(diff.base_text())) @@ -169,16 +152,16 @@ impl ExcerptAnchor { } fn bias_right(&self, snapshot: &MultiBufferSnapshot) -> Self { - if self.bias == Bias::Right { + if self.text_anchor.bias == Bias::Right { return *self; } - let Some(buffer) = snapshot.buffer_for_id(self.buffer_id) else { + let Some(buffer) = snapshot.buffer_for_id(self.text_anchor.buffer_id) else { return *self; }; let text_anchor = self.text_anchor().bias_right(&buffer); let ret = Self::in_buffer(self.path, text_anchor); if let Some(diff_base_anchor) = self.diff_base_anchor { - if let Some(diff) = snapshot.diffs.get(&self.buffer_id) + if let Some(diff) = snapshot.diffs.get(&self.text_anchor.buffer_id) && diff_base_anchor.is_valid(&diff.base_text()) { ret.with_diff_base_anchor(diff_base_anchor.bias_right(diff.base_text())) @@ -192,16 +175,10 @@ impl ExcerptAnchor { #[track_caller] pub(crate) fn in_buffer(path: PathKeyIndex, text_anchor: text::Anchor) -> Self { - let Some(buffer_id) = text_anchor.buffer_id else { - panic!("text_anchor must have a buffer_id"); - }; ExcerptAnchor { path, diff_base_anchor: None, - timestamp: text_anchor.timestamp(), - buffer_id, - offset: text_anchor.offset, - bias: text_anchor.bias, + text_anchor, } } @@ -214,7 +191,7 @@ impl ExcerptAnchor { let Some(excerpt) = cursor.item() else { return false; }; - excerpt.buffer_id == self.buffer_id + excerpt.buffer_id == self.text_anchor.buffer_id && excerpt .range .context @@ -291,7 +268,7 @@ impl Anchor { match self { Anchor::Min => Bias::Left, Anchor::Max => Bias::Right, - Anchor::Excerpt(anchor) => anchor.bias, + Anchor::Excerpt(anchor) => anchor.text_anchor.bias, } } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index e174e481182cc1f5c544359df3ed3d1438141b38..495ddfc84f639bc2ca56c5119ab9a4179e0ca49c 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -801,7 +801,7 @@ impl ExcerptRange { #[derive(Clone, Debug)] pub struct ExcerptSummary { path_key: PathKey, - max_anchor: text::Anchor, + max_anchor: Option, widest_line_number: u32, text: MBTextSummary, count: usize, @@ -811,7 +811,7 @@ impl ExcerptSummary { pub fn min() -> Self { ExcerptSummary { path_key: PathKey::min(), - max_anchor: text::Anchor::MIN, + max_anchor: None, widest_line_number: 0, text: MBTextSummary::default(), count: 0, @@ -1660,7 +1660,7 @@ impl MultiBuffer { .start .excerpt_anchor() .map(|excerpt_anchor| excerpt_anchor.text_anchor()) - .unwrap_or(text::Anchor::MIN), + .unwrap_or(text::Anchor::min_for_buffer(excerpt.buffer_id)), buffer, ) .clone(); @@ -1670,7 +1670,7 @@ impl MultiBuffer { .end .excerpt_anchor() .map(|excerpt_anchor| excerpt_anchor.text_anchor()) - .unwrap_or(text::Anchor::MAX), + .unwrap_or(text::Anchor::max_for_buffer(excerpt.buffer_id)), buffer, ) .clone(); @@ -1842,7 +1842,7 @@ impl MultiBuffer { pub fn buffer_for_anchor(&self, anchor: Anchor, cx: &App) -> Option> { match anchor { Anchor::Min => Some(self.snapshot(cx).excerpts.first()?.buffer(self)), - Anchor::Excerpt(excerpt_anchor) => self.buffer(excerpt_anchor.buffer_id), + Anchor::Excerpt(excerpt_anchor) => self.buffer(excerpt_anchor.text_anchor.buffer_id), Anchor::Max => Some(self.snapshot(cx).excerpts.first()?.buffer(self)), } } @@ -1935,14 +1935,14 @@ impl MultiBuffer { let mut futures = Vec::new(); for anchor in anchors { if let Some(excerpt_anchor) = anchor.excerpt_anchor() { - if let Some(buffer) = self.buffers.get(&excerpt_anchor.buffer_id) { + if let Some(buffer) = self.buffers.get(&excerpt_anchor.text_anchor.buffer_id) { buffer.buffer.update(cx, |buffer, _| { futures.push(buffer.wait_for_anchors([excerpt_anchor.text_anchor()])) }); } else { error = Some(anyhow!( "buffer {:?} is not part of this multi-buffer", - excerpt_anchor.buffer_id + excerpt_anchor.text_anchor.buffer_id )); break; } @@ -1966,7 +1966,11 @@ impl MultiBuffer { ) -> Option<(Entity, text::Anchor)> { let snapshot = self.read(cx); let anchor = snapshot.anchor_before(position).excerpt_anchor()?; - let buffer = self.buffers.get(&anchor.buffer_id)?.buffer.clone(); + let buffer = self + .buffers + .get(&anchor.text_anchor.buffer_id)? + .buffer + .clone(); Some((buffer, anchor.text_anchor())) } @@ -3591,13 +3595,8 @@ impl MultiBufferSnapshot { let mut anchors = anchors.peekable(); let mut cursor = self.excerpts.cursor::(()); 'anchors: while let Some(anchor) = anchors.peek() { - let Some(buffer_id) = anchor.buffer_id else { - anchors.next(); - result.push(None); - continue 'anchors; - }; - let mut same_buffer_anchors = - anchors.peeking_take_while(|a| a.buffer_id.is_some_and(|b| buffer_id == b)); + let buffer_id = anchor.buffer_id; + let mut same_buffer_anchors = anchors.peeking_take_while(|a| a.buffer_id == buffer_id); if let Some(buffer) = self.buffers.get(&buffer_id) { let path = &buffer.path_key; @@ -4920,7 +4919,7 @@ impl MultiBufferSnapshot { // A right-biased anchor at a transform boundary belongs to the // *next* transform, so advance past the current one. - if anchor.bias == Bias::Right && at_transform_end { + if anchor.text_anchor.bias == Bias::Right && at_transform_end { diff_transforms.next(); continue; } @@ -5074,6 +5073,7 @@ impl MultiBufferSnapshot { let mut summaries = Vec::new(); while let Some(anchor) = anchors.peek() { + dbg!(&anchor); let target = anchor.seek_target(self); let excerpt_anchor = match anchor { Anchor::Min => { @@ -5121,6 +5121,7 @@ impl MultiBufferSnapshot { if !excerpt.contains(&excerpt_anchor, self) { return None; } + anchors.next(); Some((excerpt_anchor.text_anchor(), excerpt_anchor)) }), ) @@ -5304,10 +5305,7 @@ impl MultiBufferSnapshot { /// Wraps the [`text::Anchor`] in a [`crate::Anchor`] if this multi-buffer is a singleton. pub fn as_singleton_anchor(&self, text_anchor: text::Anchor) -> Option { let buffer = self.as_singleton()?; - if text_anchor - .buffer_id - .is_none_or(|id| id == buffer.remote_id()) - { + if text_anchor.buffer_id == buffer.remote_id() { // todo!() bit hacky, but always right for singletons Some(Anchor::in_buffer(PathKeyIndex(0), text_anchor)) } else { @@ -6242,7 +6240,7 @@ impl MultiBufferSnapshot { anchor .excerpt_anchor() .map(|anchor| anchor.text_anchor()) - .unwrap_or(text::Anchor::MIN), + .unwrap_or(text::Anchor::min_for_buffer(buffer_snapshot.remote_id())), theme, ) .into_iter() @@ -6376,7 +6374,7 @@ impl MultiBufferSnapshot { pub fn buffer_id_for_anchor(&self, anchor: Anchor) -> Option { match anchor { Anchor::Min => self.excerpts.first().map(|excerpt| excerpt.buffer_id), - Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.buffer_id), + Anchor::Excerpt(excerpt_anchor) => Some(excerpt_anchor.text_anchor.buffer_id), Anchor::Max => self.excerpts.last().map(|excerpt| excerpt.buffer_id), } } @@ -7051,7 +7049,7 @@ impl Excerpt { pub(crate) fn contains(&self, anchor: &ExcerptAnchor, snapshot: &MultiBufferSnapshot) -> bool { self.path_key_index == anchor.path - && self.buffer_id == anchor.buffer_id + && self.buffer_id == anchor.text_anchor.buffer_id && self .range .contains(&anchor.text_anchor(), self.buffer_snapshot(snapshot)) @@ -7245,7 +7243,7 @@ impl sum_tree::Item for Excerpt { } ExcerptSummary { path_key: self.path_key.clone(), - max_anchor: self.range.context.end, + max_anchor: Some(self.range.context.end), widest_line_number: self.max_buffer_row, text: text.into(), count: 1, @@ -7372,14 +7370,13 @@ impl sum_tree::SeekTarget<'_, ExcerptSummary, ExcerptSummary> for AnchorSeekTarg // the goal is that if you seek to buffer_id 1 (path key a) // and you find that path key a now belongs to buffer_id 2, // we'll seek past buffer_id 2. - if let Some(buffer_id) = cursor_location.max_anchor.buffer_id - && buffer_id != snapshot.remote_id() + if cursor_location.max_anchor.map(|a| a.buffer_id) != Some(snapshot.remote_id()) { Ordering::Greater } else { anchor .text_anchor() - .cmp(&cursor_location.max_anchor, snapshot) + .cmp(&cursor_location.max_anchor.unwrap(), snapshot) } } else { // shouldn't happen because we expect this buffer not to have any excerpts diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index 876323b4bfb1031d7961d52e4e956acab08cc4be..f28ae0101dab783cf97c50233d64a9d325b6d278 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -3623,6 +3623,7 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { }); cx.run_until_parked(); + dbg!("BEFORE"); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(Capability::ReadWrite); multibuffer.set_all_diff_hunks_expanded(cx); @@ -3645,10 +3646,12 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { multibuffer }); + dbg!("BEFORE"); let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| { (multibuffer.snapshot(cx), multibuffer.subscribe()) }); + dbg!("BEFORE"); assert_new_snapshot( &multibuffer, &mut snapshot, @@ -3665,6 +3668,7 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { ), ); + dbg!("BEFORE"); let anchor_1 = multibuffer.read_with(cx, |multibuffer, cx| { multibuffer .buffer_anchor_to_anchor( @@ -3674,9 +3678,11 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { ) .unwrap() }); + dbg!("BEFORE"); let point_1 = snapshot.summaries_for_anchors::([&anchor_1])[0]; assert_eq!(point_1, Point::new(0, 0)); + dbg!("BEFORE"); let anchor_2 = multibuffer.read_with(cx, |multibuffer, cx| { multibuffer .buffer_anchor_to_anchor( @@ -3686,7 +3692,9 @@ async fn test_summaries_for_anchors(cx: &mut TestAppContext) { ) .unwrap() }); + dbg!("BEFORE"); let point_2 = snapshot.summaries_for_anchors::([&anchor_2])[0]; + dbg!("AFTER"); assert_eq!(point_2, Point::new(3, 0)); } diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 7a693a4e7113ceaad33e455b754f048c9b2d5784..fa16b79fc03a9eac0563c9440aad873742a3dc5d 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -545,10 +545,11 @@ impl MultiBuffer { }) } drop(cursor); - snapshot.excerpts = new_excerpts; if changed_trailing_excerpt { snapshot.trailing_excerpt_update_count += 1; + new_excerpts.update_last(|excerpt| excerpt.has_trailing_newline = false, ()) } + snapshot.excerpts = new_excerpts; let edits = Self::sync_diff_transforms(&mut snapshot, vec![edit], DiffChangeKind::BufferEdited); diff --git a/crates/project/tests/integration/project_tests.rs b/crates/project/tests/integration/project_tests.rs index 9bd0be45ae3fa1e66e8af2c43657ba039045ecef..493922ec31a850105cbb84dcd1a6a4f725ef84b9 100644 --- a/crates/project/tests/integration/project_tests.rs +++ b/crates/project/tests/integration/project_tests.rs @@ -1784,7 +1784,7 @@ async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) { DiagnosticSet::from_sorted_entries( vec![DiagnosticEntry { diagnostic: Default::default(), - range: Anchor::MIN..Anchor::MAX, + range: Anchor::min_max_range_for_buffer(buffer.remote_id()), }], &buffer.snapshot(), ), @@ -7986,9 +7986,10 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) { unstaged_diff.update(cx, |unstaged_diff, cx| { let snapshot = buffer.read(cx).snapshot(); assert_hunks( - unstaged_diff - .snapshot(cx) - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + unstaged_diff.snapshot(cx).hunks_intersecting_range( + Anchor::min_max_range_for_buffer(snapshot.remote_id()), + &snapshot, + ), &snapshot, &unstaged_diff.base_text(cx).text(), &[( @@ -8077,8 +8078,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) { diff_1.update(cx, |diff, cx| { let snapshot = buffer_1.read(cx).snapshot(); assert_hunks( - diff.snapshot(cx) - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + diff.snapshot(cx).hunks_intersecting_range( + Anchor::min_max_range_for_buffer(snapshot.remote_id()), + &snapshot, + ), &snapshot, &diff.base_text_string(cx).unwrap(), &[ @@ -8119,8 +8122,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) { diff_1.update(cx, |diff, cx| { let snapshot = buffer_1.read(cx).snapshot(); assert_hunks( - diff.snapshot(cx) - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + diff.snapshot(cx).hunks_intersecting_range( + Anchor::min_max_range_for_buffer(snapshot.remote_id()), + &snapshot, + ), &snapshot, &diff.base_text(cx).text(), &[( @@ -8149,8 +8154,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) { diff_2.update(cx, |diff, cx| { let snapshot = buffer_2.read(cx).snapshot(); assert_hunks( - diff.snapshot(cx) - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + diff.snapshot(cx).hunks_intersecting_range( + Anchor::min_max_range_for_buffer(snapshot.remote_id()), + &snapshot, + ), &snapshot, &diff.base_text_string(cx).unwrap(), &[( @@ -8171,8 +8178,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) { diff_2.update(cx, |diff, cx| { let snapshot = buffer_2.read(cx).snapshot(); assert_hunks( - diff.snapshot(cx) - .hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + diff.snapshot(cx).hunks_intersecting_range( + Anchor::min_max_range_for_buffer(snapshot.remote_id()), + &snapshot, + ), &snapshot, &diff.base_text_string(cx).unwrap(), &[( diff --git a/crates/text/src/anchor.rs b/crates/text/src/anchor.rs index 63e0570e91ef08dfce02fbbca25e97ee7519dc0a..896fea24c9d9eac4f5623754e3167b9ab1b52d57 100644 --- a/crates/text/src/anchor.rs +++ b/crates/text/src/anchor.rs @@ -24,7 +24,7 @@ pub struct Anchor { /// Whether this anchor stays attached to the character *before* or *after* /// the offset. pub bias: Bias, - pub buffer_id: Option, + pub buffer_id: BufferId, } impl Debug for Anchor { @@ -46,28 +46,7 @@ impl Debug for Anchor { } impl Anchor { - pub const MIN: Self = Self { - timestamp_replica_id: clock::Lamport::MIN.replica_id, - timestamp_value: clock::Lamport::MIN.value, - offset: u32::MIN, - bias: Bias::Left, - buffer_id: None, - }; - - pub const MAX: Self = Self { - timestamp_replica_id: clock::Lamport::MAX.replica_id, - timestamp_value: clock::Lamport::MAX.value, - offset: u32::MAX, - bias: Bias::Right, - buffer_id: None, - }; - - pub fn new( - timestamp: clock::Lamport, - offset: u32, - bias: Bias, - buffer_id: Option, - ) -> Self { + pub fn new(timestamp: clock::Lamport, offset: u32, bias: Bias, buffer_id: BufferId) -> Self { Self { timestamp_replica_id: timestamp.replica_id, timestamp_value: timestamp.value, @@ -83,7 +62,7 @@ impl Anchor { timestamp_value: clock::Lamport::MIN.value, offset: u32::MIN, bias: Bias::Left, - buffer_id: Some(buffer_id), + buffer_id, } } @@ -93,7 +72,7 @@ impl Anchor { timestamp_value: clock::Lamport::MAX.value, offset: u32::MAX, bias: Bias::Right, - buffer_id: Some(buffer_id), + buffer_id, } } @@ -171,7 +150,7 @@ impl Anchor { pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { if self.is_min() || self.is_max() { true - } else if self.buffer_id.is_none_or(|id| id != buffer.remote_id) { + } else if self.buffer_id != buffer.remote_id { false } else { let Some(fragment_id) = buffer.try_fragment_id_for_anchor(self) else { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 300eee01ea6def3b99167cfcc517d3f21d41d7d3..31c7b1e1b39c3f7674c72aa5a21abc1c0da2f354 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -2413,7 +2413,7 @@ impl BufferSnapshot { } else if anchor.is_max() { self.visible_text.len() } else { - debug_assert_eq!(anchor.buffer_id, Some(self.remote_id)); + debug_assert_eq!(anchor.buffer_id, self.remote_id); debug_assert!( self.version.observed(anchor.timestamp()), "Anchor timestamp {:?} not observed by buffer {:?}", @@ -2445,7 +2445,7 @@ impl BufferSnapshot { #[cold] fn panic_bad_anchor(&self, anchor: &Anchor) -> ! { - if anchor.buffer_id.is_some_and(|id| id != self.remote_id) { + if anchor.buffer_id != self.remote_id { panic!( "invalid anchor - buffer id does not match: anchor {anchor:?}; buffer id: {}, version: {:?}", self.remote_id, self.version @@ -2566,7 +2566,7 @@ impl BufferSnapshot { fragment.timestamp, fragment.insertion_offset + overshoot as u32, bias, - Some(self.remote_id), + self.remote_id, ) } } @@ -2574,8 +2574,7 @@ impl BufferSnapshot { pub fn can_resolve(&self, anchor: &Anchor) -> bool { anchor.is_min() || anchor.is_max() - || (Some(self.remote_id) == anchor.buffer_id - && self.version.observed(anchor.timestamp())) + || (self.remote_id == anchor.buffer_id && self.version.observed(anchor.timestamp())) } pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { @@ -2601,7 +2600,10 @@ impl BufferSnapshot { where D: TextDimension + Ord, { - self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + self.edits_since_in_range( + since, + Anchor::min_for_buffer(self.remote_id)..Anchor::max_for_buffer(self.remote_id), + ) } pub fn anchored_edits_since<'a, D>( @@ -2611,7 +2613,10 @@ impl BufferSnapshot { where D: TextDimension + Ord, { - self.anchored_edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + self.anchored_edits_since_in_range( + since, + Anchor::min_for_buffer(self.remote_id)..Anchor::max_for_buffer(self.remote_id), + ) } pub fn edits_since_in_range<'a, D>( @@ -2874,13 +2879,13 @@ impl bool> Iterator for Ed fragment.timestamp, fragment.insertion_offset, Bias::Right, - Some(self.buffer_id), + self.buffer_id, ); let end_anchor = Anchor::new( fragment.timestamp, fragment.insertion_offset + fragment.len, Bias::Left, - Some(self.buffer_id), + self.buffer_id, ); if !fragment.was_visible(self.since, self.undos) && fragment.visible {