From 159c2239cc2ec8a00008b3ff64205df7be055e2d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 4 Dec 2024 16:06:54 -0800 Subject: [PATCH 01/51] Stub in the structure of a DiffMap to go in the display map stack Co-authored-by: Conrad Co-authored-by: Cole --- crates/editor/src/display_map.rs | 1 + crates/editor/src/display_map/diff_map.rs | 319 ++++++++++++++++++++++ 2 files changed, 320 insertions(+) create mode 100644 crates/editor/src/display_map/diff_map.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a75c2ce9fa3157..23e598c342e217 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -19,6 +19,7 @@ mod block_map; mod crease_map; +mod diff_map; mod fold_map; mod inlay_map; pub(crate) mod invisibles; diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs new file mode 100644 index 00000000000000..b1b164d345fdbd --- /dev/null +++ b/crates/editor/src/display_map/diff_map.rs @@ -0,0 +1,319 @@ +use crate::{ + display_map::fold_map::{FoldBufferRows, FoldOffset, FoldSnapshot}, + Highlights, +}; +use collections::HashMap; +use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; +use language::{BufferId, Chunk}; +use project::buffer_store::BufferChangeSet; +use std::ops::Range; +use sum_tree::{Cursor, SumTree, TreeMap}; +use text::Bias; + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DiffOffset(pub usize); + +struct DiffMap { + snapshot: DiffMapSnapshot, + diff_bases: HashMap, + buffer_input_row_counts: Vec<(BufferId, u32)>, +} + +struct ChangeSetState { + change_set: Model, + last_version: Option, + _subscription: Subscription, +} + +#[derive(Clone)] +pub(crate) struct DiffMapSnapshot { + diffs: TreeMap, + diff_transforms: SumTree, + fold_snapshot: FoldSnapshot, +} + +#[derive(Debug, Clone)] +enum DiffTransform { + BufferContent { + row_count: u32, + buffer_id: BufferId, + }, + DeletedHunk { + row_count: u32, + buffer_id: BufferId, + hunk_position: text::Anchor, + base_text_start_row: u32, + }, +} + +#[derive(Debug, Clone)] +struct DiffTransformSummary { + input_row_count: u32, + output_row_count: u32, + transform_count: usize, +} + +struct DiffMapChunks<'a> { + cursor: Cursor<'a, DiffTransform, (DiffOffset, FoldOffset)>, +} + +struct DiffMapBufferRows<'a> { + cursor: Cursor<'a, DiffTransform, DiffTransformSummary>, + input_buffer_rows: FoldBufferRows<'a>, +} + +struct InputRowCount(u32); + +impl DiffMap { + pub fn new(fold_snapshot: FoldSnapshot, cx: &mut AppContext) -> (Model, DiffMapSnapshot) { + let snapshot = DiffMapSnapshot { + diffs: TreeMap::default(), + diff_transforms: SumTree::new(&()), + fold_snapshot, + }; + + // Determine the extents of every run of excerpts associated with a + // single buffer. + let mut buffer_input_row_counts = Vec::new(); + let mut current_buffer_and_start_row = None; + let fold_snapshot = &snapshot.fold_snapshot; + let inlay_snapshot = &fold_snapshot.inlay_snapshot; + let buffer_snapshot = &inlay_snapshot.buffer; + for excerpt in buffer_snapshot.all_excerpts().map(Some).chain([None]) { + let buffer_id = excerpt.as_ref().map(|e| e.buffer().remote_id()); + let position = excerpt.map_or(buffer_snapshot.max_point(), |e| e.start_point()); + let position = inlay_snapshot.to_inlay_point(position); + let position = fold_snapshot.to_fold_point(position, Bias::Right); + let row = position.row(); + if let Some((prev_buffer_id, prev_start_row)) = ¤t_buffer_and_start_row { + if buffer_id != Some(*prev_buffer_id) { + buffer_input_row_counts.push((*prev_buffer_id, row - *prev_start_row)); + current_buffer_and_start_row.take(); + } + } + if current_buffer_and_start_row.is_none() { + if let Some(buffer_id) = buffer_id { + current_buffer_and_start_row = Some((buffer_id, row)); + } + } + } + + let this = cx.new_model(|_| Self { + buffer_input_row_counts, + snapshot: snapshot.clone(), + diff_bases: HashMap::default(), + }); + + (this, snapshot) + } + + pub fn add_change_set( + &mut self, + change_set: Model, + cx: &mut ModelContext, + ) { + let buffer_id = change_set.read(cx).buffer_id; + self.buffer_diff_changed(change_set.clone(), cx); + self.diff_bases.insert( + buffer_id, + ChangeSetState { + _subscription: cx.observe(&change_set, Self::buffer_diff_changed), + last_version: None, + change_set, + }, + ); + } + + fn buffer_diff_changed( + &mut self, + change_set: Model, + cx: &mut ModelContext, + ) { + let change_set = change_set.read(cx); + let buffer_id = change_set.buffer_id; + let diff = change_set.diff_to_buffer.clone(); + + self.snapshot.diffs.insert(buffer_id, diff); + + let start_input_row = self.start_input_row_for_buffer(buffer_id); + let mut cursor = self + .snapshot + .diff_transforms + .cursor::(&()); + let mut new_transforms = SumTree::default(); + new_transforms.append( + cursor.slice(&InputRowCount(start_input_row), sum_tree::Bias::Right, &()), + &(), + ); + + new_transforms.append(cursor.suffix(&()), &()); + drop(cursor); + self.snapshot.diff_transforms = new_transforms; + } + + fn start_input_row_for_buffer(&self, buffer_id: BufferId) -> u32 { + let mut result = 0; + for (id, row_count) in &self.buffer_input_row_counts { + if *id == buffer_id { + break; + } + result += *row_count + } + result + } + + pub(super) fn expand_diff_hunks( + &mut self, + multi_buffer_range: Range, + cx: &mut ModelContext, + ) { + } + + pub(super) fn collapse_diff_hunks( + &mut self, + multi_buffer_range: Range, + cx: &mut ModelContext, + ) { + } + + pub(super) fn set_all_hunks_expanded(&mut self, expand_all: bool, cx: &mut ModelContext) { + // + } + + fn snapshot(&self) -> DiffMapSnapshot { + self.snapshot.clone() + } +} + +impl DiffMapSnapshot { + pub fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + highlights: Highlights<'a>, + ) -> DiffMapChunks<'a> { + todo!() + } + + pub fn buffer_rows(&self, start_row: u32) -> DiffMapBufferRows { + todo!() + // + } +} + +impl<'a> Iterator for DiffMapChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + todo!() + } +} + +impl sum_tree::Item for DiffTransform { + type Summary = DiffTransformSummary; + + fn summary(&self, _: &::Context) -> Self::Summary { + match self { + DiffTransform::BufferContent { row_count, .. } => DiffTransformSummary { + input_row_count: *row_count, + output_row_count: *row_count, + transform_count: 1, + }, + DiffTransform::DeletedHunk { row_count, .. } => DiffTransformSummary { + input_row_count: 0, + output_row_count: *row_count, + transform_count: 1, + }, + } + } +} + +impl sum_tree::Summary for DiffTransformSummary { + type Context = (); + + fn zero(_: &Self::Context) -> Self { + DiffTransformSummary { + input_row_count: 0, + output_row_count: 0, + transform_count: 0, + } + } + + fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + self.input_row_count += summary.input_row_count; + self.output_row_count += summary.output_row_count; + self.transform_count += summary.transform_count; + } +} + +impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for InputRowCount { + fn cmp(&self, cursor_location: &DiffTransformSummary, _: &()) -> std::cmp::Ordering { + Ord::cmp(&self.0, &cursor_location.input_row_count) + } +} + +impl<'a> Iterator for DiffMapBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + todo!() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap}; + use gpui::AppContext; + use indoc::indoc; + use multi_buffer::MultiBuffer; + use project::Project; + use settings::SettingsStore; + + #[gpui::test] + fn test_basic_diff(cx: &mut gpui::AppContext) { + init_test(cx); + + let text = indoc!( + " + one + two + five + " + ); + + let base_text = indoc!( + " + one + two + three + four + five + six + " + ); + let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); + let change_set = cx.new_model(|cx| { + BufferChangeSet::new_with_base_text( + base_text.to_string(), + buffer.read(cx).text_snapshot(), + cx, + ) + }); + + let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); + let buffer_snapshot = buffer.read(cx).snapshot(cx); + let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (diff_map, _) = DiffMap::new(fold_snapshot, cx); + diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); + } + + fn init_test(cx: &mut AppContext) { + let settings = SettingsStore::test(cx); + cx.set_global(settings); + language::init(cx); + crate::init(cx); + Project::init_settings(cx); + } +} From 67ffe999e5570b9084743fc781d2988b16a62e00 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 5 Dec 2024 12:39:04 -0800 Subject: [PATCH 02/51] Start work on DiffMapSnapshot::chunks Co-authored-by: Conrad Co-authored-by: Cole --- crates/editor/src/display_map/diff_map.rs | 451 ++++++++++++++++++---- crates/editor/src/display_map/fold_map.rs | 4 + crates/editor/src/display_map/tab_map.rs | 3 +- crates/multi_buffer/src/multi_buffer.rs | 30 ++ 4 files changed, 418 insertions(+), 70 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index b1b164d345fdbd..2eca3ca8e84941 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,22 +1,35 @@ use crate::{ display_map::fold_map::{FoldBufferRows, FoldOffset, FoldSnapshot}, - Highlights, + FoldPoint, Highlights, }; -use collections::HashMap; +use collections::{HashMap, HashSet}; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; -use language::{BufferId, Chunk}; +use language::{BufferChunks, BufferId, Chunk}; +use multi_buffer::MultiBuffer; use project::buffer_store::BufferChangeSet; -use std::ops::Range; +use std::{cmp::Ordering, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; -use text::Bias; +use text::{Bias, BufferSnapshot, Point, TextSummary, ToPoint}; + +use super::fold_map::{FoldChunks, FoldEdit}; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DiffOffset(pub usize); +impl std::ops::Add for DiffOffset { + type Output = DiffOffset; + + fn add(self, rhs: DiffOffset) -> Self::Output { + DiffOffset(self.0 + rhs.0) + } +} + struct DiffMap { snapshot: DiffMapSnapshot, + multibuffer: Model, diff_bases: HashMap, buffer_input_row_counts: Vec<(BufferId, u32)>, + all_hunks_expanded: bool, } struct ChangeSetState { @@ -25,36 +38,47 @@ struct ChangeSetState { _subscription: Subscription, } +#[derive(Clone)] +struct DiffSnapshot { + diff: git::diff::BufferDiff, + base_text: language::BufferSnapshot, +} + #[derive(Clone)] pub(crate) struct DiffMapSnapshot { - diffs: TreeMap, - diff_transforms: SumTree, + diffs: TreeMap, + transforms: SumTree, fold_snapshot: FoldSnapshot, } #[derive(Debug, Clone)] enum DiffTransform { BufferContent { - row_count: u32, - buffer_id: BufferId, + summary: TextSummary, }, DeletedHunk { - row_count: u32, - buffer_id: BufferId, - hunk_position: text::Anchor, - base_text_start_row: u32, + summary: TextSummary, + hunk_position: multi_buffer::Anchor, + base_text_start: Point, }, } #[derive(Debug, Clone)] struct DiffTransformSummary { - input_row_count: u32, - output_row_count: u32, - transform_count: usize, + input: TextSummary, + output: TextSummary, } struct DiffMapChunks<'a> { + snapshot: &'a DiffMapSnapshot, + language_aware: bool, cursor: Cursor<'a, DiffTransform, (DiffOffset, FoldOffset)>, + fold_chunks: FoldChunks<'a>, + fold_chunk: Option>, + fold_offset: FoldOffset, + offset: DiffOffset, + end_offset: DiffOffset, + diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, } struct DiffMapBufferRows<'a> { @@ -62,13 +86,17 @@ struct DiffMapBufferRows<'a> { input_buffer_rows: FoldBufferRows<'a>, } -struct InputRowCount(u32); +pub type DiffEdit = text::Edit; impl DiffMap { - pub fn new(fold_snapshot: FoldSnapshot, cx: &mut AppContext) -> (Model, DiffMapSnapshot) { + pub fn new( + fold_snapshot: FoldSnapshot, + multibuffer: Model, + cx: &mut AppContext, + ) -> (Model, DiffMapSnapshot) { let snapshot = DiffMapSnapshot { diffs: TreeMap::default(), - diff_transforms: SumTree::new(&()), + transforms: SumTree::new(&()), fold_snapshot, }; @@ -100,7 +128,9 @@ impl DiffMap { let this = cx.new_model(|_| Self { buffer_input_row_counts, + multibuffer, snapshot: snapshot.clone(), + all_hunks_expanded: false, diff_bases: HashMap::default(), }); @@ -124,6 +154,14 @@ impl DiffMap { ); } + pub fn sync( + &mut self, + fold_snapshot: FoldSnapshot, + mut fold_edits: Vec, + ) -> (DiffMapSnapshot, Vec) { + todo!() + } + fn buffer_diff_changed( &mut self, change_set: Model, @@ -132,34 +170,137 @@ impl DiffMap { let change_set = change_set.read(cx); let buffer_id = change_set.buffer_id; let diff = change_set.diff_to_buffer.clone(); + let base_text = change_set + .base_text + .as_ref() + .map(|buffer| buffer.read(cx).snapshot()); + + if let Some(base_text) = base_text.clone() { + self.snapshot.diffs.insert( + buffer_id, + DiffSnapshot { + diff: diff.clone(), + base_text, + }, + ); + } else { + self.snapshot.diffs.remove(&buffer_id); + } - self.snapshot.diffs.insert(buffer_id, diff); + let Some(buffer) = self.multibuffer.read(cx).buffer(buffer_id) else { + return; + }; - let start_input_row = self.start_input_row_for_buffer(buffer_id); - let mut cursor = self - .snapshot - .diff_transforms - .cursor::(&()); + let mut cursor = self.snapshot.transforms.cursor::(&()); let mut new_transforms = SumTree::default(); - new_transforms.append( - cursor.slice(&InputRowCount(start_input_row), sum_tree::Bias::Right, &()), - &(), - ); - new_transforms.append(cursor.suffix(&()), &()); - drop(cursor); - self.snapshot.diff_transforms = new_transforms; - } + let snapshot = buffer.read(cx); + for (excerpt_id, multibuffer_range, buffer_range) in + self.multibuffer.read(cx).ranges_for_buffer(buffer_id, cx) + { + let hunks = diff.hunks_intersecting_range(buffer_range.clone(), snapshot); + let mut start = self + .snapshot + .fold_snapshot + .make_fold_point(multibuffer_range.start, Bias::Left); + let end = self + .snapshot + .fold_snapshot + .make_fold_point(multibuffer_range.end, Bias::Right); + + new_transforms.append(cursor.slice(&start, Bias::Left, &()), &()); + let old_tree = cursor.slice(&end, Bias::Left, &()); + let old_expanded_hunk_anchors = old_tree + .iter() + .filter_map(|transform| { + if let DiffTransform::DeletedHunk { hunk_position, .. } = transform { + Some(*hunk_position) + } else { + None + } + }) + .collect::>(); + + let excerpt_start = buffer_range.start.to_point(snapshot); + + if let Some(base_text) = &base_text { + for hunk in hunks { + let hunk_start_anchor = multi_buffer::Anchor { + excerpt_id, + buffer_id: Some(buffer_id), + text_anchor: hunk.buffer_range.start, + }; + if !old_expanded_hunk_anchors.contains(&hunk_start_anchor) + || self.all_hunks_expanded + { + continue; + } + + if hunk.diff_base_byte_range.len() == 0 { + continue; + } + let mut text_cursor = base_text.as_rope().cursor(0); + let base_text_start = + text_cursor.summary::(hunk.diff_base_byte_range.start); + let base_text_summary = text_cursor.summary(hunk.diff_base_byte_range.end); + + let hunk_start_in_excerpt = + hunk.buffer_range.start.to_point(snapshot) - excerpt_start; + let hunk_end_in_excerpt = + hunk.buffer_range.end.to_point(snapshot) - excerpt_start; + let hunk_start = multibuffer_range.start + hunk_start_in_excerpt; + let hunk_end = multibuffer_range.start + hunk_end_in_excerpt; + let hunk_start = self + .snapshot + .fold_snapshot + .make_fold_point(hunk_start, Bias::Left); + let hunk_end = self + .snapshot + .fold_snapshot + .make_fold_point(hunk_end, Bias::Left); + + if hunk_start > start { + new_transforms.push( + DiffTransform::BufferContent { + summary: self + .snapshot + .fold_snapshot + .text_summary_for_range(hunk_start..hunk_end), + }, + &(), + ); + } + + start = hunk_end; + + new_transforms.push( + DiffTransform::DeletedHunk { + hunk_position: hunk_start_anchor, + summary: base_text_summary, + base_text_start, + }, + &(), + ); + } + } - fn start_input_row_for_buffer(&self, buffer_id: BufferId) -> u32 { - let mut result = 0; - for (id, row_count) in &self.buffer_input_row_counts { - if *id == buffer_id { - break; + if end > start { + new_transforms.push( + DiffTransform::BufferContent { + summary: self + .snapshot + .fold_snapshot + .text_summary_for_range(start..end), + }, + &(), + ); } - result += *row_count } - result + + new_transforms.append(cursor.suffix(&()), &()); + + drop(cursor); + self.snapshot.transforms = new_transforms; } pub(super) fn expand_diff_hunks( @@ -177,7 +318,8 @@ impl DiffMap { } pub(super) fn set_all_hunks_expanded(&mut self, expand_all: bool, cx: &mut ModelContext) { - // + self.all_hunks_expanded = expand_all; + cx.notify() } fn snapshot(&self) -> DiffMapSnapshot { @@ -186,18 +328,70 @@ impl DiffMap { } impl DiffMapSnapshot { + #[cfg(test)] + pub fn text(&self) -> String { + self.chunks(DiffOffset(0)..self.len(), false, Highlights::default()) + .map(|c| c.text) + .collect() + } + + #[cfg(test)] + pub fn len(&self) -> DiffOffset { + DiffOffset(self.transforms.summary().output.len) + } + + pub fn to_fold_offset(&self, offset: DiffOffset) -> FoldOffset { + let mut cursor = self.transforms.cursor::<(DiffOffset, FoldOffset)>(&()); + cursor.seek(&offset, Bias::Right, &()); + let mut fold_offset = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = offset.0 - cursor.start().0 .0; + fold_offset.0 += overshoot; + } + fold_offset + } + pub fn chunks<'a>( &'a self, range: Range, language_aware: bool, highlights: Highlights<'a>, ) -> DiffMapChunks<'a> { - todo!() + let mut cursor = self.transforms.cursor::<(DiffOffset, FoldOffset)>(&()); + cursor.seek(&range.end, Bias::Right, &()); + + let mut fold_end = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = range.end.0 - cursor.start().0 .0; + fold_end.0 += overshoot; + } + + cursor.seek(&range.start, Bias::Right, &()); + let mut fold_start = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = range.end.0 - cursor.start().0 .0; + fold_start.0 += overshoot; + } + + let fold_chunks = + self.fold_snapshot + .chunks(fold_start..fold_end, language_aware, highlights); + + DiffMapChunks { + snapshot: self, + language_aware, + cursor, + fold_chunk: None, + fold_chunks, + fold_offset: fold_start, + offset: range.start, + diff_base_chunks: None, + end_offset: range.end, + } } pub fn buffer_rows(&self, start_row: u32) -> DiffMapBufferRows { todo!() - // } } @@ -205,7 +399,78 @@ impl<'a> Iterator for DiffMapChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { - todo!() + if self.offset >= self.end_offset { + return None; + } + if self.offset == self.cursor.end(&()).0 { + self.cursor.next(&()); + } + + let transform = self.cursor.item()?; + + match transform { + DiffTransform::BufferContent { summary } => { + let chunk = self + .fold_chunk + .get_or_insert_with(|| self.fold_chunks.next().unwrap()); + + let chunk_end = self.offset + DiffOffset(chunk.text.len()); + let mut transform_end = self.cursor.start().0 + DiffOffset(summary.len); + + if transform_end > self.end_offset { + transform_end = self.end_offset + } + + if transform_end <= chunk_end { + self.cursor.next(&()); + } + + if transform_end < chunk_end { + self.offset = transform_end; + let (before, after) = chunk.text.split_at(transform_end.0 - self.offset.0); + chunk.text = after; + Some(Chunk { + text: before, + ..chunk.clone() + }) + } else { + self.offset = chunk_end; + self.fold_chunk.take() + } + } + DiffTransform::DeletedHunk { + summary, + hunk_position, + base_text_start, + } => { + let buffer_id = hunk_position.buffer_id?; + let base_buffer = &self.snapshot.diffs.get(&buffer_id)?.base_text; + + let diff_base_start_offset = base_buffer.point_to_offset(*base_text_start); + let diff_base_offset = + diff_base_start_offset + self.offset.0 - self.cursor.start().0 .0; + let diff_base_end_offset = diff_base_start_offset + summary.len; + + let mut chunks = if let Some((_, mut chunks)) = self + .diff_base_chunks + .take() + .filter(|(id, _)| id == &buffer_id) + { + if chunks.offset() != diff_base_offset { + chunks.seek(diff_base_offset..diff_base_end_offset); + } + chunks + } else { + base_buffer.chunks(diff_base_offset..diff_base_end_offset, self.language_aware) + }; + + let chunk = chunks.next()?; + + self.offset.0 += chunk.text.len(); + self.diff_base_chunks = Some((buffer_id, chunks)); + Some(chunk) + } + } } } @@ -214,15 +479,13 @@ impl sum_tree::Item for DiffTransform { fn summary(&self, _: &::Context) -> Self::Summary { match self { - DiffTransform::BufferContent { row_count, .. } => DiffTransformSummary { - input_row_count: *row_count, - output_row_count: *row_count, - transform_count: 1, + DiffTransform::BufferContent { summary } => DiffTransformSummary { + input: summary.clone(), + output: summary.clone(), }, - DiffTransform::DeletedHunk { row_count, .. } => DiffTransformSummary { - input_row_count: 0, - output_row_count: *row_count, - transform_count: 1, + DiffTransform::DeletedHunk { summary, .. } => DiffTransformSummary { + input: TextSummary::default(), + output: summary.clone(), }, } } @@ -233,22 +496,40 @@ impl sum_tree::Summary for DiffTransformSummary { fn zero(_: &Self::Context) -> Self { DiffTransformSummary { - input_row_count: 0, - output_row_count: 0, - transform_count: 0, + input: TextSummary::default(), + output: TextSummary::default(), } } fn add_summary(&mut self, summary: &Self, _: &Self::Context) { - self.input_row_count += summary.input_row_count; - self.output_row_count += summary.output_row_count; - self.transform_count += summary.transform_count; + self.input += &summary.input; + self.output += &summary.output; + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for FoldOffset { + fn zero(_: &()) -> Self { + FoldOffset(0) + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + self.0 += summary.input.len + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { + fn zero(_: &()) -> Self { + DiffOffset(0) + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + self.0 += summary.output.len } } -impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for InputRowCount { - fn cmp(&self, cursor_location: &DiffTransformSummary, _: &()) -> std::cmp::Ordering { - Ord::cmp(&self.0, &cursor_location.input_row_count) +impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for FoldPoint { + fn cmp(&self, cursor_location: &DiffTransformSummary, _: &()) -> Ordering { + Ord::cmp(&self.0, &cursor_location.input.lines) } } @@ -264,21 +545,23 @@ impl<'a> Iterator for DiffMapBufferRows<'a> { mod tests { use super::*; use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap}; - use gpui::AppContext; + use gpui::{AppContext, TestAppContext}; use indoc::indoc; use multi_buffer::MultiBuffer; use project::Project; use settings::SettingsStore; #[gpui::test] - fn test_basic_diff(cx: &mut gpui::AppContext) { - init_test(cx); + fn test_basic_diff(cx: &mut TestAppContext) { + cx.update(init_test); let text = indoc!( " + ERO one - two - five + TWO + three + six " ); @@ -302,11 +585,43 @@ mod tests { }); let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); - let buffer_snapshot = buffer.read(cx).snapshot(cx); + let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); - let (diff_map, _) = DiffMap::new(fold_snapshot, cx); + let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot, buffer, cx)); diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); + cx.run_until_parked(); + + assert_eq!( + diff_map.update(cx, |diff_map, cx| diff_map.snapshot().text()), + indoc!( + " + ZERO + one + TWO + three + six + " + ) + ); + + diff_map.update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(true, cx)); + + assert_eq!( + diff_map.update(cx, |diff_map, cx| diff_map.snapshot().text()), + indoc!( + " + ZERO + one + two + TWO + three + four + five + six + " + ) + ); } fn init_test(cx: &mut AppContext) { diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index c4bb4080e2613f..0eb4fa532c9e6b 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -641,6 +641,10 @@ impl FoldSnapshot { summary } + pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { + self.to_fold_point(self.inlay_snapshot.to_inlay_point(point), bias) + } + pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&()); cursor.seek(&point, Bias::Right, &()); diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 86fa492712a066..f99b65db61536e 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -317,8 +317,7 @@ impl TabSnapshot { } pub fn make_tab_point(&self, point: Point, bias: Bias) -> TabPoint { - let inlay_point = self.fold_snapshot.inlay_snapshot.to_inlay_point(point); - let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + let fold_point = self.fold_snapshot.make_fold_point(point, bias); self.to_tab_point(fold_point) } diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 60b01bc65f050a..f95a13b177aa01 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1543,6 +1543,36 @@ impl MultiBuffer { excerpts } + pub fn ranges_for_buffer( + &self, + buffer_id: BufferId, + cx: &AppContext, + ) -> Vec<(ExcerptId, Range, Range)> { + let mut ranges = Vec::new(); + let snapshot = self.read(cx); + for locator in self + .buffers + .borrow() + .get(&buffer_id) + .map(|state| &state.excerpts) + .into_iter() + .flatten() + { + let mut cursor = snapshot.excerpts.cursor::<(Option<&Locator>, Point)>(&()); + cursor.seek_forward(&Some(locator), Bias::Left, &()); + if let Some(excerpt) = cursor.item() { + if excerpt.locator == *locator { + ranges.push(( + excerpt.id, + cursor.start().1..cursor.end(&()).1, + excerpt.range.context.clone(), + )); + } + } + } + ranges + } + pub fn excerpt_buffer_ids(&self) -> Vec { self.snapshot .borrow() From 90c9ad4fd6fe9e2b1e3cbf623881823daf303f6f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 5 Dec 2024 14:30:59 -0800 Subject: [PATCH 03/51] Get DiffMapChunks working to some degree Co-authored-by: Conrad Co-authored-by: Mikayla --- crates/editor/src/display_map/diff_map.rs | 51 ++++++++++++++++------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 2eca3ca8e84941..03b2856a8e1f3f 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -209,7 +209,12 @@ impl DiffMap { .make_fold_point(multibuffer_range.end, Bias::Right); new_transforms.append(cursor.slice(&start, Bias::Left, &()), &()); - let old_tree = cursor.slice(&end, Bias::Left, &()); + start = FoldPoint(new_transforms.summary().input.lines); + let mut old_tree = cursor.slice(&end, Bias::Right, &()); + if cursor.start().input.lines < end.0 { + old_tree.extend(cursor.item().cloned(), &()); + cursor.next(&()); + } let old_expanded_hunk_anchors = old_tree .iter() .filter_map(|transform| { @@ -231,7 +236,7 @@ impl DiffMap { text_anchor: hunk.buffer_range.start, }; if !old_expanded_hunk_anchors.contains(&hunk_start_anchor) - || self.all_hunks_expanded + && !self.all_hunks_expanded { continue; } @@ -265,13 +270,13 @@ impl DiffMap { summary: self .snapshot .fold_snapshot - .text_summary_for_range(hunk_start..hunk_end), + .text_summary_for_range(start..hunk_start), }, &(), ); } - start = hunk_end; + start = hunk_start; new_transforms.push( DiffTransform::DeletedHunk { @@ -301,6 +306,9 @@ impl DiffMap { drop(cursor); self.snapshot.transforms = new_transforms; + + #[cfg(test)] + self.check_invariants(); } pub(super) fn expand_diff_hunks( @@ -325,6 +333,19 @@ impl DiffMap { fn snapshot(&self) -> DiffMapSnapshot { self.snapshot.clone() } + + #[cfg(test)] + fn check_invariants(&self) { + let snapshot = &self.snapshot; + if snapshot.transforms.summary().input.len != snapshot.fold_snapshot.len().0 { + panic!( + "incorrect input length. expected {}, got {}. transforms: {:+?}", + snapshot.fold_snapshot.len().0, + snapshot.transforms.summary().input.len, + snapshot.transforms.items(&()), + ); + } + } } impl DiffMapSnapshot { @@ -358,8 +379,8 @@ impl DiffMapSnapshot { highlights: Highlights<'a>, ) -> DiffMapChunks<'a> { let mut cursor = self.transforms.cursor::<(DiffOffset, FoldOffset)>(&()); - cursor.seek(&range.end, Bias::Right, &()); + cursor.seek(&range.end, Bias::Right, &()); let mut fold_end = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = range.end.0 - cursor.start().0 .0; @@ -369,7 +390,7 @@ impl DiffMapSnapshot { cursor.seek(&range.start, Bias::Right, &()); let mut fold_start = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { - let overshoot = range.end.0 - cursor.start().0 .0; + let overshoot = range.start.0 - cursor.start().0 .0; fold_start.0 += overshoot; } @@ -421,13 +442,9 @@ impl<'a> Iterator for DiffMapChunks<'a> { transform_end = self.end_offset } - if transform_end <= chunk_end { - self.cursor.next(&()); - } - if transform_end < chunk_end { - self.offset = transform_end; let (before, after) = chunk.text.split_at(transform_end.0 - self.offset.0); + self.offset = transform_end; chunk.text = after; Some(Chunk { text: before, @@ -557,7 +574,7 @@ mod tests { let text = indoc!( " - ERO + ZERO one TWO three @@ -588,12 +605,18 @@ mod tests { let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); - let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot, buffer, cx)); + let diff_map = cx.update(|cx| { + let (diff_map, _) = DiffMap::new(fold_snapshot, buffer, cx); + diff_map.update(cx, |diff_map, cx| { + diff_map.set_all_hunks_expanded(true, cx); + }); + diff_map + }); diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); cx.run_until_parked(); assert_eq!( - diff_map.update(cx, |diff_map, cx| diff_map.snapshot().text()), + diff_map.update(cx, |diff_map, _| diff_map.snapshot().text()), indoc!( " ZERO From 09082d103e1e03353af8ca893d64b58450592490 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 5 Dec 2024 14:37:16 -0800 Subject: [PATCH 04/51] Implement DiffMap::set_all_hunks_expanded Co-authored-by: Conrad Co-authored-by: Mikayla --- crates/editor/src/display_map/diff_map.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 03b2856a8e1f3f..e0614094fcb140 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -7,7 +7,7 @@ use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::MultiBuffer; use project::buffer_store::BufferChangeSet; -use std::{cmp::Ordering, ops::Range}; +use std::{cmp::Ordering, mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; use text::{Bias, BufferSnapshot, Point, TextSummary, ToPoint}; @@ -259,10 +259,6 @@ impl DiffMap { .snapshot .fold_snapshot .make_fold_point(hunk_start, Bias::Left); - let hunk_end = self - .snapshot - .fold_snapshot - .make_fold_point(hunk_end, Bias::Left); if hunk_start > start { new_transforms.push( @@ -327,6 +323,14 @@ impl DiffMap { pub(super) fn set_all_hunks_expanded(&mut self, expand_all: bool, cx: &mut ModelContext) { self.all_hunks_expanded = expand_all; + let change_sets = self + .diff_bases + .values() + .map(|state| state.change_set.clone()) + .collect::>(); + for change_set in change_sets { + self.buffer_diff_changed(change_set, cx) + } cx.notify() } @@ -605,13 +609,7 @@ mod tests { let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); - let diff_map = cx.update(|cx| { - let (diff_map, _) = DiffMap::new(fold_snapshot, buffer, cx); - diff_map.update(cx, |diff_map, cx| { - diff_map.set_all_hunks_expanded(true, cx); - }); - diff_map - }); + let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot, buffer, cx)); diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); cx.run_until_parked(); From d0a8b0f8b140b95940a74eccf6e1bd2f746e5a2c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 5 Dec 2024 15:38:55 -0800 Subject: [PATCH 05/51] Start work on returning edits from DiffMap::sync Co-authored-by: Conrad --- crates/editor/src/display_map/diff_map.rs | 136 ++++++++++++++++------ crates/text/src/patch.rs | 1 + 2 files changed, 102 insertions(+), 35 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index e0614094fcb140..f6ea8d9d380c31 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -2,39 +2,31 @@ use crate::{ display_map::fold_map::{FoldBufferRows, FoldOffset, FoldSnapshot}, FoldPoint, Highlights, }; -use collections::{HashMap, HashSet}; +use collections::HashMap; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::MultiBuffer; use project::buffer_store::BufferChangeSet; use std::{cmp::Ordering, mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; -use text::{Bias, BufferSnapshot, Point, TextSummary, ToPoint}; +use text::{Bias, Edit, Patch, Point, TextSummary, ToPoint}; use super::fold_map::{FoldChunks, FoldEdit}; #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DiffOffset(pub usize); -impl std::ops::Add for DiffOffset { - type Output = DiffOffset; - - fn add(self, rhs: DiffOffset) -> Self::Output { - DiffOffset(self.0 + rhs.0) - } -} - struct DiffMap { snapshot: DiffMapSnapshot, multibuffer: Model, diff_bases: HashMap, buffer_input_row_counts: Vec<(BufferId, u32)>, all_hunks_expanded: bool, + edits_since_sync: Patch, } struct ChangeSetState { change_set: Model, - last_version: Option, _subscription: Subscription, } @@ -132,6 +124,7 @@ impl DiffMap { snapshot: snapshot.clone(), all_hunks_expanded: false, diff_bases: HashMap::default(), + edits_since_sync: Patch::default(), }); (this, snapshot) @@ -148,7 +141,6 @@ impl DiffMap { buffer_id, ChangeSetState { _subscription: cx.observe(&change_set, Self::buffer_diff_changed), - last_version: None, change_set, }, ); @@ -159,7 +151,9 @@ impl DiffMap { fold_snapshot: FoldSnapshot, mut fold_edits: Vec, ) -> (DiffMapSnapshot, Vec) { - todo!() + let patch = mem::take(&mut self.edits_since_sync); + let edits = patch.into_inner(); + (self.snapshot(), edits) } fn buffer_diff_changed( @@ -210,23 +204,28 @@ impl DiffMap { new_transforms.append(cursor.slice(&start, Bias::Left, &()), &()); start = FoldPoint(new_transforms.summary().input.lines); - let mut old_tree = cursor.slice(&end, Bias::Right, &()); - if cursor.start().input.lines < end.0 { - old_tree.extend(cursor.item().cloned(), &()); + + let mut old_expanded_hunks = + HashMap::>::default(); + while cursor.start().input.lines < end.0 { + let Some(item) = cursor.item() else { + break; + }; + if let DiffTransform::DeletedHunk { + summary, + hunk_position, + .. + } = item + { + let range = DiffOffset(cursor.start().output.len) + ..DiffOffset(cursor.start().output.len + summary.len); + old_expanded_hunks.insert(*hunk_position, range); + } cursor.next(&()); } - let old_expanded_hunk_anchors = old_tree - .iter() - .filter_map(|transform| { - if let DiffTransform::DeletedHunk { hunk_position, .. } = transform { - Some(*hunk_position) - } else { - None - } - }) - .collect::>(); let excerpt_start = buffer_range.start.to_point(snapshot); + let mut new_hunk_edits = Patch::default(); if let Some(base_text) = &base_text { for hunk in hunks { @@ -235,9 +234,9 @@ impl DiffMap { buffer_id: Some(buffer_id), text_anchor: hunk.buffer_range.start, }; - if !old_expanded_hunk_anchors.contains(&hunk_start_anchor) - && !self.all_hunks_expanded - { + let was_previously_expanded = + old_expanded_hunks.remove(&hunk_start_anchor).is_some(); + if !was_previously_expanded && !self.all_hunks_expanded { continue; } @@ -247,14 +246,12 @@ impl DiffMap { let mut text_cursor = base_text.as_rope().cursor(0); let base_text_start = text_cursor.summary::(hunk.diff_base_byte_range.start); - let base_text_summary = text_cursor.summary(hunk.diff_base_byte_range.end); + let base_text_summary = + text_cursor.summary::(hunk.diff_base_byte_range.end); let hunk_start_in_excerpt = hunk.buffer_range.start.to_point(snapshot) - excerpt_start; - let hunk_end_in_excerpt = - hunk.buffer_range.end.to_point(snapshot) - excerpt_start; let hunk_start = multibuffer_range.start + hunk_start_in_excerpt; - let hunk_end = multibuffer_range.start + hunk_end_in_excerpt; let hunk_start = self .snapshot .fold_snapshot @@ -274,6 +271,15 @@ impl DiffMap { start = hunk_start; + if !was_previously_expanded { + let edit_start = DiffOffset(new_transforms.summary().output.len); + + new_hunk_edits.push(DiffEdit { + old: edit_start..edit_start, + new: edit_start..(edit_start + DiffOffset(base_text_summary.len)), + }); + } + new_transforms.push( DiffTransform::DeletedHunk { hunk_position: hunk_start_anchor, @@ -285,6 +291,20 @@ impl DiffMap { } } + let mut old_hunk_edits = old_expanded_hunks + .values() + .map(|range| Edit { + old: range.clone(), + new: range.start..range.start, + }) + .collect::>(); + old_hunk_edits.sort_by(|a, b| a.old.start.cmp(&b.old.start)); + + self.edits_since_sync = self + .edits_since_sync + .compose(Patch::new(old_hunk_edits)) + .compose(new_hunk_edits); + if end > start { new_transforms.push( DiffTransform::BufferContent { @@ -562,6 +582,28 @@ impl<'a> Iterator for DiffMapBufferRows<'a> { } } +impl std::ops::Add for DiffOffset { + type Output = DiffOffset; + + fn add(self, rhs: DiffOffset) -> Self::Output { + DiffOffset(self.0 + rhs.0) + } +} + +impl std::ops::AddAssign for DiffOffset { + fn add_assign(&mut self, rhs: DiffOffset) { + self.0 += rhs.0; + } +} + +impl std::ops::Sub for DiffOffset { + type Output = DiffOffset; + + fn sub(self, rhs: DiffOffset) -> Self::Output { + DiffOffset(self.0 - rhs.0) + } +} + #[cfg(test)] mod tests { use super::*; @@ -609,7 +651,7 @@ mod tests { let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); - let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot, buffer, cx)); + let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot.clone(), buffer, cx)); diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); cx.run_until_parked(); @@ -628,8 +670,11 @@ mod tests { diff_map.update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(true, cx)); + let (snapshot, edits) = + diff_map.update(cx, |diff_map, _| diff_map.sync(fold_snapshot, vec![])); + assert_eq!( - diff_map.update(cx, |diff_map, cx| diff_map.snapshot().text()), + snapshot.text(), indoc!( " ZERO @@ -643,6 +688,27 @@ mod tests { " ) ); + + let six = DiffOffset(text.find("six").unwrap()); + let two = DiffOffset(text.find("TWO").unwrap()); + let new_two = DiffOffset(snapshot.text().find("two").unwrap()); + let new_ttwwoo = DiffOffset(snapshot.text().find("TWO").unwrap()); + let new_four = DiffOffset(snapshot.text().find("four").unwrap()); + let new_six = DiffOffset(snapshot.text().find("six").unwrap()); + + assert_eq!( + edits, + vec![ + Edit { + old: two..two, + new: new_two..new_ttwwoo + }, + Edit { + old: six..six, + new: new_four..new_six + } + ] + ); } fn init_test(cx: &mut AppContext) { diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index bb71a80c5128b9..ff1d0c2e9ab098 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -42,6 +42,7 @@ where self.0 } + #[must_use] pub fn compose(&self, new_edits_iter: impl IntoIterator>) -> Self { let mut old_edits_iter = self.0.iter().cloned().peekable(); let mut new_edits_iter = new_edits_iter.into_iter().peekable(); From 440b632b09886243f107253c427f5150181cc56b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 5 Dec 2024 17:58:53 -0800 Subject: [PATCH 06/51] Start work on expanding and collapsing hunks --- crates/editor/src/display_map/diff_map.rs | 274 +++++++++++++--------- crates/multi_buffer/src/multi_buffer.rs | 4 + 2 files changed, 163 insertions(+), 115 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index f6ea8d9d380c31..c5b7f1293ebca7 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,26 +1,19 @@ -use crate::{ - display_map::fold_map::{FoldBufferRows, FoldOffset, FoldSnapshot}, - FoldPoint, Highlights, -}; +use super::fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldSnapshot}; +use crate::{FoldPoint, Highlights}; use collections::HashMap; +use git::diff::BufferDiff; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; -use multi_buffer::MultiBuffer; +use multi_buffer::{AnchorRangeExt, ExcerptId, MultiBuffer}; use project::buffer_store::BufferChangeSet; use std::{cmp::Ordering, mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; -use text::{Bias, Edit, Patch, Point, TextSummary, ToPoint}; - -use super::fold_map::{FoldChunks, FoldEdit}; - -#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct DiffOffset(pub usize); +use text::{Bias, Edit, Patch, Point, TextSummary, ToPoint as _}; struct DiffMap { snapshot: DiffMapSnapshot, multibuffer: Model, diff_bases: HashMap, - buffer_input_row_counts: Vec<(BufferId, u32)>, all_hunks_expanded: bool, edits_since_sync: Patch, } @@ -80,6 +73,9 @@ struct DiffMapBufferRows<'a> { pub type DiffEdit = text::Edit; +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DiffOffset(pub usize); + impl DiffMap { pub fn new( fold_snapshot: FoldSnapshot, @@ -92,34 +88,7 @@ impl DiffMap { fold_snapshot, }; - // Determine the extents of every run of excerpts associated with a - // single buffer. - let mut buffer_input_row_counts = Vec::new(); - let mut current_buffer_and_start_row = None; - let fold_snapshot = &snapshot.fold_snapshot; - let inlay_snapshot = &fold_snapshot.inlay_snapshot; - let buffer_snapshot = &inlay_snapshot.buffer; - for excerpt in buffer_snapshot.all_excerpts().map(Some).chain([None]) { - let buffer_id = excerpt.as_ref().map(|e| e.buffer().remote_id()); - let position = excerpt.map_or(buffer_snapshot.max_point(), |e| e.start_point()); - let position = inlay_snapshot.to_inlay_point(position); - let position = fold_snapshot.to_fold_point(position, Bias::Right); - let row = position.row(); - if let Some((prev_buffer_id, prev_start_row)) = ¤t_buffer_and_start_row { - if buffer_id != Some(*prev_buffer_id) { - buffer_input_row_counts.push((*prev_buffer_id, row - *prev_start_row)); - current_buffer_and_start_row.take(); - } - } - if current_buffer_and_start_row.is_none() { - if let Some(buffer_id) = buffer_id { - current_buffer_and_start_row = Some((buffer_id, row)); - } - } - } - let this = cx.new_model(|_| Self { - buffer_input_row_counts, multibuffer, snapshot: snapshot.clone(), all_hunks_expanded: false, @@ -149,11 +118,11 @@ impl DiffMap { pub fn sync( &mut self, fold_snapshot: FoldSnapshot, - mut fold_edits: Vec, + fold_edits: Vec, ) -> (DiffMapSnapshot, Vec) { let patch = mem::take(&mut self.edits_since_sync); let edits = patch.into_inner(); - (self.snapshot(), edits) + (self.snapshot.clone(), edits) } fn buffer_diff_changed( @@ -181,18 +150,97 @@ impl DiffMap { self.snapshot.diffs.remove(&buffer_id); } - let Some(buffer) = self.multibuffer.read(cx).buffer(buffer_id) else { - return; - }; + let multibuffer = self.multibuffer.read(cx); + let changed_ranges = multibuffer + .ranges_for_buffer(buffer_id, cx) + .into_iter() + .map(|(excerpt_id, range, buffer_range)| (excerpt_id, buffer_id, range, buffer_range)) + .collect(); + self.recompute_expanded_hunks(changed_ranges, cx); + } - let mut cursor = self.snapshot.transforms.cursor::(&()); + pub(super) fn expand_diff_hunks( + &mut self, + ranges: Vec>, + cx: &mut ModelContext, + ) { + let mut changed_ranges = Vec::new(); + { + let multibuffer = self.multibuffer.read(cx); + let snapshot = multibuffer.read(cx); + for range in &ranges { + let multibuffer_range = range.to_point(&*snapshot); + for (buffer, buffer_range, excerpt_id) in + multibuffer.range_to_buffer_ranges(range.clone(), cx) + { + let buffer = buffer.read(cx); + if let Some(excerpt_range) = snapshot.range_for_excerpt::(excerpt_id) { + changed_ranges.push(( + excerpt_id, + buffer.remote_id(), + multibuffer_range.start.max(excerpt_range.start) + ..multibuffer_range.end.min(excerpt_range.end), + buffer.anchor_before(buffer_range.start) + ..buffer.anchor_after(buffer_range.end), + )); + } + } + } + } + + self.recompute_expanded_hunks(changed_ranges, cx); + } + + pub(super) fn collapse_diff_hunks( + &mut self, + multi_buffer_range: Range, + cx: &mut ModelContext, + ) { + } + + pub(super) fn set_all_hunks_expanded(&mut self, expand_all: bool, cx: &mut ModelContext) { + self.all_hunks_expanded = expand_all; + let changes = self + .multibuffer + .read(cx) + .read(cx) + .all_excerpts() + .map(|excerpt| { + ( + excerpt.id(), + excerpt.buffer().remote_id(), + excerpt.start_point()..excerpt.end_point(), + excerpt.buffer_range(), + ) + }) + .collect(); + self.recompute_expanded_hunks(changes, cx); + } + + fn recompute_expanded_hunks( + &mut self, + ranges: Vec<(ExcerptId, BufferId, Range, Range)>, + cx: &mut ModelContext, + ) { + let mut cursor = self + .snapshot + .transforms + .cursor::<(FoldPoint, DiffOffset)>(&()); let mut new_transforms = SumTree::default(); - let snapshot = buffer.read(cx); - for (excerpt_id, multibuffer_range, buffer_range) in - self.multibuffer.read(cx).ranges_for_buffer(buffer_id, cx) - { - let hunks = diff.hunks_intersecting_range(buffer_range.clone(), snapshot); + for (excerpt_id, buffer_id, multibuffer_range, buffer_range) in ranges { + let Some(buffer) = self.multibuffer.read(cx).buffer(buffer_id) else { + continue; + }; + let buffer = buffer.read(cx); + + let change_set_state = self.snapshot.diffs.get(&buffer_id); + let diff = change_set_state + .map(|state| state.diff.clone()) + .unwrap_or_else(|| BufferDiff::new(buffer)); + let base_text = change_set_state.map(|state| state.base_text.clone()); + + let hunks = diff.hunks_intersecting_range(buffer_range.clone(), buffer); let mut start = self .snapshot .fold_snapshot @@ -207,24 +255,18 @@ impl DiffMap { let mut old_expanded_hunks = HashMap::>::default(); - while cursor.start().input.lines < end.0 { + while cursor.start().0 < end { let Some(item) = cursor.item() else { break; }; - if let DiffTransform::DeletedHunk { - summary, - hunk_position, - .. - } = item - { - let range = DiffOffset(cursor.start().output.len) - ..DiffOffset(cursor.start().output.len + summary.len); + if let DiffTransform::DeletedHunk { hunk_position, .. } = item { + let range = cursor.start().1..cursor.end(&()).1; old_expanded_hunks.insert(*hunk_position, range); } cursor.next(&()); } - let excerpt_start = buffer_range.start.to_point(snapshot); + let excerpt_start = buffer_range.start.to_point(buffer); let mut new_hunk_edits = Patch::default(); if let Some(base_text) = &base_text { @@ -250,7 +292,7 @@ impl DiffMap { text_cursor.summary::(hunk.diff_base_byte_range.end); let hunk_start_in_excerpt = - hunk.buffer_range.start.to_point(snapshot) - excerpt_start; + hunk.buffer_range.start.to_point(buffer) - excerpt_start; let hunk_start = multibuffer_range.start + hunk_start_in_excerpt; let hunk_start = self .snapshot @@ -319,45 +361,14 @@ impl DiffMap { } new_transforms.append(cursor.suffix(&()), &()); - drop(cursor); self.snapshot.transforms = new_transforms; + cx.notify(); #[cfg(test)] self.check_invariants(); } - pub(super) fn expand_diff_hunks( - &mut self, - multi_buffer_range: Range, - cx: &mut ModelContext, - ) { - } - - pub(super) fn collapse_diff_hunks( - &mut self, - multi_buffer_range: Range, - cx: &mut ModelContext, - ) { - } - - pub(super) fn set_all_hunks_expanded(&mut self, expand_all: bool, cx: &mut ModelContext) { - self.all_hunks_expanded = expand_all; - let change_sets = self - .diff_bases - .values() - .map(|state| state.change_set.clone()) - .collect::>(); - for change_set in change_sets { - self.buffer_diff_changed(change_set, cx) - } - cx.notify() - } - - fn snapshot(&self) -> DiffMapSnapshot { - self.snapshot.clone() - } - #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; @@ -568,6 +579,16 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { } } +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for FoldPoint { + fn zero(_: &()) -> Self { + FoldPoint(Point::zero()) + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + self.0 += summary.input.lines + } +} + impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for FoldPoint { fn cmp(&self, cursor_location: &DiffTransformSummary, _: &()) -> Ordering { Ord::cmp(&self.0, &cursor_location.input.lines) @@ -627,7 +648,6 @@ mod tests { six " ); - let base_text = indoc!( " one @@ -638,6 +658,7 @@ mod tests { six " ); + let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); let change_set = cx.new_model(|cx| { BufferChangeSet::new_with_base_text( @@ -655,8 +676,12 @@ mod tests { diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); cx.run_until_parked(); + let (snapshot1, _) = diff_map.update(cx, |diff_map, _| { + diff_map.sync(fold_snapshot.clone(), vec![]) + }); + assert_eq!( - diff_map.update(cx, |diff_map, _| diff_map.snapshot().text()), + snapshot1.text(), indoc!( " ZERO @@ -670,11 +695,12 @@ mod tests { diff_map.update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(true, cx)); - let (snapshot, edits) = - diff_map.update(cx, |diff_map, _| diff_map.sync(fold_snapshot, vec![])); + let (snapshot2, edits) = diff_map.update(cx, |diff_map, _| { + diff_map.sync(fold_snapshot.clone(), vec![]) + }); assert_eq!( - snapshot.text(), + snapshot2.text(), indoc!( " ZERO @@ -689,26 +715,44 @@ mod tests { ) ); - let six = DiffOffset(text.find("six").unwrap()); - let two = DiffOffset(text.find("TWO").unwrap()); - let new_two = DiffOffset(snapshot.text().find("two").unwrap()); - let new_ttwwoo = DiffOffset(snapshot.text().find("TWO").unwrap()); - let new_four = DiffOffset(snapshot.text().find("four").unwrap()); - let new_six = DiffOffset(snapshot.text().find("six").unwrap()); + check_edits(&snapshot1, &snapshot2, &edits); + + diff_map.update(cx, |diff_map, cx| { + diff_map.set_all_hunks_expanded(false, cx) + }); + let (snapshot3, edits) = diff_map.update(cx, |diff_map, _| { + diff_map.sync(fold_snapshot.clone(), vec![]) + }); assert_eq!( - edits, - vec![ - Edit { - old: two..two, - new: new_two..new_ttwwoo - }, - Edit { - old: six..six, - new: new_four..new_six - } - ] + snapshot3.text(), + indoc!( + " + ZERO + one + TWO + three + six + " + ) ); + check_edits(&snapshot2, &snapshot3, &edits); + } + + fn check_edits( + old_snapshot: &DiffMapSnapshot, + new_snapshot: &DiffMapSnapshot, + edits: &[DiffEdit], + ) { + let mut text = old_snapshot.text(); + let new_text = new_snapshot.text(); + for edit in edits.iter().rev() { + text.replace_range( + edit.old.start.0..edit.old.end.0, + &new_text[edit.new.start.0..edit.new.end.0], + ); + } + pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits); } fn init_test(cx: &mut AppContext) { diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index f95a13b177aa01..df769ae9932fb8 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -4668,6 +4668,10 @@ impl<'a> MultiBufferExcerpt<'a> { self.excerpt_position } + pub fn end_point(&self) -> Point { + self.excerpt_position + self.excerpt.text_summary.lines + } + /// Maps an offset within the [`MultiBuffer`] to an offset within the [`Buffer`] pub fn map_offset_to_buffer(&self, offset: usize) -> usize { self.excerpt.buffer_start_offset() From 011880c48ca797a4835a2775d44649a4bab981d8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Dec 2024 11:55:13 -0800 Subject: [PATCH 07/51] Start work on expanding and collapsing hunks by range Co-authored-by: Cole --- crates/editor/src/display_map/diff_map.rs | 253 +++++++++++++--------- 1 file changed, 146 insertions(+), 107 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index c5b7f1293ebca7..c91107d7b1af27 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -4,7 +4,7 @@ use collections::HashMap; use git::diff::BufferDiff; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; -use multi_buffer::{AnchorRangeExt, ExcerptId, MultiBuffer}; +use multi_buffer::{Anchor, AnchorRangeExt, ExcerptId, MultiBuffer}; use project::buffer_store::BufferChangeSet; use std::{cmp::Ordering, mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; @@ -73,6 +73,21 @@ struct DiffMapBufferRows<'a> { pub type DiffEdit = text::Edit; +enum ExpandedHunkChange { + BufferDiffUpdated { + change_set: Model, + }, + Edited { + edits: Vec, + }, + ExpandHunks { + ranges: Vec>, + }, + CollapseHunks { + ranges: Vec>, + }, +} + #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DiffOffset(pub usize); @@ -130,33 +145,7 @@ impl DiffMap { change_set: Model, cx: &mut ModelContext, ) { - let change_set = change_set.read(cx); - let buffer_id = change_set.buffer_id; - let diff = change_set.diff_to_buffer.clone(); - let base_text = change_set - .base_text - .as_ref() - .map(|buffer| buffer.read(cx).snapshot()); - - if let Some(base_text) = base_text.clone() { - self.snapshot.diffs.insert( - buffer_id, - DiffSnapshot { - diff: diff.clone(), - base_text, - }, - ); - } else { - self.snapshot.diffs.remove(&buffer_id); - } - - let multibuffer = self.multibuffer.read(cx); - let changed_ranges = multibuffer - .ranges_for_buffer(buffer_id, cx) - .into_iter() - .map(|(excerpt_id, range, buffer_range)| (excerpt_id, buffer_id, range, buffer_range)) - .collect(); - self.recompute_expanded_hunks(changed_ranges, cx); + self.recompute_expanded_hunks(ExpandedHunkChange::BufferDiffUpdated { change_set }, cx); } pub(super) fn expand_diff_hunks( @@ -164,71 +153,102 @@ impl DiffMap { ranges: Vec>, cx: &mut ModelContext, ) { - let mut changed_ranges = Vec::new(); - { - let multibuffer = self.multibuffer.read(cx); - let snapshot = multibuffer.read(cx); - for range in &ranges { - let multibuffer_range = range.to_point(&*snapshot); - for (buffer, buffer_range, excerpt_id) in - multibuffer.range_to_buffer_ranges(range.clone(), cx) - { - let buffer = buffer.read(cx); - if let Some(excerpt_range) = snapshot.range_for_excerpt::(excerpt_id) { - changed_ranges.push(( - excerpt_id, - buffer.remote_id(), - multibuffer_range.start.max(excerpt_range.start) - ..multibuffer_range.end.min(excerpt_range.end), - buffer.anchor_before(buffer_range.start) - ..buffer.anchor_after(buffer_range.end), - )); - } - } - } - } - - self.recompute_expanded_hunks(changed_ranges, cx); + self.recompute_expanded_hunks(ExpandedHunkChange::ExpandHunks { ranges }, cx); } pub(super) fn collapse_diff_hunks( &mut self, - multi_buffer_range: Range, + ranges: Vec>, cx: &mut ModelContext, ) { + self.recompute_expanded_hunks(ExpandedHunkChange::CollapseHunks { ranges }, cx); } - pub(super) fn set_all_hunks_expanded(&mut self, expand_all: bool, cx: &mut ModelContext) { - self.all_hunks_expanded = expand_all; - let changes = self - .multibuffer - .read(cx) - .read(cx) - .all_excerpts() - .map(|excerpt| { - ( - excerpt.id(), - excerpt.buffer().remote_id(), - excerpt.start_point()..excerpt.end_point(), - excerpt.buffer_range(), - ) - }) - .collect(); - self.recompute_expanded_hunks(changes, cx); + pub(super) fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext) { + self.all_hunks_expanded = true; + self.recompute_expanded_hunks( + ExpandedHunkChange::ExpandHunks { + ranges: vec![Anchor::min()..Anchor::max()], + }, + cx, + ); } fn recompute_expanded_hunks( &mut self, - ranges: Vec<(ExcerptId, BufferId, Range, Range)>, + change: ExpandedHunkChange, cx: &mut ModelContext, ) { + let multibuffer = self.multibuffer.read(cx); + let multibuffer_snapshot = multibuffer.snapshot(cx); + + let affected_ranges: Vec<_> = match &change { + ExpandedHunkChange::BufferDiffUpdated { change_set } => { + let change_set = change_set.read(cx); + let buffer_id = change_set.buffer_id; + let diff = change_set.diff_to_buffer.clone(); + let base_text = change_set + .base_text + .as_ref() + .map(|buffer| buffer.read(cx).snapshot()); + + if let Some(base_text) = base_text.clone() { + self.snapshot.diffs.insert( + buffer_id, + DiffSnapshot { + diff: diff.clone(), + base_text, + }, + ); + } else { + self.snapshot.diffs.remove(&buffer_id); + } + + let multibuffer = self.multibuffer.read(cx); + multibuffer + .ranges_for_buffer(buffer_id, cx) + .into_iter() + .map(|(excerpt_id, range, buffer_range)| { + (excerpt_id, buffer_id, range, buffer_range) + }) + .collect() + } + ExpandedHunkChange::Edited { edits } => todo!(), + ExpandedHunkChange::ExpandHunks { ranges } + | ExpandedHunkChange::CollapseHunks { ranges } => { + let mut changed_ranges = Vec::new(); + for range in ranges.iter() { + let multibuffer_range = range.to_point(&multibuffer_snapshot); + for (buffer, buffer_range, excerpt_id) in + multibuffer.range_to_buffer_ranges(range.clone(), cx) + { + let buffer = buffer.read(cx); + if let Some(excerpt_range) = + multibuffer_snapshot.range_for_excerpt::(excerpt_id) + { + changed_ranges.push(( + excerpt_id, + buffer.remote_id(), + multibuffer_range.start.max(excerpt_range.start) + ..multibuffer_range.end.min(excerpt_range.end), + buffer.anchor_before(buffer_range.start) + ..buffer.anchor_after(buffer_range.end), + )); + } + } + } + changed_ranges + } + }; + let mut cursor = self .snapshot .transforms .cursor::<(FoldPoint, DiffOffset)>(&()); let mut new_transforms = SumTree::default(); + let mut edits = Patch::default(); - for (excerpt_id, buffer_id, multibuffer_range, buffer_range) in ranges { + for (excerpt_id, buffer_id, multibuffer_range, buffer_range) in affected_ranges { let Some(buffer) = self.multibuffer.read(cx).buffer(buffer_id) else { continue; }; @@ -241,21 +261,23 @@ impl DiffMap { let base_text = change_set_state.map(|state| state.base_text.clone()); let hunks = diff.hunks_intersecting_range(buffer_range.clone(), buffer); - let mut start = self + let mut affected_range_start = self .snapshot .fold_snapshot .make_fold_point(multibuffer_range.start, Bias::Left); - let end = self + let affected_range_end = self .snapshot .fold_snapshot .make_fold_point(multibuffer_range.end, Bias::Right); - new_transforms.append(cursor.slice(&start, Bias::Left, &()), &()); - start = FoldPoint(new_transforms.summary().input.lines); + new_transforms.append(cursor.slice(&affected_range_start, Bias::Left, &()), &()); + affected_range_start = FoldPoint(new_transforms.summary().input.lines); + + // let edit_old_start = (); let mut old_expanded_hunks = HashMap::>::default(); - while cursor.start().0 < end { + while cursor.start().0 < affected_range_end { let Some(item) = cursor.item() else { break; }; @@ -267,7 +289,6 @@ impl DiffMap { } let excerpt_start = buffer_range.start.to_point(buffer); - let mut new_hunk_edits = Patch::default(); if let Some(base_text) = &base_text { for hunk in hunks { @@ -276,15 +297,41 @@ impl DiffMap { buffer_id: Some(buffer_id), text_anchor: hunk.buffer_range.start, }; + let hunk_end_anchor = multi_buffer::Anchor { + excerpt_id, + buffer_id: Some(buffer_id), + text_anchor: hunk.buffer_range.end, + }; + let was_previously_expanded = - old_expanded_hunks.remove(&hunk_start_anchor).is_some(); - if !was_previously_expanded && !self.all_hunks_expanded { - continue; - } + old_expanded_hunks.contains_key(&hunk_start_anchor); + let mut should_expand_hunk = was_previously_expanded || self.all_hunks_expanded; + + match &change { + ExpandedHunkChange::ExpandHunks { ranges } => { + should_expand_hunk |= ranges.iter().any(|range| { + range.overlaps( + &(hunk_start_anchor..hunk_end_anchor), + &multibuffer_snapshot, + ) + }) + } + ExpandedHunkChange::CollapseHunks { ranges } => { + should_expand_hunk &= !ranges.iter().any(|range| { + range.overlaps( + &(hunk_start_anchor..hunk_end_anchor), + &multibuffer_snapshot, + ) + }) + } + _ => {} + }; - if hunk.diff_base_byte_range.len() == 0 { + if !should_expand_hunk || hunk.diff_base_byte_range.len() == 0 { continue; } + + old_expanded_hunks.remove(&hunk_start_anchor); let mut text_cursor = base_text.as_rope().cursor(0); let base_text_start = text_cursor.summary::(hunk.diff_base_byte_range.start); @@ -299,24 +346,25 @@ impl DiffMap { .fold_snapshot .make_fold_point(hunk_start, Bias::Left); - if hunk_start > start { + if hunk_start > affected_range_start { new_transforms.push( DiffTransform::BufferContent { summary: self .snapshot .fold_snapshot - .text_summary_for_range(start..hunk_start), + .text_summary_for_range(affected_range_start..hunk_start), }, &(), ); } - start = hunk_start; + affected_range_start = hunk_start; if !was_previously_expanded { + let edit_old_start = cursor.start().1; let edit_start = DiffOffset(new_transforms.summary().output.len); - new_hunk_edits.push(DiffEdit { + edits.push(DiffEdit { old: edit_start..edit_start, new: edit_start..(edit_start + DiffOffset(base_text_summary.len)), }); @@ -333,33 +381,21 @@ impl DiffMap { } } - let mut old_hunk_edits = old_expanded_hunks - .values() - .map(|range| Edit { - old: range.clone(), - new: range.start..range.start, - }) - .collect::>(); - old_hunk_edits.sort_by(|a, b| a.old.start.cmp(&b.old.start)); - - self.edits_since_sync = self - .edits_since_sync - .compose(Patch::new(old_hunk_edits)) - .compose(new_hunk_edits); - - if end > start { + if affected_range_end > affected_range_start { new_transforms.push( DiffTransform::BufferContent { summary: self .snapshot .fold_snapshot - .text_summary_for_range(start..end), + .text_summary_for_range(affected_range_start..affected_range_end), }, &(), ); } } + self.edits_since_sync = self.edits_since_sync.compose(edits); + new_transforms.append(cursor.suffix(&()), &()); drop(cursor); self.snapshot.transforms = new_transforms; @@ -631,7 +667,7 @@ mod tests { use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap}; use gpui::{AppContext, TestAppContext}; use indoc::indoc; - use multi_buffer::MultiBuffer; + use multi_buffer::{Anchor, MultiBuffer}; use project::Project; use settings::SettingsStore; @@ -693,7 +729,9 @@ mod tests { ) ); - diff_map.update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(true, cx)); + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); let (snapshot2, edits) = diff_map.update(cx, |diff_map, _| { diff_map.sync(fold_snapshot.clone(), vec![]) @@ -718,7 +756,7 @@ mod tests { check_edits(&snapshot1, &snapshot2, &edits); diff_map.update(cx, |diff_map, cx| { - diff_map.set_all_hunks_expanded(false, cx) + diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); let (snapshot3, edits) = diff_map.update(cx, |diff_map, _| { @@ -739,6 +777,7 @@ mod tests { check_edits(&snapshot2, &snapshot3, &edits); } + #[track_caller] fn check_edits( old_snapshot: &DiffMapSnapshot, new_snapshot: &DiffMapSnapshot, From 946ae93f682d490f11d49553d392ce864695d6d6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 9 Dec 2024 16:26:16 -0800 Subject: [PATCH 08/51] Compute diff map edits in a single pass through the old and new trees Co-authored-by: Cole --- crates/editor/src/display_map/diff_map.rs | 357 +++++++++++++--------- crates/editor/src/display_map/fold_map.rs | 83 ++++- 2 files changed, 284 insertions(+), 156 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index c91107d7b1af27..32a40c1342f8c4 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,14 +1,13 @@ use super::fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldSnapshot}; use crate::{FoldPoint, Highlights}; use collections::HashMap; -use git::diff::BufferDiff; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; -use multi_buffer::{Anchor, AnchorRangeExt, ExcerptId, MultiBuffer}; +use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, ToOffset}; use project::buffer_store::BufferChangeSet; use std::{cmp::Ordering, mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; -use text::{Bias, Edit, Patch, Point, TextSummary, ToPoint as _}; +use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; struct DiffMap { snapshot: DiffMapSnapshot, @@ -43,7 +42,8 @@ enum DiffTransform { }, DeletedHunk { summary: TextSummary, - hunk_position: multi_buffer::Anchor, + buffer_id: BufferId, + base_text_byte_range: Range, base_text_start: Point, }, } @@ -73,9 +73,9 @@ struct DiffMapBufferRows<'a> { pub type DiffEdit = text::Edit; -enum ExpandedHunkChange { +enum DiffMapOperation { BufferDiffUpdated { - change_set: Model, + buffer_id: BufferId, }, Edited { edits: Vec, @@ -136,8 +136,7 @@ impl DiffMap { fold_edits: Vec, ) -> (DiffMapSnapshot, Vec) { let patch = mem::take(&mut self.edits_since_sync); - let edits = patch.into_inner(); - (self.snapshot.clone(), edits) + (self.snapshot.clone(), patch.into_inner()) } fn buffer_diff_changed( @@ -145,7 +144,26 @@ impl DiffMap { change_set: Model, cx: &mut ModelContext, ) { - self.recompute_expanded_hunks(ExpandedHunkChange::BufferDiffUpdated { change_set }, cx); + let change_set = change_set.read(cx); + let buffer_id = change_set.buffer_id; + let diff = change_set.diff_to_buffer.clone(); + let base_text = change_set + .base_text + .as_ref() + .map(|buffer| buffer.read(cx).snapshot()); + + if let Some(base_text) = base_text.clone() { + self.snapshot.diffs.insert( + buffer_id, + DiffSnapshot { + diff: diff.clone(), + base_text, + }, + ); + } else { + self.snapshot.diffs.remove(&buffer_id); + } + self.recompute_transforms(DiffMapOperation::BufferDiffUpdated { buffer_id }, cx); } pub(super) fn expand_diff_hunks( @@ -153,7 +171,7 @@ impl DiffMap { ranges: Vec>, cx: &mut ModelContext, ) { - self.recompute_expanded_hunks(ExpandedHunkChange::ExpandHunks { ranges }, cx); + self.recompute_transforms(DiffMapOperation::ExpandHunks { ranges }, cx); } pub(super) fn collapse_diff_hunks( @@ -161,70 +179,56 @@ impl DiffMap { ranges: Vec>, cx: &mut ModelContext, ) { - self.recompute_expanded_hunks(ExpandedHunkChange::CollapseHunks { ranges }, cx); + self.recompute_transforms(DiffMapOperation::CollapseHunks { ranges }, cx); } pub(super) fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext) { self.all_hunks_expanded = true; - self.recompute_expanded_hunks( - ExpandedHunkChange::ExpandHunks { + self.recompute_transforms( + DiffMapOperation::ExpandHunks { ranges: vec![Anchor::min()..Anchor::max()], }, cx, ); } - fn recompute_expanded_hunks( + fn recompute_transforms( &mut self, - change: ExpandedHunkChange, + operation: DiffMapOperation, cx: &mut ModelContext, ) { let multibuffer = self.multibuffer.read(cx); let multibuffer_snapshot = multibuffer.snapshot(cx); - let affected_ranges: Vec<_> = match &change { - ExpandedHunkChange::BufferDiffUpdated { change_set } => { - let change_set = change_set.read(cx); - let buffer_id = change_set.buffer_id; - let diff = change_set.diff_to_buffer.clone(); - let base_text = change_set - .base_text - .as_ref() - .map(|buffer| buffer.read(cx).snapshot()); - - if let Some(base_text) = base_text.clone() { - self.snapshot.diffs.insert( - buffer_id, - DiffSnapshot { - diff: diff.clone(), - base_text, - }, - ); - } else { - self.snapshot.diffs.remove(&buffer_id); - } - + let affected_ranges: Vec<_> = match &operation { + DiffMapOperation::BufferDiffUpdated { buffer_id } => { let multibuffer = self.multibuffer.read(cx); multibuffer - .ranges_for_buffer(buffer_id, cx) + .ranges_for_buffer(*buffer_id, cx) .into_iter() .map(|(excerpt_id, range, buffer_range)| { - (excerpt_id, buffer_id, range, buffer_range) + ( + excerpt_id, + *buffer_id, + ToOffset::to_offset(&range.start, &multibuffer_snapshot) + ..ToOffset::to_offset(&range.end, &multibuffer_snapshot), + buffer_range, + ) }) .collect() } - ExpandedHunkChange::Edited { edits } => todo!(), - ExpandedHunkChange::ExpandHunks { ranges } - | ExpandedHunkChange::CollapseHunks { ranges } => { + DiffMapOperation::Edited { edits } => todo!(), + DiffMapOperation::ExpandHunks { ranges } + | DiffMapOperation::CollapseHunks { ranges } => { let mut changed_ranges = Vec::new(); for range in ranges.iter() { - let multibuffer_range = range.to_point(&multibuffer_snapshot); + let multibuffer_range = range.to_offset(&multibuffer_snapshot); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(range.clone(), cx) { let buffer = buffer.read(cx); if let Some(excerpt_range) = - multibuffer_snapshot.range_for_excerpt::(excerpt_id) + multibuffer_snapshot.range_for_excerpt::(excerpt_id) { changed_ranges.push(( excerpt_id, @@ -244,7 +248,7 @@ impl DiffMap { let mut cursor = self .snapshot .transforms - .cursor::<(FoldPoint, DiffOffset)>(&()); + .cursor::<(FoldOffset, DiffOffset)>(&()); let mut new_transforms = SumTree::default(); let mut edits = Patch::default(); @@ -252,46 +256,26 @@ impl DiffMap { let Some(buffer) = self.multibuffer.read(cx).buffer(buffer_id) else { continue; }; - let buffer = buffer.read(cx); - let change_set_state = self.snapshot.diffs.get(&buffer_id); - let diff = change_set_state - .map(|state| state.diff.clone()) - .unwrap_or_else(|| BufferDiff::new(buffer)); - let base_text = change_set_state.map(|state| state.base_text.clone()); - let hunks = diff.hunks_intersecting_range(buffer_range.clone(), buffer); - let mut affected_range_start = self + let buffer = buffer.read(cx); + let excerpt_start = buffer_range.start.to_offset(buffer); + let affected_range_start = self .snapshot .fold_snapshot - .make_fold_point(multibuffer_range.start, Bias::Left); + .make_fold_offset(multibuffer_range.start, Bias::Left); let affected_range_end = self .snapshot .fold_snapshot - .make_fold_point(multibuffer_range.end, Bias::Right); + .make_fold_offset(multibuffer_range.end, Bias::Right); new_transforms.append(cursor.slice(&affected_range_start, Bias::Left, &()), &()); - affected_range_start = FoldPoint(new_transforms.summary().input.lines); - - // let edit_old_start = (); - - let mut old_expanded_hunks = - HashMap::>::default(); - while cursor.start().0 < affected_range_end { - let Some(item) = cursor.item() else { - break; - }; - if let DiffTransform::DeletedHunk { hunk_position, .. } = item { - let range = cursor.start().1..cursor.end(&()).1; - old_expanded_hunks.insert(*hunk_position, range); - } - cursor.next(&()); - } - let excerpt_start = buffer_range.start.to_point(buffer); + if let Some(change_set_state) = change_set_state { + let diff = &change_set_state.diff; + let base_text = &change_set_state.base_text; - if let Some(base_text) = &base_text { - for hunk in hunks { + for hunk in diff.hunks_intersecting_range(buffer_range.clone(), buffer) { let hunk_start_anchor = multi_buffer::Anchor { excerpt_id, buffer_id: Some(buffer_id), @@ -302,13 +286,57 @@ impl DiffMap { buffer_id: Some(buffer_id), text_anchor: hunk.buffer_range.end, }; + let hunk_start_multibuffer_offset = multibuffer_range.start + + (hunk.buffer_range.start.to_offset(buffer) - excerpt_start); + let hunk_start_fold_offset = self + .snapshot + .fold_snapshot + .make_fold_offset(hunk_start_multibuffer_offset, Bias::Left); + + self.push_buffer_content_transform(&mut new_transforms, hunk_start_fold_offset); + + let mut prev_old_transform_start = cursor.start().1; + while cursor.start().0 < hunk_start_fold_offset { + let Some(item) = cursor.item() else { + break; + }; + if let DiffTransform::DeletedHunk { + base_text_byte_range, + .. + } = item + { + let old_range = prev_old_transform_start + ..prev_old_transform_start + DiffOffset(base_text_byte_range.len()); + let new_offset = DiffOffset(new_transforms.summary().output.len); + let edit = Edit { + old: old_range, + new: new_offset..new_offset, + }; + dbg!(&edit); + edits.push(edit); + } + prev_old_transform_start = cursor.start().1; + cursor.next(&()); + } - let was_previously_expanded = - old_expanded_hunks.contains_key(&hunk_start_anchor); - let mut should_expand_hunk = was_previously_expanded || self.all_hunks_expanded; + let mut was_previously_expanded = false; + if let Some(item) = cursor.item() { + if let DiffTransform::DeletedHunk { + base_text_byte_range, + .. + } = item + { + if cursor.start().0 == hunk_start_fold_offset + && *base_text_byte_range == hunk.diff_base_byte_range + { + was_previously_expanded = true; + } + } + } - match &change { - ExpandedHunkChange::ExpandHunks { ranges } => { + let mut should_expand_hunk = was_previously_expanded || self.all_hunks_expanded; + match &operation { + DiffMapOperation::ExpandHunks { ranges } => { should_expand_hunk |= ranges.iter().any(|range| { range.overlaps( &(hunk_start_anchor..hunk_end_anchor), @@ -316,7 +344,7 @@ impl DiffMap { ) }) } - ExpandedHunkChange::CollapseHunks { ranges } => { + DiffMapOperation::CollapseHunks { ranges } => { should_expand_hunk &= !ranges.iter().any(|range| { range.overlaps( &(hunk_start_anchor..hunk_end_anchor), @@ -326,72 +354,74 @@ impl DiffMap { } _ => {} }; - - if !should_expand_hunk || hunk.diff_base_byte_range.len() == 0 { - continue; + if hunk.diff_base_byte_range.len() == 0 { + should_expand_hunk = false; } - old_expanded_hunks.remove(&hunk_start_anchor); - let mut text_cursor = base_text.as_rope().cursor(0); - let base_text_start = - text_cursor.summary::(hunk.diff_base_byte_range.start); - let base_text_summary = - text_cursor.summary::(hunk.diff_base_byte_range.end); - - let hunk_start_in_excerpt = - hunk.buffer_range.start.to_point(buffer) - excerpt_start; - let hunk_start = multibuffer_range.start + hunk_start_in_excerpt; - let hunk_start = self - .snapshot - .fold_snapshot - .make_fold_point(hunk_start, Bias::Left); + if should_expand_hunk { + let mut text_cursor = base_text.as_rope().cursor(0); + let base_text_start = + text_cursor.summary::(hunk.diff_base_byte_range.start); + let base_text_summary = + text_cursor.summary::(hunk.diff_base_byte_range.end); + + if !was_previously_expanded { + let old_offset = prev_old_transform_start; + let new_start = DiffOffset(new_transforms.summary().output.len); + let edit = Edit { + old: old_offset..old_offset, + new: new_start + ..new_start + DiffOffset(hunk.diff_base_byte_range.len()), + }; + dbg!(&edit); + edits.push(edit); + } - if hunk_start > affected_range_start { new_transforms.push( - DiffTransform::BufferContent { - summary: self - .snapshot - .fold_snapshot - .text_summary_for_range(affected_range_start..hunk_start), + DiffTransform::DeletedHunk { + base_text_byte_range: hunk.diff_base_byte_range.clone(), + summary: base_text_summary, + buffer_id, + base_text_start, }, &(), ); + } else if was_previously_expanded { + let old_start = cursor.start().1; + let new_offset = DiffOffset(new_transforms.summary().output.len); + let edit = Edit { + old: old_start..old_start + DiffOffset(hunk.diff_base_byte_range.len()), + new: new_offset..new_offset, + }; + dbg!(&edit); + edits.push(edit); + cursor.next(&()); } + } - affected_range_start = hunk_start; - - if !was_previously_expanded { - let edit_old_start = cursor.start().1; - let edit_start = DiffOffset(new_transforms.summary().output.len); - - edits.push(DiffEdit { - old: edit_start..edit_start, - new: edit_start..(edit_start + DiffOffset(base_text_summary.len)), - }); + while cursor.start().0 < affected_range_end { + let Some(item) = cursor.item() else { + break; + }; + if let DiffTransform::DeletedHunk { + base_text_byte_range, + .. + } = item + { + let old_start = cursor.start().1; + let new_offset = DiffOffset(new_transforms.summary().output.len); + let edit = Edit { + old: old_start..old_start + DiffOffset(base_text_byte_range.len()), + new: new_offset..new_offset, + }; + dbg!(&edit); + edits.push(edit); } - - new_transforms.push( - DiffTransform::DeletedHunk { - hunk_position: hunk_start_anchor, - summary: base_text_summary, - base_text_start, - }, - &(), - ); + cursor.next(&()); } } - if affected_range_end > affected_range_start { - new_transforms.push( - DiffTransform::BufferContent { - summary: self - .snapshot - .fold_snapshot - .text_summary_for_range(affected_range_start..affected_range_end), - }, - &(), - ); - } + self.push_buffer_content_transform(&mut new_transforms, affected_range_end); } self.edits_since_sync = self.edits_since_sync.compose(edits); @@ -405,6 +435,36 @@ impl DiffMap { self.check_invariants(); } + fn push_buffer_content_transform( + &self, + new_transforms: &mut SumTree, + end_point: FoldOffset, + ) { + let summary_to_add = self.snapshot.fold_snapshot.text_summary_for_offset_range( + FoldOffset(new_transforms.summary().input.len)..end_point, + ); + if summary_to_add.len > 0 { + let mut did_extend = false; + new_transforms.update_last( + |last_transform| { + if let DiffTransform::BufferContent { summary } = last_transform { + did_extend = true; + *summary += summary_to_add.clone(); + } + }, + &(), + ); + if !did_extend { + new_transforms.push( + DiffTransform::BufferContent { + summary: summary_to_add, + }, + &(), + ) + } + } + } + #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; @@ -528,21 +588,22 @@ impl<'a> Iterator for DiffMapChunks<'a> { } DiffTransform::DeletedHunk { summary, - hunk_position, + buffer_id, base_text_start, + base_text_byte_range, + .. } => { - let buffer_id = hunk_position.buffer_id?; let base_buffer = &self.snapshot.diffs.get(&buffer_id)?.base_text; - let diff_base_start_offset = base_buffer.point_to_offset(*base_text_start); + let diff_base_start_offset = base_text_byte_range.start; + let diff_base_end_offset = base_text_byte_range.end; let diff_base_offset = diff_base_start_offset + self.offset.0 - self.cursor.start().0 .0; - let diff_base_end_offset = diff_base_start_offset + summary.len; let mut chunks = if let Some((_, mut chunks)) = self .diff_base_chunks .take() - .filter(|(id, _)| id == &buffer_id) + .filter(|(id, _)| id == buffer_id) { if chunks.offset() != diff_base_offset { chunks.seek(diff_base_offset..diff_base_end_offset); @@ -555,7 +616,7 @@ impl<'a> Iterator for DiffMapChunks<'a> { let chunk = chunks.next()?; self.offset.0 += chunk.text.len(); - self.diff_base_chunks = Some((buffer_id, chunks)); + self.diff_base_chunks = Some((*buffer_id, chunks)); Some(chunk) } } @@ -704,11 +765,11 @@ mod tests { ) }); - let buffer = cx.new_model(|cx| MultiBuffer::singleton(buffer, cx)); - let buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); + let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + let buffer_snapshot = multibuffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); - let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot.clone(), buffer, cx)); + let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot.clone(), multibuffer, cx)); diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); cx.run_until_parked(); @@ -786,11 +847,23 @@ mod tests { let mut text = old_snapshot.text(); let new_text = new_snapshot.text(); for edit in edits.iter().rev() { + if !text.is_char_boundary(edit.old.start.0) + || !text.is_char_boundary(edit.old.end.0) + || !new_text.is_char_boundary(edit.new.start.0) + || !new_text.is_char_boundary(edit.new.end.0) + { + panic!( + "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}", + edits, text, new_text + ); + } + text.replace_range( edit.old.start.0..edit.old.end.0, &new_text[edit.new.start.0..edit.new.end.0], ); } + pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits); } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 0eb4fa532c9e6b..c512c2bd435c32 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -641,10 +641,56 @@ impl FoldSnapshot { summary } + pub fn text_summary_for_offset_range(&self, range: Range) -> TextSummary { + let mut summary = TextSummary::default(); + + let mut cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&()); + cursor.seek(&range.start, Bias::Right, &()); + if let Some(transform) = cursor.item() { + let start_in_transform = range.start.0 - cursor.start().0 .0; + let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0; + if let Some(placeholder) = transform.placeholder.as_ref() { + summary = + TextSummary::from(&placeholder.text[start_in_transform..end_in_transform]); + } else { + let inlay_start = InlayOffset(cursor.start().1 .0 + start_in_transform); + let inlay_end = InlayOffset(cursor.start().1 .0 + end_in_transform); + summary = self + .inlay_snapshot + .text_summary_for_range(inlay_start..inlay_end); + } + } + + if range.end > cursor.end(&()).0 { + cursor.next(&()); + summary += &cursor + .summary::<_, TransformSummary>(&range.end, Bias::Right, &()) + .output; + if let Some(transform) = cursor.item() { + let end_in_transform = range.end.0 - cursor.start().0 .0; + if let Some(placeholder) = transform.placeholder.as_ref() { + summary += TextSummary::from(&placeholder.text[..end_in_transform]); + } else { + let inlay_start = cursor.start().1; + let inlay_end = InlayOffset(cursor.start().1 .0 + end_in_transform); + summary += self + .inlay_snapshot + .text_summary_for_range(inlay_start..inlay_end); + } + } + } + + summary + } + pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { self.to_fold_point(self.inlay_snapshot.to_inlay_point(point), bias) } + pub fn make_fold_offset(&self, buffer_offset: usize, bias: Bias) -> FoldOffset { + self.to_fold_offset(self.inlay_snapshot.to_inlay_offset(buffer_offset), bias) + } + pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&()); cursor.seek(&point, Bias::Right, &()); @@ -663,6 +709,24 @@ impl FoldSnapshot { } } + pub fn to_fold_offset(&self, inlay_offset: InlayOffset, bias: Bias) -> FoldOffset { + let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(&()); + cursor.seek(&inlay_offset, Bias::Right, &()); + if cursor.item().map_or(false, |t| t.is_fold()) { + if bias == Bias::Left || inlay_offset == cursor.start().0 { + cursor.start().1 + } else { + cursor.end(&()).1 + } + } else { + let overshoot = inlay_offset.0 - cursor.start().0 .0; + FoldOffset(cmp::min( + cursor.start().1 .0 + overshoot, + cursor.end(&()).1 .0, + )) + } + } + pub fn len(&self) -> FoldOffset { FoldOffset(self.transforms.summary().output.len) } @@ -1844,21 +1908,12 @@ mod tests { let text = snapshot.text(); for _ in 0..5 { - let start_row = rng.gen_range(0..=snapshot.max_point().row()); - let start_column = rng.gen_range(0..=snapshot.line_len(start_row)); - let end_row = rng.gen_range(0..=snapshot.max_point().row()); - let end_column = rng.gen_range(0..=snapshot.line_len(end_row)); - let mut start = - snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left); - let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right); - if start > end { - mem::swap(&mut start, &mut end); - } - - let lines = start..end; - let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot); + let end = + snapshot.clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Right); + let start = snapshot.clip_offset(FoldOffset(rng.gen_range(0..=end.0)), Left); + let bytes = start..end; assert_eq!( - snapshot.text_summary_for_range(lines), + snapshot.text_summary_for_offset_range(bytes.clone()), TextSummary::from(&text[bytes.start.0..bytes.end.0]) ) } From 023f9239b4b05ad5c5d541d108c741156df6b3ce Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Dec 2024 16:30:47 -0800 Subject: [PATCH 09/51] Restructure DiffMap::sync to prepare for handling buffer edits Co-authored-by: Cole --- crates/editor/src/display_map/diff_map.rs | 607 +++++++++++++--------- crates/multi_buffer/src/multi_buffer.rs | 11 + 2 files changed, 378 insertions(+), 240 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 32a40c1342f8c4..0986c9db5ad8dc 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,11 +1,11 @@ -use super::fold_map::{FoldBufferRows, FoldChunks, FoldEdit, FoldOffset, FoldSnapshot}; -use crate::{FoldPoint, Highlights}; +use super::inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlaySnapshot}; +use crate::{Highlights, InlayOffset}; use collections::HashMap; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, ToOffset}; use project::buffer_store::BufferChangeSet; -use std::{cmp::Ordering, mem, ops::Range}; +use std::{mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; @@ -32,7 +32,7 @@ struct DiffSnapshot { pub(crate) struct DiffMapSnapshot { diffs: TreeMap, transforms: SumTree, - fold_snapshot: FoldSnapshot, + inlay_snapshot: InlaySnapshot, } #[derive(Debug, Clone)] @@ -57,10 +57,10 @@ struct DiffTransformSummary { struct DiffMapChunks<'a> { snapshot: &'a DiffMapSnapshot, language_aware: bool, - cursor: Cursor<'a, DiffTransform, (DiffOffset, FoldOffset)>, - fold_chunks: FoldChunks<'a>, + cursor: Cursor<'a, DiffTransform, (DiffOffset, InlayOffset)>, + fold_chunks: InlayChunks<'a>, fold_chunk: Option>, - fold_offset: FoldOffset, + fold_offset: InlayOffset, offset: DiffOffset, end_offset: DiffOffset, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, @@ -68,7 +68,7 @@ struct DiffMapChunks<'a> { struct DiffMapBufferRows<'a> { cursor: Cursor<'a, DiffTransform, DiffTransformSummary>, - input_buffer_rows: FoldBufferRows<'a>, + input_buffer_rows: InlayBufferRows<'a>, } pub type DiffEdit = text::Edit; @@ -78,7 +78,8 @@ enum DiffMapOperation { buffer_id: BufferId, }, Edited { - edits: Vec, + inlay_snapshot: InlaySnapshot, + edits: Vec, }, ExpandHunks { ranges: Vec>, @@ -93,14 +94,19 @@ pub struct DiffOffset(pub usize); impl DiffMap { pub fn new( - fold_snapshot: FoldSnapshot, + inlay_snapshot: InlaySnapshot, multibuffer: Model, cx: &mut AppContext, ) -> (Model, DiffMapSnapshot) { let snapshot = DiffMapSnapshot { diffs: TreeMap::default(), - transforms: SumTree::new(&()), - fold_snapshot, + transforms: SumTree::from_item( + DiffTransform::BufferContent { + summary: inlay_snapshot.text_summary(), + }, + &(), + ), + inlay_snapshot, }; let this = cx.new_model(|_| Self { @@ -132,11 +138,21 @@ impl DiffMap { pub fn sync( &mut self, - fold_snapshot: FoldSnapshot, - fold_edits: Vec, + inlay_snapshot: InlaySnapshot, + edits: Vec, + cx: &mut ModelContext, ) -> (DiffMapSnapshot, Vec) { - let patch = mem::take(&mut self.edits_since_sync); - (self.snapshot.clone(), patch.into_inner()) + self.recompute_transforms( + DiffMapOperation::Edited { + inlay_snapshot, + edits, + }, + cx, + ); + ( + self.snapshot.clone(), + mem::take(&mut self.edits_since_sync).into_inner(), + ) } fn buffer_diff_changed( @@ -200,228 +216,262 @@ impl DiffMap { let multibuffer = self.multibuffer.read(cx); let multibuffer_snapshot = multibuffer.snapshot(cx); - let affected_ranges: Vec<_> = match &operation { + let changes: Vec<(InlayEdit, Range)> = match &operation { DiffMapOperation::BufferDiffUpdated { buffer_id } => { + let buffer_id = *buffer_id; let multibuffer = self.multibuffer.read(cx); multibuffer - .ranges_for_buffer(*buffer_id, cx) + .ranges_for_buffer(buffer_id, cx) .into_iter() .map(|(excerpt_id, range, buffer_range)| { + let multibuffer_start = + ToOffset::to_offset(&range.start, &multibuffer_snapshot); + let multibuffer_end = + ToOffset::to_offset(&range.end, &multibuffer_snapshot); + let inlay_start = self + .snapshot + .inlay_snapshot + .to_inlay_offset(multibuffer_start); + let inlay_end = self + .snapshot + .inlay_snapshot + .to_inlay_offset(multibuffer_end); ( - excerpt_id, - *buffer_id, - ToOffset::to_offset(&range.start, &multibuffer_snapshot) - ..ToOffset::to_offset(&range.end, &multibuffer_snapshot), - buffer_range, + InlayEdit { + old: inlay_start..inlay_end, + new: inlay_start..inlay_end, + }, + multibuffer_start..multibuffer_end, ) }) .collect() } - DiffMapOperation::Edited { edits } => todo!(), + DiffMapOperation::Edited { + inlay_snapshot, + edits, + } => { + let mut changes = Vec::new(); + for edit in edits { + let multibuffer_start = inlay_snapshot.to_buffer_offset(edit.new.start); + let multibuffer_end = inlay_snapshot.to_buffer_offset(edit.new.end); + let multibuffer_range = multibuffer_start..multibuffer_end; + changes.push((edit.clone(), multibuffer_range)) + } + changes + } DiffMapOperation::ExpandHunks { ranges } | DiffMapOperation::CollapseHunks { ranges } => { - let mut changed_ranges = Vec::new(); + let mut changes = Vec::new(); for range in ranges.iter() { let multibuffer_range = range.to_offset(&multibuffer_snapshot); - for (buffer, buffer_range, excerpt_id) in - multibuffer.range_to_buffer_ranges(range.clone(), cx) - { - let buffer = buffer.read(cx); - if let Some(excerpt_range) = - multibuffer_snapshot.range_for_excerpt::(excerpt_id) - { - changed_ranges.push(( - excerpt_id, - buffer.remote_id(), - multibuffer_range.start.max(excerpt_range.start) - ..multibuffer_range.end.min(excerpt_range.end), - buffer.anchor_before(buffer_range.start) - ..buffer.anchor_after(buffer_range.end), - )); - } - } + let inlay_start = self + .snapshot + .inlay_snapshot + .to_inlay_offset(multibuffer_range.start); + let inlay_end = self + .snapshot + .inlay_snapshot + .to_inlay_offset(multibuffer_range.end); + changes.push(( + InlayEdit { + old: inlay_start..inlay_end, + new: inlay_start..inlay_end, + }, + multibuffer_range, + )); } - changed_ranges + changes } }; let mut cursor = self .snapshot .transforms - .cursor::<(FoldOffset, DiffOffset)>(&()); + .cursor::<(InlayOffset, DiffOffset)>(&()); let mut new_transforms = SumTree::default(); let mut edits = Patch::default(); - for (excerpt_id, buffer_id, multibuffer_range, buffer_range) in affected_ranges { - let Some(buffer) = self.multibuffer.read(cx).buffer(buffer_id) else { - continue; - }; - let change_set_state = self.snapshot.diffs.get(&buffer_id); - - let buffer = buffer.read(cx); - let excerpt_start = buffer_range.start.to_offset(buffer); - let affected_range_start = self - .snapshot - .fold_snapshot - .make_fold_offset(multibuffer_range.start, Bias::Left); - let affected_range_end = self - .snapshot - .fold_snapshot - .make_fold_offset(multibuffer_range.end, Bias::Right); - - new_transforms.append(cursor.slice(&affected_range_start, Bias::Left, &()), &()); - - if let Some(change_set_state) = change_set_state { - let diff = &change_set_state.diff; - let base_text = &change_set_state.base_text; - - for hunk in diff.hunks_intersecting_range(buffer_range.clone(), buffer) { - let hunk_start_anchor = multi_buffer::Anchor { - excerpt_id, - buffer_id: Some(buffer_id), - text_anchor: hunk.buffer_range.start, - }; - let hunk_end_anchor = multi_buffer::Anchor { - excerpt_id, - buffer_id: Some(buffer_id), - text_anchor: hunk.buffer_range.end, - }; - let hunk_start_multibuffer_offset = multibuffer_range.start - + (hunk.buffer_range.start.to_offset(buffer) - excerpt_start); - let hunk_start_fold_offset = self - .snapshot - .fold_snapshot - .make_fold_offset(hunk_start_multibuffer_offset, Bias::Left); + for (inlay_edit, multibuffer_range) in changes { + new_transforms.append(cursor.slice(&inlay_edit.old.start, Bias::Left, &()), &()); - self.push_buffer_content_transform(&mut new_transforms, hunk_start_fold_offset); + for (buffer, buffer_range, excerpt_id) in + multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) + { + let excerpt_range = multibuffer_snapshot + .range_for_excerpt::(excerpt_id) + .unwrap(); + let excerpt_buffer_range = multibuffer_snapshot + .buffer_range_for_excerpt(excerpt_id) + .unwrap(); + let buffer_id = buffer.read(cx).remote_id(); + let diff_state = self.snapshot.diffs.get(&buffer_id); + + let buffer = buffer.read(cx); + let buffer_anchor_range = + buffer.anchor_after(buffer_range.start)..buffer.anchor_before(buffer_range.end); + let change_start_buffer_offset = buffer_range.start; + if let Some(diff_state) = diff_state { + let diff = &diff_state.diff; + let base_text = &diff_state.base_text; + + for hunk in diff.hunks_intersecting_range(buffer_anchor_range, buffer) { + let hunk_anchor_range = { + let start = multi_buffer::Anchor { + excerpt_id, + buffer_id: Some(buffer_id), + text_anchor: hunk.buffer_range.start, + }; + let end = multi_buffer::Anchor { + excerpt_id, + buffer_id: Some(buffer_id), + text_anchor: hunk.buffer_range.end, + }; + start..end + }; - let mut prev_old_transform_start = cursor.start().1; - while cursor.start().0 < hunk_start_fold_offset { - let Some(item) = cursor.item() else { - break; + let hunk_start_buffer_offset = hunk.buffer_range.start.to_offset(buffer); + if hunk_start_buffer_offset < change_start_buffer_offset { + continue; + } + + let hunk_start_multibuffer_offset = excerpt_range.start + + hunk_start_buffer_offset + - excerpt_buffer_range.start.to_offset(buffer); + let hunk_start_inlay_offset = self + .snapshot + .inlay_snapshot + .to_inlay_offset(hunk_start_multibuffer_offset); + + self.push_buffer_content_transform( + &mut new_transforms, + hunk_start_inlay_offset, + ); + + let mut prev_old_transform_start = cursor.start().1; + while cursor.start().0 < hunk_start_inlay_offset { + let Some(item) = cursor.item() else { + break; + }; + if let DiffTransform::DeletedHunk { + base_text_byte_range, + .. + } = item + { + let old_range = prev_old_transform_start + ..prev_old_transform_start + + DiffOffset(base_text_byte_range.len()); + let new_offset = DiffOffset(new_transforms.summary().output.len); + let edit = Edit { + old: old_range, + new: new_offset..new_offset, + }; + dbg!(&edit); + edits.push(edit); + } + prev_old_transform_start = cursor.start().1; + cursor.next(&()); + } + + let mut was_previously_expanded = false; + if let Some(item) = cursor.item() { + if let DiffTransform::DeletedHunk { + base_text_byte_range, + .. + } = item + { + if cursor.start().0 == hunk_start_inlay_offset + && *base_text_byte_range == hunk.diff_base_byte_range + { + was_previously_expanded = true; + } + } + } + + let mut should_expand_hunk = + was_previously_expanded || self.all_hunks_expanded; + match &operation { + DiffMapOperation::ExpandHunks { ranges } => { + should_expand_hunk |= ranges.iter().any(|range| { + range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) + }) + } + DiffMapOperation::CollapseHunks { ranges } => { + should_expand_hunk &= !ranges.iter().any(|range| { + range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) + }) + } + _ => {} }; - if let DiffTransform::DeletedHunk { - base_text_byte_range, - .. - } = item - { - let old_range = prev_old_transform_start - ..prev_old_transform_start + DiffOffset(base_text_byte_range.len()); + if hunk.diff_base_byte_range.len() == 0 { + should_expand_hunk = false; + } + + if should_expand_hunk { + let mut text_cursor = base_text.as_rope().cursor(0); + let base_text_start = + text_cursor.summary::(hunk.diff_base_byte_range.start); + let base_text_summary = + text_cursor.summary::(hunk.diff_base_byte_range.end); + + if !was_previously_expanded { + let old_offset = prev_old_transform_start; + let new_start = DiffOffset(new_transforms.summary().output.len); + let edit = Edit { + old: old_offset..old_offset, + new: new_start + ..new_start + DiffOffset(hunk.diff_base_byte_range.len()), + }; + dbg!(&edit); + edits.push(edit); + } + + new_transforms.push( + DiffTransform::DeletedHunk { + base_text_byte_range: hunk.diff_base_byte_range.clone(), + summary: base_text_summary, + buffer_id, + base_text_start, + }, + &(), + ); + } else if was_previously_expanded { + let old_start = cursor.start().1; let new_offset = DiffOffset(new_transforms.summary().output.len); let edit = Edit { - old: old_range, + old: old_start + ..old_start + DiffOffset(hunk.diff_base_byte_range.len()), new: new_offset..new_offset, }; dbg!(&edit); edits.push(edit); + cursor.next(&()); } - prev_old_transform_start = cursor.start().1; - cursor.next(&()); } - let mut was_previously_expanded = false; - if let Some(item) = cursor.item() { + while cursor.start().0 < inlay_edit.old.end { + let Some(item) = cursor.item() else { + break; + }; if let DiffTransform::DeletedHunk { base_text_byte_range, .. } = item { - if cursor.start().0 == hunk_start_fold_offset - && *base_text_byte_range == hunk.diff_base_byte_range - { - was_previously_expanded = true; - } - } - } - - let mut should_expand_hunk = was_previously_expanded || self.all_hunks_expanded; - match &operation { - DiffMapOperation::ExpandHunks { ranges } => { - should_expand_hunk |= ranges.iter().any(|range| { - range.overlaps( - &(hunk_start_anchor..hunk_end_anchor), - &multibuffer_snapshot, - ) - }) - } - DiffMapOperation::CollapseHunks { ranges } => { - should_expand_hunk &= !ranges.iter().any(|range| { - range.overlaps( - &(hunk_start_anchor..hunk_end_anchor), - &multibuffer_snapshot, - ) - }) - } - _ => {} - }; - if hunk.diff_base_byte_range.len() == 0 { - should_expand_hunk = false; - } - - if should_expand_hunk { - let mut text_cursor = base_text.as_rope().cursor(0); - let base_text_start = - text_cursor.summary::(hunk.diff_base_byte_range.start); - let base_text_summary = - text_cursor.summary::(hunk.diff_base_byte_range.end); - - if !was_previously_expanded { - let old_offset = prev_old_transform_start; - let new_start = DiffOffset(new_transforms.summary().output.len); + let old_start = cursor.start().1; + let new_offset = DiffOffset(new_transforms.summary().output.len); let edit = Edit { - old: old_offset..old_offset, - new: new_start - ..new_start + DiffOffset(hunk.diff_base_byte_range.len()), + old: old_start..old_start + DiffOffset(base_text_byte_range.len()), + new: new_offset..new_offset, }; dbg!(&edit); edits.push(edit); } - - new_transforms.push( - DiffTransform::DeletedHunk { - base_text_byte_range: hunk.diff_base_byte_range.clone(), - summary: base_text_summary, - buffer_id, - base_text_start, - }, - &(), - ); - } else if was_previously_expanded { - let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().output.len); - let edit = Edit { - old: old_start..old_start + DiffOffset(hunk.diff_base_byte_range.len()), - new: new_offset..new_offset, - }; - dbg!(&edit); - edits.push(edit); cursor.next(&()); } } - - while cursor.start().0 < affected_range_end { - let Some(item) = cursor.item() else { - break; - }; - if let DiffTransform::DeletedHunk { - base_text_byte_range, - .. - } = item - { - let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().output.len); - let edit = Edit { - old: old_start..old_start + DiffOffset(base_text_byte_range.len()), - new: new_offset..new_offset, - }; - dbg!(&edit); - edits.push(edit); - } - cursor.next(&()); - } } - self.push_buffer_content_transform(&mut new_transforms, affected_range_end); + self.push_buffer_content_transform(&mut new_transforms, cursor.start().0); } self.edits_since_sync = self.edits_since_sync.compose(edits); @@ -438,11 +488,12 @@ impl DiffMap { fn push_buffer_content_transform( &self, new_transforms: &mut SumTree, - end_point: FoldOffset, + end_offset: InlayOffset, ) { - let summary_to_add = self.snapshot.fold_snapshot.text_summary_for_offset_range( - FoldOffset(new_transforms.summary().input.len)..end_point, - ); + let summary_to_add = self + .snapshot + .inlay_snapshot + .text_summary_for_range(InlayOffset(new_transforms.summary().input.len)..end_offset); if summary_to_add.len > 0 { let mut did_extend = false; new_transforms.update_last( @@ -468,14 +519,32 @@ impl DiffMap { #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; - if snapshot.transforms.summary().input.len != snapshot.fold_snapshot.len().0 { + if snapshot.transforms.summary().input.len != snapshot.inlay_snapshot.len().0 { panic!( "incorrect input length. expected {}, got {}. transforms: {:+?}", - snapshot.fold_snapshot.len().0, + snapshot.inlay_snapshot.len().0, snapshot.transforms.summary().input.len, snapshot.transforms.items(&()), ); } + + let mut prev_transform_is_buffer_content = false; + for item in snapshot.transforms.iter() { + match item { + DiffTransform::BufferContent { summary } => { + if prev_transform_is_buffer_content { + panic!("multiple adjacent buffer content transforms"); + } + prev_transform_is_buffer_content = true; + if summary.len == 0 { + panic!("empty buffer content transform"); + } + } + DiffTransform::DeletedHunk { .. } => { + prev_transform_is_buffer_content = false; + } + } + } } } @@ -492,8 +561,8 @@ impl DiffMapSnapshot { DiffOffset(self.transforms.summary().output.len) } - pub fn to_fold_offset(&self, offset: DiffOffset) -> FoldOffset { - let mut cursor = self.transforms.cursor::<(DiffOffset, FoldOffset)>(&()); + pub fn to_inlay_offset(&self, offset: DiffOffset) -> InlayOffset { + let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); cursor.seek(&offset, Bias::Right, &()); let mut fold_offset = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { @@ -509,7 +578,7 @@ impl DiffMapSnapshot { language_aware: bool, highlights: Highlights<'a>, ) -> DiffMapChunks<'a> { - let mut cursor = self.transforms.cursor::<(DiffOffset, FoldOffset)>(&()); + let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); cursor.seek(&range.end, Bias::Right, &()); let mut fold_end = cursor.start().1; @@ -526,7 +595,7 @@ impl DiffMapSnapshot { } let fold_chunks = - self.fold_snapshot + self.inlay_snapshot .chunks(fold_start..fold_end, language_aware, highlights); DiffMapChunks { @@ -656,9 +725,9 @@ impl sum_tree::Summary for DiffTransformSummary { } } -impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for FoldOffset { +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayOffset { fn zero(_: &()) -> Self { - FoldOffset(0) + InlayOffset(0) } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { @@ -676,22 +745,6 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { } } -impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for FoldPoint { - fn zero(_: &()) -> Self { - FoldPoint(Point::zero()) - } - - fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.input.lines - } -} - -impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for FoldPoint { - fn cmp(&self, cursor_location: &DiffTransformSummary, _: &()) -> Ordering { - Ord::cmp(&self.0, &cursor_location.input.lines) - } -} - impl<'a> Iterator for DiffMapBufferRows<'a> { type Item = Option; @@ -725,7 +778,7 @@ impl std::ops::Sub for DiffOffset { #[cfg(test)] mod tests { use super::*; - use crate::display_map::{fold_map::FoldMap, inlay_map::InlayMap}; + use crate::display_map::inlay_map::InlayMap; use gpui::{AppContext, TestAppContext}; use indoc::indoc; use multi_buffer::{Anchor, MultiBuffer}; @@ -766,19 +819,19 @@ mod tests { }); let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); - let buffer_snapshot = multibuffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); - let (diff_map, _) = cx.update(|cx| DiffMap::new(fold_snapshot.clone(), multibuffer, cx)); + let (multibuffer_snapshot, multibuffer_edits) = + multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); + let (diff_map, _) = + cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); cx.run_until_parked(); - let (snapshot1, _) = diff_map.update(cx, |diff_map, _| { - diff_map.sync(fold_snapshot.clone(), vec![]) + let (mut snapshot, _) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), vec![], cx) }); - assert_eq!( - snapshot1.text(), + snapshot.text(), indoc!( " ZERO @@ -787,19 +840,18 @@ mod tests { three six " - ) + ), ); diff_map.update(cx, |diff_map, cx| { diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); - - let (snapshot2, edits) = diff_map.update(cx, |diff_map, _| { - diff_map.sync(fold_snapshot.clone(), vec![]) + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), vec![], cx) }); - - assert_eq!( - snapshot2.text(), + assert_new_snapshot( + &mut snapshot, + sync, indoc!( " ZERO @@ -811,20 +863,18 @@ mod tests { five six " - ) + ), ); - check_edits(&snapshot1, &snapshot2, &edits); - diff_map.update(cx, |diff_map, cx| { diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); - - let (snapshot3, edits) = diff_map.update(cx, |diff_map, _| { - diff_map.sync(fold_snapshot.clone(), vec![]) + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), vec![], cx) }); - assert_eq!( - snapshot3.text(), + assert_new_snapshot( + &mut snapshot, + sync, indoc!( " ZERO @@ -833,9 +883,86 @@ mod tests { three six " + ), + ); + + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks( + vec![ + multibuffer_snapshot.anchor_before(Point::new(2, 0)) + ..multibuffer_snapshot.anchor_before(Point::new(2, 0)), + ], + cx, ) + }); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one + two + TWO + three + six + " + ), + ); + + buffer.update(cx, |buffer, cx| { + buffer.edit_via_marked_text( + indoc!( + " + ZERO + one« hundred + thousand» + TWO + three + six + " + ), + None, + cx, + ); + }); + + let multibuffer_snapshot = multibuffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); + let (inlay_snapshot, edits) = inlay_map.sync( + multibuffer_snapshot, + multibuffer_edits.consume().into_inner(), ); - check_edits(&snapshot2, &snapshot3, &edits); + let sync = diff_map.update(cx, |diff_map, cx| diff_map.sync(inlay_snapshot, edits, cx)); + + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one hundred + thousand + two + TWO + three + six + " + ), + ); + } + + #[track_caller] + fn assert_new_snapshot( + snapshot: &mut DiffMapSnapshot, + (new_snapshot, edits): (DiffMapSnapshot, Vec>), + expected_text: &str, + ) { + assert_eq!(new_snapshot.text(), expected_text); + check_edits(snapshot, &new_snapshot, &edits); + *snapshot = new_snapshot; } #[track_caller] diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index df769ae9932fb8..16d627bc18f5dd 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -4073,6 +4073,17 @@ impl MultiBufferSnapshot { } } + pub fn buffer_range_for_excerpt(&self, excerpt_id: ExcerptId) -> Option> { + let mut cursor = self.excerpts.cursor::>(&()); + let locator = self.excerpt_locator_for_id(excerpt_id); + if cursor.seek(&Some(locator), Bias::Left, &()) { + if let Some(excerpt) = cursor.item() { + return Some(excerpt.range.context.clone()); + } + } + None + } + fn excerpt(&self, excerpt_id: ExcerptId) -> Option<&Excerpt> { let mut cursor = self.excerpts.cursor::>(&()); let locator = self.excerpt_locator_for_id(excerpt_id); From 33833533bd4bcc5bb3294a3c6bd1dcfd2f8c4737 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 10 Dec 2024 17:19:29 -0800 Subject: [PATCH 10/51] Get DiffMap::sync working for a basic test Co-authored-by: Cole --- crates/editor/src/display_map/diff_map.rs | 72 +++++++++++++++-------- 1 file changed, 49 insertions(+), 23 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 0986c9db5ad8dc..c00b85d8753b13 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -216,7 +216,7 @@ impl DiffMap { let multibuffer = self.multibuffer.read(cx); let multibuffer_snapshot = multibuffer.snapshot(cx); - let changes: Vec<(InlayEdit, Range)> = match &operation { + let changes: Vec<(InlayEdit, bool, Range)> = match &operation { DiffMapOperation::BufferDiffUpdated { buffer_id } => { let buffer_id = *buffer_id; let multibuffer = self.multibuffer.read(cx); @@ -241,6 +241,7 @@ impl DiffMap { old: inlay_start..inlay_end, new: inlay_start..inlay_end, }, + false, multibuffer_start..multibuffer_end, ) }) @@ -255,8 +256,9 @@ impl DiffMap { let multibuffer_start = inlay_snapshot.to_buffer_offset(edit.new.start); let multibuffer_end = inlay_snapshot.to_buffer_offset(edit.new.end); let multibuffer_range = multibuffer_start..multibuffer_end; - changes.push((edit.clone(), multibuffer_range)) + changes.push((edit.clone(), true, multibuffer_range)) } + self.snapshot.inlay_snapshot = inlay_snapshot.clone(); changes } DiffMapOperation::ExpandHunks { ranges } @@ -277,6 +279,7 @@ impl DiffMap { old: inlay_start..inlay_end, new: inlay_start..inlay_end, }, + false, multibuffer_range, )); } @@ -291,8 +294,16 @@ impl DiffMap { let mut new_transforms = SumTree::default(); let mut edits = Patch::default(); - for (inlay_edit, multibuffer_range) in changes { - new_transforms.append(cursor.slice(&inlay_edit.old.start, Bias::Left, &()), &()); + for (mut edit, is_inlay_edit, multibuffer_range) in changes { + new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + + let edit_start_overshoot = (edit.old.start - cursor.start().0).0; + edit.new.start.0 -= edit_start_overshoot; + edit.old.start = cursor.start().0; + + let diff_edit_old_start = cursor.start().1 + DiffOffset(edit_start_overshoot); + let diff_edit_new_start = + DiffOffset(new_transforms.summary().output.len + edit_start_overshoot); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) @@ -447,31 +458,46 @@ impl DiffMap { cursor.next(&()); } } + } - while cursor.start().0 < inlay_edit.old.end { - let Some(item) = cursor.item() else { - break; + while cursor.start().0 < edit.old.end { + let Some(item) = cursor.item() else { + break; + }; + if let DiffTransform::DeletedHunk { + base_text_byte_range, + .. + } = item + { + let old_start = cursor.start().1; + let new_offset = DiffOffset(new_transforms.summary().output.len); + let edit = Edit { + old: old_start..old_start + DiffOffset(base_text_byte_range.len()), + new: new_offset..new_offset, }; - if let DiffTransform::DeletedHunk { - base_text_byte_range, - .. - } = item - { - let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().output.len); - let edit = Edit { - old: old_start..old_start + DiffOffset(base_text_byte_range.len()), - new: new_offset..new_offset, - }; - dbg!(&edit); - edits.push(edit); - } - cursor.next(&()); + dbg!(&edit); + edits.push(edit); } + cursor.next(&()); } } - self.push_buffer_content_transform(&mut new_transforms, cursor.start().0); + let edit_undershoot = (cursor.start().0 - edit.old.end).0; + edit.new.end.0 += edit_undershoot; + edit.old.end = cursor.start().0; + + self.push_buffer_content_transform(&mut new_transforms, edit.new.end); + + let diff_edit_old_end = cursor.start().1 - DiffOffset(edit_undershoot); + let diff_edit_new_end = + DiffOffset(new_transforms.summary().output.len - edit_undershoot); + + if is_inlay_edit { + edits.push(DiffEdit { + old: diff_edit_old_start..diff_edit_old_end, + new: diff_edit_new_start..diff_edit_new_end, + }) + } } self.edits_since_sync = self.edits_since_sync.compose(edits); From a20715d6635b1062668968c494716398b3d07338 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Dec 2024 11:36:44 -0800 Subject: [PATCH 11/51] Start implementing DiffMap::buffer_rows --- crates/editor/src/display_map/diff_map.rs | 77 ++++++++++++++++++++--- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index c00b85d8753b13..478e7c29fc8a74 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,5 +1,5 @@ use super::inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlaySnapshot}; -use crate::{Highlights, InlayOffset}; +use crate::{Highlights, InlayOffset, InlayPoint}; use collections::HashMap; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; @@ -67,7 +67,8 @@ struct DiffMapChunks<'a> { } struct DiffMapBufferRows<'a> { - cursor: Cursor<'a, DiffTransform, DiffTransformSummary>, + cursor: Cursor<'a, DiffTransform, (DiffPoint, InlayPoint)>, + diff_point: DiffPoint, input_buffer_rows: InlayBufferRows<'a>, } @@ -92,6 +93,9 @@ enum DiffMapOperation { #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DiffOffset(pub usize); +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DiffPoint(pub Point); + impl DiffMap { pub fn new( inlay_snapshot: InlaySnapshot, @@ -223,7 +227,7 @@ impl DiffMap { multibuffer .ranges_for_buffer(buffer_id, cx) .into_iter() - .map(|(excerpt_id, range, buffer_range)| { + .map(|(_, range, _)| { let multibuffer_start = ToOffset::to_offset(&range.start, &multibuffer_snapshot); let multibuffer_end = @@ -638,7 +642,23 @@ impl DiffMapSnapshot { } pub fn buffer_rows(&self, start_row: u32) -> DiffMapBufferRows { - todo!() + if start_row > self.transforms.summary().output.lines.row { + panic!("invalid diff map row {}", start_row); + } + + let diff_point = DiffPoint(Point::new(start_row, 0)); + let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&()); + cursor.seek(&diff_point, Bias::Left, &()); + + let overshoot = diff_point.0 - cursor.start().0 .0; + let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot); + let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row()); + + DiffMapBufferRows { + diff_point, + input_buffer_rows, + cursor, + } } } @@ -718,6 +738,22 @@ impl<'a> Iterator for DiffMapChunks<'a> { } } +impl<'a> Iterator for DiffMapBufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + let result = match self.cursor.item()? { + DiffTransform::DeletedHunk { .. } => Some(None), + DiffTransform::BufferContent { .. } => self.input_buffer_rows.next(), + }; + self.diff_point.0 += Point::new(1, 0); + if self.diff_point >= self.cursor.end(&()).0 { + self.cursor.next(&()); + } + result + } +} + impl sum_tree::Item for DiffTransform { type Summary = DiffTransformSummary; @@ -771,11 +807,23 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { } } -impl<'a> Iterator for DiffMapBufferRows<'a> { - type Item = Option; +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayPoint { + fn zero(_: &()) -> Self { + InlayPoint(Point::zero()) + } - fn next(&mut self) -> Option { - todo!() + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + self.0 += summary.input.lines + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffPoint { + fn zero(_: &()) -> Self { + DiffPoint(Point::zero()) + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + self.0 += summary.output.lines } } @@ -891,6 +939,19 @@ mod tests { " ), ); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + vec![ + Some(0), + Some(1), + None, + Some(2), + Some(3), + None, + None, + Some(4) + ] + ); diff_map.update(cx, |diff_map, cx| { diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) From 61e42b986a3419af559885bb1c925e8c595daed2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 11 Dec 2024 17:10:34 -0800 Subject: [PATCH 12/51] Integrate DiffMap into the DisplayMap --- crates/editor/src/display_map.rs | 62 ++- crates/editor/src/display_map/block_map.rs | 55 ++- crates/editor/src/display_map/diff_map.rs | 154 ++++++- crates/editor/src/display_map/fold_map.rs | 502 +++++++++++---------- crates/editor/src/display_map/tab_map.rs | 28 +- crates/editor/src/display_map/wrap_map.rs | 17 +- 6 files changed, 540 insertions(+), 278 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 23e598c342e217..62c1cd454476c2 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -36,6 +36,7 @@ pub use block_map::{ use block_map::{BlockRow, BlockSnapshot}; use collections::{HashMap, HashSet}; pub use crease_map::*; +use diff_map::{DiffMap, DiffMapSnapshot}; pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint}; use fold_map::{FoldMap, FoldSnapshot}; use gpui::{ @@ -96,6 +97,8 @@ pub struct DisplayMap { buffer_subscription: BufferSubscription, /// Decides where the [`Inlay`]s should be displayed. inlay_map: InlayMap, + /// Decides where diff hunks should be. + diff_map: Model, /// Decides where the fold indicators should be and tracks parts of a source file that are currently folded. fold_map: FoldMap, /// Keeps track of hard tabs in a buffer. @@ -135,6 +138,7 @@ impl DisplayMap { let buffer_snapshot = buffer.read(cx).snapshot(cx); let crease_map = CreaseMap::new(&buffer_snapshot); let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot); + let (diff_map, snapshot) = DiffMap::new(snapshot, buffer.clone(), cx); let (fold_map, snapshot) = FoldMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx); @@ -153,6 +157,7 @@ impl DisplayMap { buffer_subscription, fold_map, inlay_map, + diff_map, tab_map, wrap_map, block_map, @@ -169,7 +174,10 @@ impl DisplayMap { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); - let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits); + let (diff_snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), edits, cx) + }); + let (fold_snapshot, edits) = self.fold_map.read(diff_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self @@ -218,6 +226,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -291,6 +302,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -320,6 +334,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -364,6 +381,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -382,6 +402,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -400,6 +423,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -418,6 +444,9 @@ impl DisplayMap { let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -493,6 +522,9 @@ impl DisplayMap { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); @@ -502,6 +534,9 @@ impl DisplayMap { self.block_map.read(snapshot, edits); let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -692,9 +727,11 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) { loop { let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); - let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left); + let mut diff_point = self.diff_snapshot().to_diff_point(inlay_point); + let mut fold_point = self.fold_snapshot.to_fold_point(diff_point, Bias::Left); fold_point.0.column = 0; - inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + diff_point = fold_point.to_diff_point(&self.fold_snapshot); + inlay_point = self.diff_snapshot().to_inlay_point(diff_point); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Left); @@ -710,9 +747,11 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) { loop { let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); - let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right); + let mut diff_point = self.diff_snapshot().to_diff_point(inlay_point); + let mut fold_point = self.fold_snapshot.to_fold_point(diff_point, Bias::Right); fold_point.0.column = self.fold_snapshot.line_len(fold_point.row()); - inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + diff_point = fold_point.to_diff_point(&self.fold_snapshot); + inlay_point = self.diff_snapshot().to_inlay_point(diff_point); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Right); @@ -754,7 +793,8 @@ impl DisplaySnapshot { pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint { let inlay_point = self.inlay_snapshot.to_inlay_point(point); - let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + let diff_point = self.diff_snapshot().to_diff_point(inlay_point); + let fold_point = self.fold_snapshot.to_fold_point(diff_point, bias); let tab_point = self.tab_snapshot.to_tab_point(fold_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); @@ -786,7 +826,8 @@ impl DisplaySnapshot { let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; - fold_point.to_inlay_point(&self.fold_snapshot) + let diff_point = fold_point.to_diff_point(&self.fold_snapshot); + self.diff_snapshot().to_inlay_point(diff_point) } pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint { @@ -1263,6 +1304,10 @@ impl DisplaySnapshot { pub fn excerpt_header_height(&self) -> u32 { self.block_snapshot.excerpt_header_height } + + fn diff_snapshot(&self) -> &DiffMapSnapshot { + &self.fold_snapshot.diff_map_snapshot + } } #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)] @@ -1367,7 +1412,8 @@ impl DisplayPoint { let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; - let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot); + let diff_point = fold_point.to_diff_point(&map.fold_snapshot); + let inlay_point = map.diff_snapshot().to_inlay_point(diff_point); map.inlay_snapshot .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point)) } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 1300537a2a216f..d1952b176608f8 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1752,7 +1752,8 @@ fn offset_for_row(s: &str, target: u32) -> (u32, usize) { mod tests { use super::*; use crate::display_map::{ - fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, wrap_map::WrapMap, + diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap, + wrap_map::WrapMap, }; use gpui::{div, font, px, AppContext, Context as _, Element}; use language::{Buffer, Capability}; @@ -1786,7 +1787,9 @@ mod tests { let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (diff_map, diff_snapshot) = + cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); + let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx)); @@ -1933,7 +1936,10 @@ mod tests { let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -1996,7 +2002,8 @@ mod tests { let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone()); - let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, multi_buffer.clone(), cx); + let (_, fold_snapshot) = FoldMap::new(diff_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx); @@ -2034,7 +2041,9 @@ mod tests { let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_diff_map, diff_snapshot) = + cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); + let (_fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (_wrap_map, wraps_snapshot) = cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx)); @@ -2136,7 +2145,8 @@ mod tests { let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx)); let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); + let (_, fold_snapshot) = FoldMap::new(diff_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = cx.update(|cx| { WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx) @@ -2180,7 +2190,9 @@ mod tests { let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe()); let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (diff_map, diff_snapshot) = + cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); + let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); let tab_size = 1.try_into().unwrap(); let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size); let (wrap_map, wraps_snapshot) = @@ -2210,7 +2222,10 @@ mod tests { buffer_snapshot.clone(), buffer_subscription.consume().into_inner(), ); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) @@ -2233,7 +2248,10 @@ mod tests { buffer_snapshot.clone(), buffer_subscription.consume().into_inner(), ); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) @@ -2332,7 +2350,9 @@ mod tests { let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (diff_map, diff_snapshot) = + cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); + let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = cx .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx)); @@ -2396,7 +2416,10 @@ mod tests { let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot.clone(), vec![]); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -2421,7 +2444,10 @@ mod tests { let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot.clone(), vec![]); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -2444,7 +2470,10 @@ mod tests { let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot.clone(), buffer_edits); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 478e7c29fc8a74..37e93075b94ee3 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -3,13 +3,13 @@ use crate::{Highlights, InlayOffset, InlayPoint}; use collections::HashMap; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; -use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, ToOffset}; +use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset}; use project::buffer_store::BufferChangeSet; use std::{mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; -struct DiffMap { +pub(crate) struct DiffMap { snapshot: DiffMapSnapshot, multibuffer: Model, diff_bases: HashMap, @@ -32,6 +32,7 @@ struct DiffSnapshot { pub(crate) struct DiffMapSnapshot { diffs: TreeMap, transforms: SumTree, + pub(crate) version: usize, inlay_snapshot: InlaySnapshot, } @@ -54,7 +55,7 @@ struct DiffTransformSummary { output: TextSummary, } -struct DiffMapChunks<'a> { +pub(crate) struct DiffMapChunks<'a> { snapshot: &'a DiffMapSnapshot, language_aware: bool, cursor: Cursor<'a, DiffTransform, (DiffOffset, InlayOffset)>, @@ -66,7 +67,8 @@ struct DiffMapChunks<'a> { diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, } -struct DiffMapBufferRows<'a> { +#[derive(Clone)] +pub(crate) struct DiffMapBufferRows<'a> { cursor: Cursor<'a, DiffTransform, (DiffPoint, InlayPoint)>, diff_point: DiffPoint, input_buffer_rows: InlayBufferRows<'a>, @@ -104,6 +106,7 @@ impl DiffMap { ) -> (Model, DiffMapSnapshot) { let snapshot = DiffMapSnapshot { diffs: TreeMap::default(), + version: 0, transforms: SumTree::from_item( DiffTransform::BufferContent { summary: inlay_snapshot.text_summary(), @@ -509,6 +512,7 @@ impl DiffMap { new_transforms.append(cursor.suffix(&()), &()); drop(cursor); self.snapshot.transforms = new_transforms; + self.snapshot.version += 1; cx.notify(); #[cfg(test)] @@ -586,20 +590,110 @@ impl DiffMapSnapshot { .collect() } - #[cfg(test)] pub fn len(&self) -> DiffOffset { DiffOffset(self.transforms.summary().output.len) } + pub fn text_summary(&self) -> TextSummary { + self.transforms.summary().output.clone() + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + todo!() + } + + pub fn buffer(&self) -> &MultiBufferSnapshot { + &self.inlay_snapshot.buffer + } + + pub fn to_point(&self, offset: DiffOffset) -> DiffPoint { + let mut cursor = self.transforms.cursor::<(DiffOffset, DiffPoint)>(&()); + cursor.seek(&offset, Bias::Right, &()); + let mut point = cursor.start().1; + match cursor.item() { + Some(_) => todo!(), + None => todo!(), + } + todo!(); + point + } + + pub fn clip_point(&self, point: DiffPoint, bias: Bias) -> DiffPoint { + todo!() + } + + pub fn to_offset(&self, offset: DiffPoint) -> DiffOffset { + let mut cursor = self.transforms.cursor::<(DiffPoint, DiffOffset)>(&()); + cursor.seek(&offset, Bias::Right, &()); + let mut point = cursor.start().1; + match cursor.item() { + Some(_) => todo!(), + None => todo!(), + } + todo!(); + point + } + pub fn to_inlay_offset(&self, offset: DiffOffset) -> InlayOffset { let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); cursor.seek(&offset, Bias::Right, &()); - let mut fold_offset = cursor.start().1; + let mut inlay_offset = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = offset.0 - cursor.start().0 .0; + inlay_offset.0 += overshoot; + } + inlay_offset + } + + pub fn to_inlay_point(&self, point: DiffPoint) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&()); + cursor.seek(&point, Bias::Right, &()); + let mut inlay_point = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = point.0 - cursor.start().0 .0; + inlay_point.0 += overshoot; + } + inlay_point + } + + pub fn to_diff_offset(&self, offset: InlayOffset) -> DiffOffset { + let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&()); + cursor.seek(&offset, Bias::Right, &()); + let mut diff_offset = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = offset.0 - cursor.start().0 .0; - fold_offset.0 += overshoot; + diff_offset.0 += overshoot; + } + diff_offset + } + + pub fn to_diff_point(&self, point: InlayPoint) -> DiffPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&()); + cursor.seek(&point, Bias::Right, &()); + let mut diff_point = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = point.0 - cursor.start().0 .0; + diff_point.0 += overshoot; } - fold_offset + diff_point + } + + pub fn make_diff_offset(&self, buffer_offset: usize) -> DiffOffset { + self.to_diff_offset(self.inlay_snapshot.to_inlay_offset(buffer_offset)) + } + + pub fn make_diff_point(&self, buffer_point: Point) -> DiffPoint { + self.to_diff_point(self.inlay_snapshot.to_inlay_point(buffer_point)) + } + + pub fn to_buffer_offset(&self, diff_offset: DiffOffset) -> usize { + self.inlay_snapshot + .to_buffer_offset(self.to_inlay_offset(diff_offset)) + } + + pub fn to_buffer_point(&self, diff_point: DiffPoint) -> Point { + self.inlay_snapshot + .to_buffer_point(self.to_inlay_point(diff_point)) } pub fn chunks<'a>( @@ -662,6 +756,16 @@ impl DiffMapSnapshot { } } +impl<'a> DiffMapChunks<'a> { + pub fn seek(&mut self, range: Range) { + todo!() + } + + pub fn offset(&self) -> DiffOffset { + self.offset + } +} + impl<'a> Iterator for DiffMapChunks<'a> { type Item = Chunk<'a>; @@ -738,6 +842,12 @@ impl<'a> Iterator for DiffMapChunks<'a> { } } +impl<'a> DiffMapBufferRows<'a> { + pub fn seek(&mut self, row: u32) { + todo!() + } +} + impl<'a> Iterator for DiffMapBufferRows<'a> { type Item = Option; @@ -849,6 +959,34 @@ impl std::ops::Sub for DiffOffset { } } +impl std::ops::SubAssign for DiffOffset { + fn sub_assign(&mut self, rhs: DiffOffset) { + self.0 -= rhs.0; + } +} + +impl std::ops::Add for DiffPoint { + type Output = DiffPoint; + + fn add(self, rhs: DiffPoint) -> Self::Output { + DiffPoint(self.0 + rhs.0) + } +} + +impl std::ops::AddAssign for DiffPoint { + fn add_assign(&mut self, rhs: DiffPoint) { + self.0 += rhs.0; + } +} + +impl std::ops::Sub for DiffPoint { + type Output = DiffPoint; + + fn sub(self, rhs: DiffPoint) -> Self::Output { + DiffPoint(self.0 - rhs.0) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index c512c2bd435c32..371fec6a1540d4 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,5 +1,7 @@ use super::{ - inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, + diff_map::{ + DiffEdit, DiffMapBufferRows, DiffMapChunks, DiffMapSnapshot, DiffOffset, DiffPoint, + }, Highlights, }; use gpui::{AnyElement, ElementId, WindowContext}; @@ -92,11 +94,11 @@ impl FoldPoint { &mut self.0.column } - pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint { - let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); + pub fn to_diff_point(self, snapshot: &FoldSnapshot) -> DiffPoint { + let mut cursor = snapshot.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); cursor.seek(&self, Bias::Right, &()); let overshoot = self.0 - cursor.start().0 .0; - InlayPoint(cursor.start().1 .0 + overshoot) + DiffPoint(cursor.start().1 .0 + overshoot) } pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { @@ -110,8 +112,8 @@ impl FoldPoint { let transform = cursor.item().expect("display point out of range"); assert!(transform.placeholder.is_none()); let end_inlay_offset = snapshot - .inlay_snapshot - .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot)); + .diff_map_snapshot + .to_offset(DiffPoint(cursor.start().1.input.lines + overshoot)); offset += end_inlay_offset.0 - cursor.start().1.input.len; } FoldOffset(offset) @@ -137,9 +139,9 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut folds = Vec::new(); - let snapshot = self.0.snapshot.inlay_snapshot.clone(); + let snapshot = self.0.snapshot.diff_map_snapshot.clone(); + let buffer = snapshot.buffer(); for (range, fold_text) in ranges.into_iter() { - let buffer = &snapshot.buffer; let range = range.start.to_offset(buffer)..range.end.to_offset(buffer); // Ignore any empty ranges. @@ -160,15 +162,14 @@ impl<'a> FoldMapWriter<'a> { placeholder: fold_text, }); - let inlay_range = - snapshot.to_inlay_offset(range.start)..snapshot.to_inlay_offset(range.end); - edits.push(InlayEdit { - old: inlay_range.clone(), - new: inlay_range, + let diff_range = + snapshot.make_diff_offset(range.start)..snapshot.make_diff_offset(range.end); + edits.push(DiffEdit { + old: diff_range.clone(), + new: diff_range, }); } - let buffer = &snapshot.buffer; folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer)); self.0.snapshot.folds = { @@ -219,8 +220,8 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut fold_ixs_to_delete = Vec::new(); - let snapshot = self.0.snapshot.inlay_snapshot.clone(); - let buffer = &snapshot.buffer; + let snapshot = self.0.snapshot.diff_map_snapshot.clone(); + let buffer = snapshot.buffer(); for range in ranges.into_iter() { let range = range.start.to_offset(buffer)..range.end.to_offset(buffer); let mut folds_cursor = @@ -230,11 +231,11 @@ impl<'a> FoldMapWriter<'a> { fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer); if should_unfold(fold) { if offset_range.end > offset_range.start { - let inlay_range = snapshot.to_inlay_offset(offset_range.start) - ..snapshot.to_inlay_offset(offset_range.end); - edits.push(InlayEdit { - old: inlay_range.clone(), - new: inlay_range, + let diff_range = snapshot.make_diff_offset(offset_range.start) + ..snapshot.make_diff_offset(offset_range.end); + edits.push(DiffEdit { + old: diff_range.clone(), + new: diff_range, }); } fold_ixs_to_delete.push(*folds_cursor.start()); @@ -272,21 +273,21 @@ pub(crate) struct FoldMap { } impl FoldMap { - pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { + pub(crate) fn new(diff_map_snapshot: DiffMapSnapshot) -> (Self, FoldSnapshot) { let this = Self { snapshot: FoldSnapshot { - folds: SumTree::new(&inlay_snapshot.buffer), + folds: SumTree::new(diff_map_snapshot.buffer()), transforms: SumTree::from_item( Transform { summary: TransformSummary { - input: inlay_snapshot.text_summary(), - output: inlay_snapshot.text_summary(), + input: diff_map_snapshot.text_summary(), + output: diff_map_snapshot.text_summary(), }, placeholder: None, }, &(), ), - inlay_snapshot: inlay_snapshot.clone(), + diff_map_snapshot: diff_map_snapshot.clone(), version: 0, }, next_fold_id: FoldId::default(), @@ -297,20 +298,20 @@ impl FoldMap { pub fn read( &mut self, - inlay_snapshot: InlaySnapshot, - edits: Vec, + diff_map_snapshot: DiffMapSnapshot, + edits: Vec, ) -> (FoldSnapshot, Vec) { - let edits = self.sync(inlay_snapshot, edits); + let edits = self.sync(diff_map_snapshot, edits); self.check_invariants(); (self.snapshot.clone(), edits) } pub fn write( &mut self, - inlay_snapshot: InlaySnapshot, - edits: Vec, + diff_map_snapshot: DiffMapSnapshot, + edits: Vec, ) -> (FoldMapWriter, FoldSnapshot, Vec) { - let (snapshot, edits) = self.read(inlay_snapshot, edits); + let (snapshot, edits) = self.read(diff_map_snapshot, edits); (FoldMapWriter(self), snapshot, edits) } @@ -318,7 +319,7 @@ impl FoldMap { if cfg!(test) { assert_eq!( self.snapshot.transforms.summary().input.len, - self.snapshot.inlay_snapshot.len().0, + self.snapshot.diff_map_snapshot.len().0, "transform tree does not match inlay snapshot's length" ); @@ -338,7 +339,7 @@ impl FoldMap { if let Some(next_fold) = folds.peek() { let comparison = fold .range - .cmp(&next_fold.range, &self.snapshot.inlay_snapshot.buffer); + .cmp(&next_fold.range, &self.snapshot.diff_map_snapshot.buffer()); assert!(comparison.is_le()); } } @@ -347,21 +348,21 @@ impl FoldMap { fn sync( &mut self, - inlay_snapshot: InlaySnapshot, - inlay_edits: Vec, + diff_map_snapshot: DiffMapSnapshot, + diff_edits: Vec, ) -> Vec { - if inlay_edits.is_empty() { - if self.snapshot.inlay_snapshot.version != inlay_snapshot.version { + if diff_edits.is_empty() { + if self.snapshot.diff_map_snapshot.version != diff_map_snapshot.version { self.snapshot.version += 1; } - self.snapshot.inlay_snapshot = inlay_snapshot; + self.snapshot.diff_map_snapshot = diff_map_snapshot; Vec::new() } else { - let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); + let mut inlay_edits_iter = diff_edits.iter().cloned().peekable(); let mut new_transforms = SumTree::::default(); - let mut cursor = self.snapshot.transforms.cursor::(&()); - cursor.seek(&InlayOffset(0), Bias::Right, &()); + let mut cursor = self.snapshot.transforms.cursor::(&()); + cursor.seek(&DiffOffset(0), Bias::Right, &()); while let Some(mut edit) = inlay_edits_iter.next() { if let Some(item) = cursor.item() { @@ -407,34 +408,35 @@ impl FoldMap { } edit.new.end = - InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); + DiffOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); - let anchor = inlay_snapshot - .buffer - .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); + let anchor = diff_map_snapshot + .buffer() + .anchor_before(diff_map_snapshot.to_buffer_offset(edit.new.start)); let mut folds_cursor = self .snapshot .folds - .cursor::(&inlay_snapshot.buffer); + .cursor::(&diff_map_snapshot.buffer()); folds_cursor.seek( &FoldRange(anchor..Anchor::max()), Bias::Left, - &inlay_snapshot.buffer, + &diff_map_snapshot.buffer(), ); let mut folds = iter::from_fn({ - let inlay_snapshot = &inlay_snapshot; + let diff_map_snapshot = &diff_map_snapshot; move || { let item = folds_cursor.item().map(|fold| { - let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer); - let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer); + let buffer_start = + fold.range.start.to_offset(&diff_map_snapshot.buffer()); + let buffer_end = fold.range.end.to_offset(&diff_map_snapshot.buffer()); ( fold.clone(), - inlay_snapshot.to_inlay_offset(buffer_start) - ..inlay_snapshot.to_inlay_offset(buffer_end), + diff_map_snapshot.make_diff_offset(buffer_start) + ..diff_map_snapshot.make_diff_offset(buffer_end), ) }); - folds_cursor.next(&inlay_snapshot.buffer); + folds_cursor.next(&diff_map_snapshot.buffer()); item } }) @@ -462,8 +464,8 @@ impl FoldMap { } if fold_range.start.0 > sum.input.len { - let text_summary = inlay_snapshot - .text_summary_for_range(InlayOffset(sum.input.len)..fold_range.start); + let text_summary = diff_map_snapshot + .text_summary_for_range(DiffOffset(sum.input.len)..fold_range.start); push_isomorphic(&mut new_transforms, text_summary); } @@ -475,7 +477,7 @@ impl FoldMap { Transform { summary: TransformSummary { output: TextSummary::from(ELLIPSIS), - input: inlay_snapshot + input: diff_map_snapshot .text_summary_for_range(fold_range.start..fold_range.end), }, placeholder: Some(TransformPlaceholder { @@ -499,29 +501,29 @@ impl FoldMap { let sum = new_transforms.summary(); if sum.input.len < edit.new.end.0 { - let text_summary = inlay_snapshot - .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end); + let text_summary = diff_map_snapshot + .text_summary_for_range(DiffOffset(sum.input.len)..edit.new.end); push_isomorphic(&mut new_transforms, text_summary); } } new_transforms.append(cursor.suffix(&()), &()); if new_transforms.is_empty() { - let text_summary = inlay_snapshot.text_summary(); + let text_summary = diff_map_snapshot.text_summary(); push_isomorphic(&mut new_transforms, text_summary); } drop(cursor); - let mut fold_edits = Vec::with_capacity(inlay_edits.len()); + let mut fold_edits = Vec::with_capacity(diff_edits.len()); { let mut old_transforms = self .snapshot .transforms - .cursor::<(InlayOffset, FoldOffset)>(&()); - let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(&()); + .cursor::<(DiffOffset, FoldOffset)>(&()); + let mut new_transforms = new_transforms.cursor::<(DiffOffset, FoldOffset)>(&()); - for mut edit in inlay_edits { + for mut edit in diff_edits { old_transforms.seek(&edit.old.start, Bias::Left, &()); if old_transforms.item().map_or(false, |t| t.is_fold()) { edit.old.start = old_transforms.start().0; @@ -562,7 +564,7 @@ impl FoldMap { } self.snapshot.transforms = new_transforms; - self.snapshot.inlay_snapshot = inlay_snapshot; + self.snapshot.diff_map_snapshot = diff_map_snapshot; self.snapshot.version += 1; fold_edits } @@ -573,7 +575,7 @@ impl FoldMap { pub struct FoldSnapshot { transforms: SumTree, folds: SumTree, - pub inlay_snapshot: InlaySnapshot, + pub diff_map_snapshot: DiffMapSnapshot, pub version: usize, } @@ -585,15 +587,19 @@ impl FoldSnapshot { .collect() } + pub fn buffer(&self) -> &MultiBufferSnapshot { + self.diff_map_snapshot.buffer() + } + #[cfg(test)] pub fn fold_count(&self) -> usize { - self.folds.items(&self.inlay_snapshot.buffer).len() + self.folds.items(self.diff_map_snapshot.buffer()).len() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); + let mut cursor = self.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); cursor.seek(&range.start, Bias::Right, &()); if let Some(transform) = cursor.item() { let start_in_transform = range.start.0 - cursor.start().0 .0; @@ -605,13 +611,13 @@ impl FoldSnapshot { ); } else { let inlay_start = self - .inlay_snapshot - .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform)); + .diff_map_snapshot + .to_offset(DiffPoint(cursor.start().1 .0 + start_in_transform)); let inlay_end = self - .inlay_snapshot - .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); + .diff_map_snapshot + .to_offset(DiffPoint(cursor.start().1 .0 + end_in_transform)); summary = self - .inlay_snapshot + .diff_map_snapshot .text_summary_for_range(inlay_start..inlay_end); } } @@ -627,12 +633,12 @@ impl FoldSnapshot { summary += TextSummary::from(&placeholder.text[..end_in_transform.column as usize]); } else { - let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1); + let inlay_start = self.diff_map_snapshot.to_offset(cursor.start().1); let inlay_end = self - .inlay_snapshot - .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); + .diff_map_snapshot + .to_offset(DiffPoint(cursor.start().1 .0 + end_in_transform)); summary += self - .inlay_snapshot + .diff_map_snapshot .text_summary_for_range(inlay_start..inlay_end); } } @@ -644,7 +650,7 @@ impl FoldSnapshot { pub fn text_summary_for_offset_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&()); + let mut cursor = self.transforms.cursor::<(FoldOffset, DiffOffset)>(&()); cursor.seek(&range.start, Bias::Right, &()); if let Some(transform) = cursor.item() { let start_in_transform = range.start.0 - cursor.start().0 .0; @@ -653,10 +659,10 @@ impl FoldSnapshot { summary = TextSummary::from(&placeholder.text[start_in_transform..end_in_transform]); } else { - let inlay_start = InlayOffset(cursor.start().1 .0 + start_in_transform); - let inlay_end = InlayOffset(cursor.start().1 .0 + end_in_transform); + let inlay_start = DiffOffset(cursor.start().1 .0 + start_in_transform); + let inlay_end = DiffOffset(cursor.start().1 .0 + end_in_transform); summary = self - .inlay_snapshot + .diff_map_snapshot .text_summary_for_range(inlay_start..inlay_end); } } @@ -672,9 +678,9 @@ impl FoldSnapshot { summary += TextSummary::from(&placeholder.text[..end_in_transform]); } else { let inlay_start = cursor.start().1; - let inlay_end = InlayOffset(cursor.start().1 .0 + end_in_transform); + let inlay_end = DiffOffset(cursor.start().1 .0 + end_in_transform); summary += self - .inlay_snapshot + .diff_map_snapshot .text_summary_for_range(inlay_start..inlay_end); } } @@ -684,15 +690,15 @@ impl FoldSnapshot { } pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { - self.to_fold_point(self.inlay_snapshot.to_inlay_point(point), bias) + self.to_fold_point(self.diff_map_snapshot.make_diff_point(point), bias) } pub fn make_fold_offset(&self, buffer_offset: usize, bias: Bias) -> FoldOffset { - self.to_fold_offset(self.inlay_snapshot.to_inlay_offset(buffer_offset), bias) + self.to_fold_offset(self.diff_map_snapshot.make_diff_offset(buffer_offset), bias) } - pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&()); + pub fn to_fold_point(&self, point: DiffPoint, bias: Bias) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(DiffPoint, FoldPoint)>(&()); cursor.seek(&point, Bias::Right, &()); if cursor.item().map_or(false, |t| t.is_fold()) { if bias == Bias::Left || point == cursor.start().0 { @@ -709,8 +715,8 @@ impl FoldSnapshot { } } - pub fn to_fold_offset(&self, inlay_offset: InlayOffset, bias: Bias) -> FoldOffset { - let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(&()); + pub fn to_fold_offset(&self, inlay_offset: DiffOffset, bias: Bias) -> FoldOffset { + let mut cursor = self.transforms.cursor::<(DiffOffset, FoldOffset)>(&()); cursor.seek(&inlay_offset, Bias::Right, &()); if cursor.item().map_or(false, |t| t.is_fold()) { if bias == Bias::Left || inlay_offset == cursor.start().0 { @@ -747,12 +753,12 @@ impl FoldSnapshot { } let fold_point = FoldPoint::new(start_row, 0); - let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); + let mut cursor = self.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); cursor.seek(&fold_point, Bias::Left, &()); let overshoot = fold_point.0 - cursor.start().0 .0; - let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot); - let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row()); + let diff_point = DiffPoint(cursor.start().1 .0 + overshoot); + let input_buffer_rows = self.diff_map_snapshot.buffer_rows(diff_point.0.row); FoldBufferRows { fold_point, @@ -774,12 +780,12 @@ impl FoldSnapshot { where T: ToOffset, { - let buffer = &self.inlay_snapshot.buffer; + let buffer = self.buffer(); let range = range.start.to_offset(buffer)..range.end.to_offset(buffer); - let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false); + let mut folds = intersecting_folds(&self.diff_map_snapshot, &self.folds, range, false); iter::from_fn(move || { let item = folds.item(); - folds.next(&self.inlay_snapshot.buffer); + folds.next(buffer); item }) } @@ -788,23 +794,23 @@ impl FoldSnapshot { where T: ToOffset, { - let buffer_offset = offset.to_offset(&self.inlay_snapshot.buffer); - let inlay_offset = self.inlay_snapshot.to_inlay_offset(buffer_offset); - let mut cursor = self.transforms.cursor::(&()); - cursor.seek(&inlay_offset, Bias::Right, &()); + let buffer_offset = offset.to_offset(self.buffer()); + let diff_offset = self.diff_map_snapshot.make_diff_offset(buffer_offset); + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&diff_offset, Bias::Right, &()); cursor.item().map_or(false, |t| t.placeholder.is_some()) } pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool { - let mut inlay_point = self - .inlay_snapshot - .to_inlay_point(Point::new(buffer_row.0, 0)); - let mut cursor = self.transforms.cursor::(&()); - cursor.seek(&inlay_point, Bias::Right, &()); + let mut diff_point = self + .diff_map_snapshot + .make_diff_point(Point::new(buffer_row.0, 0)); + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&diff_point, Bias::Right, &()); loop { match cursor.item() { Some(transform) => { - let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point); + let buffer_point = self.diff_map_snapshot.to_buffer_point(diff_point); if buffer_point.row != buffer_row.0 { return false; } else if transform.placeholder.is_some() { @@ -814,11 +820,11 @@ impl FoldSnapshot { None => return false, } - if cursor.end(&()).row() == inlay_point.row() { + if cursor.end(&()).0.row == diff_point.0.row { cursor.next(&()); } else { - inlay_point.0 += Point::new(1, 0); - cursor.seek(&inlay_point, Bias::Right, &()); + diff_point.0 += Point::new(1, 0); + cursor.seek(&diff_point, Bias::Right, &()); } } } @@ -829,12 +835,12 @@ impl FoldSnapshot { language_aware: bool, highlights: Highlights<'a>, ) -> FoldChunks<'a> { - let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&()); + let mut transform_cursor = self.transforms.cursor::<(FoldOffset, DiffOffset)>(&()); transform_cursor.seek(&range.start, Bias::Right, &()); let inlay_start = { let overshoot = range.start.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + InlayOffset(overshoot) + transform_cursor.start().1 + DiffOffset(overshoot) }; let transform_end = transform_cursor.end(&()); @@ -846,20 +852,20 @@ impl FoldSnapshot { inlay_start } else if range.end < transform_end.0 { let overshoot = range.end.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + InlayOffset(overshoot) + transform_cursor.start().1 + DiffOffset(overshoot) } else { transform_end.1 }; FoldChunks { transform_cursor, - inlay_chunks: self.inlay_snapshot.chunks( + diff_map_chunks: self.diff_map_snapshot.chunks( inlay_start..inlay_end, language_aware, highlights, ), - inlay_chunk: None, - inlay_offset: inlay_start, + diff_chunk: None, + diff_offset: inlay_start, output_offset: range.start, max_output_offset: range.end, } @@ -884,7 +890,7 @@ impl FoldSnapshot { } pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); + let mut cursor = self.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); cursor.seek(&point, Bias::Right, &()); if let Some(transform) = cursor.item() { let transform_start = cursor.start().0 .0; @@ -895,10 +901,10 @@ impl FoldSnapshot { FoldPoint(cursor.end(&()).0 .0) } } else { - let overshoot = InlayPoint(point.0 - transform_start); - let inlay_point = cursor.start().1 + overshoot; - let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias); - FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0) + let overshoot = point.0 - transform_start; + let diff_point = DiffPoint(cursor.start().1 .0 + overshoot); + let clipped_diff_point = self.diff_map_snapshot.clip_point(diff_point, bias); + FoldPoint(cursor.start().0 .0 + (clipped_diff_point - cursor.start().1).0) } } else { FoldPoint(self.transforms.summary().output.lines) @@ -933,12 +939,12 @@ fn push_isomorphic(transforms: &mut SumTree, summary: TextSummary) { } fn intersecting_folds<'a>( - inlay_snapshot: &'a InlaySnapshot, + diff_map_snapshot: &'a DiffMapSnapshot, folds: &'a SumTree, range: Range, inclusive: bool, ) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> { - let buffer = &inlay_snapshot.buffer; + let buffer = diff_map_snapshot.buffer(); let start = buffer.anchor_before(range.start.to_offset(buffer)); let end = buffer.anchor_after(range.end.to_offset(buffer)); let mut cursor = folds.filter::<_, usize>(buffer, move |summary| { @@ -955,7 +961,7 @@ fn intersecting_folds<'a>( cursor } -fn consolidate_inlay_edits(mut edits: Vec) -> Vec { +fn consolidate_inlay_edits(mut edits: Vec) -> Vec { edits.sort_unstable_by(|a, b| { a.old .start @@ -1203,8 +1209,8 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { #[derive(Clone)] pub struct FoldBufferRows<'a> { - cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>, - input_buffer_rows: InlayBufferRows<'a>, + cursor: Cursor<'a, Transform, (FoldPoint, DiffPoint)>, + input_buffer_rows: DiffMapBufferRows<'a>, fold_point: FoldPoint, } @@ -1213,8 +1219,8 @@ impl<'a> FoldBufferRows<'a> { let fold_point = FoldPoint::new(row, 0); self.cursor.seek(&fold_point, Bias::Left, &()); let overshoot = fold_point.0 - self.cursor.start().0 .0; - let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot); - self.input_buffer_rows.seek(inlay_point.row()); + let diff_point = DiffPoint(self.cursor.start().1 .0 + overshoot); + self.input_buffer_rows.seek(diff_point.0.row); self.fold_point = fold_point; } } @@ -1234,7 +1240,7 @@ impl<'a> Iterator for FoldBufferRows<'a> { if self.cursor.item().is_some() { if traversed_fold { - self.input_buffer_rows.seek(self.cursor.start().1.row()); + self.input_buffer_rows.seek(self.cursor.start().1 .0.row); self.input_buffer_rows.next(); } *self.fold_point.row_mut() += 1; @@ -1246,10 +1252,10 @@ impl<'a> Iterator for FoldBufferRows<'a> { } pub struct FoldChunks<'a> { - transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>, - inlay_chunks: InlayChunks<'a>, - inlay_chunk: Option<(InlayOffset, Chunk<'a>)>, - inlay_offset: InlayOffset, + transform_cursor: Cursor<'a, Transform, (FoldOffset, DiffOffset)>, + diff_map_chunks: DiffMapChunks<'a>, + diff_chunk: Option<(DiffOffset, Chunk<'a>)>, + diff_offset: DiffOffset, output_offset: FoldOffset, max_output_offset: FoldOffset, } @@ -1258,29 +1264,29 @@ impl<'a> FoldChunks<'a> { pub(crate) fn seek(&mut self, range: Range) { self.transform_cursor.seek(&range.start, Bias::Right, &()); - let inlay_start = { + let diff_start = { let overshoot = range.start.0 - self.transform_cursor.start().0 .0; - self.transform_cursor.start().1 + InlayOffset(overshoot) + self.transform_cursor.start().1 + DiffOffset(overshoot) }; let transform_end = self.transform_cursor.end(&()); - let inlay_end = if self + let diff_end = if self .transform_cursor .item() .map_or(true, |transform| transform.is_fold()) { - inlay_start + diff_start } else if range.end < transform_end.0 { let overshoot = range.end.0 - self.transform_cursor.start().0 .0; - self.transform_cursor.start().1 + InlayOffset(overshoot) + self.transform_cursor.start().1 + DiffOffset(overshoot) } else { transform_end.1 }; - self.inlay_chunks.seek(inlay_start..inlay_end); - self.inlay_chunk = None; - self.inlay_offset = inlay_start; + self.diff_map_chunks.seek(diff_start..diff_end); + self.diff_chunk = None; + self.diff_offset = diff_start; self.output_offset = range.start; self.max_output_offset = range.end; } @@ -1299,10 +1305,10 @@ impl<'a> Iterator for FoldChunks<'a> { // If we're in a fold, then return the fold's display text and // advance the transform and buffer cursors to the end of the fold. if let Some(placeholder) = transform.placeholder.as_ref() { - self.inlay_chunk.take(); - self.inlay_offset += InlayOffset(transform.summary.input.len); + self.diff_chunk.take(); + self.diff_offset += DiffOffset(transform.summary.input.len); - while self.inlay_offset >= self.transform_cursor.end(&()).1 + while self.diff_offset >= self.transform_cursor.end(&()).1 && self.transform_cursor.item().is_some() { self.transform_cursor.next(&()); @@ -1318,43 +1324,46 @@ impl<'a> Iterator for FoldChunks<'a> { // When we reach a non-fold region, seek the underlying text // chunk iterator to the next unfolded range. - if self.inlay_offset == self.transform_cursor.start().1 - && self.inlay_chunks.offset() != self.inlay_offset + if self.diff_offset == self.transform_cursor.start().1 + && self.diff_map_chunks.offset() != self.diff_offset { let transform_start = self.transform_cursor.start(); let transform_end = self.transform_cursor.end(&()); let inlay_end = if self.max_output_offset < transform_end.0 { let overshoot = self.max_output_offset.0 - transform_start.0 .0; - transform_start.1 + InlayOffset(overshoot) + transform_start.1 + DiffOffset(overshoot) } else { transform_end.1 }; - self.inlay_chunks.seek(self.inlay_offset..inlay_end); + self.diff_map_chunks.seek(self.diff_offset..inlay_end); } // Retrieve a chunk from the current location in the buffer. - if self.inlay_chunk.is_none() { - let chunk_offset = self.inlay_chunks.offset(); - self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk)); + if self.diff_chunk.is_none() { + let chunk_offset = self.diff_map_chunks.offset(); + self.diff_chunk = self + .diff_map_chunks + .next() + .map(|chunk| (chunk_offset, chunk)); } // Otherwise, take a chunk from the buffer's text. - if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() { - let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len()); + if let Some((buffer_chunk_start, mut chunk)) = self.diff_chunk.clone() { + let buffer_chunk_end = buffer_chunk_start + DiffOffset(chunk.text.len()); let transform_end = self.transform_cursor.end(&()).1; let chunk_end = buffer_chunk_end.min(transform_end); chunk.text = &chunk.text - [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; + [(self.diff_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; if chunk_end == transform_end { self.transform_cursor.next(&()); } else if chunk_end == buffer_chunk_end { - self.inlay_chunk.take(); + self.diff_chunk.take(); } - self.inlay_offset = chunk_end; + self.diff_offset = chunk_end; self.output_offset.0 += chunk.text.len(); return Some(chunk); } @@ -1376,18 +1385,20 @@ impl FoldOffset { Point::new(0, (self.0 - cursor.start().0 .0) as u32) } else { let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; - let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset)); + let inlay_point = snapshot + .diff_map_snapshot + .to_point(DiffOffset(inlay_offset)); inlay_point.0 - cursor.start().1.input.lines }; FoldPoint(cursor.start().1.output.lines + overshoot) } #[cfg(test)] - pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { - let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(&()); + pub fn to_diff_offset(self, snapshot: &FoldSnapshot) -> DiffOffset { + let mut cursor = snapshot.transforms.cursor::<(FoldOffset, DiffOffset)>(&()); cursor.seek(&self, Bias::Right, &()); let overshoot = self.0 - cursor.start().0 .0; - InlayOffset(cursor.start().1 .0 + overshoot) + DiffOffset(cursor.start().1 .0 + overshoot) } } @@ -1423,7 +1434,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffPoint { fn zero(_cx: &()) -> Self { Default::default() } @@ -1433,7 +1444,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffOffset { fn zero(_cx: &()) -> Self { Default::default() } @@ -1448,12 +1459,14 @@ pub type FoldEdit = Edit; #[cfg(test)] mod tests { use super::*; - use crate::{display_map::inlay_map::InlayMap, MultiBuffer, ToPoint}; + use crate::{ + display_map::{diff_map::DiffMap, inlay_map::InlayMap}, + MultiBuffer, ToPoint, + }; use collections::HashSet; use rand::prelude::*; use settings::SettingsStore; use std::{env, mem}; - use text::Patch; use util::test::sample_text; use util::RandomCharIter; use Bias::{Left, Right}; @@ -1465,9 +1478,10 @@ mod tests { let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (diff_map, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let mut map = FoldMap::new(diff_snapshot.clone()).0; - let (mut writer, _, _) = map.write(inlay_snapshot, vec![]); + let (mut writer, _, _) = map.write(diff_snapshot, vec![]); let (snapshot2, edits) = writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()), @@ -1501,7 +1515,10 @@ mod tests { let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); - let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (snapshot3, edits) = map.read(diff_snapshot, diff_edits); assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee"); assert_eq!( edits, @@ -1523,17 +1540,20 @@ mod tests { }); let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); - let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (snapshot4, _) = map.read(diff_snapshot.clone(), diff_edits); assert_eq!(snapshot4.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), false); - let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]); + let (snapshot5, _) = map.read(diff_snapshot.clone(), vec![]); assert_eq!(snapshot5.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), true); - let (snapshot6, _) = map.read(inlay_snapshot, vec![]); + let (snapshot6, _) = map.read(diff_snapshot, vec![]); assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee"); } @@ -1544,44 +1564,45 @@ mod tests { let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (diff_map, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); { - let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let mut map = FoldMap::new(diff_snapshot.clone()).0; - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![(5..8, FoldPlaceholder::test())]); - let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "abcde⋯ijkl"); // Create an fold adjacent to the start of the first fold. - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![ (0..1, FoldPlaceholder::test()), (2..5, FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯ijkl"); // Create an fold adjacent to the end of the first fold. - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![ (11..11, FoldPlaceholder::test()), (8..10, FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯kl"); } { - let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let mut map = FoldMap::new(diff_snapshot.clone()).0; // Create two adjacent folds. - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![ (0..2, FoldPlaceholder::test()), (2..5, FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(inlay_snapshot, vec![]); + let (snapshot, _) = map.read(diff_snapshot, vec![]); assert_eq!(snapshot.text(), "⋯fghijkl"); // Edit within one of the folds. @@ -1591,7 +1612,10 @@ mod tests { }); let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); - let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (snapshot, _) = map.read(diff_snapshot, diff_edits); assert_eq!(snapshot.text(), "12345⋯fghijkl"); } } @@ -1601,15 +1625,16 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); - let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let mut map = FoldMap::new(diff_snapshot.clone()).0; + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()), (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(inlay_snapshot, vec![]); + let (snapshot, _) = map.read(diff_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1620,14 +1645,15 @@ mod tests { let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (diff_map, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let mut map = FoldMap::new(diff_snapshot.clone()).0; - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee"); let buffer_snapshot = buffer.update(cx, |buffer, cx| { @@ -1636,7 +1662,10 @@ mod tests { }); let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); - let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (snapshot, _) = map.read(diff_snapshot, diff_edits); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1645,16 +1674,17 @@ mod tests { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let mut map = FoldMap::new(diff_snapshot.clone()).0; - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()), (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); let fold_ranges = snapshot .folds_in_range(Point::new(1, 0)..Point::new(1, 3)) .map(|fold| { @@ -1686,50 +1716,53 @@ mod tests { MultiBuffer::build_random(&mut rng, cx) }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (diff_map, mut diff_snapshot) = + DiffMap::new(inlay_snapshot.clone(), buffer.clone(), cx); + let mut map = FoldMap::new(diff_snapshot.clone()).0; - let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); + let (mut initial_snapshot, _) = map.read(diff_snapshot.clone(), vec![]); let mut snapshot_edits = Vec::new(); let mut next_inlay_id = 0; for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); - let mut buffer_edits = Vec::new(); + let mut inlay_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=39 => { snapshot_edits.extend(map.randomly_mutate(&mut rng)); } 40..=59 => { - let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); - inlay_edits = edits; + (inlay_snapshot, inlay_edits) = + inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + } + _ => { + (inlay_snapshot, inlay_edits) = buffer.update(cx, |buffer, cx| { + let subscription = buffer.subscribe(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_mutate(&mut rng, edit_count, cx); + buffer_snapshot = buffer.snapshot(cx); + let buffer_edits = subscription.consume().into_inner(); + log::info!("editing {:?}", buffer_edits); + inlay_map.sync(buffer_snapshot.clone(), buffer_edits) + }) } - _ => buffer.update(cx, |buffer, cx| { - let subscription = buffer.subscribe(); - let edit_count = rng.gen_range(1..=5); - buffer.randomly_mutate(&mut rng, edit_count, cx); - buffer_snapshot = buffer.snapshot(cx); - let edits = subscription.consume().into_inner(); - log::info!("editing {:?}", edits); - buffer_edits.extend(edits); - }), }; - let (inlay_snapshot, new_inlay_edits) = - inlay_map.sync(buffer_snapshot.clone(), buffer_edits); log::info!("inlay text {:?}", inlay_snapshot.text()); + let (new_diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), inlay_edits, cx) + }); + diff_snapshot = new_diff_snapshot; - let inlay_edits = Patch::new(inlay_edits) - .compose(new_inlay_edits) - .into_inner(); - let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits); + let (snapshot, edits) = map.read(diff_snapshot.clone(), diff_edits); snapshot_edits.push((snapshot.clone(), edits)); - let mut expected_text: String = inlay_snapshot.text().to_string(); + let mut expected_text: String = diff_snapshot.text().to_string(); for fold_range in map.merged_folds().into_iter().rev() { - let fold_inlay_start = inlay_snapshot.to_inlay_offset(fold_range.start); - let fold_inlay_end = inlay_snapshot.to_inlay_offset(fold_range.end); + let fold_inlay_start = diff_snapshot.make_diff_offset(fold_range.start); + let fold_inlay_end = diff_snapshot.make_diff_offset(fold_range.end); expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯"); } @@ -1782,19 +1815,19 @@ mod tests { let mut fold_offset = FoldOffset(0); let mut char_column = 0; for c in expected_text.chars() { - let inlay_point = fold_point.to_inlay_point(&snapshot); - let inlay_offset = fold_offset.to_inlay_offset(&snapshot); + let diff_point = fold_point.to_diff_point(&snapshot); + let diff_offset = fold_offset.to_diff_offset(&snapshot); assert_eq!( - snapshot.to_fold_point(inlay_point, Right), + snapshot.to_fold_point(diff_point, Right), fold_point, "{:?} -> fold point", - inlay_point, + diff_point, ); assert_eq!( - inlay_snapshot.to_offset(inlay_point), - inlay_offset, - "inlay_snapshot.to_offset({:?})", - inlay_point, + diff_snapshot.to_offset(diff_point), + diff_offset, + "diff_map_snapshot.to_offset({:?})", + diff_point, ); assert_eq!( fold_point.to_offset(&snapshot), @@ -1940,15 +1973,16 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); - let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot.clone(), buffer.clone(), cx); + let mut map = FoldMap::new(diff_snapshot.clone()).0; - let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(inlay_snapshot, vec![]); + let (snapshot, _) = map.read(diff_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n"); assert_eq!( snapshot.buffer_rows(0).collect::>(), @@ -1964,8 +1998,8 @@ mod tests { impl FoldMap { fn merged_folds(&self) -> Vec> { - let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); - let buffer = &inlay_snapshot.buffer; + let diff_map_snapshot = self.snapshot.diff_map_snapshot.clone(); + let buffer = diff_map_snapshot.buffer(); let mut folds = self.snapshot.folds.items(buffer); // Ensure sorting doesn't change how folds get merged and displayed. folds.sort_by(|a, b| a.range.cmp(&b.range, buffer)); @@ -2000,8 +2034,8 @@ mod tests { let mut snapshot_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=39 if !self.snapshot.folds.is_empty() => { - let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); - let buffer = &inlay_snapshot.buffer; + let diff_map_snapshot = self.snapshot.diff_map_snapshot.clone(); + let buffer = diff_map_snapshot.buffer(); let mut to_unfold = Vec::new(); for _ in 0..rng.gen_range(1..=3) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -2010,14 +2044,14 @@ mod tests { } let inclusive = rng.gen(); log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive); - let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); + let (mut writer, snapshot, edits) = self.write(diff_map_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.unfold_intersecting(to_unfold, inclusive); snapshot_edits.push((snapshot, edits)); } _ => { - let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); - let buffer = &inlay_snapshot.buffer; + let diff_map_snapshot = self.snapshot.diff_map_snapshot.clone(); + let buffer = diff_map_snapshot.buffer(); let mut to_fold = Vec::new(); for _ in 0..rng.gen_range(1..=2) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -2025,7 +2059,7 @@ mod tests { to_fold.push((start..end, FoldPlaceholder::test())); } log::info!("folding {:?}", to_fold); - let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); + let (mut writer, snapshot, edits) = self.write(diff_map_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_fold); snapshot_edits.push((snapshot, edits)); diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index f99b65db61536e..4dd6fd7e242fed 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -164,7 +164,7 @@ pub struct TabSnapshot { impl TabSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - &self.fold_snapshot.inlay_snapshot.buffer + self.fold_snapshot.diff_map_snapshot.buffer() } pub fn line_len(&self, row: u32) -> u32 { @@ -323,9 +323,9 @@ impl TabSnapshot { pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { let fold_point = self.to_fold_point(point, bias).0; - let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); + let inlay_point = fold_point.to_diff_point(&self.fold_snapshot); self.fold_snapshot - .inlay_snapshot + .diff_map_snapshot .to_buffer_point(inlay_point) } @@ -601,7 +601,7 @@ impl<'a> Iterator for TabChunks<'a> { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, inlay_map::InlayMap}, + display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap}, MultiBuffer, }; use rand::{prelude::StdRng, Rng}; @@ -611,7 +611,8 @@ mod tests { let buffer = MultiBuffer::build_simple("", cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let (_, fold_snapshot) = FoldMap::new(diff_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); @@ -628,7 +629,8 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let (_, fold_snapshot) = FoldMap::new(diff_snapshot); let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; @@ -675,7 +677,8 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let (_, fold_snapshot) = FoldMap::new(diff_snapshot); let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; @@ -689,7 +692,8 @@ mod tests { let buffer = MultiBuffer::build_simple(input, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let (_, fold_snapshot) = FoldMap::new(diff_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!( @@ -749,10 +753,12 @@ mod tests { log::info!("Buffer text: {:?}", buffer_snapshot.text()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone()); + let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + + log::info!("DiffMap text: {:?}", diff_snapshot.text()); + let (mut fold_map, _) = FoldMap::new(diff_snapshot.clone()); fold_map.randomly_mutate(&mut rng); - let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]); + let (fold_snapshot, _) = fold_map.read(diff_snapshot, vec![]); log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index ceb91ce0ab24c5..2ac82a8e4fc19b 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1161,7 +1161,7 @@ fn consolidate_wrap_edits(edits: Vec) -> Vec { mod tests { use super::*; use crate::{ - display_map::{fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, + display_map::{diff_map::DiffMap, fold_map::FoldMap, inlay_map::InlayMap, tab_map::TabMap}, MultiBuffer, }; use gpui::{font, px, test::observe}; @@ -1211,7 +1211,10 @@ mod tests { log::info!("Buffer text: {:?}", buffer_snapshot.text()); let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot.clone()); + let (diff_map, diff_snapshot) = + cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); + log::info!("DiffMap text: {:?}", diff_snapshot.text()); + let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot.clone()); log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); @@ -1272,7 +1275,10 @@ mod tests { 40..=59 => { let (inlay_snapshot, inlay_edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (mut snapshot, wrap_edits) = @@ -1296,7 +1302,10 @@ mod tests { let (inlay_snapshot, inlay_edits) = inlay_map.sync(buffer_snapshot.clone(), buffer_edits); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot, inlay_edits, cx) + }); + let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); From 20ec116932812294c7a290311301356420c6f32e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Dec 2024 21:32:07 -0700 Subject: [PATCH 13/51] Implement to_offset/to_point --- crates/editor/src/display_map/diff_map.rs | 196 +++++++++++++++++----- 1 file changed, 153 insertions(+), 43 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 37e93075b94ee3..07b7d5a6aece70 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -51,8 +51,23 @@ enum DiffTransform { #[derive(Debug, Clone)] struct DiffTransformSummary { - input: TextSummary, - output: TextSummary, + inlay_coords: TextSummary, + diff_coords: TextSummary, +} + +impl DiffTransformSummary { + pub fn inlay_point(&self) -> InlayPoint { + InlayPoint(self.inlay_coords.lines) + } + pub fn inlay_offset(&self) -> InlayOffset { + InlayOffset(self.inlay_coords.len) + } + pub fn diff_point(&self) -> DiffPoint { + DiffPoint(self.diff_coords.lines) + } + pub fn diff_offset(&self) -> DiffOffset { + DiffOffset(self.diff_coords.len) + } } pub(crate) struct DiffMapChunks<'a> { @@ -98,6 +113,12 @@ pub struct DiffOffset(pub usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DiffPoint(pub Point); +impl DiffPoint { + pub fn new(row: u32, col: u32) -> Self { + DiffPoint(Point::new(row, col)) + } +} + impl DiffMap { pub fn new( inlay_snapshot: InlaySnapshot, @@ -310,7 +331,7 @@ impl DiffMap { let diff_edit_old_start = cursor.start().1 + DiffOffset(edit_start_overshoot); let diff_edit_new_start = - DiffOffset(new_transforms.summary().output.len + edit_start_overshoot); + DiffOffset(new_transforms.summary().diff_coords.len + edit_start_overshoot); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) @@ -378,7 +399,8 @@ impl DiffMap { let old_range = prev_old_transform_start ..prev_old_transform_start + DiffOffset(base_text_byte_range.len()); - let new_offset = DiffOffset(new_transforms.summary().output.len); + let new_offset = + DiffOffset(new_transforms.summary().diff_coords.len); let edit = Edit { old: old_range, new: new_offset..new_offset, @@ -433,7 +455,8 @@ impl DiffMap { if !was_previously_expanded { let old_offset = prev_old_transform_start; - let new_start = DiffOffset(new_transforms.summary().output.len); + let new_start = + DiffOffset(new_transforms.summary().diff_coords.len); let edit = Edit { old: old_offset..old_offset, new: new_start @@ -454,7 +477,7 @@ impl DiffMap { ); } else if was_previously_expanded { let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().output.len); + let new_offset = DiffOffset(new_transforms.summary().diff_coords.len); let edit = Edit { old: old_start ..old_start + DiffOffset(hunk.diff_base_byte_range.len()), @@ -477,7 +500,7 @@ impl DiffMap { } = item { let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().output.len); + let new_offset = DiffOffset(new_transforms.summary().diff_coords.len); let edit = Edit { old: old_start..old_start + DiffOffset(base_text_byte_range.len()), new: new_offset..new_offset, @@ -497,7 +520,7 @@ impl DiffMap { let diff_edit_old_end = cursor.start().1 - DiffOffset(edit_undershoot); let diff_edit_new_end = - DiffOffset(new_transforms.summary().output.len - edit_undershoot); + DiffOffset(new_transforms.summary().diff_coords.len - edit_undershoot); if is_inlay_edit { edits.push(DiffEdit { @@ -524,10 +547,9 @@ impl DiffMap { new_transforms: &mut SumTree, end_offset: InlayOffset, ) { - let summary_to_add = self - .snapshot - .inlay_snapshot - .text_summary_for_range(InlayOffset(new_transforms.summary().input.len)..end_offset); + let summary_to_add = self.snapshot.inlay_snapshot.text_summary_for_range( + InlayOffset(new_transforms.summary().inlay_coords.len)..end_offset, + ); if summary_to_add.len > 0 { let mut did_extend = false; new_transforms.update_last( @@ -553,11 +575,11 @@ impl DiffMap { #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; - if snapshot.transforms.summary().input.len != snapshot.inlay_snapshot.len().0 { + if snapshot.transforms.summary().inlay_coords.len != snapshot.inlay_snapshot.len().0 { panic!( "incorrect input length. expected {}, got {}. transforms: {:+?}", snapshot.inlay_snapshot.len().0, - snapshot.transforms.summary().input.len, + snapshot.transforms.summary().inlay_coords.len, snapshot.transforms.items(&()), ); } @@ -591,11 +613,11 @@ impl DiffMapSnapshot { } pub fn len(&self) -> DiffOffset { - DiffOffset(self.transforms.summary().output.len) + DiffOffset(self.transforms.summary().diff_coords.len) } pub fn text_summary(&self) -> TextSummary { - self.transforms.summary().output.clone() + self.transforms.summary().diff_coords.clone() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { @@ -607,31 +629,78 @@ impl DiffMapSnapshot { } pub fn to_point(&self, offset: DiffOffset) -> DiffPoint { - let mut cursor = self.transforms.cursor::<(DiffOffset, DiffPoint)>(&()); + let mut cursor = self.transforms.cursor::(&()); cursor.seek(&offset, Bias::Right, &()); - let mut point = cursor.start().1; + let start_transform = cursor.start(); + let overshoot = offset - start_transform.diff_offset(); + if overshoot.0 == 0 { + return start_transform.diff_point(); + } + match cursor.item() { - Some(_) => todo!(), - None => todo!(), + Some(DiffTransform::BufferContent { .. }) => { + let end_inlay_offset = start_transform.inlay_offset() + InlayOffset(overshoot.0); + let end_inlay_point = self.inlay_snapshot.to_point(end_inlay_offset); + start_transform.diff_point() + + DiffPoint((end_inlay_point - start_transform.inlay_point()).0) + } + Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + base_text_byte_range, + .. + }) => { + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", offset) + }; + let end_buffer_offset = base_text_byte_range.start + overshoot.0; + let end_buffer_point = buffer_diff.base_text.offset_to_point(end_buffer_offset); + start_transform.diff_point() + DiffPoint(end_buffer_point - base_text_start) + } + None => { + panic!("{:?} is past end of buffer", offset) + } } - todo!(); - point } pub fn clip_point(&self, point: DiffPoint, bias: Bias) -> DiffPoint { todo!() } - pub fn to_offset(&self, offset: DiffPoint) -> DiffOffset { - let mut cursor = self.transforms.cursor::<(DiffPoint, DiffOffset)>(&()); - cursor.seek(&offset, Bias::Right, &()); - let mut point = cursor.start().1; + pub fn to_offset(&self, point: DiffPoint) -> DiffOffset { + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&point, Bias::Right, &()); + let start_transform = cursor.start(); + let overshoot = point - start_transform.diff_point(); + if overshoot.0.is_zero() { + return start_transform.diff_offset(); + } + match cursor.item() { - Some(_) => todo!(), - None => todo!(), + Some(DiffTransform::BufferContent { .. }) => { + let end_inlay_point = start_transform.inlay_point() + InlayPoint(overshoot.0); + let end_inlay_offset = self.inlay_snapshot.to_offset(end_inlay_point); + start_transform.diff_offset() + + DiffOffset((end_inlay_offset - start_transform.inlay_offset()).0) + } + Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + base_text_byte_range, + .. + }) => { + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", point) + }; + let end_buffer_point = *base_text_start + overshoot.0; + let end_buffer_offset = buffer_diff.base_text.point_to_offset(end_buffer_point); + start_transform.diff_offset() + + DiffOffset(end_buffer_offset - base_text_byte_range.start) + } + None => { + panic!("{:?} is past end of buffer", point) + } } - todo!(); - point } pub fn to_inlay_offset(&self, offset: DiffOffset) -> InlayOffset { @@ -736,7 +805,7 @@ impl DiffMapSnapshot { } pub fn buffer_rows(&self, start_row: u32) -> DiffMapBufferRows { - if start_row > self.transforms.summary().output.lines.row { + if start_row > self.transforms.summary().diff_coords.lines.row { panic!("invalid diff map row {}", start_row); } @@ -870,12 +939,12 @@ impl sum_tree::Item for DiffTransform { fn summary(&self, _: &::Context) -> Self::Summary { match self { DiffTransform::BufferContent { summary } => DiffTransformSummary { - input: summary.clone(), - output: summary.clone(), + inlay_coords: summary.clone(), + diff_coords: summary.clone(), }, DiffTransform::DeletedHunk { summary, .. } => DiffTransformSummary { - input: TextSummary::default(), - output: summary.clone(), + inlay_coords: TextSummary::default(), + diff_coords: summary.clone(), }, } } @@ -886,14 +955,14 @@ impl sum_tree::Summary for DiffTransformSummary { fn zero(_: &Self::Context) -> Self { DiffTransformSummary { - input: TextSummary::default(), - output: TextSummary::default(), + inlay_coords: TextSummary::default(), + diff_coords: TextSummary::default(), } } fn add_summary(&mut self, summary: &Self, _: &Self::Context) { - self.input += &summary.input; - self.output += &summary.output; + self.inlay_coords += &summary.inlay_coords; + self.diff_coords += &summary.diff_coords; } } @@ -903,7 +972,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayOffset { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.input.len + self.0 += summary.inlay_coords.len } } @@ -913,7 +982,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.output.len + self.0 += summary.diff_coords.len } } @@ -923,7 +992,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayPoint { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.input.lines + self.0 += summary.inlay_coords.lines } } @@ -933,7 +1002,27 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffPoint { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.output.lines + self.0 += summary.diff_coords.lines + } +} + +impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for DiffPoint { + fn cmp( + &self, + cursor_location: &DiffTransformSummary, + _: &::Context, + ) -> std::cmp::Ordering { + Ord::cmp(self, &cursor_location.diff_point()) + } +} + +impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for DiffOffset { + fn cmp( + &self, + cursor_location: &DiffTransformSummary, + _: &::Context, + ) -> std::cmp::Ordering { + Ord::cmp(self, &cursor_location.diff_offset()) } } @@ -1091,6 +1180,27 @@ mod tests { ] ); + for (point, offset) in &[ + ( + DiffPoint::new(0, 0), + DiffOffset(snapshot.text().find("ZERO").unwrap()), + ), + ( + DiffPoint::new(2, 2), + DiffOffset(snapshot.text().find("two").unwrap() + 2), + ), + ( + DiffPoint::new(4, 3), + DiffOffset(snapshot.text().find("three").unwrap() + 3), + ), + (DiffPoint::new(8, 0), DiffOffset(snapshot.text().len())), + ] { + let actual = snapshot.to_offset(*point); + assert_eq!(actual, *offset, "for {:?}", point); + let actual = snapshot.to_point(*offset); + assert_eq!(actual, *point, "for {:?}", offset); + } + diff_map.update(cx, |diff_map, cx| { diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); From 2630dfdd2360406144b570b11c3308aacff07178 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Dec 2024 22:04:12 -0700 Subject: [PATCH 14/51] implement clip_point --- crates/editor/src/display_map/diff_map.rs | 140 ++++++++++++++++++++-- 1 file changed, 127 insertions(+), 13 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 07b7d5a6aece70..124a061a094069 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -639,10 +639,10 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { - let end_inlay_offset = start_transform.inlay_offset() + InlayOffset(overshoot.0); - let end_inlay_point = self.inlay_snapshot.to_point(end_inlay_offset); + let inlay_offset = start_transform.inlay_offset() + InlayOffset(overshoot.0); + let inlay_point = self.inlay_snapshot.to_point(inlay_offset); start_transform.diff_point() - + DiffPoint((end_inlay_point - start_transform.inlay_point()).0) + + DiffPoint((inlay_point - start_transform.inlay_point()).0) } Some(DiffTransform::DeletedHunk { buffer_id, @@ -653,9 +653,9 @@ impl DiffMapSnapshot { let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", offset) }; - let end_buffer_offset = base_text_byte_range.start + overshoot.0; - let end_buffer_point = buffer_diff.base_text.offset_to_point(end_buffer_offset); - start_transform.diff_point() + DiffPoint(end_buffer_point - base_text_start) + let buffer_offset = base_text_byte_range.start + overshoot.0; + let buffer_point = buffer_diff.base_text.offset_to_point(buffer_offset); + start_transform.diff_point() + DiffPoint(buffer_point - base_text_start) } None => { panic!("{:?} is past end of buffer", offset) @@ -664,7 +664,35 @@ impl DiffMapSnapshot { } pub fn clip_point(&self, point: DiffPoint, bias: Bias) -> DiffPoint { - todo!() + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&point, Bias::Right, &()); + let start_transform = cursor.start(); + let overshoot = point - start_transform.diff_point(); + if overshoot.0.is_zero() { + return start_transform.diff_point(); + } + + match cursor.item() { + Some(DiffTransform::BufferContent { .. }) => { + let inlay_point = start_transform.inlay_point() + InlayPoint(overshoot.0); + let clipped = self.inlay_snapshot.clip_point(inlay_point, bias); + start_transform.diff_point() + + DiffPoint((clipped - start_transform.inlay_point()).0) + } + Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + .. + }) => { + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", point) + }; + let buffer_point = *base_text_start + overshoot.0; + let clipped = buffer_diff.base_text.clip_point(buffer_point, bias); + start_transform.diff_point() + DiffPoint(clipped - base_text_start) + } + None => cursor.end(&()).diff_point(), + } } pub fn to_offset(&self, point: DiffPoint) -> DiffOffset { @@ -678,10 +706,10 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { - let end_inlay_point = start_transform.inlay_point() + InlayPoint(overshoot.0); - let end_inlay_offset = self.inlay_snapshot.to_offset(end_inlay_point); + let inlay_point = start_transform.inlay_point() + InlayPoint(overshoot.0); + let inlay_offset = self.inlay_snapshot.to_offset(inlay_point); start_transform.diff_offset() - + DiffOffset((end_inlay_offset - start_transform.inlay_offset()).0) + + DiffOffset((inlay_offset - start_transform.inlay_offset()).0) } Some(DiffTransform::DeletedHunk { buffer_id, @@ -692,10 +720,10 @@ impl DiffMapSnapshot { let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", point) }; - let end_buffer_point = *base_text_start + overshoot.0; - let end_buffer_offset = buffer_diff.base_text.point_to_offset(end_buffer_point); + let buffer_point = *base_text_start + overshoot.0; + let buffer_offset = buffer_diff.base_text.point_to_offset(buffer_point); start_transform.diff_offset() - + DiffOffset(end_buffer_offset - base_text_byte_range.start) + + DiffOffset(buffer_offset - base_text_byte_range.start) } None => { panic!("{:?} is past end of buffer", point) @@ -1289,6 +1317,92 @@ mod tests { ); } + #[gpui::test] + fn test_diff_map_clipping(cx: &mut TestAppContext) { + cx.update(init_test); + + let text = indoc!( + "₀ + ₃ + ₄ + ₅ + " + ); + let base_text = indoc!( + "₀ + ₁ + ₂ + ₃ + ₄ + " + ); + + let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); + let change_set = cx.new_model(|cx| { + BufferChangeSet::new_with_base_text( + base_text.to_string(), + buffer.read(cx).text_snapshot(), + cx, + ) + }); + + let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + let (multibuffer_snapshot, multibuffer_edits) = + multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); + let (_, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); + let (diff_map, _) = + cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); + diff_map.update(cx, |diff_map, cx| { + diff_map.set_all_hunks_expanded(cx); + diff_map.add_change_set(change_set, cx); + }); + cx.run_until_parked(); + let (diff_snapshot, _) = + diff_map.update(cx, |diff_map, cx| diff_map.sync(inlay_snapshot, vec![], cx)); + + assert_eq!( + diff_snapshot.text(), + indoc! {" + ₀ + ₁ + ₂ + ₃ + ₄ + ₅ + "} + ); + + for (point, (left, right)) in [ + ( + DiffPoint::new(0, 0), // start + (DiffPoint::new(0, 0), DiffPoint::new(0, 0)), + ), + ( + DiffPoint::new(1, 1), // deleted + (DiffPoint::new(1, 0), DiffPoint::new(1, 3)), + ), + ( + DiffPoint::new(3, 1), // unchanged + (DiffPoint::new(3, 0), DiffPoint::new(3, 3)), + ), + ( + DiffPoint::new(5, 2), // inserted + (DiffPoint::new(5, 0), DiffPoint::new(5, 3)), + ), + ( + DiffPoint::new(6, 0), // end + (DiffPoint::new(6, 0), DiffPoint::new(6, 0)), + ), + ( + DiffPoint::new(7, 7), // beyond + (DiffPoint::new(6, 0), DiffPoint::new(6, 0)), + ), + ] { + assert_eq!(left, diff_snapshot.clip_point(point, Bias::Left)); + assert_eq!(right, diff_snapshot.clip_point(point, Bias::Right)); + } + } + #[track_caller] fn assert_new_snapshot( snapshot: &mut DiffMapSnapshot, From 9dc5ebab852de1feaad722b2d70371113974e167 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Dec 2024 22:13:28 -0700 Subject: [PATCH 15/51] better naming? --- crates/editor/src/display_map/diff_map.rs | 62 +++++++++++------------ 1 file changed, 30 insertions(+), 32 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 124a061a094069..043466028487b8 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -51,22 +51,22 @@ enum DiffTransform { #[derive(Debug, Clone)] struct DiffTransformSummary { - inlay_coords: TextSummary, - diff_coords: TextSummary, + inlay_map: TextSummary, + diff_map: TextSummary, } impl DiffTransformSummary { pub fn inlay_point(&self) -> InlayPoint { - InlayPoint(self.inlay_coords.lines) + InlayPoint(self.inlay_map.lines) } pub fn inlay_offset(&self) -> InlayOffset { - InlayOffset(self.inlay_coords.len) + InlayOffset(self.inlay_map.len) } pub fn diff_point(&self) -> DiffPoint { - DiffPoint(self.diff_coords.lines) + DiffPoint(self.diff_map.lines) } pub fn diff_offset(&self) -> DiffOffset { - DiffOffset(self.diff_coords.len) + DiffOffset(self.diff_map.len) } } @@ -331,7 +331,7 @@ impl DiffMap { let diff_edit_old_start = cursor.start().1 + DiffOffset(edit_start_overshoot); let diff_edit_new_start = - DiffOffset(new_transforms.summary().diff_coords.len + edit_start_overshoot); + DiffOffset(new_transforms.summary().diff_map.len + edit_start_overshoot); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) @@ -399,8 +399,7 @@ impl DiffMap { let old_range = prev_old_transform_start ..prev_old_transform_start + DiffOffset(base_text_byte_range.len()); - let new_offset = - DiffOffset(new_transforms.summary().diff_coords.len); + let new_offset = DiffOffset(new_transforms.summary().diff_map.len); let edit = Edit { old: old_range, new: new_offset..new_offset, @@ -455,8 +454,7 @@ impl DiffMap { if !was_previously_expanded { let old_offset = prev_old_transform_start; - let new_start = - DiffOffset(new_transforms.summary().diff_coords.len); + let new_start = DiffOffset(new_transforms.summary().diff_map.len); let edit = Edit { old: old_offset..old_offset, new: new_start @@ -477,7 +475,7 @@ impl DiffMap { ); } else if was_previously_expanded { let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().diff_coords.len); + let new_offset = DiffOffset(new_transforms.summary().diff_map.len); let edit = Edit { old: old_start ..old_start + DiffOffset(hunk.diff_base_byte_range.len()), @@ -500,7 +498,7 @@ impl DiffMap { } = item { let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().diff_coords.len); + let new_offset = DiffOffset(new_transforms.summary().diff_map.len); let edit = Edit { old: old_start..old_start + DiffOffset(base_text_byte_range.len()), new: new_offset..new_offset, @@ -520,7 +518,7 @@ impl DiffMap { let diff_edit_old_end = cursor.start().1 - DiffOffset(edit_undershoot); let diff_edit_new_end = - DiffOffset(new_transforms.summary().diff_coords.len - edit_undershoot); + DiffOffset(new_transforms.summary().diff_map.len - edit_undershoot); if is_inlay_edit { edits.push(DiffEdit { @@ -548,7 +546,7 @@ impl DiffMap { end_offset: InlayOffset, ) { let summary_to_add = self.snapshot.inlay_snapshot.text_summary_for_range( - InlayOffset(new_transforms.summary().inlay_coords.len)..end_offset, + InlayOffset(new_transforms.summary().inlay_map.len)..end_offset, ); if summary_to_add.len > 0 { let mut did_extend = false; @@ -575,11 +573,11 @@ impl DiffMap { #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; - if snapshot.transforms.summary().inlay_coords.len != snapshot.inlay_snapshot.len().0 { + if snapshot.transforms.summary().inlay_map.len != snapshot.inlay_snapshot.len().0 { panic!( "incorrect input length. expected {}, got {}. transforms: {:+?}", snapshot.inlay_snapshot.len().0, - snapshot.transforms.summary().inlay_coords.len, + snapshot.transforms.summary().inlay_map.len, snapshot.transforms.items(&()), ); } @@ -613,11 +611,11 @@ impl DiffMapSnapshot { } pub fn len(&self) -> DiffOffset { - DiffOffset(self.transforms.summary().diff_coords.len) + DiffOffset(self.transforms.summary().diff_map.len) } pub fn text_summary(&self) -> TextSummary { - self.transforms.summary().diff_coords.clone() + self.transforms.summary().diff_map.clone() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { @@ -833,7 +831,7 @@ impl DiffMapSnapshot { } pub fn buffer_rows(&self, start_row: u32) -> DiffMapBufferRows { - if start_row > self.transforms.summary().diff_coords.lines.row { + if start_row > self.transforms.summary().diff_map.lines.row { panic!("invalid diff map row {}", start_row); } @@ -967,12 +965,12 @@ impl sum_tree::Item for DiffTransform { fn summary(&self, _: &::Context) -> Self::Summary { match self { DiffTransform::BufferContent { summary } => DiffTransformSummary { - inlay_coords: summary.clone(), - diff_coords: summary.clone(), + inlay_map: summary.clone(), + diff_map: summary.clone(), }, DiffTransform::DeletedHunk { summary, .. } => DiffTransformSummary { - inlay_coords: TextSummary::default(), - diff_coords: summary.clone(), + inlay_map: TextSummary::default(), + diff_map: summary.clone(), }, } } @@ -983,14 +981,14 @@ impl sum_tree::Summary for DiffTransformSummary { fn zero(_: &Self::Context) -> Self { DiffTransformSummary { - inlay_coords: TextSummary::default(), - diff_coords: TextSummary::default(), + inlay_map: TextSummary::default(), + diff_map: TextSummary::default(), } } fn add_summary(&mut self, summary: &Self, _: &Self::Context) { - self.inlay_coords += &summary.inlay_coords; - self.diff_coords += &summary.diff_coords; + self.inlay_map += &summary.inlay_map; + self.diff_map += &summary.diff_map; } } @@ -1000,7 +998,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayOffset { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.inlay_coords.len + self.0 += summary.inlay_map.len } } @@ -1010,7 +1008,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.diff_coords.len + self.0 += summary.diff_map.len } } @@ -1020,7 +1018,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayPoint { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.inlay_coords.lines + self.0 += summary.inlay_map.lines } } @@ -1030,7 +1028,7 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffPoint { } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.diff_coords.lines + self.0 += summary.diff_map.lines } } From a8a91778785a40f10c5b6d0449ff77b628438f9e Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Dec 2024 23:12:58 -0700 Subject: [PATCH 16/51] implement text_summary_for_range --- crates/editor/src/display_map/diff_map.rs | 140 +++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 043466028487b8..a07b9ace83f414 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -619,7 +619,83 @@ impl DiffMapSnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - todo!() + let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); + cursor.seek(&range.start, Bias::Right, &()); + + let Some(first_transform) = cursor.item() else { + return TextSummary::default(); + }; + + let (diff_transform_start, inlay_transform_start) = cursor.start().clone(); + let (diff_transform_end, _) = cursor.end(&()); + let diff_start = range.start; + let diff_end = std::cmp::min(range.end, diff_transform_end); + + let mut result = match first_transform { + DiffTransform::BufferContent { .. } => { + let inlay_start = + inlay_transform_start + InlayOffset((diff_start - diff_transform_start).0); + let inlay_end = + inlay_transform_start + InlayOffset((diff_end - diff_transform_start).0); + + self.inlay_snapshot + .text_summary_for_range(inlay_start..inlay_end) + } + DiffTransform::DeletedHunk { + buffer_id, + base_text_byte_range, + .. + } => { + let buffer_start = + base_text_byte_range.start + (diff_start - diff_transform_start).0; + let buffer_end = base_text_byte_range.start + (diff_end - diff_transform_start).0; + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", range.start) + }; + + buffer_diff + .base_text + .text_summary_for_range(buffer_start..buffer_end) + } + }; + if range.end < diff_transform_end { + return result; + } + + cursor.next(&()); + result = result + cursor.summary(&range.end, Bias::Right, &()); + + let Some(last_transform) = cursor.item() else { + return result; + }; + + let (diff_transform_start, inlay_transform_start) = cursor.start().clone(); + + result += match last_transform { + DiffTransform::BufferContent { .. } => { + let inlay_end = + inlay_transform_start + InlayOffset((range.end - diff_transform_start).0); + + self.inlay_snapshot + .text_summary_for_range(inlay_transform_start..inlay_end) + } + DiffTransform::DeletedHunk { + base_text_byte_range, + buffer_id, + .. + } => { + let buffer_end = base_text_byte_range.start + (range.end - diff_transform_start).0; + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", range.end) + }; + + buffer_diff + .base_text + .text_summary_for_range(base_text_byte_range.start..buffer_end) + } + }; + + result } pub fn buffer(&self) -> &MultiBufferSnapshot { @@ -1032,6 +1108,16 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffPoint { } } +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for TextSummary { + fn zero(_cx: &()) -> Self { + Default::default() + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + *self += &summary.diff_map; + } +} + impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for DiffPoint { fn cmp( &self, @@ -1111,6 +1197,7 @@ mod tests { use multi_buffer::{Anchor, MultiBuffer}; use project::Project; use settings::SettingsStore; + use text::OffsetUtf16; #[gpui::test] fn test_basic_diff(cx: &mut TestAppContext) { @@ -1345,7 +1432,7 @@ mod tests { }); let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); - let (multibuffer_snapshot, multibuffer_edits) = + let (multibuffer_snapshot, _) = multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); let (_, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); let (diff_map, _) = @@ -1399,6 +1486,55 @@ mod tests { assert_eq!(left, diff_snapshot.clip_point(point, Bias::Left)); assert_eq!(right, diff_snapshot.clip_point(point, Bias::Right)); } + + assert_eq!( + diff_snapshot.text_summary_for_range(DiffOffset(0)..DiffOffset(0)), + TextSummary::default() + ); + assert_eq!( + diff_snapshot.text_summary_for_range(diff_snapshot.len()..diff_snapshot.len()), + TextSummary::default() + ); + let full_summary = TextSummary { + len: 24, + len_utf16: OffsetUtf16(12), + lines: Point { row: 6, column: 0 }, + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 0, + longest_row_chars: 1, + }; + let partial_summary = TextSummary { + len: 8, + len_utf16: OffsetUtf16(4), + lines: Point { row: 2, column: 0 }, + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 0, + longest_row_chars: 1, + }; + + let two = DiffOffset(diff_snapshot.text().find("₂").unwrap()); + let four = DiffOffset(diff_snapshot.text().find("₄").unwrap()); + + assert_eq!( + diff_snapshot.text_summary_for_range(DiffOffset(0)..diff_snapshot.len()), + full_summary + ); + assert_eq!( + diff_snapshot.text_summary_for_range(DiffOffset(0)..two), + partial_summary + ); + assert_eq!( + diff_snapshot.text_summary_for_range(two..four), + partial_summary + ); + assert_eq!( + diff_snapshot.text_summary_for_range(four..diff_snapshot.len()), + partial_summary + ); } #[track_caller] From 6e724c11689f647eb2f9b21726a8e5e3b49ab2be Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Wed, 11 Dec 2024 23:43:29 -0700 Subject: [PATCH 17/51] seek on DiffMapBufferRows --- crates/editor/src/display_map/diff_map.rs | 60 +++++++++++++++++++++-- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index a07b9ace83f414..b2bbdb594ceb6a 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -117,6 +117,13 @@ impl DiffPoint { pub fn new(row: u32, col: u32) -> Self { DiffPoint(Point::new(row, col)) } + + pub fn row(&self) -> u32 { + self.0.row + } + pub fn column(&self) -> u32 { + self.0.column + } } impl DiffMap { @@ -913,11 +920,18 @@ impl DiffMapSnapshot { let diff_point = DiffPoint(Point::new(start_row, 0)); let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&()); - cursor.seek(&diff_point, Bias::Left, &()); + cursor.seek(&diff_point, Bias::Right, &()); - let overshoot = diff_point.0 - cursor.start().0 .0; - let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot); - let input_buffer_rows = self.inlay_snapshot.buffer_rows(inlay_point.row()); + let (diff_transform_start, inlay_transform_start) = cursor.start().clone(); + + let overshoot = if matches!(cursor.item(), Some(DiffTransform::BufferContent { .. })) { + diff_point.row() - diff_transform_start.row() + } else { + 0 + }; + let input_buffer_rows = self + .inlay_snapshot + .buffer_rows(inlay_transform_start.row() + overshoot); DiffMapBufferRows { diff_point, @@ -1015,7 +1029,19 @@ impl<'a> Iterator for DiffMapChunks<'a> { impl<'a> DiffMapBufferRows<'a> { pub fn seek(&mut self, row: u32) { - todo!() + self.diff_point = DiffPoint::new(row, 0); + self.cursor.seek(&self.diff_point, Bias::Right, &()); + let (diff_transform_start, inlay_transform_start) = self.cursor.start().clone(); + let overshoot = if matches!( + self.cursor.item(), + Some(DiffTransform::BufferContent { .. }) + ) { + self.diff_point.row() - diff_transform_start.row() + } else { + 0 + }; + self.input_buffer_rows + .seek(inlay_transform_start.row() + overshoot); } } @@ -1293,6 +1319,30 @@ mod tests { ] ); + assert_eq!( + snapshot.buffer_rows(4).collect::>(), + vec![Some(3), None, None, Some(4)] + ); + assert_eq!( + snapshot.buffer_rows(5).collect::>(), + vec![None, None, Some(4)] + ); + assert_eq!( + snapshot.buffer_rows(6).collect::>(), + vec![None, Some(4)] + ); + + let mut buffer_rows = snapshot.buffer_rows(0); + buffer_rows.seek(7); + assert_eq!(buffer_rows.next(), Some(Some(4))); + buffer_rows.seek(6); + assert_eq!(buffer_rows.next(), Some(None)); + buffer_rows.seek(5); + assert_eq!(buffer_rows.next(), Some(None)); + buffer_rows.seek(4); + assert_eq!(buffer_rows.next(), Some(Some(3))); + drop(buffer_rows); + for (point, offset) in &[ ( DiffPoint::new(0, 0), From e53d8bb8d2374e010a2d9aae0f609c3890179826 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 09:59:37 -0800 Subject: [PATCH 18/51] wip --- crates/editor/src/display_map/diff_map.rs | 88 +++++++++++++++++------ 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index b2bbdb594ceb6a..89d6ec0abe9f3d 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -29,7 +29,7 @@ struct DiffSnapshot { } #[derive(Clone)] -pub(crate) struct DiffMapSnapshot { +pub struct DiffMapSnapshot { diffs: TreeMap, transforms: SumTree, pub(crate) version: usize, @@ -70,20 +70,20 @@ impl DiffTransformSummary { } } -pub(crate) struct DiffMapChunks<'a> { +pub struct DiffMapChunks<'a> { snapshot: &'a DiffMapSnapshot, language_aware: bool, cursor: Cursor<'a, DiffTransform, (DiffOffset, InlayOffset)>, - fold_chunks: InlayChunks<'a>, - fold_chunk: Option>, - fold_offset: InlayOffset, + inlay_chunks: InlayChunks<'a>, + inlay_chunk: Option>, + inlay_offset: InlayOffset, offset: DiffOffset, end_offset: DiffOffset, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, } #[derive(Clone)] -pub(crate) struct DiffMapBufferRows<'a> { +pub struct DiffMapBufferRows<'a> { cursor: Cursor<'a, DiffTransform, (DiffPoint, InlayPoint)>, diff_point: DiffPoint, input_buffer_rows: InlayBufferRows<'a>, @@ -883,30 +883,30 @@ impl DiffMapSnapshot { let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); cursor.seek(&range.end, Bias::Right, &()); - let mut fold_end = cursor.start().1; + let mut inlay_end = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = range.end.0 - cursor.start().0 .0; - fold_end.0 += overshoot; + inlay_end.0 += overshoot; } cursor.seek(&range.start, Bias::Right, &()); - let mut fold_start = cursor.start().1; + let mut inlay_start = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = range.start.0 - cursor.start().0 .0; - fold_start.0 += overshoot; + inlay_start.0 += overshoot; } - let fold_chunks = + let inlay_chunks = self.inlay_snapshot - .chunks(fold_start..fold_end, language_aware, highlights); + .chunks(inlay_start..inlay_end, language_aware, highlights); DiffMapChunks { snapshot: self, language_aware, cursor, - fold_chunk: None, - fold_chunks, - fold_offset: fold_start, + inlay_chunk: None, + inlay_chunks, + inlay_offset: inlay_start, offset: range.start, diff_base_chunks: None, end_offset: range.end, @@ -943,7 +943,25 @@ impl DiffMapSnapshot { impl<'a> DiffMapChunks<'a> { pub fn seek(&mut self, range: Range) { - todo!() + self.cursor.seek(&range.end, Bias::Right, &()); + let mut inlay_end = self.cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { + let overshoot = range.end.0 - self.cursor.start().0 .0; + inlay_end.0 += overshoot; + } + + self.cursor.seek(&range.start, Bias::Right, &()); + let mut inlay_start = self.cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { + let overshoot = range.start.0 - self.cursor.start().0 .0; + inlay_start.0 += overshoot; + } + + self.inlay_chunks.seek(inlay_start..inlay_end); + self.inlay_chunk.take(); + self.inlay_offset = inlay_start; + self.offset = range.start; + self.end_offset = range.end; } pub fn offset(&self) -> DiffOffset { @@ -967,8 +985,8 @@ impl<'a> Iterator for DiffMapChunks<'a> { match transform { DiffTransform::BufferContent { summary } => { let chunk = self - .fold_chunk - .get_or_insert_with(|| self.fold_chunks.next().unwrap()); + .inlay_chunk + .get_or_insert_with(|| self.inlay_chunks.next().unwrap()); let chunk_end = self.offset + DiffOffset(chunk.text.len()); let mut transform_end = self.cursor.start().0 + DiffOffset(summary.len); @@ -987,13 +1005,11 @@ impl<'a> Iterator for DiffMapChunks<'a> { }) } else { self.offset = chunk_end; - self.fold_chunk.take() + self.inlay_chunk.take() } } DiffTransform::DeletedHunk { - summary, buffer_id, - base_text_start, base_text_byte_range, .. } => { @@ -1226,7 +1242,7 @@ mod tests { use text::OffsetUtf16; #[gpui::test] - fn test_basic_diff(cx: &mut TestAppContext) { + fn test_basic_diff_map_updates(cx: &mut TestAppContext) { cx.update(init_test); let text = indoc!( @@ -1305,6 +1321,9 @@ mod tests { " ), ); + + assert_chunks_in_ranges(&snapshot); + assert_eq!( snapshot.buffer_rows(0).collect::>(), vec![ @@ -1627,6 +1646,31 @@ mod tests { pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits); } + #[track_caller] + fn assert_chunks_in_ranges(snapshot: &DiffMapSnapshot) { + let full_text = snapshot.text(); + for ix in 0..full_text.len() { + let offset = DiffOffset(ix); + let mut chunks = + snapshot.chunks(DiffOffset(0)..snapshot.len(), false, Default::default()); + chunks.seek(offset..snapshot.len()); + let tail = chunks.map(|chunk| chunk.text).collect::(); + assert_eq!(tail, &full_text[ix..]); + + let tail = snapshot + .chunks(offset..snapshot.len(), false, Default::default()) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!(tail, &full_text[ix..]); + + let head = snapshot + .chunks(DiffOffset(0)..offset, false, Default::default()) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!(head, &full_text[..ix]); + } + } + fn init_test(cx: &mut AppContext) { let settings = SettingsStore::test(cx); cx.set_global(settings); From c015553b1fd235be1161deb381eb909dcc80a0c3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 10:41:40 -0800 Subject: [PATCH 19/51] Get assertions about DiffMap::chunks passing Co-authored-by: Conrad --- crates/editor/src/display_map/diff_map.rs | 30 +++++++++++++---------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 89d6ec0abe9f3d..1de489c3109e78 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1013,28 +1013,32 @@ impl<'a> Iterator for DiffMapChunks<'a> { base_text_byte_range, .. } => { - let base_buffer = &self.snapshot.diffs.get(&buffer_id)?.base_text; - - let diff_base_start_offset = base_text_byte_range.start; - let diff_base_end_offset = base_text_byte_range.end; - let diff_base_offset = - diff_base_start_offset + self.offset.0 - self.cursor.start().0 .0; + let hunk_start_offset = self.cursor.start().0; + let base_text_start_offset = base_text_byte_range.start; + let base_text_end_offset = base_text_byte_range.end; + let diff_base_end_offset = base_text_end_offset + .min(base_text_start_offset + self.end_offset.0 - hunk_start_offset.0); + let diff_base_start_offset = + base_text_start_offset + self.offset.0 - hunk_start_offset.0; let mut chunks = if let Some((_, mut chunks)) = self .diff_base_chunks .take() .filter(|(id, _)| id == buffer_id) { - if chunks.offset() != diff_base_offset { - chunks.seek(diff_base_offset..diff_base_end_offset); + if chunks.offset() != diff_base_start_offset { + chunks.seek(diff_base_start_offset..diff_base_end_offset); } chunks } else { - base_buffer.chunks(diff_base_offset..diff_base_end_offset, self.language_aware) + let base_buffer = &self.snapshot.diffs.get(&buffer_id)?.base_text; + base_buffer.chunks( + diff_base_start_offset..diff_base_end_offset, + self.language_aware, + ) }; let chunk = chunks.next()?; - self.offset.0 += chunk.text.len(); self.diff_base_chunks = Some((*buffer_id, chunks)); Some(chunk) @@ -1655,19 +1659,19 @@ mod tests { snapshot.chunks(DiffOffset(0)..snapshot.len(), false, Default::default()); chunks.seek(offset..snapshot.len()); let tail = chunks.map(|chunk| chunk.text).collect::(); - assert_eq!(tail, &full_text[ix..]); + assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..); let tail = snapshot .chunks(offset..snapshot.len(), false, Default::default()) .map(|chunk| chunk.text) .collect::(); - assert_eq!(tail, &full_text[ix..]); + assert_eq!(tail, &full_text[ix..], "start from range: {:?}", ix..); let head = snapshot .chunks(DiffOffset(0)..offset, false, Default::default()) .map(|chunk| chunk.text) .collect::(); - assert_eq!(head, &full_text[..ix]); + assert_eq!(head, &full_text[..ix], "start with range: {:?}", ..ix); } } From 99edc9583c1afe58f5cd2e921d3675d6b65fb613 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 11:55:37 -0800 Subject: [PATCH 20/51] Fix DiffMap::buffer_rows to handle last empty line of buffer Co-authored-by: Conrad --- crates/editor/src/display_map/diff_map.rs | 38 +++++++++++++++++------ 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 1de489c3109e78..ec7ca44d5a9f6c 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -597,7 +597,7 @@ impl DiffMap { panic!("multiple adjacent buffer content transforms"); } prev_transform_is_buffer_content = true; - if summary.len == 0 { + if summary.len == 0 && !snapshot.buffer().is_empty() { panic!("empty buffer content transform"); } } @@ -874,7 +874,7 @@ impl DiffMapSnapshot { .to_buffer_point(self.to_inlay_point(diff_point)) } - pub fn chunks<'a>( + pub(crate) fn chunks<'a>( &'a self, range: Range, language_aware: bool, @@ -1069,9 +1069,10 @@ impl<'a> Iterator for DiffMapBufferRows<'a> { type Item = Option; fn next(&mut self) -> Option { - let result = match self.cursor.item()? { - DiffTransform::DeletedHunk { .. } => Some(None), - DiffTransform::BufferContent { .. } => self.input_buffer_rows.next(), + let result = if let Some(DiffTransform::DeletedHunk { .. }) = self.cursor.item() { + Some(None) + } else { + self.input_buffer_rows.next() }; self.diff_point.0 += Point::new(1, 0); if self.diff_point >= self.cursor.end(&()).0 { @@ -1338,21 +1339,22 @@ mod tests { Some(3), None, None, - Some(4) + Some(4), + Some(5) ] ); assert_eq!( snapshot.buffer_rows(4).collect::>(), - vec![Some(3), None, None, Some(4)] + vec![Some(3), None, None, Some(4), Some(5)] ); assert_eq!( snapshot.buffer_rows(5).collect::>(), - vec![None, None, Some(4)] + vec![None, None, Some(4), Some(5)] ); assert_eq!( snapshot.buffer_rows(6).collect::>(), - vec![None, Some(4)] + vec![None, Some(4), Some(5)] ); let mut buffer_rows = snapshot.buffer_rows(0); @@ -1610,6 +1612,24 @@ mod tests { ); } + #[gpui::test] + fn test_empty_diff_map(cx: &mut TestAppContext) { + cx.update(init_test); + + let text = ""; + + let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); + let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + let (multibuffer_snapshot, _multibuffer_edits) = + multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); + let (_inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); + + let (_diff_map, diff_snapshot) = + cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); + + assert_eq!(diff_snapshot.buffer_rows(0).collect::>(), [Some(0)]); + } + #[track_caller] fn assert_new_snapshot( snapshot: &mut DiffMapSnapshot, From ad87dfe9085910ea1808ef231fa4b39fe1090069 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 12:52:29 -0800 Subject: [PATCH 21/51] Get more editor tests passing Correctly handle multiple edits to DiffMap Co-authored-by: Conrad --- crates/editor/src/display_map/diff_map.rs | 355 ++++++++++++---------- 1 file changed, 192 insertions(+), 163 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index ec7ca44d5a9f6c..a9e65c73ba0741 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -329,209 +329,206 @@ impl DiffMap { let mut new_transforms = SumTree::default(); let mut edits = Patch::default(); - for (mut edit, is_inlay_edit, multibuffer_range) in changes { + let mut changes = changes.into_iter().peekable(); + while let Some((mut edit, mut is_inlay_edit, mut multibuffer_range)) = changes.next() { new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); - let edit_start_overshoot = (edit.old.start - cursor.start().0).0; - edit.new.start.0 -= edit_start_overshoot; - edit.old.start = cursor.start().0; + loop { + let old_overshoot = (edit.old.start - cursor.start().0).0; + let new_overshoot = edit.new.start.0 - new_transforms.summary().inlay_map.len; + let diff_edit_old_start = cursor.start().1 + DiffOffset(old_overshoot); + let diff_edit_new_start = + DiffOffset(new_transforms.summary().diff_map.len + new_overshoot); - let diff_edit_old_start = cursor.start().1 + DiffOffset(edit_start_overshoot); - let diff_edit_new_start = - DiffOffset(new_transforms.summary().diff_map.len + edit_start_overshoot); - - for (buffer, buffer_range, excerpt_id) in - multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) - { - let excerpt_range = multibuffer_snapshot - .range_for_excerpt::(excerpt_id) - .unwrap(); - let excerpt_buffer_range = multibuffer_snapshot - .buffer_range_for_excerpt(excerpt_id) - .unwrap(); - let buffer_id = buffer.read(cx).remote_id(); - let diff_state = self.snapshot.diffs.get(&buffer_id); - - let buffer = buffer.read(cx); - let buffer_anchor_range = - buffer.anchor_after(buffer_range.start)..buffer.anchor_before(buffer_range.end); - let change_start_buffer_offset = buffer_range.start; - if let Some(diff_state) = diff_state { - let diff = &diff_state.diff; - let base_text = &diff_state.base_text; - - for hunk in diff.hunks_intersecting_range(buffer_anchor_range, buffer) { - let hunk_anchor_range = { - let start = multi_buffer::Anchor { - excerpt_id, - buffer_id: Some(buffer_id), - text_anchor: hunk.buffer_range.start, - }; - let end = multi_buffer::Anchor { - excerpt_id, - buffer_id: Some(buffer_id), - text_anchor: hunk.buffer_range.end, + for (buffer, buffer_range, excerpt_id) in + multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) + { + let excerpt_range = multibuffer_snapshot + .range_for_excerpt::(excerpt_id) + .unwrap(); + let excerpt_buffer_range = multibuffer_snapshot + .buffer_range_for_excerpt(excerpt_id) + .unwrap(); + let buffer_id = buffer.read(cx).remote_id(); + let diff_state = self.snapshot.diffs.get(&buffer_id); + + let buffer = buffer.read(cx); + let buffer_anchor_range = buffer.anchor_after(buffer_range.start) + ..buffer.anchor_before(buffer_range.end); + let change_start_buffer_offset = buffer_range.start; + if let Some(diff_state) = diff_state { + let diff = &diff_state.diff; + let base_text = &diff_state.base_text; + + for hunk in diff.hunks_intersecting_range(buffer_anchor_range, buffer) { + let hunk_anchor_range = { + let start = multi_buffer::Anchor { + excerpt_id, + buffer_id: Some(buffer_id), + text_anchor: hunk.buffer_range.start, + }; + let end = multi_buffer::Anchor { + excerpt_id, + buffer_id: Some(buffer_id), + text_anchor: hunk.buffer_range.end, + }; + start..end }; - start..end - }; - - let hunk_start_buffer_offset = hunk.buffer_range.start.to_offset(buffer); - if hunk_start_buffer_offset < change_start_buffer_offset { - continue; - } - let hunk_start_multibuffer_offset = excerpt_range.start - + hunk_start_buffer_offset - - excerpt_buffer_range.start.to_offset(buffer); - let hunk_start_inlay_offset = self - .snapshot - .inlay_snapshot - .to_inlay_offset(hunk_start_multibuffer_offset); + let hunk_start_buffer_offset = + hunk.buffer_range.start.to_offset(buffer); + if hunk_start_buffer_offset < change_start_buffer_offset { + continue; + } - self.push_buffer_content_transform( - &mut new_transforms, - hunk_start_inlay_offset, - ); + let hunk_start_multibuffer_offset = excerpt_range.start + + hunk_start_buffer_offset + - excerpt_buffer_range.start.to_offset(buffer); + let hunk_start_inlay_offset = self + .snapshot + .inlay_snapshot + .to_inlay_offset(hunk_start_multibuffer_offset); + + self.push_buffer_content_transform( + &mut new_transforms, + hunk_start_inlay_offset, + ); - let mut prev_old_transform_start = cursor.start().1; - while cursor.start().0 < hunk_start_inlay_offset { - let Some(item) = cursor.item() else { - break; - }; - if let DiffTransform::DeletedHunk { - base_text_byte_range, - .. - } = item - { - let old_range = prev_old_transform_start - ..prev_old_transform_start - + DiffOffset(base_text_byte_range.len()); - let new_offset = DiffOffset(new_transforms.summary().diff_map.len); - let edit = Edit { - old: old_range, - new: new_offset..new_offset, + while cursor.end(&()).0 <= hunk_start_inlay_offset { + let Some(item) = cursor.item() else { + break; }; - dbg!(&edit); - edits.push(edit); + if let DiffTransform::DeletedHunk { .. } = item { + let new_offset = + DiffOffset(new_transforms.summary().diff_map.len); + let edit = Edit { + old: cursor.start().1..cursor.end(&()).1, + new: new_offset..new_offset, + }; + edits.push(edit); + } + cursor.next(&()); } - prev_old_transform_start = cursor.start().1; - cursor.next(&()); - } - let mut was_previously_expanded = false; - if let Some(item) = cursor.item() { - if let DiffTransform::DeletedHunk { - base_text_byte_range, - .. - } = item - { - if cursor.start().0 == hunk_start_inlay_offset - && *base_text_byte_range == hunk.diff_base_byte_range + let mut was_previously_expanded = false; + if let Some(item) = cursor.item() { + if let DiffTransform::DeletedHunk { + base_text_byte_range, + .. + } = item { - was_previously_expanded = true; + if cursor.start().0 == hunk_start_inlay_offset + && *base_text_byte_range == hunk.diff_base_byte_range + { + was_previously_expanded = true; + } } } - } - let mut should_expand_hunk = - was_previously_expanded || self.all_hunks_expanded; - match &operation { - DiffMapOperation::ExpandHunks { ranges } => { - should_expand_hunk |= ranges.iter().any(|range| { - range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) - }) - } - DiffMapOperation::CollapseHunks { ranges } => { - should_expand_hunk &= !ranges.iter().any(|range| { - range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) - }) + let mut should_expand_hunk = + was_previously_expanded || self.all_hunks_expanded; + match &operation { + DiffMapOperation::ExpandHunks { ranges } => { + should_expand_hunk |= ranges.iter().any(|range| { + range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) + }) + } + DiffMapOperation::CollapseHunks { ranges } => { + should_expand_hunk &= !ranges.iter().any(|range| { + range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) + }) + } + _ => {} + }; + if hunk.diff_base_byte_range.len() == 0 { + should_expand_hunk = false; } - _ => {} - }; - if hunk.diff_base_byte_range.len() == 0 { - should_expand_hunk = false; - } - if should_expand_hunk { - let mut text_cursor = base_text.as_rope().cursor(0); - let base_text_start = - text_cursor.summary::(hunk.diff_base_byte_range.start); - let base_text_summary = - text_cursor.summary::(hunk.diff_base_byte_range.end); + if should_expand_hunk { + let mut text_cursor = base_text.as_rope().cursor(0); + let base_text_start = + text_cursor.summary::(hunk.diff_base_byte_range.start); + let base_text_summary = text_cursor + .summary::(hunk.diff_base_byte_range.end); + + if !was_previously_expanded { + let hunk_overshoot = + (hunk_start_inlay_offset - cursor.start().0).0; + let old_offset = cursor.start().1 + DiffOffset(hunk_overshoot); + let new_start = + DiffOffset(new_transforms.summary().diff_map.len); + let new_end = + new_start + DiffOffset(hunk.diff_base_byte_range.len()); + let edit = Edit { + old: old_offset..old_offset, + new: new_start..new_end, + }; + edits.push(edit); + } - if !was_previously_expanded { - let old_offset = prev_old_transform_start; - let new_start = DiffOffset(new_transforms.summary().diff_map.len); + new_transforms.push( + DiffTransform::DeletedHunk { + base_text_byte_range: hunk.diff_base_byte_range.clone(), + summary: base_text_summary, + buffer_id, + base_text_start, + }, + &(), + ); + } else if was_previously_expanded { + let old_start = cursor.start().1; + let new_offset = DiffOffset(new_transforms.summary().diff_map.len); let edit = Edit { - old: old_offset..old_offset, - new: new_start - ..new_start + DiffOffset(hunk.diff_base_byte_range.len()), + old: old_start + ..old_start + DiffOffset(hunk.diff_base_byte_range.len()), + new: new_offset..new_offset, }; - dbg!(&edit); edits.push(edit); + cursor.next(&()); } - - new_transforms.push( - DiffTransform::DeletedHunk { - base_text_byte_range: hunk.diff_base_byte_range.clone(), - summary: base_text_summary, - buffer_id, - base_text_start, - }, - &(), - ); - } else if was_previously_expanded { - let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().diff_map.len); - let edit = Edit { - old: old_start - ..old_start + DiffOffset(hunk.diff_base_byte_range.len()), - new: new_offset..new_offset, - }; - dbg!(&edit); - edits.push(edit); - cursor.next(&()); } } } - while cursor.start().0 < edit.old.end { + while cursor.end(&()).0 <= edit.old.end { let Some(item) = cursor.item() else { break; }; - if let DiffTransform::DeletedHunk { - base_text_byte_range, - .. - } = item - { - let old_start = cursor.start().1; + if let DiffTransform::DeletedHunk { .. } = item { let new_offset = DiffOffset(new_transforms.summary().diff_map.len); let edit = Edit { - old: old_start..old_start + DiffOffset(base_text_byte_range.len()), + old: cursor.start().1..cursor.end(&()).1, new: new_offset..new_offset, }; - dbg!(&edit); edits.push(edit); } cursor.next(&()); } - } - let edit_undershoot = (cursor.start().0 - edit.old.end).0; - edit.new.end.0 += edit_undershoot; - edit.old.end = cursor.start().0; + let old_overshoot = (edit.old.end - cursor.start().0).0; + self.push_buffer_content_transform(&mut new_transforms, edit.new.end); + let diff_edit_old_end = cursor.start().1 + DiffOffset(old_overshoot); + let diff_edit_new_end = DiffOffset(new_transforms.summary().diff_map.len); - self.push_buffer_content_transform(&mut new_transforms, edit.new.end); + if is_inlay_edit { + edits.push(DiffEdit { + old: diff_edit_old_start..diff_edit_old_end, + new: diff_edit_new_start..diff_edit_new_end, + }) + } - let diff_edit_old_end = cursor.start().1 - DiffOffset(edit_undershoot); - let diff_edit_new_end = - DiffOffset(new_transforms.summary().diff_map.len - edit_undershoot); + if let Some((next_edit, _, _)) = changes.peek() { + if next_edit.old.start < cursor.end(&()).0 { + (edit, is_inlay_edit, multibuffer_range) = changes.next().unwrap(); + continue; + } + } - if is_inlay_edit { - edits.push(DiffEdit { - old: diff_edit_old_start..diff_edit_old_end, - new: diff_edit_new_start..diff_edit_new_end, - }) + let suffix = (cursor.end(&()).0 - edit.old.end).0; + let transform_end = InlayOffset(new_transforms.summary().inlay_map.len + suffix); + self.push_buffer_content_transform(&mut new_transforms, transform_end); + cursor.next(&()); + break; } } @@ -1477,6 +1474,38 @@ mod tests { ); } + #[gpui::test] + fn test_diff_map_multiple_buffer_edits(cx: &mut TestAppContext) { + cx.update(init_test); + + let text = "hello world"; + let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); + + let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + let (multibuffer_snapshot, multibuffer_edits) = + multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); + let (diff_map, _) = + cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); + + let (mut snapshot, _) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), vec![], cx) + }); + assert_eq!(snapshot.text(), "hello world"); + + buffer.update(cx, |buffer, cx| { + buffer.edit([(4..5, "a"), (9..11, "k")], None, cx); + }); + let multibuffer_snapshot = multibuffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); + let (inlay_snapshot, edits) = inlay_map.sync( + multibuffer_snapshot, + multibuffer_edits.consume().into_inner(), + ); + let sync = diff_map.update(cx, |diff_map, cx| diff_map.sync(inlay_snapshot, edits, cx)); + + assert_new_snapshot(&mut snapshot, sync, indoc!("hella work")); + } + #[gpui::test] fn test_diff_map_clipping(cx: &mut TestAppContext) { cx.update(init_test); From dc814cb3af229f65ed941781e5f99b064df18953 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 14:26:58 -0800 Subject: [PATCH 22/51] Start integrating the new DiffMap into the editor Co-authored-by: Conrad --- crates/editor/src/display_map.rs | 36 ++++ crates/editor/src/display_map/diff_map.rs | 193 ++++++++++++------- crates/editor/src/display_map/inlay_map.rs | 4 - crates/editor/src/editor.rs | 51 +++-- crates/editor/src/git/project_diff.rs | 2 +- crates/editor/src/hunk_diff.rs | 12 +- crates/editor/src/proposed_changes_editor.rs | 2 +- 7 files changed, 208 insertions(+), 92 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f69c39bdcb541c..48ee29154530ca 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -55,6 +55,7 @@ use multi_buffer::{ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, ToOffset, ToPoint, }; +use project::buffer_store::BufferChangeSet; use serde::Deserialize; use std::{ any::TypeId, @@ -372,6 +373,41 @@ impl DisplayMap { self.crease_map.remove(crease_ids, &snapshot) } + pub fn add_change_set( + &mut self, + change_set: Model, + cx: &mut ModelContext, + ) { + self.diff_map.update(cx, |diff_map, cx| { + diff_map.add_change_set(change_set, cx); + }); + } + + pub fn has_expanded_diff_hunks_in_ranges( + &mut self, + ranges: &[Range], + cx: &mut ModelContext, + ) -> bool { + self.diff_map + .read(cx) + .has_expanded_diff_hunks_in_ranges(ranges) + } + + pub fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext) { + self.diff_map + .update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx)) + } + + pub fn expand_diff_hunks(&mut self, ranges: Vec>, cx: &mut ModelContext) { + self.diff_map + .update(cx, |diff_map, cx| diff_map.expand_diff_hunks(ranges, cx)) + } + + pub fn collapse_diff_hunks(&mut self, ranges: Vec>, cx: &mut ModelContext) { + self.diff_map + .update(cx, |diff_map, cx| diff_map.collapse_diff_hunks(ranges, cx)) + } + pub fn insert_blocks( &mut self, blocks: impl IntoIterator>, diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index a9e65c73ba0741..26bcbb41f8d631 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -12,16 +12,11 @@ use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; pub(crate) struct DiffMap { snapshot: DiffMapSnapshot, multibuffer: Model, - diff_bases: HashMap, + change_set_subscriptions: HashMap, all_hunks_expanded: bool, edits_since_sync: Patch, } -struct ChangeSetState { - change_set: Model, - _subscription: Subscription, -} - #[derive(Clone)] struct DiffSnapshot { diff: git::diff::BufferDiff, @@ -40,6 +35,7 @@ pub struct DiffMapSnapshot { enum DiffTransform { BufferContent { summary: TextSummary, + is_inserted_hunk: bool, }, DeletedHunk { summary: TextSummary, @@ -138,6 +134,7 @@ impl DiffMap { transforms: SumTree::from_item( DiffTransform::BufferContent { summary: inlay_snapshot.text_summary(), + is_inserted_hunk: false, }, &(), ), @@ -148,7 +145,7 @@ impl DiffMap { multibuffer, snapshot: snapshot.clone(), all_hunks_expanded: false, - diff_bases: HashMap::default(), + change_set_subscriptions: HashMap::default(), edits_since_sync: Patch::default(), }); @@ -162,12 +159,9 @@ impl DiffMap { ) { let buffer_id = change_set.read(cx).buffer_id; self.buffer_diff_changed(change_set.clone(), cx); - self.diff_bases.insert( + self.change_set_subscriptions.insert( buffer_id, - ChangeSetState { - _subscription: cx.observe(&change_set, Self::buffer_diff_changed), - change_set, - }, + cx.observe(&change_set, Self::buffer_diff_changed), ); } @@ -217,6 +211,26 @@ impl DiffMap { self.recompute_transforms(DiffMapOperation::BufferDiffUpdated { buffer_id }, cx); } + pub(super) fn has_expanded_diff_hunks_in_ranges( + &self, + ranges: &[Range], + ) -> bool { + let mut cursor = self.snapshot.transforms.cursor::(&()); + for range in ranges { + let range = range.to_offset(self.snapshot.buffer()); + let inlay_start = self.snapshot.inlay_snapshot.to_inlay_offset(range.start); + let inlay_end = self.snapshot.inlay_snapshot.to_inlay_offset(range.end); + cursor.seek(&inlay_start, Bias::Right, &()); + while *cursor.start() < inlay_end { + if let Some(DiffTransform::DeletedHunk { .. }) = cursor.item() { + return true; + } + cursor.next(&()); + } + } + false + } + pub(super) fn expand_diff_hunks( &mut self, ranges: Vec>, @@ -333,6 +347,7 @@ impl DiffMap { while let Some((mut edit, mut is_inlay_edit, mut multibuffer_range)) = changes.next() { new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + let mut end_of_current_insert = InlayOffset(0); loop { let old_overshoot = (edit.old.start - cursor.start().0).0; let new_overshoot = edit.new.start.0 - new_transforms.summary().inlay_map.len; @@ -381,9 +396,11 @@ impl DiffMap { continue; } + let excerpt_buffer_range_start_offset = + excerpt_buffer_range.start.to_offset(buffer); let hunk_start_multibuffer_offset = excerpt_range.start + hunk_start_buffer_offset - - excerpt_buffer_range.start.to_offset(buffer); + - excerpt_buffer_range_start_offset; let hunk_start_inlay_offset = self .snapshot .inlay_snapshot @@ -392,6 +409,7 @@ impl DiffMap { self.push_buffer_content_transform( &mut new_transforms, hunk_start_inlay_offset, + end_of_current_insert, ); while cursor.end(&()).0 <= hunk_start_inlay_offset { @@ -440,41 +458,54 @@ impl DiffMap { } _ => {} }; - if hunk.diff_base_byte_range.len() == 0 { - should_expand_hunk = false; - } if should_expand_hunk { - let mut text_cursor = base_text.as_rope().cursor(0); - let base_text_start = - text_cursor.summary::(hunk.diff_base_byte_range.start); - let base_text_summary = text_cursor - .summary::(hunk.diff_base_byte_range.end); - - if !was_previously_expanded { - let hunk_overshoot = - (hunk_start_inlay_offset - cursor.start().0).0; - let old_offset = cursor.start().1 + DiffOffset(hunk_overshoot); - let new_start = - DiffOffset(new_transforms.summary().diff_map.len); - let new_end = - new_start + DiffOffset(hunk.diff_base_byte_range.len()); - let edit = Edit { - old: old_offset..old_offset, - new: new_start..new_end, - }; - edits.push(edit); + if hunk.diff_base_byte_range.len() > 0 { + if !was_previously_expanded { + let hunk_overshoot = + (hunk_start_inlay_offset - cursor.start().0).0; + let old_offset = + cursor.start().1 + DiffOffset(hunk_overshoot); + let new_start = + DiffOffset(new_transforms.summary().diff_map.len); + let new_end = + new_start + DiffOffset(hunk.diff_base_byte_range.len()); + let edit = Edit { + old: old_offset..old_offset, + new: new_start..new_end, + }; + edits.push(edit); + } + + let mut text_cursor = base_text.as_rope().cursor(0); + let base_text_start = text_cursor + .summary::(hunk.diff_base_byte_range.start); + let base_text_summary = text_cursor + .summary::(hunk.diff_base_byte_range.end); + new_transforms.push( + DiffTransform::DeletedHunk { + base_text_byte_range: hunk.diff_base_byte_range.clone(), + summary: base_text_summary, + buffer_id, + base_text_start, + }, + &(), + ); } - new_transforms.push( - DiffTransform::DeletedHunk { - base_text_byte_range: hunk.diff_base_byte_range.clone(), - summary: base_text_summary, - buffer_id, - base_text_start, - }, - &(), - ); + let hunk_end_buffer_offset = + hunk.buffer_range.end.to_offset(buffer); + + if hunk_end_buffer_offset > hunk_start_buffer_offset { + let hunk_end_multibuffer_offset = excerpt_range.start + + hunk_end_buffer_offset + - excerpt_buffer_range_start_offset; + let hunk_end_inlay_offset = self + .snapshot + .inlay_snapshot + .to_inlay_offset(hunk_end_multibuffer_offset); + end_of_current_insert = hunk_end_inlay_offset; + } } else if was_previously_expanded { let old_start = cursor.start().1; let new_offset = DiffOffset(new_transforms.summary().diff_map.len); @@ -506,7 +537,11 @@ impl DiffMap { } let old_overshoot = (edit.old.end - cursor.start().0).0; - self.push_buffer_content_transform(&mut new_transforms, edit.new.end); + self.push_buffer_content_transform( + &mut new_transforms, + edit.new.end, + end_of_current_insert, + ); let diff_edit_old_end = cursor.start().1 + DiffOffset(old_overshoot); let diff_edit_new_end = DiffOffset(new_transforms.summary().diff_map.len); @@ -526,7 +561,11 @@ impl DiffMap { let suffix = (cursor.end(&()).0 - edit.old.end).0; let transform_end = InlayOffset(new_transforms.summary().inlay_map.len + suffix); - self.push_buffer_content_transform(&mut new_transforms, transform_end); + self.push_buffer_content_transform( + &mut new_transforms, + transform_end, + end_of_current_insert, + ); cursor.next(&()); break; } @@ -548,17 +587,33 @@ impl DiffMap { &self, new_transforms: &mut SumTree, end_offset: InlayOffset, + end_of_current_inserted_hunk: InlayOffset, ) { - let summary_to_add = self.snapshot.inlay_snapshot.text_summary_for_range( - InlayOffset(new_transforms.summary().inlay_map.len)..end_offset, - ); - if summary_to_add.len > 0 { + for (end_offset, region_is_inserted_hunk) in [ + (end_offset.min(end_of_current_inserted_hunk), true), + (end_offset, false), + ] { + let start_offset = InlayOffset(new_transforms.summary().inlay_map.len); + if end_offset <= start_offset { + continue; + } + let summary_to_add = self + .snapshot + .inlay_snapshot + .text_summary_for_range(start_offset..end_offset); + let mut did_extend = false; new_transforms.update_last( |last_transform| { - if let DiffTransform::BufferContent { summary } = last_transform { - did_extend = true; - *summary += summary_to_add.clone(); + if let DiffTransform::BufferContent { + summary, + is_inserted_hunk, + } = last_transform + { + if *is_inserted_hunk == region_is_inserted_hunk { + did_extend = true; + *summary += summary_to_add.clone(); + } } }, &(), @@ -567,6 +622,7 @@ impl DiffMap { new_transforms.push( DiffTransform::BufferContent { summary: summary_to_add, + is_inserted_hunk: region_is_inserted_hunk, }, &(), ) @@ -586,22 +642,27 @@ impl DiffMap { ); } - let mut prev_transform_is_buffer_content = false; + let mut prev_transform: Option<&DiffTransform> = None; for item in snapshot.transforms.iter() { - match item { - DiffTransform::BufferContent { summary } => { - if prev_transform_is_buffer_content { - panic!("multiple adjacent buffer content transforms"); - } - prev_transform_is_buffer_content = true; - if summary.len == 0 && !snapshot.buffer().is_empty() { - panic!("empty buffer content transform"); + if let DiffTransform::BufferContent { + summary, + is_inserted_hunk: is_new, + } = item + { + if let Some(DiffTransform::BufferContent { + is_inserted_hunk: prev_is_new, + .. + }) = prev_transform + { + if *is_new == *prev_is_new { + panic!("multiple adjacent buffer content transforms with the same is_new value"); } } - DiffTransform::DeletedHunk { .. } => { - prev_transform_is_buffer_content = false; + if summary.len == 0 && !snapshot.buffer().is_empty() { + panic!("empty buffer content transform"); } } + prev_transform = Some(item); } } } @@ -980,7 +1041,7 @@ impl<'a> Iterator for DiffMapChunks<'a> { let transform = self.cursor.item()?; match transform { - DiffTransform::BufferContent { summary } => { + DiffTransform::BufferContent { summary, .. } => { let chunk = self .inlay_chunk .get_or_insert_with(|| self.inlay_chunks.next().unwrap()); @@ -1084,7 +1145,7 @@ impl sum_tree::Item for DiffTransform { fn summary(&self, _: &::Context) -> Self::Summary { match self { - DiffTransform::BufferContent { summary } => DiffTransformSummary { + DiffTransform::BufferContent { summary, .. } => DiffTransformSummary { inlay_map: summary.clone(), diff_map: summary.clone(), }, diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index e4884d3c435688..8acec1f7150e07 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -272,10 +272,6 @@ impl<'a> InlayChunks<'a> { self.highlight_endpoints = highlight_endpoints.into_iter().peekable(); self.active_highlights.clear(); } - - pub fn offset(&self) -> InlayOffset { - self.output_offset - } } impl<'a> Iterator for InlayChunks<'a> { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 82558650ae5ac8..d347294ca9b963 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -1176,7 +1176,12 @@ impl Editor { let mut code_action_providers = Vec::new(); if let Some(project) = project.clone() { - get_unstaged_changes_for_buffers(&project, buffer.read(cx).all_buffers(), cx); + get_unstaged_changes_for_buffers( + &project, + buffer.read(cx).all_buffers(), + display_map.clone(), + cx, + ); code_action_providers.push(Arc::new(project) as Arc<_>); } @@ -10660,6 +10665,23 @@ impl Editor { self.display_map.read(cx).fold_placeholder.clone() } + pub fn set_expand_all_diff_hunks(&mut self, cx: &mut AppContext) { + self.display_map.update(cx, |display_map, cx| { + display_map.set_all_hunks_expanded(cx); + }); + } + + pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext) { + let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect(); + self.display_map.update(cx, |display_map, cx| { + if display_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) { + display_map.collapse_diff_hunks(ranges, cx) + } else { + display_map.expand_diff_hunks(ranges, cx) + } + }) + } + pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut ViewContext) { if hovered != self.gutter_hovered { self.gutter_hovered = hovered; @@ -11963,7 +11985,12 @@ impl Editor { let buffer_id = buffer.read(cx).remote_id(); if !self.diff_map.diff_bases.contains_key(&buffer_id) { if let Some(project) = &self.project { - get_unstaged_changes_for_buffers(project, [buffer.clone()], cx); + get_unstaged_changes_for_buffers( + project, + [buffer.clone()], + self.display_map.clone(), + cx, + ); } } cx.emit(EditorEvent::ExcerptsAdded { @@ -12668,7 +12695,8 @@ impl Editor { fn get_unstaged_changes_for_buffers( project: &Model, buffers: impl IntoIterator>, - cx: &mut ViewContext, + display_map: Model, + cx: &mut AppContext, ) { let mut tasks = Vec::new(); project.update(cx, |project, cx| { @@ -12676,16 +12704,17 @@ fn get_unstaged_changes_for_buffers( tasks.push(project.open_unstaged_changes(buffer.clone(), cx)) } }); - cx.spawn(|this, mut cx| async move { + cx.spawn(|mut cx| async move { let change_sets = futures::future::join_all(tasks).await; - this.update(&mut cx, |this, cx| { - for change_set in change_sets { - if let Some(change_set) = change_set.log_err() { - this.diff_map.add_change_set(change_set, cx); + display_map + .update(&mut cx, |display_map, cx| { + for change_set in change_sets { + if let Some(change_set) = change_set.log_err() { + display_map.add_change_set(change_set, cx); + } } - } - }) - .ok(); + }) + .ok(); }) .detach(); } diff --git a/crates/editor/src/git/project_diff.rs b/crates/editor/src/git/project_diff.rs index e76e5922dbe9a8..9434af8cfcfd5a 100644 --- a/crates/editor/src/git/project_diff.rs +++ b/crates/editor/src/git/project_diff.rs @@ -150,7 +150,7 @@ impl ProjectDiffEditor { let editor = cx.new_view(|cx| { let mut diff_display_editor = Editor::for_multibuffer(excerpts.clone(), Some(project.clone()), true, cx); - diff_display_editor.set_expand_all_diff_hunks(); + diff_display_editor.set_expand_all_diff_hunks(cx); diff_display_editor }); diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 2102c111f66e83..a2adde941e6fbc 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -198,9 +198,9 @@ impl DiffMapSnapshot { } impl Editor { - pub fn set_expand_all_diff_hunks(&mut self) { - self.diff_map.expand_all = true; - } + // pub fn set_expand_all_diff_hunks(&mut self) { + // self.diff_map.expand_all = true; + // } pub(super) fn toggle_hovered_hunk( &mut self, @@ -214,12 +214,6 @@ impl Editor { } } - pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext) { - let snapshot = self.snapshot(cx); - let selections = self.selections.all(cx); - self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx); - } - pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext) { let snapshot = self.snapshot(cx); let display_rows_with_expanded_hunks = self diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index f4934c32b0abc4..5fe9fc885bafe8 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -61,7 +61,7 @@ impl ProposedChangesEditor { let mut this = Self { editor: cx.new_view(|cx| { let mut editor = Editor::for_multibuffer(multibuffer.clone(), project, true, cx); - editor.set_expand_all_diff_hunks(); + editor.set_expand_all_diff_hunks(cx); editor.set_completion_provider(None); editor.clear_code_action_providers(); editor.set_semantics_provider( From cc4ecacc04d3a0567c6e4c32fe03d405f1bcab4e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 14:51:19 -0800 Subject: [PATCH 23/51] Fix dropping of ChangeSet in DiffMap Co-authored-by: Michael --- crates/editor/src/display_map/diff_map.rs | 16 ++++++++++++---- crates/editor/src/element.rs | 3 +-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 26bcbb41f8d631..df5b92d1af206d 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -12,11 +12,16 @@ use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; pub(crate) struct DiffMap { snapshot: DiffMapSnapshot, multibuffer: Model, - change_set_subscriptions: HashMap, + diff_bases: HashMap, all_hunks_expanded: bool, edits_since_sync: Patch, } +struct ChangeSetState { + _change_set: Model, + _subscription: Subscription, +} + #[derive(Clone)] struct DiffSnapshot { diff: git::diff::BufferDiff, @@ -145,7 +150,7 @@ impl DiffMap { multibuffer, snapshot: snapshot.clone(), all_hunks_expanded: false, - change_set_subscriptions: HashMap::default(), + diff_bases: HashMap::default(), edits_since_sync: Patch::default(), }); @@ -159,9 +164,12 @@ impl DiffMap { ) { let buffer_id = change_set.read(cx).buffer_id; self.buffer_diff_changed(change_set.clone(), cx); - self.change_set_subscriptions.insert( + self.diff_bases.insert( buffer_id, - cx.observe(&change_set, Self::buffer_diff_changed), + ChangeSetState { + _subscription: cx.observe(&change_set, Self::buffer_diff_changed), + _change_set: change_set, + }, ); } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 25c419fa62eca2..118b324ca1a958 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1346,9 +1346,8 @@ impl EditorElement { .unwrap_err(); let mut expanded_hunks = expanded_hunks[expanded_hunks_start_ix..].iter().peekable(); - let mut display_hunks: Vec<(DisplayDiffHunk, Option)> = editor + let mut display_hunks: Vec<(DisplayDiffHunk, Option)> = snapshot .diff_map - .snapshot .diff_hunks_in_range(buffer_start..buffer_end, &buffer_snapshot) .filter_map(|hunk| { let display_hunk = diff_hunk_to_display(&hunk, snapshot); From 9b526a4d42fa65de3bca80fa75a2a4938a64d855 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 15:50:27 -0800 Subject: [PATCH 24/51] Start work on providing git statuses for each line of diff map Co-authored-by: Michael --- crates/editor/src/display_map/diff_map.rs | 172 +++++++++++++++------- crates/editor/src/display_map/fold_map.rs | 2 +- 2 files changed, 118 insertions(+), 56 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index df5b92d1af206d..204bac9797a2dd 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,6 +1,7 @@ use super::inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlaySnapshot}; use crate::{Highlights, InlayOffset, InlayPoint}; use collections::HashMap; +use git::diff::DiffHunkStatus; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset}; @@ -1131,14 +1132,38 @@ impl<'a> DiffMapBufferRows<'a> { } } +#[derive(Debug, PartialEq, Eq)] +pub struct LineInfo { + pub row: Option, + pub diff_status: Option, +} + impl<'a> Iterator for DiffMapBufferRows<'a> { - type Item = Option; + type Item = LineInfo; fn next(&mut self) -> Option { - let result = if let Some(DiffTransform::DeletedHunk { .. }) = self.cursor.item() { - Some(None) - } else { - self.input_buffer_rows.next() + let result = match self.cursor.item() { + Some(DiffTransform::DeletedHunk { .. }) => Some(LineInfo { + row: None, + diff_status: Some(DiffHunkStatus::Removed), + }), + Some(DiffTransform::BufferContent { + is_inserted_hunk, .. + }) => { + let row = self.input_buffer_rows.next(); + row.map(|row| LineInfo { + row, + diff_status: if *is_inserted_hunk { + Some(DiffHunkStatus::Added) + } else { + None + }, + }) + } + None => self.input_buffer_rows.next().map(|row| LineInfo { + row, + diff_status: None, + }), }; self.diff_point.0 += Point::new(1, 0); if self.diff_point >= self.cursor.end(&()).0 { @@ -1381,22 +1406,23 @@ mod tests { sync, indoc!( " - ZERO - one - two - TWO - three - four - five - six + + ZERO + one + - two + + TWO + three + - four + - five + six " ), ); - assert_chunks_in_ranges(&snapshot); - assert_eq!( - snapshot.buffer_rows(0).collect::>(), + snapshot + .buffer_rows(0) + .map(|info| info.row) + .collect::>(), vec![ Some(0), Some(1), @@ -1410,29 +1436,8 @@ mod tests { ] ); - assert_eq!( - snapshot.buffer_rows(4).collect::>(), - vec![Some(3), None, None, Some(4), Some(5)] - ); - assert_eq!( - snapshot.buffer_rows(5).collect::>(), - vec![None, None, Some(4), Some(5)] - ); - assert_eq!( - snapshot.buffer_rows(6).collect::>(), - vec![None, Some(4), Some(5)] - ); - - let mut buffer_rows = snapshot.buffer_rows(0); - buffer_rows.seek(7); - assert_eq!(buffer_rows.next(), Some(Some(4))); - buffer_rows.seek(6); - assert_eq!(buffer_rows.next(), Some(None)); - buffer_rows.seek(5); - assert_eq!(buffer_rows.next(), Some(None)); - buffer_rows.seek(4); - assert_eq!(buffer_rows.next(), Some(Some(3))); - drop(buffer_rows); + assert_chunks_in_ranges(&snapshot); + assert_consistent_line_numbers(&snapshot); for (point, offset) in &[ ( @@ -1492,12 +1497,12 @@ mod tests { sync, indoc!( " - ZERO - one - two - TWO - three - six + ZERO + one + - two + + TWO + three + six " ), ); @@ -1531,13 +1536,13 @@ mod tests { sync, indoc!( " - ZERO - one hundred - thousand - two - TWO - three - six + ZERO + one hundred + thousand + - two + + TWO + three + six " ), ); @@ -1725,16 +1730,49 @@ mod tests { let (_diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); - assert_eq!(diff_snapshot.buffer_rows(0).collect::>(), [Some(0)]); + assert_eq!( + diff_snapshot + .buffer_rows(0) + .map(|info| info.row) + .collect::>(), + [Some(0)] + ); } #[track_caller] fn assert_new_snapshot( snapshot: &mut DiffMapSnapshot, (new_snapshot, edits): (DiffMapSnapshot, Vec>), - expected_text: &str, + expected_diff: &str, ) { - assert_eq!(new_snapshot.text(), expected_text); + let actual_text = new_snapshot.text(); + let line_infos = new_snapshot.buffer_rows(0).collect::>(); + let has_diff = line_infos.iter().any(|info| info.diff_status.is_some()); + let actual_diff = actual_text + .split('\n') + .zip(line_infos) + .map(|(line, info)| { + let marker = match info.diff_status { + Some(DiffHunkStatus::Added) => "+ ", + Some(DiffHunkStatus::Removed) => "- ", + Some(DiffHunkStatus::Modified) => unreachable!(), + None => { + if has_diff { + " " + } else { + "" + } + } + }; + if line.is_empty() { + String::new() + } else { + format!("{marker}{line}") + } + }) + .collect::>() + .join("\n"); + pretty_assertions::assert_eq!(actual_diff, expected_diff); check_edits(snapshot, &new_snapshot, &edits); *snapshot = new_snapshot; } @@ -1793,6 +1831,30 @@ mod tests { } } + #[track_caller] + fn assert_consistent_line_numbers(snapshot: &DiffMapSnapshot) { + let all_line_numbers = snapshot.buffer_rows(0).collect::>(); + for start_row in 1..all_line_numbers.len() { + let line_numbers = snapshot.buffer_rows(start_row as u32).collect::>(); + assert_eq!( + line_numbers, + all_line_numbers[start_row..], + "start_row: {start_row}" + ); + + for seek_row in 0..all_line_numbers.len() { + let mut numbers = snapshot.buffer_rows(start_row as u32); + numbers.seek(seek_row as u32); + let line_numbers = numbers.collect::>(); + assert_eq!( + line_numbers, + all_line_numbers[seek_row..], + "seek_row: {seek_row}, start_row: {start_row}" + ); + } + } + } + fn init_test(cx: &mut AppContext) { let settings = SettingsStore::test(cx); cx.set_global(settings); diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 371fec6a1540d4..d9b07ee753a6e9 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1244,7 +1244,7 @@ impl<'a> Iterator for FoldBufferRows<'a> { self.input_buffer_rows.next(); } *self.fold_point.row_mut() += 1; - self.input_buffer_rows.next() + self.input_buffer_rows.next().map(|info| info.row) } else { None } From b552e32b18e81105c7a1cdd8b49553ab72aae84e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 16:40:56 -0800 Subject: [PATCH 25/51] Replace buffer rows iterators with row info iterators that also give git status Co-authored-by: Michael --- crates/collab/src/tests/editor_tests.rs | 32 ++++- crates/editor/src/display_map.rs | 20 ++-- crates/editor/src/display_map/block_map.rs | 32 ++--- crates/editor/src/display_map/diff_map.rs | 48 ++++---- crates/editor/src/display_map/fold_map.rs | 50 +++++--- crates/editor/src/display_map/tab_map.rs | 4 +- crates/editor/src/display_map/wrap_map.rs | 35 +++--- crates/editor/src/element.rs | 97 ++++++++------- crates/editor/src/git/blame.rs | 132 +++++++++++++++------ 9 files changed, 278 insertions(+), 172 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 773ee97a9d2ede..35588a0911343b 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -9,6 +9,7 @@ use editor::{ ConfirmCodeAction, ConfirmCompletion, ConfirmRename, ContextMenuFirst, Redo, Rename, ToggleCodeActions, Undo, }, + display_map::RowInfo, test::editor_test_context::{AssertionContextManager, EditorTestContext}, Editor, }; @@ -20,7 +21,6 @@ use language::{ language_settings::{AllLanguageSettings, InlayHintSettings}, FakeLspAdapter, }; -use multi_buffer::MultiBufferRow; use project::{ project_settings::{InlineBlameSettings, ProjectSettings}, SERVER_PROGRESS_THROTTLE_TIMEOUT, @@ -2075,7 +2075,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA let blame = editor_b.blame().expect("editor_b should have blame now"); let entries = blame.update(cx, |blame, cx| { blame - .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx) + .blame_for_rows( + &(0..4) + .map(|row| RowInfo { + buffer_row: Some(row), + ..Default::default() + }) + .collect::>(), + cx, + ) .collect::>() }); @@ -2114,7 +2122,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA let blame = editor_b.blame().expect("editor_b should have blame now"); let entries = blame.update(cx, |blame, cx| { blame - .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx) + .blame_for_rows( + &(0..4) + .map(|row| RowInfo { + buffer_row: Some(row), + ..Default::default() + }) + .collect::>(), + cx, + ) .collect::>() }); @@ -2141,7 +2157,15 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA let blame = editor_b.blame().expect("editor_b should have blame now"); let entries = blame.update(cx, |blame, cx| { blame - .blame_for_rows((0..4).map(MultiBufferRow).map(Some), cx) + .blame_for_rows( + &(0..4) + .map(|row| RowInfo { + buffer_row: Some(row), + ..Default::default() + }) + .collect::>(), + cx, + ) .collect::>() }); diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 48ee29154530ca..6e1296c0862f15 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -30,8 +30,8 @@ use crate::{ hover_links::InlayHighlight, movement::TextLayoutDetails, EditorStyle, InlayId, RowExt, }; pub use block_map::{ - Block, BlockBufferRows, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, - BlockPlacement, BlockPoint, BlockProperties, BlockStyle, CustomBlockId, RenderBlock, + Block, BlockChunks as DisplayChunks, BlockContext, BlockId, BlockMap, BlockPlacement, + BlockPoint, BlockProperties, BlockRows, BlockStyle, CustomBlockId, RenderBlock, }; use block_map::{BlockRow, BlockSnapshot}; use collections::{HashMap, HashSet}; @@ -39,6 +39,7 @@ pub use crease_map::*; use diff_map::{DiffMap, DiffMapSnapshot}; pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint}; use fold_map::{FoldMap, FoldSnapshot}; +use git::diff::DiffHunkStatus; use gpui::{ AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle, }; @@ -79,6 +80,12 @@ pub enum FoldStatus { Foldable, } +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct RowInfo { + pub buffer_row: Option, + pub diff_status: Option, +} + pub type RenderFoldToggle = Arc AnyElement>; pub trait ToDisplayPoint { @@ -747,13 +754,8 @@ impl DisplaySnapshot { self.buffer_snapshot.len() == 0 } - pub fn buffer_rows( - &self, - start_row: DisplayRow, - ) -> impl Iterator> + '_ { - self.block_snapshot - .buffer_rows(BlockRow(start_row.0)) - .map(|row| row.map(MultiBufferRow)) + pub fn row_infos(&self, start_row: DisplayRow) -> impl Iterator + '_ { + self.block_snapshot.row_infos(BlockRow(start_row.0)) } pub fn widest_line_number(&self) -> u32 { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 5f5b14194af8d8..866a6e66af579a 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -2,7 +2,7 @@ use super::{ wrap_map::{self, WrapEdit, WrapPoint, WrapSnapshot}, Highlights, }; -use crate::{EditorStyle, GutterDimensions}; +use crate::{EditorStyle, GutterDimensions, RowInfo}; use collections::{Bound, HashMap, HashSet}; use gpui::{AnyElement, EntityId, Pixels, WindowContext}; use language::{Chunk, Patch, Point}; @@ -398,9 +398,9 @@ pub struct BlockChunks<'a> { } #[derive(Clone)] -pub struct BlockBufferRows<'a> { +pub struct BlockRows<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, - input_buffer_rows: wrap_map::WrapBufferRows<'a>, + input_rows: wrap_map::WrapRows<'a>, output_row: BlockRow, started: bool, } @@ -1239,7 +1239,7 @@ impl BlockSnapshot { } } - pub(super) fn buffer_rows(&self, start_row: BlockRow) -> BlockBufferRows { + pub(super) fn row_infos(&self, start_row: BlockRow) -> BlockRows { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(&()); cursor.seek(&start_row, Bias::Right, &()); let (output_start, input_start) = cursor.start(); @@ -1252,9 +1252,9 @@ impl BlockSnapshot { 0 }; let input_start_row = input_start.0 + overshoot; - BlockBufferRows { + BlockRows { transforms: cursor, - input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row), + input_rows: self.wrap_snapshot.row_infos(input_start_row), output_row: start_row, started: false, } @@ -1645,8 +1645,8 @@ impl<'a> Iterator for BlockChunks<'a> { } } -impl<'a> Iterator for BlockBufferRows<'a> { - type Item = Option; +impl<'a> Iterator for BlockRows<'a> { + type Item = RowInfo; fn next(&mut self) -> Option { if self.started { @@ -1675,19 +1675,19 @@ impl<'a> Iterator for BlockBufferRows<'a> { .as_ref() .map_or(true, |block| block.is_replacement()) { - self.input_buffer_rows.seek(self.transforms.start().1 .0); + self.input_rows.seek(self.transforms.start().1 .0); } } let transform = self.transforms.item()?; if let Some(block) = transform.block.as_ref() { if block.is_replacement() && self.transforms.start().0 == self.output_row { - Some(self.input_buffer_rows.next().unwrap()) + Some(self.input_rows.next().unwrap()) } else { - Some(None) + Some(RowInfo::default()) } } else { - Some(self.input_buffer_rows.next().unwrap()) + Some(self.input_rows.next().unwrap()) } } } @@ -1964,7 +1964,10 @@ mod tests { ); assert_eq!( - snapshot.buffer_rows(BlockRow(0)).collect::>(), + snapshot + .row_infos(BlockRow(0)) + .map(|row_info| row_info.buffer_row) + .collect::>(), &[ Some(0), None, @@ -2715,7 +2718,8 @@ mod tests { ); assert_eq!( blocks_snapshot - .buffer_rows(BlockRow(start_row as u32)) + .row_infos(BlockRow(start_row as u32)) + .map(|row_info| row_info.buffer_row) .collect::>(), &expected_buffer_rows[start_row..], "incorrect buffer_rows starting at row {:?}", diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 204bac9797a2dd..40ea0d4348fa97 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,5 +1,5 @@ use super::inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlaySnapshot}; -use crate::{Highlights, InlayOffset, InlayPoint}; +use crate::{Highlights, InlayOffset, InlayPoint, RowInfo}; use collections::HashMap; use git::diff::DiffHunkStatus; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; @@ -85,7 +85,7 @@ pub struct DiffMapChunks<'a> { } #[derive(Clone)] -pub struct DiffMapBufferRows<'a> { +pub struct DiffMapRows<'a> { cursor: Cursor<'a, DiffTransform, (DiffPoint, InlayPoint)>, diff_point: DiffPoint, input_buffer_rows: InlayBufferRows<'a>, @@ -980,7 +980,7 @@ impl DiffMapSnapshot { } } - pub fn buffer_rows(&self, start_row: u32) -> DiffMapBufferRows { + pub fn row_infos(&self, start_row: u32) -> DiffMapRows { if start_row > self.transforms.summary().diff_map.lines.row { panic!("invalid diff map row {}", start_row); } @@ -1000,7 +1000,7 @@ impl DiffMapSnapshot { .inlay_snapshot .buffer_rows(inlay_transform_start.row() + overshoot); - DiffMapBufferRows { + DiffMapRows { diff_point, input_buffer_rows, cursor, @@ -1114,7 +1114,7 @@ impl<'a> Iterator for DiffMapChunks<'a> { } } -impl<'a> DiffMapBufferRows<'a> { +impl<'a> DiffMapRows<'a> { pub fn seek(&mut self, row: u32) { self.diff_point = DiffPoint::new(row, 0); self.cursor.seek(&self.diff_point, Bias::Right, &()); @@ -1132,27 +1132,21 @@ impl<'a> DiffMapBufferRows<'a> { } } -#[derive(Debug, PartialEq, Eq)] -pub struct LineInfo { - pub row: Option, - pub diff_status: Option, -} - -impl<'a> Iterator for DiffMapBufferRows<'a> { - type Item = LineInfo; +impl<'a> Iterator for DiffMapRows<'a> { + type Item = RowInfo; fn next(&mut self) -> Option { let result = match self.cursor.item() { - Some(DiffTransform::DeletedHunk { .. }) => Some(LineInfo { - row: None, + Some(DiffTransform::DeletedHunk { .. }) => Some(RowInfo { + buffer_row: None, diff_status: Some(DiffHunkStatus::Removed), }), Some(DiffTransform::BufferContent { is_inserted_hunk, .. }) => { let row = self.input_buffer_rows.next(); - row.map(|row| LineInfo { - row, + row.map(|row| RowInfo { + buffer_row: row, diff_status: if *is_inserted_hunk { Some(DiffHunkStatus::Added) } else { @@ -1160,8 +1154,8 @@ impl<'a> Iterator for DiffMapBufferRows<'a> { }, }) } - None => self.input_buffer_rows.next().map(|row| LineInfo { - row, + None => self.input_buffer_rows.next().map(|row| RowInfo { + buffer_row: row, diff_status: None, }), }; @@ -1420,8 +1414,8 @@ mod tests { assert_eq!( snapshot - .buffer_rows(0) - .map(|info| info.row) + .row_infos(0) + .map(|info| info.buffer_row) .collect::>(), vec![ Some(0), @@ -1732,8 +1726,8 @@ mod tests { assert_eq!( diff_snapshot - .buffer_rows(0) - .map(|info| info.row) + .row_infos(0) + .map(|info| info.buffer_row) .collect::>(), [Some(0)] ); @@ -1746,7 +1740,7 @@ mod tests { expected_diff: &str, ) { let actual_text = new_snapshot.text(); - let line_infos = new_snapshot.buffer_rows(0).collect::>(); + let line_infos = new_snapshot.row_infos(0).collect::>(); let has_diff = line_infos.iter().any(|info| info.diff_status.is_some()); let actual_diff = actual_text .split('\n') @@ -1833,9 +1827,9 @@ mod tests { #[track_caller] fn assert_consistent_line_numbers(snapshot: &DiffMapSnapshot) { - let all_line_numbers = snapshot.buffer_rows(0).collect::>(); + let all_line_numbers = snapshot.row_infos(0).collect::>(); for start_row in 1..all_line_numbers.len() { - let line_numbers = snapshot.buffer_rows(start_row as u32).collect::>(); + let line_numbers = snapshot.row_infos(start_row as u32).collect::>(); assert_eq!( line_numbers, all_line_numbers[start_row..], @@ -1843,7 +1837,7 @@ mod tests { ); for seek_row in 0..all_line_numbers.len() { - let mut numbers = snapshot.buffer_rows(start_row as u32); + let mut numbers = snapshot.row_infos(start_row as u32); numbers.seek(seek_row as u32); let line_numbers = numbers.collect::>(); assert_eq!( diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index d9b07ee753a6e9..425fb3c39298f0 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,7 +1,7 @@ +use crate::RowInfo; + use super::{ - diff_map::{ - DiffEdit, DiffMapBufferRows, DiffMapChunks, DiffMapSnapshot, DiffOffset, DiffPoint, - }, + diff_map::{DiffEdit, DiffMapChunks, DiffMapRows, DiffMapSnapshot, DiffOffset, DiffPoint}, Highlights, }; use gpui::{AnyElement, ElementId, WindowContext}; @@ -747,7 +747,7 @@ impl FoldSnapshot { (line_end - line_start) as u32 } - pub fn buffer_rows(&self, start_row: u32) -> FoldBufferRows { + pub fn row_infos(&self, start_row: u32) -> FoldRows { if start_row > self.transforms.summary().output.lines.row { panic!("invalid display row {}", start_row); } @@ -758,11 +758,11 @@ impl FoldSnapshot { let overshoot = fold_point.0 - cursor.start().0 .0; let diff_point = DiffPoint(cursor.start().1 .0 + overshoot); - let input_buffer_rows = self.diff_map_snapshot.buffer_rows(diff_point.0.row); + let input_rows = self.diff_map_snapshot.row_infos(diff_point.0.row); - FoldBufferRows { + FoldRows { fold_point, - input_buffer_rows, + input_rows, cursor, } } @@ -1208,25 +1208,25 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { } #[derive(Clone)] -pub struct FoldBufferRows<'a> { +pub struct FoldRows<'a> { cursor: Cursor<'a, Transform, (FoldPoint, DiffPoint)>, - input_buffer_rows: DiffMapBufferRows<'a>, + input_rows: DiffMapRows<'a>, fold_point: FoldPoint, } -impl<'a> FoldBufferRows<'a> { +impl<'a> FoldRows<'a> { pub(crate) fn seek(&mut self, row: u32) { let fold_point = FoldPoint::new(row, 0); self.cursor.seek(&fold_point, Bias::Left, &()); let overshoot = fold_point.0 - self.cursor.start().0 .0; let diff_point = DiffPoint(self.cursor.start().1 .0 + overshoot); - self.input_buffer_rows.seek(diff_point.0.row); + self.input_rows.seek(diff_point.0.row); self.fold_point = fold_point; } } -impl<'a> Iterator for FoldBufferRows<'a> { - type Item = Option; +impl<'a> Iterator for FoldRows<'a> { + type Item = RowInfo; fn next(&mut self) -> Option { let mut traversed_fold = false; @@ -1240,11 +1240,11 @@ impl<'a> Iterator for FoldBufferRows<'a> { if self.cursor.item().is_some() { if traversed_fold { - self.input_buffer_rows.seek(self.cursor.start().1 .0.row); - self.input_buffer_rows.next(); + self.input_rows.seek(self.cursor.start().1 .0.row); + self.input_rows.next(); } *self.fold_point.row_mut() += 1; - self.input_buffer_rows.next().map(|info| info.row) + self.input_rows.next() } else { None } @@ -1878,7 +1878,10 @@ mod tests { let mut fold_row = 0; while fold_row < expected_buffer_rows.len() as u32 { assert_eq!( - snapshot.buffer_rows(fold_row).collect::>(), + snapshot + .row_infos(fold_row) + .map(|row_info| row_info.buffer_row) + .collect::>(), expected_buffer_rows[(fold_row as usize)..], "wrong buffer rows starting at fold row {}", fold_row, @@ -1985,10 +1988,19 @@ mod tests { let (snapshot, _) = map.read(diff_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n"); assert_eq!( - snapshot.buffer_rows(0).collect::>(), + snapshot + .row_infos(0) + .map(|row_info| row_info.buffer_row) + .collect::>(), [Some(0), Some(3), Some(5), Some(6)] ); - assert_eq!(snapshot.buffer_rows(3).collect::>(), [Some(6)]); + assert_eq!( + snapshot + .row_infos(3) + .map(|row_info| row_info.buffer_row) + .collect::>(), + [Some(6)] + ); } fn init_test(cx: &mut gpui::AppContext) { diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 4dd6fd7e242fed..98f30c2645f43c 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -272,8 +272,8 @@ impl TabSnapshot { } } - pub fn buffer_rows(&self, row: u32) -> fold_map::FoldBufferRows<'_> { - self.fold_snapshot.buffer_rows(row) + pub fn rows(&self, row: u32) -> fold_map::FoldRows<'_> { + self.fold_snapshot.row_infos(row) } #[cfg(test)] diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 2ac82a8e4fc19b..84afc4654bb305 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,7 @@ +use crate::RowInfo; + use super::{ - fold_map::FoldBufferRows, + fold_map::FoldRows, tab_map::{self, TabEdit, TabPoint, TabSnapshot}, Highlights, }; @@ -60,16 +62,16 @@ pub struct WrapChunks<'a> { } #[derive(Clone)] -pub struct WrapBufferRows<'a> { - input_buffer_rows: FoldBufferRows<'a>, - input_buffer_row: Option, +pub struct WrapRows<'a> { + input_buffer_rows: FoldRows<'a>, + input_buffer_row: RowInfo, output_row: u32, soft_wrapped: bool, max_output_row: u32, transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, } -impl<'a> WrapBufferRows<'a> { +impl<'a> WrapRows<'a> { pub(crate) fn seek(&mut self, start_row: u32) { self.transforms .seek(&WrapPoint::new(start_row, 0), Bias::Left, &()); @@ -717,7 +719,7 @@ impl WrapSnapshot { self.transforms.summary().output.longest_row } - pub fn buffer_rows(&self, start_row: u32) -> WrapBufferRows { + pub fn row_infos(&self, start_row: u32) -> WrapRows { let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(&()); transforms.seek(&WrapPoint::new(start_row, 0), Bias::Left, &()); let mut input_row = transforms.start().1.row(); @@ -725,9 +727,9 @@ impl WrapSnapshot { input_row += start_row - transforms.start().0.row(); } let soft_wrapped = transforms.item().map_or(false, |t| !t.is_isomorphic()); - let mut input_buffer_rows = self.tab_snapshot.buffer_rows(input_row); + let mut input_buffer_rows = self.tab_snapshot.rows(input_row); let input_buffer_row = input_buffer_rows.next().unwrap(); - WrapBufferRows { + WrapRows { transforms, input_buffer_row, input_buffer_rows, @@ -847,7 +849,7 @@ impl WrapSnapshot { } let text = language::Rope::from(self.text().as_str()); - let mut input_buffer_rows = self.tab_snapshot.buffer_rows(0); + let mut input_buffer_rows = self.tab_snapshot.rows(0); let mut expected_buffer_rows = Vec::new(); let mut prev_tab_row = 0; for display_row in 0..=self.max_point().row() { @@ -855,7 +857,7 @@ impl WrapSnapshot { if tab_point.row() == prev_tab_row && display_row != 0 { expected_buffer_rows.push(None); } else { - expected_buffer_rows.push(input_buffer_rows.next().unwrap()); + expected_buffer_rows.push(input_buffer_rows.next().unwrap().buffer_row); } prev_tab_row = tab_point.row(); @@ -864,7 +866,8 @@ impl WrapSnapshot { for start_display_row in 0..expected_buffer_rows.len() { assert_eq!( - self.buffer_rows(start_display_row as u32) + self.row_infos(start_display_row as u32) + .map(|row_info| row_info.buffer_row) .collect::>(), &expected_buffer_rows[start_display_row..], "invalid buffer_rows({}..)", @@ -958,8 +961,8 @@ impl<'a> Iterator for WrapChunks<'a> { } } -impl<'a> Iterator for WrapBufferRows<'a> { - type Item = Option; +impl<'a> Iterator for WrapRows<'a> { + type Item = RowInfo; fn next(&mut self) -> Option { if self.output_row > self.max_output_row { @@ -979,7 +982,11 @@ impl<'a> Iterator for WrapBufferRows<'a> { self.soft_wrapped = true; } - Some(if soft_wrapped { None } else { buffer_row }) + Some(if soft_wrapped { + RowInfo::default() + } else { + buffer_row + }) } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 118b324ca1a958..18490faa222619 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -21,8 +21,8 @@ use crate::{ DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown, - LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection, - SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, + LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowInfo, RowRangeExt, SelectPhase, + Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, }; use client::ParticipantIndex; @@ -1416,7 +1416,7 @@ impl EditorElement { fn layout_inline_blame( &self, display_row: DisplayRow, - display_snapshot: &DisplaySnapshot, + row_info: &RowInfo, line_layout: &LineWithInvisibles, crease_trailer: Option<&CreaseTrailerLayout>, em_width: Pixels, @@ -1439,13 +1439,10 @@ impl EditorElement { .as_ref() .map(|(w, _)| w.clone()); - let display_point = DisplayPoint::new(display_row, 0); - let buffer_row = MultiBufferRow(display_point.to_point(display_snapshot).row); - let blame = self.editor.read(cx).blame.clone()?; let blame_entry = blame .update(cx, |blame, cx| { - blame.blame_for_rows([Some(buffer_row)], cx).next() + blame.blame_for_rows(&[*row_info], cx).next() }) .flatten()?; @@ -1485,7 +1482,7 @@ impl EditorElement { #[allow(clippy::too_many_arguments)] fn layout_blame_entries( &self, - buffer_rows: impl Iterator>, + buffer_rows: &[RowInfo], em_width: Pixels, scroll_position: gpui::Point, line_height: Pixels, @@ -1813,7 +1810,7 @@ impl EditorElement { let end = rows.end.max(relative_to); let buffer_rows = snapshot - .buffer_rows(start) + .row_infos(start) .take(1 + end.minus(start) as usize) .collect::>(); @@ -1821,7 +1818,7 @@ impl EditorElement { let mut delta = 1; let mut i = head_idx + 1; while i < buffer_rows.len() as u32 { - if buffer_rows[i as usize].is_some() { + if buffer_rows[i as usize].buffer_row.is_some() { if rows.contains(&DisplayRow(i + start.0)) { relative_rows.insert(DisplayRow(i + start.0), delta); } @@ -1831,13 +1828,13 @@ impl EditorElement { } delta = 1; i = head_idx.min(buffer_rows.len() as u32 - 1); - while i > 0 && buffer_rows[i as usize].is_none() { + while i > 0 && buffer_rows[i as usize].buffer_row.is_none() { i -= 1; } while i > 0 { i -= 1; - if buffer_rows[i as usize].is_some() { + if buffer_rows[i as usize].buffer_row.is_some() { if rows.contains(&DisplayRow(i + start.0)) { relative_rows.insert(DisplayRow(i + start.0), delta); } @@ -1851,7 +1848,7 @@ impl EditorElement { fn layout_line_numbers( &self, rows: Range, - buffer_rows: impl Iterator>, + buffer_rows: &[RowInfo], active_rows: &BTreeMap, newest_selection_head: Option, snapshot: &EditorSnapshot, @@ -1893,8 +1890,7 @@ impl EditorElement { buffer_rows .into_iter() .enumerate() - .map(|(ix, multibuffer_row)| { - let multibuffer_row = multibuffer_row?; + .map(|(ix, row_info)| { let display_row = DisplayRow(rows.start.0 + ix as u32); let color = if active_rows.contains_key(&display_row) { cx.theme().colors().editor_active_line_number @@ -1902,7 +1898,7 @@ impl EditorElement { cx.theme().colors().editor_line_number }; line_number.clear(); - let default_number = multibuffer_row.0 + 1; + let default_number = row_info.buffer_row? + 1; let number = relative_rows .get(&DisplayRow(ix as u32 + rows.start.0)) .unwrap_or(&default_number); @@ -1927,7 +1923,7 @@ impl EditorElement { fn layout_crease_toggles( &self, rows: Range, - buffer_rows: impl IntoIterator>, + row_infos: &[RowInfo], active_rows: &BTreeMap, snapshot: &EditorSnapshot, cx: &mut WindowContext, @@ -1936,22 +1932,21 @@ impl EditorElement { && snapshot.mode == EditorMode::Full && self.editor.read(cx).is_singleton(cx); if include_fold_statuses { - buffer_rows + row_infos .into_iter() .enumerate() - .map(|(ix, row)| { - if let Some(multibuffer_row) = row { - let display_row = DisplayRow(rows.start.0 + ix as u32); - let active = active_rows.contains_key(&display_row); - snapshot.render_crease_toggle( - multibuffer_row, - active, - self.editor.clone(), - cx, - ) - } else { - None - } + .map(|(ix, info)| { + let row = info.buffer_row?; + let display_row = DisplayRow(rows.start.0 + ix as u32); + let active = active_rows.contains_key(&display_row); + + // todo(max): Retrieve the multibuffer row correctly + snapshot.render_crease_toggle( + MultiBufferRow(row), + active, + self.editor.clone(), + cx, + ) }) .collect() } else { @@ -1961,15 +1956,16 @@ impl EditorElement { fn layout_crease_trailers( &self, - buffer_rows: impl IntoIterator>, + buffer_rows: impl IntoIterator, snapshot: &EditorSnapshot, cx: &mut WindowContext, ) -> Vec> { buffer_rows .into_iter() - .map(|row| { - if let Some(multibuffer_row) = row { - snapshot.render_crease_trailer(multibuffer_row, cx) + .map(|row_info| { + // FIXME: These are not really MultiBufferRow?! + if let Some(row) = row_info.buffer_row { + snapshot.render_crease_trailer(MultiBufferRow(row), cx) } else { None } @@ -5364,12 +5360,15 @@ impl Element for EditorElement { ); let end_row = DisplayRow(end_row); - let buffer_rows = snapshot - .buffer_rows(start_row) + let row_infos = snapshot + .row_infos(start_row) .take((start_row..end_row).len()) - .collect::>(); - let is_row_soft_wrapped = - |row| buffer_rows.get(row).copied().flatten().is_none(); + .collect::>(); + let is_row_soft_wrapped = |row: usize| { + row_infos + .get(row) + .map_or(true, |info| info.buffer_row.is_none()) + }; let start_anchor = if start_row == Default::default() { Anchor::min() @@ -5428,7 +5427,7 @@ impl Element for EditorElement { let line_numbers = self.layout_line_numbers( start_row..end_row, - buffer_rows.iter().copied(), + &row_infos, &active_rows, newest_selection_head, &snapshot, @@ -5438,14 +5437,14 @@ impl Element for EditorElement { let mut crease_toggles = cx.with_element_namespace("crease_toggles", |cx| { self.layout_crease_toggles( start_row..end_row, - buffer_rows.iter().copied(), + &row_infos, &active_rows, &snapshot, cx, ) }); let crease_trailers = cx.with_element_namespace("crease_trailers", |cx| { - self.layout_crease_trailers(buffer_rows.iter().copied(), &snapshot, cx) + self.layout_crease_trailers(row_infos.iter().copied(), &snapshot, cx) }); let display_hunks = self.layout_gutter_git_hunks( @@ -5576,11 +5575,12 @@ impl Element for EditorElement { let display_row = newest_selection_head.row(); if (start_row..end_row).contains(&display_row) { let line_ix = display_row.minus(start_row) as usize; + let row_info = &row_infos[line_ix]; let line_layout = &line_layouts[line_ix]; let crease_trailer_layout = crease_trailers[line_ix].as_ref(); inline_blame = self.layout_inline_blame( display_row, - &snapshot.display_snapshot, + row_info, line_layout, crease_trailer_layout, em_width, @@ -5593,7 +5593,7 @@ impl Element for EditorElement { } let blamed_display_rows = self.layout_blame_entries( - buffer_rows.into_iter(), + &row_infos, em_width, scroll_position, line_height, @@ -6679,7 +6679,12 @@ mod tests { .update_window(*window, |_, cx| { element.layout_line_numbers( DisplayRow(0)..DisplayRow(6), - (0..6).map(MultiBufferRow).map(Some), + &(0..6) + .map(|row| RowInfo { + buffer_row: Some(row), + ..Default::default() + }) + .collect::>(), &Default::default(), Some(DisplayPoint::new(DisplayRow(0), 0)), &snapshot, diff --git a/crates/editor/src/git/blame.rs b/crates/editor/src/git/blame.rs index b4fe2efec6ac8f..fad75cbb3a9e5f 100644 --- a/crates/editor/src/git/blame.rs +++ b/crates/editor/src/git/blame.rs @@ -9,12 +9,13 @@ use git::{ use gpui::{Model, ModelContext, Subscription, Task}; use http_client::HttpClient; use language::{markdown, Bias, Buffer, BufferSnapshot, Edit, LanguageRegistry, ParsedMarkdown}; -use multi_buffer::MultiBufferRow; use project::{Project, ProjectItem}; use smallvec::SmallVec; use sum_tree::SumTree; use url::Url; +use crate::RowInfo; + #[derive(Clone, Debug, Default)] pub struct GitBlameEntry { pub rows: u32, @@ -194,15 +195,15 @@ impl GitBlame { pub fn blame_for_rows<'a>( &'a mut self, - rows: impl 'a + IntoIterator>, + rows: &'a [RowInfo], cx: &mut ModelContext, ) -> impl 'a + Iterator> { self.sync(cx); let mut cursor = self.entries.cursor::(&()); - rows.into_iter().map(move |row| { - let row = row?; - cursor.seek_forward(&row.0, Bias::Right, &()); + rows.into_iter().map(move |info| { + let row = info.buffer_row?; + cursor.seek_forward(&row, Bias::Right, &()); cursor.item()?.blame.clone() }) } @@ -563,15 +564,38 @@ mod tests { use unindent::Unindent as _; use util::RandomCharIter; - macro_rules! assert_blame_rows { - ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => { - assert_eq!( - $blame - .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx) - .collect::>(), - $expected - ); - }; + // macro_rules! assert_blame_rows { + // ($blame:expr, $rows:expr, $expected:expr, $cx:expr) => { + // assert_eq!( + // $blame + // .blame_for_rows($rows.map(MultiBufferRow).map(Some), $cx) + // .collect::>(), + // $expected + // ); + // }; + // } + + #[track_caller] + fn assert_blame_rows( + blame: &mut GitBlame, + rows: Range, + expected: Vec>, + cx: &mut ModelContext, + ) { + assert_eq!( + blame + .blame_for_rows( + &rows + .map(|row| RowInfo { + buffer_row: Some(row), + ..Default::default() + }) + .collect::>(), + cx + ) + .collect::>(), + expected + ); } fn init_test(cx: &mut gpui::TestAppContext) { @@ -634,7 +658,15 @@ mod tests { blame.update(cx, |blame, cx| { assert_eq!( blame - .blame_for_rows((0..1).map(MultiBufferRow).map(Some), cx) + .blame_for_rows( + &(0..1) + .map(|row| RowInfo { + buffer_row: Some(row), + ..Default::default() + }) + .collect::>(), + cx + ) .collect::>(), vec![None] ); @@ -698,7 +730,15 @@ mod tests { // All lines assert_eq!( blame - .blame_for_rows((0..8).map(MultiBufferRow).map(Some), cx) + .blame_for_rows( + &(0..8) + .map(|buffer_row| RowInfo { + buffer_row: Some(buffer_row), + ..Default::default() + }) + .collect::>(), + cx + ) .collect::>(), vec![ Some(blame_entry("1b1b1b", 0..1)), @@ -714,7 +754,15 @@ mod tests { // Subset of lines assert_eq!( blame - .blame_for_rows((1..4).map(MultiBufferRow).map(Some), cx) + .blame_for_rows( + &(1..4) + .map(|buffer_row| RowInfo { + buffer_row: Some(buffer_row), + ..Default::default() + }) + .collect::>(), + cx + ) .collect::>(), vec![ Some(blame_entry("0d0d0d", 1..2)), @@ -725,7 +773,17 @@ mod tests { // Subset of lines, with some not displayed assert_eq!( blame - .blame_for_rows(vec![Some(MultiBufferRow(1)), None, None], cx) + .blame_for_rows( + &[ + RowInfo { + buffer_row: Some(1), + ..Default::default() + }, + Default::default(), + Default::default(), + ], + cx + ) .collect::>(), vec![Some(blame_entry("0d0d0d", 1..2)), None, None] ); @@ -777,16 +835,16 @@ mod tests { git_blame.update(cx, |blame, cx| { // Sanity check before edits: make sure that we get the same blame entry for all // lines. - assert_blame_rows!( + assert_blame_rows( blame, - (0..4), + 0..4, vec![ Some(blame_entry("1b1b1b", 0..4)), Some(blame_entry("1b1b1b", 0..4)), Some(blame_entry("1b1b1b", 0..4)), Some(blame_entry("1b1b1b", 0..4)), ], - cx + cx, ); }); @@ -795,11 +853,11 @@ mod tests { buffer.edit([(Point::new(0, 0)..Point::new(0, 0), "X")], None, cx); }); git_blame.update(cx, |blame, cx| { - assert_blame_rows!( + assert_blame_rows( blame, - (0..2), + 0..2, vec![None, Some(blame_entry("1b1b1b", 0..4))], - cx + cx, ); }); // Modify a single line, in the middle of the line @@ -807,21 +865,21 @@ mod tests { buffer.edit([(Point::new(1, 2)..Point::new(1, 2), "X")], None, cx); }); git_blame.update(cx, |blame, cx| { - assert_blame_rows!( + assert_blame_rows( blame, - (1..4), + 1..4, vec![ None, Some(blame_entry("1b1b1b", 0..4)), - Some(blame_entry("1b1b1b", 0..4)) + Some(blame_entry("1b1b1b", 0..4)), ], - cx + cx, ); }); // Before we insert a newline at the end, sanity check: git_blame.update(cx, |blame, cx| { - assert_blame_rows!(blame, (3..4), vec![Some(blame_entry("1b1b1b", 0..4))], cx); + assert_blame_rows(blame, 3..4, vec![Some(blame_entry("1b1b1b", 0..4))], cx); }); // Insert a newline at the end buffer.update(cx, |buffer, cx| { @@ -829,17 +887,17 @@ mod tests { }); // Only the new line is marked as edited: git_blame.update(cx, |blame, cx| { - assert_blame_rows!( + assert_blame_rows( blame, - (3..5), + 3..5, vec![Some(blame_entry("1b1b1b", 0..4)), None], - cx + cx, ); }); // Before we insert a newline at the start, sanity check: git_blame.update(cx, |blame, cx| { - assert_blame_rows!(blame, (2..3), vec![Some(blame_entry("1b1b1b", 0..4)),], cx); + assert_blame_rows(blame, 2..3, vec![Some(blame_entry("1b1b1b", 0..4))], cx); }); // Usage example @@ -849,11 +907,11 @@ mod tests { }); // Only the new line is marked as edited: git_blame.update(cx, |blame, cx| { - assert_blame_rows!( + assert_blame_rows( blame, - (2..4), - vec![None, Some(blame_entry("1b1b1b", 0..4)),], - cx + 2..4, + vec![None, Some(blame_entry("1b1b1b", 0..4))], + cx, ); }); } From 07eb573a2c65999263937267a92e0db848b5fded Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 16:51:50 -0800 Subject: [PATCH 26/51] Highlight background of expanded hunks Co-authored-by: Michael --- crates/editor/src/element.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 18490faa222619..b119074fb5ff5a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -5385,9 +5385,21 @@ impl Element for EditorElement { ) }; - let highlighted_rows = self + let mut highlighted_rows = self .editor .update(cx, |editor, cx| editor.highlighted_display_rows(cx)); + + for (ix, row_info) in row_infos.iter().enumerate() { + let color = match row_info.diff_status { + Some(DiffHunkStatus::Added) => style.status.created_background, + Some(DiffHunkStatus::Removed) => style.status.deleted_background, + _ => continue, + }; + highlighted_rows + .entry(start_row + DisplayRow(ix as u32)) + .or_insert(color); + } + let highlighted_ranges = self.editor.read(cx).background_highlights_in_range( start_anchor..end_anchor, &snapshot.display_snapshot, From 9025fb226ae3dfc5597667351968aba3353577b6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 17:33:04 -0800 Subject: [PATCH 27/51] Update more editor rendering to use the new DiffMap --- crates/editor/src/display_map.rs | 29 +++- crates/editor/src/display_map/diff_map.rs | 79 ++++++++++- crates/editor/src/editor.rs | 24 ++-- crates/editor/src/editor_tests.rs | 32 +++-- crates/editor/src/element.rs | 123 +++++++---------- crates/editor/src/git/project_diff.rs | 14 +- crates/editor/src/hunk_diff.rs | 134 ++----------------- crates/editor/src/proposed_changes_editor.rs | 8 +- 8 files changed, 203 insertions(+), 240 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 6e1296c0862f15..2c6b0369fa0df1 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -53,8 +53,8 @@ use language::{ }; use lsp::DiagnosticSeverity; use multi_buffer::{ - Anchor, AnchorRangeExt, MultiBuffer, MultiBufferPoint, MultiBufferRow, MultiBufferSnapshot, - ToOffset, ToPoint, + Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, + MultiBufferSnapshot, ToOffset, ToPoint, }; use project::buffer_store::BufferChangeSet; use serde::Deserialize; @@ -750,6 +750,29 @@ impl DisplaySnapshot { self.fold_snapshot.fold_count() } + pub fn diff_hunks<'a>(&'a self) -> impl Iterator + 'a { + self.diff_snapshot() + .diff_hunks_in_range(0..self.buffer_snapshot.len()) + } + + pub fn diff_hunks_in_range<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> impl Iterator + 'a { + self.diff_snapshot().diff_hunks_in_range(range) + } + + pub fn diff_hunks_in_range_rev<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> impl Iterator + 'a { + self.diff_snapshot().diff_hunks_in_range_rev(range) + } + + pub fn has_diff_hunks(&self) -> bool { + self.diff_snapshot().has_diff_hunks() + } + pub fn is_empty(&self) -> bool { self.buffer_snapshot.len() == 0 } @@ -1349,7 +1372,7 @@ impl DisplaySnapshot { self.block_snapshot.excerpt_header_height } - fn diff_snapshot(&self) -> &DiffMapSnapshot { + pub(crate) fn diff_snapshot(&self) -> &DiffMapSnapshot { &self.fold_snapshot.diff_map_snapshot } } diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 40ea0d4348fa97..0e0b315d0102f5 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -4,7 +4,10 @@ use collections::HashMap; use git::diff::DiffHunkStatus; use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; -use multi_buffer::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset}; +use multi_buffer::{ + Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferSnapshot, + ToOffset, +}; use project::buffer_store::BufferChangeSet; use std::{mem, ops::Range}; use sum_tree::{Cursor, SumTree, TreeMap}; @@ -677,6 +680,80 @@ impl DiffMap { } impl DiffMapSnapshot { + pub fn diff_hunks_in_range<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> impl Iterator + 'a { + let buffer_snapshot = self.buffer(); + let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot); + buffer_snapshot + .excerpts_for_range(range.clone()) + .filter_map(move |excerpt| { + let buffer = excerpt.buffer(); + let buffer_id = buffer.remote_id(); + let diff = &self.diffs.get(&buffer_id)?.diff; + let buffer_range = excerpt.map_range_to_buffer(range.clone()); + let buffer_range = + buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); + Some( + diff.hunks_intersecting_range(buffer_range, excerpt.buffer()) + .map(move |hunk| { + let start = + excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0)); + let end = + excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0)); + MultiBufferDiffHunk { + row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row), + buffer_id, + buffer_range: hunk.buffer_range.clone(), + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + } + }), + ) + }) + .flatten() + } + + pub fn diff_hunks_in_range_rev<'a, T: ToOffset>( + &'a self, + range: Range, + ) -> impl Iterator + 'a { + let buffer_snapshot = self.buffer(); + let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot); + buffer_snapshot + .excerpts_for_range_rev(range.clone()) + .filter_map(move |excerpt| { + let buffer = excerpt.buffer(); + let buffer_id = buffer.remote_id(); + let diff = &self.diffs.get(&buffer_id)?.diff; + let buffer_range = excerpt.map_range_to_buffer(range.clone()); + let buffer_range = + buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); + Some( + diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer()) + .map(move |hunk| { + let start_row = excerpt + .map_point_from_buffer(Point::new(hunk.row_range.start, 0)) + .row; + let end_row = excerpt + .map_point_from_buffer(Point::new(hunk.row_range.end, 0)) + .row; + MultiBufferDiffHunk { + row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row), + buffer_id, + buffer_range: hunk.buffer_range.clone(), + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + } + }), + ) + }) + .flatten() + } + + pub fn has_diff_hunks(&self) -> bool { + self.diffs.values().any(|diff| !diff.diff.is_empty()) + } + #[cfg(test)] pub fn text(&self) -> String { self.chunks(DiffOffset(0)..self.len(), false, Highlights::default()) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index d347294ca9b963..3adf2105e32200 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -89,7 +89,7 @@ use gpui::{ use highlight_matching_bracket::refresh_matching_bracket_highlights; use hover_popover::{hide_hover, HoverState}; pub(crate) use hunk_diff::HoveredHunk; -use hunk_diff::{diff_hunk_to_display, DiffMap, DiffMapSnapshot}; +use hunk_diff::{diff_hunk_to_display, DiffMap}; use indent_guides::ActiveIndentGuidesState; use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy}; pub use inline_completion::Direction; @@ -696,7 +696,6 @@ pub struct EditorSnapshot { git_blame_gutter_max_author_length: Option, pub display_snapshot: DisplaySnapshot, pub placeholder_text: Option>, - diff_map: DiffMapSnapshot, is_focused: bool, scroll_anchor: ScrollAnchor, ongoing_scroll: OngoingScroll, @@ -1536,7 +1535,6 @@ impl Editor { scroll_anchor: self.scroll_manager.anchor(), ongoing_scroll: self.scroll_manager.ongoing_scroll(), placeholder_text: self.placeholder_text.clone(), - diff_map: self.diff_map.snapshot(), is_focused: self.focus_handle.is_focused(cx), current_line_highlight: self .current_line_highlight @@ -9078,9 +9076,8 @@ impl Editor { snapshot, position, ix > 0, - snapshot.diff_map.diff_hunks_in_range( + snapshot.diff_hunks_in_range( position + Point::new(1, 0)..snapshot.buffer_snapshot.max_point(), - &snapshot.buffer_snapshot, ), cx, ) { @@ -9110,9 +9107,7 @@ impl Editor { snapshot, position, ix > 0, - snapshot - .diff_map - .diff_hunks_in_range_rev(Point::zero()..position, &snapshot.buffer_snapshot), + snapshot.diff_hunks_in_range_rev(Point::zero()..position), cx, ) { return Some(hunk); @@ -10671,6 +10666,12 @@ impl Editor { }); } + pub fn expand_all_diff_hunks(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext) { + self.display_map.update(cx, |display_map, cx| { + display_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); + } + pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext) { let ranges: Vec<_> = self.selections.disjoint.iter().map(|s| s.range()).collect(); self.display_map.update(cx, |display_map, cx| { @@ -13022,10 +13023,9 @@ pub fn hunks_for_ranges( for query_range in ranges { let query_rows = MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1); - for hunk in snapshot.diff_map.diff_hunks_in_range( - Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0), - &snapshot.buffer_snapshot, - ) { + for hunk in snapshot + .diff_hunks_in_range(Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0)) + { // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it // when the caret is just above or just below the deleted hunk. let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed; diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 65c5347d368dd4..52a87920213517 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3363,8 +3363,7 @@ async fn test_custom_newlines_cause_no_false_positive_diffs( let snapshot = editor.snapshot(cx); assert_eq!( snapshot - .diff_map - .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot) + .diff_hunks_in_range(0..snapshot.buffer_snapshot.len()) .collect::>(), Vec::new(), "Should not have any diffs for files with custom newlines" @@ -11544,7 +11543,9 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) { cx, ) }); - editor.diff_map.add_change_set(change_set, cx) + editor.display_map.update(cx, |display_map, cx| { + display_map.add_change_set(change_set, cx) + }); } }); cx.executor().run_until_parked(); @@ -12053,7 +12054,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks( executor.run_until_parked(); cx.update_editor(|editor, cx| { - editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); + editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); cx.assert_state_with_diff( @@ -12098,7 +12099,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks( ); cx.update_editor(|editor, cx| { - editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); + editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); cx.assert_state_with_diff( @@ -12181,7 +12182,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui: executor.run_until_parked(); cx.update_editor(|editor, cx| { - editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); + editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); @@ -12374,7 +12375,9 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) cx, ) }); - editor.diff_map.add_change_set(change_set, cx) + editor.display_map.update(cx, |display_map, cx| { + display_map.add_change_set(change_set, cx) + }); } }) .unwrap(); @@ -12484,14 +12487,16 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext let buffer = buffer.read(cx).text_snapshot(); let change_set = cx .new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx)); - editor.diff_map.add_change_set(change_set, cx) + editor.display_map.update(cx, |display_map, cx| { + display_map.add_change_set(change_set, cx) + }) }) .unwrap(); let mut cx = EditorTestContext::for_editor(editor, cx).await; cx.run_until_parked(); - cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx)); + cx.update_editor(|editor, cx| editor.expand_all_diff_hunks(&Default::default(), cx)); cx.executor().run_until_parked(); cx.assert_state_with_diff( @@ -12555,7 +12560,7 @@ async fn test_edits_around_expanded_insertion_hunks( executor.run_until_parked(); cx.update_editor(|editor, cx| { - editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); + editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); @@ -12746,7 +12751,7 @@ async fn test_edits_around_expanded_deletion_hunks( executor.run_until_parked(); cx.update_editor(|editor, cx| { - editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); + editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); @@ -12888,7 +12893,7 @@ async fn test_edit_after_expanded_modification_hunk( cx.set_diff_base(&diff_base); executor.run_until_parked(); cx.update_editor(|editor, cx| { - editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx); + editor.expand_all_diff_hunks(&ExpandAllHunkDiffs, cx); }); executor.run_until_parked(); @@ -14012,8 +14017,7 @@ fn assert_hunk_revert( let reverted_hunk_statuses = cx.update_editor(|editor, cx| { let snapshot = editor.snapshot(cx); let reverted_hunk_statuses = snapshot - .diff_map - .diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot) + .diff_hunks_in_range(0..snapshot.buffer_snapshot.len()) .map(|hunk| hunk_status(&hunk)) .collect::>(); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index b119074fb5ff5a..48cbc66274cdce 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -380,7 +380,7 @@ impl EditorElement { register_action(view, cx, Editor::toggle_git_blame); register_action(view, cx, Editor::toggle_git_blame_inline); register_action(view, cx, Editor::toggle_hunk_diff); - register_action(view, cx, Editor::expand_all_hunk_diffs); + register_action(view, cx, Editor::expand_all_diff_hunks); register_action(view, cx, |editor, action, cx| { if let Some(task) = editor.format(action, cx) { task.detach_and_log_err(cx); @@ -1175,7 +1175,7 @@ impl EditorElement { let editor = self.editor.read(cx); let is_singleton = editor.is_singleton(cx); // Git - (is_singleton && scrollbar_settings.git_diff && !snapshot.diff_map.is_empty()) + (is_singleton && scrollbar_settings.git_diff && snapshot.has_diff_hunks()) || // Buffer Search Results (is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::()) @@ -1347,8 +1347,9 @@ impl EditorElement { let mut expanded_hunks = expanded_hunks[expanded_hunks_start_ix..].iter().peekable(); let mut display_hunks: Vec<(DisplayDiffHunk, Option)> = snapshot - .diff_map - .diff_hunks_in_range(buffer_start..buffer_end, &buffer_snapshot) + .display_snapshot + .diff_snapshot() + .diff_hunks_in_range(buffer_start..buffer_end) .filter_map(|hunk| { let display_hunk = diff_hunk_to_display(&hunk, snapshot); @@ -3904,32 +3905,29 @@ impl EditorElement { let max_point = snapshot.display_snapshot.buffer_snapshot.max_point(); let mut marker_quads = Vec::new(); if scrollbar_settings.git_diff { - let marker_row_ranges = snapshot - .diff_map - .diff_hunks(&snapshot.buffer_snapshot) - .map(|hunk| { - let start_display_row = - MultiBufferPoint::new(hunk.row_range.start.0, 0) - .to_display_point(&snapshot.display_snapshot) - .row(); - let mut end_display_row = - MultiBufferPoint::new(hunk.row_range.end.0, 0) - .to_display_point(&snapshot.display_snapshot) - .row(); - if end_display_row != start_display_row { - end_display_row.0 -= 1; - } - let color = match hunk_status(&hunk) { - DiffHunkStatus::Added => theme.status().created, - DiffHunkStatus::Modified => theme.status().modified, - DiffHunkStatus::Removed => theme.status().deleted, - }; - ColoredRange { - start: start_display_row, - end: end_display_row, - color, - } - }); + let marker_row_ranges = snapshot.diff_hunks().map(|hunk| { + let start_display_row = + MultiBufferPoint::new(hunk.row_range.start.0, 0) + .to_display_point(&snapshot.display_snapshot) + .row(); + let mut end_display_row = + MultiBufferPoint::new(hunk.row_range.end.0, 0) + .to_display_point(&snapshot.display_snapshot) + .row(); + if end_display_row != start_display_row { + end_display_row.0 -= 1; + } + let color = match hunk_status(&hunk) { + DiffHunkStatus::Added => theme.status().created, + DiffHunkStatus::Modified => theme.status().modified, + DiffHunkStatus::Removed => theme.status().deleted, + }; + ColoredRange { + start: start_display_row, + end: end_display_row, + color, + } + }); marker_quads.extend( scrollbar_layout @@ -5695,22 +5693,6 @@ impl Element for EditorElement { let gutter_settings = EditorSettings::get_global(cx).gutter; - let expanded_add_hunks_by_rows = self.editor.update(cx, |editor, _| { - editor - .diff_map - .hunks(false) - .filter(|hunk| hunk.status == DiffHunkStatus::Added) - .map(|expanded_hunk| { - let start_row = expanded_hunk - .hunk_range - .start - .to_display_point(&snapshot) - .row(); - (start_row, expanded_hunk.clone()) - }) - .collect::>() - }); - let rows_with_hunk_bounds = display_hunks .iter() .filter_map(|(hunk, hitbox)| Some((hunk, hitbox.as_ref()?.bounds))) @@ -5755,35 +5737,26 @@ impl Element for EditorElement { if show_code_actions { let newest_selection_point = newest_selection_head.to_point(&snapshot.display_snapshot); - let newest_selection_display_row = - newest_selection_point.to_display_point(&snapshot).row(); - if !expanded_add_hunks_by_rows - .contains_key(&newest_selection_display_row) - { - let buffer = snapshot.buffer_snapshot.buffer_line_for_row( - MultiBufferRow(newest_selection_point.row), - ); - if let Some((buffer, range)) = buffer { - let buffer_id = buffer.remote_id(); - let row = range.start.row; - let has_test_indicator = self - .editor - .read(cx) - .tasks - .contains_key(&(buffer_id, row)); - - if !has_test_indicator { - code_actions_indicator = self - .layout_code_actions_indicator( - line_height, - newest_selection_head, - scroll_pixel_position, - &gutter_dimensions, - &gutter_hitbox, - &rows_with_hunk_bounds, - cx, - ); - } + let buffer = snapshot.buffer_snapshot.buffer_line_for_row( + MultiBufferRow(newest_selection_point.row), + ); + if let Some((buffer, range)) = buffer { + let buffer_id = buffer.remote_id(); + let row = range.start.row; + let has_test_indicator = + self.editor.read(cx).tasks.contains_key(&(buffer_id, row)); + + if !has_test_indicator { + code_actions_indicator = self + .layout_code_actions_indicator( + line_height, + newest_selection_head, + scroll_pixel_position, + &gutter_dimensions, + &gutter_hitbox, + &rows_with_hunk_bounds, + cx, + ); } } } diff --git a/crates/editor/src/git/project_diff.rs b/crates/editor/src/git/project_diff.rs index 9434af8cfcfd5a..f8dce4368a1cf2 100644 --- a/crates/editor/src/git/project_diff.rs +++ b/crates/editor/src/git/project_diff.rs @@ -311,9 +311,11 @@ impl ProjectDiffEditor { .update(&mut cx, |project_diff_editor, cx| { project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx); project_diff_editor.editor.update(cx, |editor, cx| { - for change_set in change_sets { - editor.diff_map.add_change_set(change_set, cx) - } + editor.display_map.update(cx, |display_map, cx| { + for change_set in change_sets { + display_map.add_change_set(change_set, cx) + } + }); }); }) .ok(); @@ -1193,9 +1195,9 @@ mod tests { cx, ) }); - file_a_editor - .diff_map - .add_change_set(change_set.clone(), cx); + file_a_editor.display_map.update(cx, |display_map, cx| { + display_map.add_change_set(change_set.clone(), cx) + }); project.update(cx, |project, cx| { project.buffer_store().update(cx, |buffer_store, cx| { buffer_store.set_change_set( diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index a2adde941e6fbc..4d83ecb9ea8b0b 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -23,8 +23,8 @@ use workspace::Item; use crate::{ editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks, ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, - DisplayRow, DisplaySnapshot, Editor, EditorElement, ExpandAllHunkDiffs, GoToHunk, GoToPrevHunk, - RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff, + DisplayRow, DisplaySnapshot, Editor, EditorElement, GoToHunk, GoToPrevHunk, RevertFile, + RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff, }; #[derive(Debug, Clone)] @@ -75,50 +75,7 @@ pub enum DisplayDiffHunk { }, } -impl DiffMap { - pub fn snapshot(&self) -> DiffMapSnapshot { - self.snapshot.clone() - } - - pub fn add_change_set( - &mut self, - change_set: Model, - cx: &mut ViewContext, - ) { - let buffer_id = change_set.read(cx).buffer_id; - self.snapshot - .0 - .insert(buffer_id, change_set.read(cx).diff_to_buffer.clone()); - self.diff_bases.insert( - buffer_id, - DiffBaseState { - last_version: None, - _subscription: cx.observe(&change_set, move |editor, change_set, cx| { - editor - .diff_map - .snapshot - .0 - .insert(buffer_id, change_set.read(cx).diff_to_buffer.clone()); - Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, cx); - }), - change_set, - }, - ); - Editor::sync_expanded_diff_hunks(self, buffer_id, cx); - } - - pub fn hunks(&self, include_folded: bool) -> impl Iterator { - self.hunks - .iter() - .filter(move |hunk| include_folded || !hunk.folded) - } -} - impl DiffMapSnapshot { - pub fn is_empty(&self) -> bool { - self.0.values().all(|diff| diff.is_empty()) - } - pub fn diff_hunks<'a>( &'a self, buffer_snapshot: &'a MultiBufferSnapshot, @@ -159,42 +116,6 @@ impl DiffMapSnapshot { }) .flatten() } - - pub fn diff_hunks_in_range_rev<'a, T: ToOffset>( - &'a self, - range: Range, - buffer_snapshot: &'a MultiBufferSnapshot, - ) -> impl Iterator + 'a { - let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot); - buffer_snapshot - .excerpts_for_range_rev(range.clone()) - .filter_map(move |excerpt| { - let buffer = excerpt.buffer(); - let buffer_id = buffer.remote_id(); - let diff = self.0.get(&buffer_id)?; - let buffer_range = excerpt.map_range_to_buffer(range.clone()); - let buffer_range = - buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); - Some( - diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer()) - .map(move |hunk| { - let start_row = excerpt - .map_point_from_buffer(Point::new(hunk.row_range.start, 0)) - .row; - let end_row = excerpt - .map_point_from_buffer(Point::new(hunk.row_range.end, 0)) - .row; - MultiBufferDiffHunk { - row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row), - buffer_id, - buffer_range: hunk.buffer_range.clone(), - diff_base_byte_range: hunk.diff_base_byte_range.clone(), - } - }), - ) - }) - .flatten() - } } impl Editor { @@ -214,41 +135,6 @@ impl Editor { } } - pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext) { - let snapshot = self.snapshot(cx); - let display_rows_with_expanded_hunks = self - .diff_map - .hunks(false) - .map(|hunk| &hunk.hunk_range) - .map(|anchor_range| { - ( - anchor_range - .start - .to_display_point(&snapshot.display_snapshot) - .row(), - anchor_range - .end - .to_display_point(&snapshot.display_snapshot) - .row(), - ) - }) - .collect::>(); - let hunks = self - .diff_map - .snapshot - .diff_hunks(&snapshot.display_snapshot.buffer_snapshot) - .filter(|hunk| { - let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0) - .to_display_point(&snapshot.display_snapshot) - ..Point::new(hunk.row_range.end.0, 0) - .to_display_point(&snapshot.display_snapshot); - let row_range_end = - display_rows_with_expanded_hunks.get(&hunk_display_row_range.start.row()); - row_range_end.is_none() || row_range_end != Some(&hunk_display_row_range.end.row()) - }); - self.toggle_hunks_expanded(hunks.collect(), cx); - } - fn toggle_hunks_expanded( &mut self, hunks_to_toggle: Vec, @@ -904,8 +790,7 @@ impl Editor { .update(&mut cx, |editor, cx| { let snapshot = editor.snapshot(cx); let mut recalculated_hunks = snapshot - .diff_map - .diff_hunks(&snapshot.buffer_snapshot) + .diff_hunks() .filter(|hunk| hunk.buffer_id == buffer_id) .fuse() .peekable(); @@ -1413,7 +1298,9 @@ mod tests { cx, ) }); - editor.diff_map.add_change_set(change_set, cx) + editor.display_map.update(cx, |display_map, cx| { + display_map.add_change_set(change_set, cx) + }); } }) .unwrap(); @@ -1465,8 +1352,7 @@ mod tests { assert_eq!( snapshot - .diff_map - .diff_hunks_in_range(Point::zero()..Point::new(12, 0), &snapshot.buffer_snapshot) + .diff_hunks_in_range(Point::zero()..Point::new(12, 0)) .map(|hunk| (hunk_status(&hunk), hunk.row_range)) .collect::>(), &expected, @@ -1474,11 +1360,7 @@ mod tests { assert_eq!( snapshot - .diff_map - .diff_hunks_in_range_rev( - Point::zero()..Point::new(12, 0), - &snapshot.buffer_snapshot - ) + .diff_hunks_in_range_rev(Point::zero()..Point::new(12, 0)) .map(|hunk| (hunk_status(&hunk), hunk.row_range)) .collect::>(), expected diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index 5fe9fc885bafe8..eb4b11cff388a0 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -223,9 +223,11 @@ impl ProposedChangesEditor { self.buffer_entries = buffer_entries; self.editor.update(cx, |editor, cx| { editor.change_selections(None, cx, |selections| selections.refresh()); - for change_set in new_change_sets { - editor.diff_map.add_change_set(change_set, cx) - } + editor.display_map.update(cx, |display_map, cx| { + for change_set in new_change_sets { + display_map.add_change_set(change_set, cx) + } + }) }); } From 243d7c6d5a8141d04ad3c37e5271c552926220ac Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 12 Dec 2024 17:58:10 -0800 Subject: [PATCH 28/51] WIP - Failing diff map test for hunks staying expanded on diff updates --- crates/editor/src/display_map/diff_map.rs | 65 ++++++++++++++++++++++- 1 file changed, 63 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 0e0b315d0102f5..ec35c0e5e136f3 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1447,7 +1447,9 @@ mod tests { let (mut inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); let (diff_map, _) = cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); - diff_map.update(cx, |diff_map, cx| diff_map.add_change_set(change_set, cx)); + diff_map.update(cx, |diff_map, cx| { + diff_map.add_change_set(change_set.clone(), cx) + }); cx.run_until_parked(); let (mut snapshot, _) = diff_map.update(cx, |diff_map, cx| { @@ -1578,6 +1580,35 @@ mod tests { ), ); + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks( + vec![ + multibuffer_snapshot.anchor_before(Point::new(3, 0)) + ..multibuffer_snapshot.anchor_before(Point::new(4, 0)), + ], + cx, + ) + }); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one + - two + + TWO + three + - four + - five + six + " + ), + ); + buffer.update(cx, |buffer, cx| { buffer.edit_via_marked_text( indoc!( @@ -1600,7 +1631,9 @@ mod tests { multibuffer_snapshot, multibuffer_edits.consume().into_inner(), ); - let sync = diff_map.update(cx, |diff_map, cx| diff_map.sync(inlay_snapshot, edits, cx)); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), edits, cx) + }); assert_new_snapshot( &mut snapshot, @@ -1613,6 +1646,34 @@ mod tests { - two + TWO three + - four + - five + six + " + ), + ); + + let _ = change_set.update(cx, |change_set, cx| { + change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx) + }); + cx.run_until_parked(); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(inlay_snapshot.clone(), Vec::new(), cx) + }); + + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one hundred + thousand + TWO + three + - four + - five six " ), From 41c5e45ca2f2cb43d3360d96b8f499d6d81f5145 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Fri, 13 Dec 2024 11:20:46 -0700 Subject: [PATCH 29/51] Fix bugs in DiffMap's handling of edits and diff updates --- crates/editor/src/display_map/diff_map.rs | 60 +++++++++++------------ 1 file changed, 28 insertions(+), 32 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index ec35c0e5e136f3..f48ad775abb6db 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -357,8 +357,9 @@ impl DiffMap { let mut changes = changes.into_iter().peekable(); while let Some((mut edit, mut is_inlay_edit, mut multibuffer_range)) = changes.next() { - new_transforms.append(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + new_transforms.append(cursor.slice(&edit.old.start, Bias::Right, &()), &()); + let mut delta = 0_isize; let mut end_of_current_insert = InlayOffset(0); loop { let old_overshoot = (edit.old.start - cursor.start().0).0; @@ -424,15 +425,20 @@ impl DiffMap { end_of_current_insert, ); - while cursor.end(&()).0 <= hunk_start_inlay_offset { + while cursor.end(&()).0 < hunk_start_inlay_offset + || (cursor.end(&()).0 == hunk_start_inlay_offset + && cursor.start().0 < hunk_start_inlay_offset) + { let Some(item) = cursor.item() else { break; }; if let DiffTransform::DeletedHunk { .. } = item { + let old_range = cursor.start().1..cursor.end(&()).1; let new_offset = - DiffOffset(new_transforms.summary().diff_map.len); + DiffOffset((old_range.start.0 as isize + delta) as usize); + delta -= (old_range.end - old_range.start).0 as isize; let edit = Edit { - old: cursor.start().1..cursor.end(&()).1, + old: old_range, new: new_offset..new_offset, }; edits.push(edit); @@ -441,18 +447,14 @@ impl DiffMap { } let mut was_previously_expanded = false; - if let Some(item) = cursor.item() { - if let DiffTransform::DeletedHunk { - base_text_byte_range, - .. - } = item - { - if cursor.start().0 == hunk_start_inlay_offset - && *base_text_byte_range == hunk.diff_base_byte_range - { - was_previously_expanded = true; - } - } + if cursor.start().0 == hunk_start_inlay_offset { + was_previously_expanded = match cursor.item() { + Some(DiffTransform::DeletedHunk { .. }) => true, + Some(DiffTransform::BufferContent { + is_inserted_hunk, .. + }) => *is_inserted_hunk, + None => false, + }; } let mut should_expand_hunk = @@ -482,6 +484,7 @@ impl DiffMap { DiffOffset(new_transforms.summary().diff_map.len); let new_end = new_start + DiffOffset(hunk.diff_base_byte_range.len()); + delta += hunk.diff_base_byte_range.len() as isize; let edit = Edit { old: old_offset..old_offset, new: new_start..new_end, @@ -518,16 +521,10 @@ impl DiffMap { .to_inlay_offset(hunk_end_multibuffer_offset); end_of_current_insert = hunk_end_inlay_offset; } - } else if was_previously_expanded { - let old_start = cursor.start().1; - let new_offset = DiffOffset(new_transforms.summary().diff_map.len); - let edit = Edit { - old: old_start - ..old_start + DiffOffset(hunk.diff_base_byte_range.len()), - new: new_offset..new_offset, - }; - edits.push(edit); - cursor.next(&()); + + if was_previously_expanded { + cursor.next(&()); + } } } } @@ -538,9 +535,11 @@ impl DiffMap { break; }; if let DiffTransform::DeletedHunk { .. } = item { - let new_offset = DiffOffset(new_transforms.summary().diff_map.len); + let old_range = cursor.start().1..cursor.end(&()).1; + let new_offset = DiffOffset((old_range.start.0 as isize + delta) as usize); + delta -= (old_range.end - old_range.start).0 as isize; let edit = Edit { - old: cursor.start().1..cursor.end(&()).1, + old: old_range, new: new_offset..new_offset, }; edits.push(edit); @@ -548,12 +547,12 @@ impl DiffMap { cursor.next(&()); } - let old_overshoot = (edit.old.end - cursor.start().0).0; self.push_buffer_content_transform( &mut new_transforms, edit.new.end, end_of_current_insert, ); + let old_overshoot = (edit.old.end - cursor.start().0).0; let diff_edit_old_end = cursor.start().1 + DiffOffset(old_overshoot); let diff_edit_new_end = DiffOffset(new_transforms.summary().diff_map.len); @@ -1634,7 +1633,6 @@ mod tests { let sync = diff_map.update(cx, |diff_map, cx| { diff_map.sync(inlay_snapshot.clone(), edits, cx) }); - assert_new_snapshot( &mut snapshot, sync, @@ -1657,11 +1655,9 @@ mod tests { change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx) }); cx.run_until_parked(); - let sync = diff_map.update(cx, |diff_map, cx| { diff_map.sync(inlay_snapshot.clone(), Vec::new(), cx) }); - assert_new_snapshot( &mut snapshot, sync, From 82fbe27c08b62939be69962125f6c33666a10ce8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 13 Dec 2024 16:42:07 -0800 Subject: [PATCH 30/51] Fix some problems with expanding and collapsing diff hunks Co-authored-by: Conrad --- crates/editor/src/display_map/diff_map.rs | 557 ++++++++++++++++------ 1 file changed, 401 insertions(+), 156 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index f48ad775abb6db..ba2ae9e37d30b7 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -6,7 +6,7 @@ use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::{ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferSnapshot, - ToOffset, + ToOffset, ToPoint, }; use project::buffer_store::BufferChangeSet; use std::{mem, ops::Range}; @@ -285,10 +285,8 @@ impl DiffMap { .ranges_for_buffer(buffer_id, cx) .into_iter() .map(|(_, range, _)| { - let multibuffer_start = - ToOffset::to_offset(&range.start, &multibuffer_snapshot); - let multibuffer_end = - ToOffset::to_offset(&range.end, &multibuffer_snapshot); + let multibuffer_start = multibuffer_snapshot.point_to_offset(range.start); + let multibuffer_end = multibuffer_snapshot.point_to_offset(range.end); let inlay_start = self .snapshot .inlay_snapshot @@ -326,22 +324,31 @@ impl DiffMap { | DiffMapOperation::CollapseHunks { ranges } => { let mut changes = Vec::new(); for range in ranges.iter() { - let multibuffer_range = range.to_offset(&multibuffer_snapshot); + let multibuffer_start = range.start.to_point(&multibuffer_snapshot); + let multibuffer_start = multibuffer_snapshot + .point_to_offset(Point::new(multibuffer_start.row, 0)) + .saturating_sub(1); + let multibuffer_end = range.end.to_point(&multibuffer_snapshot); + let multibuffer_end = multibuffer_snapshot.len().min( + multibuffer_snapshot + .point_to_offset(Point::new(multibuffer_end.row + 1, 0)) + + 1, + ); let inlay_start = self .snapshot .inlay_snapshot - .to_inlay_offset(multibuffer_range.start); + .to_inlay_offset(multibuffer_start); let inlay_end = self .snapshot .inlay_snapshot - .to_inlay_offset(multibuffer_range.end); + .to_inlay_offset(multibuffer_end); changes.push(( InlayEdit { old: inlay_start..inlay_end, new: inlay_start..inlay_end, }, false, - multibuffer_range, + multibuffer_start..multibuffer_end, )); } changes @@ -357,7 +364,8 @@ impl DiffMap { let mut changes = changes.into_iter().peekable(); while let Some((mut edit, mut is_inlay_edit, mut multibuffer_range)) = changes.next() { - new_transforms.append(cursor.slice(&edit.old.start, Bias::Right, &()), &()); + let to_skip = cursor.slice(&edit.old.start, Bias::Right, &()); + self.append_transforms(&mut new_transforms, to_skip); let mut delta = 0_isize; let mut end_of_current_insert = InlayOffset(0); @@ -381,8 +389,8 @@ impl DiffMap { let diff_state = self.snapshot.diffs.get(&buffer_id); let buffer = buffer.read(cx); - let buffer_anchor_range = buffer.anchor_after(buffer_range.start) - ..buffer.anchor_before(buffer_range.end); + let buffer_anchor_range = buffer.anchor_before(buffer_range.start) + ..buffer.anchor_after(buffer_range.end); let change_start_buffer_offset = buffer_range.start; if let Some(diff_state) = diff_state { let diff = &diff_state.diff; @@ -405,9 +413,7 @@ impl DiffMap { let hunk_start_buffer_offset = hunk.buffer_range.start.to_offset(buffer); - if hunk_start_buffer_offset < change_start_buffer_offset { - continue; - } + let hunk_end_buffer_offset = hunk.buffer_range.end.to_offset(buffer); let excerpt_buffer_range_start_offset = excerpt_buffer_range.start.to_offset(buffer); @@ -457,24 +463,39 @@ impl DiffMap { }; } + let hunk_is_deletion = + hunk_start_buffer_offset == hunk_end_buffer_offset; + let mut should_expand_hunk = was_previously_expanded || self.all_hunks_expanded; match &operation { DiffMapOperation::ExpandHunks { ranges } => { - should_expand_hunk |= ranges.iter().any(|range| { - range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) - }) + if hunk_is_deletion { + should_expand_hunk = true; + } else { + should_expand_hunk |= ranges.iter().any(|range| { + range + .overlaps(&hunk_anchor_range, &multibuffer_snapshot) + }) + } } DiffMapOperation::CollapseHunks { ranges } => { - should_expand_hunk &= !ranges.iter().any(|range| { - range.overlaps(&hunk_anchor_range, &multibuffer_snapshot) - }) + if hunk_is_deletion { + should_expand_hunk = false; + } else { + should_expand_hunk &= !ranges.iter().any(|range| { + range + .overlaps(&hunk_anchor_range, &multibuffer_snapshot) + }) + } } _ => {} }; if should_expand_hunk { - if hunk.diff_base_byte_range.len() > 0 { + if hunk.diff_base_byte_range.len() > 0 + && hunk_start_buffer_offset >= change_start_buffer_offset + { if !was_previously_expanded { let hunk_overshoot = (hunk_start_inlay_offset - cursor.start().0).0; @@ -508,9 +529,6 @@ impl DiffMap { ); } - let hunk_end_buffer_offset = - hunk.buffer_range.end.to_offset(buffer); - if hunk_end_buffer_offset > hunk_start_buffer_offset { let hunk_end_multibuffer_offset = excerpt_range.start + hunk_end_buffer_offset @@ -582,9 +600,9 @@ impl DiffMap { } } + self.append_transforms(&mut new_transforms, cursor.suffix(&())); self.edits_since_sync = self.edits_since_sync.compose(edits); - new_transforms.append(cursor.suffix(&()), &()); drop(cursor); self.snapshot.transforms = new_transforms; self.snapshot.version += 1; @@ -594,6 +612,31 @@ impl DiffMap { self.check_invariants(); } + fn append_transforms( + &self, + new_transforms: &mut SumTree, + subtree: SumTree, + ) { + if let Some(DiffTransform::BufferContent { + is_inserted_hunk, + summary, + }) = subtree.first() + { + if self.extend_last_buffer_content_transform( + new_transforms, + *is_inserted_hunk, + summary.clone(), + ) { + let mut cursor = subtree.cursor::<()>(&()); + cursor.next(&()); + cursor.next(&()); + new_transforms.append(cursor.suffix(&()), &()); + return; + } + } + new_transforms.append(subtree, &()); + } + fn push_buffer_content_transform( &self, new_transforms: &mut SumTree, @@ -613,23 +656,11 @@ impl DiffMap { .inlay_snapshot .text_summary_for_range(start_offset..end_offset); - let mut did_extend = false; - new_transforms.update_last( - |last_transform| { - if let DiffTransform::BufferContent { - summary, - is_inserted_hunk, - } = last_transform - { - if *is_inserted_hunk == region_is_inserted_hunk { - did_extend = true; - *summary += summary_to_add.clone(); - } - } - }, - &(), - ); - if !did_extend { + if !self.extend_last_buffer_content_transform( + new_transforms, + region_is_inserted_hunk, + summary_to_add.clone(), + ) { new_transforms.push( DiffTransform::BufferContent { summary: summary_to_add, @@ -641,6 +672,31 @@ impl DiffMap { } } + fn extend_last_buffer_content_transform( + &self, + new_transforms: &mut SumTree, + region_is_inserted_hunk: bool, + summary_to_add: TextSummary, + ) -> bool { + let mut did_extend = false; + new_transforms.update_last( + |last_transform| { + if let DiffTransform::BufferContent { + summary, + is_inserted_hunk, + } = last_transform + { + if *is_inserted_hunk == region_is_inserted_hunk { + *summary += summary_to_add.clone(); + did_extend = true; + } + } + }, + &(), + ); + did_extend + } + #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; @@ -657,16 +713,18 @@ impl DiffMap { for item in snapshot.transforms.iter() { if let DiffTransform::BufferContent { summary, - is_inserted_hunk: is_new, + is_inserted_hunk, } = item { if let Some(DiffTransform::BufferContent { - is_inserted_hunk: prev_is_new, + is_inserted_hunk: prev_is_inserted_hunk, .. }) = prev_transform { - if *is_new == *prev_is_new { - panic!("multiple adjacent buffer content transforms with the same is_new value"); + if *is_inserted_hunk == *prev_is_inserted_hunk { + panic!( + "multiple adjacent buffer content transforms with is_inserted_hunk = {is_inserted_hunk}. transforms: {:+?}", + snapshot.transforms.items(&())); } } if summary.len == 0 && !snapshot.buffer().is_empty() { @@ -1402,6 +1460,7 @@ mod tests { use crate::display_map::inlay_map::InlayMap; use gpui::{AppContext, TestAppContext}; use indoc::indoc; + use language::Buffer; use multi_buffer::{Anchor, MultiBuffer}; use project::Project; use settings::SettingsStore; @@ -1431,29 +1490,7 @@ mod tests { " ); - let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); - let change_set = cx.new_model(|cx| { - BufferChangeSet::new_with_base_text( - base_text.to_string(), - buffer.read(cx).text_snapshot(), - cx, - ) - }); - - let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); - let (multibuffer_snapshot, multibuffer_edits) = - multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); - let (diff_map, _) = - cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); - diff_map.update(cx, |diff_map, cx| { - diff_map.add_change_set(change_set.clone(), cx) - }); - cx.run_until_parked(); - - let (mut snapshot, _) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), vec![], cx) - }); + let (diff_map, mut snapshot, mut deps) = build_diff_map(text, Some(base_text), cx); assert_eq!( snapshot.text(), indoc!( @@ -1471,7 +1508,7 @@ mod tests { diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1536,7 +1573,7 @@ mod tests { diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1552,17 +1589,13 @@ mod tests { ), ); + // Expand the first diff hunk diff_map.update(cx, |diff_map, cx| { - diff_map.expand_diff_hunks( - vec![ - multibuffer_snapshot.anchor_before(Point::new(2, 0)) - ..multibuffer_snapshot.anchor_before(Point::new(2, 0)), - ], - cx, - ) + let position = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.expand_diff_hunks(vec![position..position], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1579,17 +1612,13 @@ mod tests { ), ); + // Expand the second diff hunk diff_map.update(cx, |diff_map, cx| { - diff_map.expand_diff_hunks( - vec![ - multibuffer_snapshot.anchor_before(Point::new(3, 0)) - ..multibuffer_snapshot.anchor_before(Point::new(4, 0)), - ], - cx, - ) + let position = deps.multibuffer_snapshot.anchor_before(Point::new(3, 0)); + diff_map.expand_diff_hunks(vec![position..position], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1608,7 +1637,8 @@ mod tests { ), ); - buffer.update(cx, |buffer, cx| { + // Edit the buffer before the first hunk + let edits = deps.update_buffer(cx, |buffer, cx| { buffer.edit_via_marked_text( indoc!( " @@ -1625,13 +1655,8 @@ mod tests { ); }); - let multibuffer_snapshot = multibuffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); - let (inlay_snapshot, edits) = inlay_map.sync( - multibuffer_snapshot, - multibuffer_edits.consume().into_inner(), - ); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), edits, cx) + diff_map.sync(deps.inlay_snapshot.clone(), edits, cx) }); assert_new_snapshot( &mut snapshot, @@ -1651,12 +1676,13 @@ mod tests { ), ); - let _ = change_set.update(cx, |change_set, cx| { - change_set.recalculate_diff(buffer.read(cx).text_snapshot(), cx) + // Recalculate the diff, changing the first diff hunk. + let _ = deps.change_set.update(cx, |change_set, cx| { + change_set.recalculate_diff(deps.buffer.read(cx).text_snapshot(), cx) }); cx.run_until_parked(); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), Vec::new(), cx) + diff_map.sync(deps.inlay_snapshot.clone(), Vec::new(), cx) }); assert_new_snapshot( &mut snapshot, @@ -1681,29 +1707,16 @@ mod tests { cx.update(init_test); let text = "hello world"; - let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); - - let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); - let (multibuffer_snapshot, multibuffer_edits) = - multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); - let (diff_map, _) = - cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); - let (mut snapshot, _) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), vec![], cx) - }); + let (diff_map, mut snapshot, mut deps) = build_diff_map(text, None, cx); assert_eq!(snapshot.text(), "hello world"); - buffer.update(cx, |buffer, cx| { + let edits = deps.update_buffer(cx, |buffer, cx| { buffer.edit([(4..5, "a"), (9..11, "k")], None, cx); }); - let multibuffer_snapshot = multibuffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); - let (inlay_snapshot, edits) = inlay_map.sync( - multibuffer_snapshot, - multibuffer_edits.consume().into_inner(), - ); - let sync = diff_map.update(cx, |diff_map, cx| diff_map.sync(inlay_snapshot, edits, cx)); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot, edits, cx) + }); assert_new_snapshot(&mut snapshot, sync, indoc!("hella work")); } @@ -1728,39 +1741,25 @@ mod tests { " ); - let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); - let change_set = cx.new_model(|cx| { - BufferChangeSet::new_with_base_text( - base_text.to_string(), - buffer.read(cx).text_snapshot(), - cx, - ) - }); + let (diff_map, mut diff_snapshot, deps) = build_diff_map(text, Some(base_text), cx); + diff_map.update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx)); + cx.run_until_parked(); - let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); - let (multibuffer_snapshot, _) = - multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); - let (_, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); - let (diff_map, _) = - cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); - diff_map.update(cx, |diff_map, cx| { - diff_map.set_all_hunks_expanded(cx); - diff_map.add_change_set(change_set, cx); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot, vec![], cx) }); - cx.run_until_parked(); - let (diff_snapshot, _) = - diff_map.update(cx, |diff_map, cx| diff_map.sync(inlay_snapshot, vec![], cx)); - assert_eq!( - diff_snapshot.text(), + assert_new_snapshot( + &mut diff_snapshot, + sync, indoc! {" - ₀ - ₁ - ₂ - ₃ - ₄ - ₅ - "} + ₀ + - ₁ + - ₂ + ₃ + ₄ + + ₅ + "}, ); for (point, (left, right)) in [ @@ -1847,17 +1846,7 @@ mod tests { fn test_empty_diff_map(cx: &mut TestAppContext) { cx.update(init_test); - let text = ""; - - let buffer = cx.new_model(|cx| language::Buffer::local(text, cx)); - let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); - let (multibuffer_snapshot, _multibuffer_edits) = - multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); - let (_inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); - - let (_diff_map, diff_snapshot) = - cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); - + let (_diff_map, diff_snapshot, _deps) = build_diff_map("", None, cx); assert_eq!( diff_snapshot .row_infos(0) @@ -1867,6 +1856,187 @@ mod tests { ); } + #[gpui::test] + fn test_expand_collapse_hunks(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = indoc!( + " + one + two + three + four + five + six + seven + eight + " + ); + let text = indoc!( + " + one + two + five + six; seven + eight + " + ); + + let (diff_map, mut snapshot, deps) = build_diff_map(text, Some(base_text), cx); + + // Expand at the line right right after a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + - three + - four + five + six; seven + eight + " + ), + ); + + // Expand at the line right right after a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.collapse_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + five + six; seven + eight + " + ), + ); + + // Expand at the line right right above a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(1, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + - three + - four + five + six; seven + eight + " + ), + ); + } + + #[gpui::test] + fn test_expand_collapse_insertion_hunk(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = indoc!( + " + one + two + seven + eight + " + ); + let text = indoc!( + " + one + two + three + four + five + six + seven + eight + " + ); + + let (diff_map, mut snapshot, deps) = build_diff_map(text, Some(base_text), cx); + + // Expand at the line right right after a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + + three + + four + + five + + six + seven + eight + " + ), + ); + + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.collapse_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + three + four + five + six + seven + eight + " + ), + ); + } + #[track_caller] fn assert_new_snapshot( snapshot: &mut DiffMapSnapshot, @@ -1983,6 +2153,81 @@ mod tests { } } + struct DiffMapDeps { + buffer: Model, + multibuffer: Model, + change_set: Model, + inlay_map: InlayMap, + multibuffer_snapshot: MultiBufferSnapshot, + inlay_snapshot: InlaySnapshot, + multibuffer_edits: text::Subscription, + } + + fn build_diff_map( + text: &str, + base_text: Option<&str>, + cx: &mut TestAppContext, + ) -> (Model, DiffMapSnapshot, DiffMapDeps) { + let buffer = cx.new_model(|cx| Buffer::local(text, cx)); + + let change_set = cx.new_model(|cx| { + let text_snapshot = buffer.read(cx).text_snapshot(); + let mut change_set = BufferChangeSet::new(&text_snapshot); + if let Some(base_text) = base_text { + let _ = change_set.set_base_text(base_text.to_string(), text_snapshot, cx); + } + change_set + }); + + let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + let (multibuffer_snapshot, multibuffer_edits) = + multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); + + let (inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); + + let (diff_map, diff_map_snapshot) = + cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); + diff_map.update(cx, |diff_map, cx| { + diff_map.add_change_set(change_set.clone(), cx) + }); + cx.run_until_parked(); + + ( + diff_map, + diff_map_snapshot, + DiffMapDeps { + buffer, + multibuffer, + change_set, + inlay_map, + multibuffer_snapshot, + inlay_snapshot, + multibuffer_edits, + }, + ) + } + + impl DiffMapDeps { + fn update_buffer( + &mut self, + cx: &mut TestAppContext, + f: impl FnOnce(&mut Buffer, &mut ModelContext), + ) -> Vec { + self.buffer.update(cx, f); + + self.multibuffer_snapshot = self + .multibuffer + .read_with(cx, |buffer, cx| buffer.snapshot(cx)); + let (inlay_snapshot, edits) = self.inlay_map.sync( + self.multibuffer_snapshot.clone(), + self.multibuffer_edits.consume().into_inner(), + ); + self.inlay_snapshot = inlay_snapshot; + edits + } + } + fn init_test(cx: &mut AppContext) { let settings = SettingsStore::test(cx); cx.set_global(settings); From 4369890646c2353bc4e38017e54742be31ffb354 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 13 Dec 2024 17:17:57 -0800 Subject: [PATCH 31/51] Move logic for defining edits outside of recompute_transforms --- crates/editor/src/display_map/diff_map.rs | 285 ++++++++++------------ 1 file changed, 133 insertions(+), 152 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index ba2ae9e37d30b7..f312d2fad1167b 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -96,20 +96,10 @@ pub struct DiffMapRows<'a> { pub type DiffEdit = text::Edit; -enum DiffMapOperation { - BufferDiffUpdated { - buffer_id: BufferId, - }, - Edited { - inlay_snapshot: InlaySnapshot, - edits: Vec, - }, - ExpandHunks { - ranges: Vec>, - }, - CollapseHunks { - ranges: Vec>, - }, +enum ChangeKind { + DiffUpdated, + InputEdited, + ExpandOrCollapseHunks { range: Range, expand: bool }, } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] @@ -183,13 +173,18 @@ impl DiffMap { edits: Vec, cx: &mut ModelContext, ) -> (DiffMapSnapshot, Vec) { - self.recompute_transforms( - DiffMapOperation::Edited { - inlay_snapshot, - edits, - }, - cx, - ); + let changes = edits + .iter() + .map(|edit| { + let start = inlay_snapshot.to_buffer_offset(edit.new.start); + let end = inlay_snapshot.to_buffer_offset(edit.new.end); + (edit.clone(), start..end, ChangeKind::InputEdited) + }) + .collect::>(); + self.snapshot.inlay_snapshot = inlay_snapshot.clone(); + + self.recompute_transforms(changes, cx); + ( self.snapshot.clone(), mem::take(&mut self.edits_since_sync).into_inner(), @@ -220,7 +215,34 @@ impl DiffMap { } else { self.snapshot.diffs.remove(&buffer_id); } - self.recompute_transforms(DiffMapOperation::BufferDiffUpdated { buffer_id }, cx); + + let multibuffer = self.multibuffer.read(cx); + let multibuffer_snapshot = self.snapshot.buffer(); + let changes = multibuffer + .ranges_for_buffer(buffer_id, cx) + .into_iter() + .map(|(_, range, _)| { + let multibuffer_start = multibuffer_snapshot.point_to_offset(range.start); + let multibuffer_end = multibuffer_snapshot.point_to_offset(range.end); + let inlay_start = self + .snapshot + .inlay_snapshot + .to_inlay_offset(multibuffer_start); + let inlay_end = self + .snapshot + .inlay_snapshot + .to_inlay_offset(multibuffer_end); + ( + InlayEdit { + old: inlay_start..inlay_end, + new: inlay_start..inlay_end, + }, + multibuffer_start..multibuffer_end, + ChangeKind::DiffUpdated, + ) + }) + .collect(); + self.recompute_transforms(changes, cx); } pub(super) fn has_expanded_diff_hunks_in_ranges( @@ -248,7 +270,7 @@ impl DiffMap { ranges: Vec>, cx: &mut ModelContext, ) { - self.recompute_transforms(DiffMapOperation::ExpandHunks { ranges }, cx); + self.expand_or_collapse_diff_hunks(ranges, true, cx); } pub(super) fn collapse_diff_hunks( @@ -256,104 +278,62 @@ impl DiffMap { ranges: Vec>, cx: &mut ModelContext, ) { - self.recompute_transforms(DiffMapOperation::CollapseHunks { ranges }, cx); + self.expand_or_collapse_diff_hunks(ranges, false, cx); } pub(super) fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext) { self.all_hunks_expanded = true; - self.recompute_transforms( - DiffMapOperation::ExpandHunks { - ranges: vec![Anchor::min()..Anchor::max()], - }, - cx, - ); + self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], true, cx); + } + + fn expand_or_collapse_diff_hunks( + &mut self, + ranges: Vec>, + expand: bool, + cx: &mut ModelContext, + ) { + let multibuffer_snapshot = self.snapshot.buffer(); + let mut changes = Vec::new(); + for range in ranges.iter() { + let multibuffer_start = range.start.to_point(multibuffer_snapshot); + let multibuffer_end = range.end.to_point(multibuffer_snapshot); + let multibuffer_start = + multibuffer_snapshot.point_to_offset(Point::new(multibuffer_start.row, 0)); + let multibuffer_end = + multibuffer_snapshot.point_to_offset(Point::new(multibuffer_end.row + 1, 0)); + let expanded_multibuffer_start = multibuffer_start.saturating_sub(1); + let expanded_multibuffer_end = multibuffer_snapshot.len().min(multibuffer_end + 1); + let inlay_start = self + .snapshot + .inlay_snapshot + .to_inlay_offset(expanded_multibuffer_start); + let inlay_end = self + .snapshot + .inlay_snapshot + .to_inlay_offset(expanded_multibuffer_end); + changes.push(( + InlayEdit { + old: inlay_start..inlay_end, + new: inlay_start..inlay_end, + }, + expanded_multibuffer_start..expanded_multibuffer_end, + ChangeKind::ExpandOrCollapseHunks { + range: multibuffer_start..multibuffer_end, + expand, + }, + )); + } + + self.recompute_transforms(changes, cx); } fn recompute_transforms( &mut self, - operation: DiffMapOperation, + changes: Vec<(InlayEdit, Range, ChangeKind)>, cx: &mut ModelContext, ) { let multibuffer = self.multibuffer.read(cx); - let multibuffer_snapshot = multibuffer.snapshot(cx); - - let changes: Vec<(InlayEdit, bool, Range)> = match &operation { - DiffMapOperation::BufferDiffUpdated { buffer_id } => { - let buffer_id = *buffer_id; - let multibuffer = self.multibuffer.read(cx); - multibuffer - .ranges_for_buffer(buffer_id, cx) - .into_iter() - .map(|(_, range, _)| { - let multibuffer_start = multibuffer_snapshot.point_to_offset(range.start); - let multibuffer_end = multibuffer_snapshot.point_to_offset(range.end); - let inlay_start = self - .snapshot - .inlay_snapshot - .to_inlay_offset(multibuffer_start); - let inlay_end = self - .snapshot - .inlay_snapshot - .to_inlay_offset(multibuffer_end); - ( - InlayEdit { - old: inlay_start..inlay_end, - new: inlay_start..inlay_end, - }, - false, - multibuffer_start..multibuffer_end, - ) - }) - .collect() - } - DiffMapOperation::Edited { - inlay_snapshot, - edits, - } => { - let mut changes = Vec::new(); - for edit in edits { - let multibuffer_start = inlay_snapshot.to_buffer_offset(edit.new.start); - let multibuffer_end = inlay_snapshot.to_buffer_offset(edit.new.end); - let multibuffer_range = multibuffer_start..multibuffer_end; - changes.push((edit.clone(), true, multibuffer_range)) - } - self.snapshot.inlay_snapshot = inlay_snapshot.clone(); - changes - } - DiffMapOperation::ExpandHunks { ranges } - | DiffMapOperation::CollapseHunks { ranges } => { - let mut changes = Vec::new(); - for range in ranges.iter() { - let multibuffer_start = range.start.to_point(&multibuffer_snapshot); - let multibuffer_start = multibuffer_snapshot - .point_to_offset(Point::new(multibuffer_start.row, 0)) - .saturating_sub(1); - let multibuffer_end = range.end.to_point(&multibuffer_snapshot); - let multibuffer_end = multibuffer_snapshot.len().min( - multibuffer_snapshot - .point_to_offset(Point::new(multibuffer_end.row + 1, 0)) - + 1, - ); - let inlay_start = self - .snapshot - .inlay_snapshot - .to_inlay_offset(multibuffer_start); - let inlay_end = self - .snapshot - .inlay_snapshot - .to_inlay_offset(multibuffer_end); - changes.push(( - InlayEdit { - old: inlay_start..inlay_end, - new: inlay_start..inlay_end, - }, - false, - multibuffer_start..multibuffer_end, - )); - } - changes - } - }; + let multibuffer_snapshot = self.snapshot.buffer(); let mut cursor = self .snapshot @@ -363,7 +343,7 @@ impl DiffMap { let mut edits = Patch::default(); let mut changes = changes.into_iter().peekable(); - while let Some((mut edit, mut is_inlay_edit, mut multibuffer_range)) = changes.next() { + while let Some((mut edit, mut multibuffer_range, mut operation)) = changes.next() { let to_skip = cursor.slice(&edit.old.start, Bias::Right, &()); self.append_transforms(&mut new_transforms, to_skip); @@ -397,20 +377,6 @@ impl DiffMap { let base_text = &diff_state.base_text; for hunk in diff.hunks_intersecting_range(buffer_anchor_range, buffer) { - let hunk_anchor_range = { - let start = multi_buffer::Anchor { - excerpt_id, - buffer_id: Some(buffer_id), - text_anchor: hunk.buffer_range.start, - }; - let end = multi_buffer::Anchor { - excerpt_id, - buffer_id: Some(buffer_id), - text_anchor: hunk.buffer_range.end, - }; - start..end - }; - let hunk_start_buffer_offset = hunk.buffer_range.start.to_offset(buffer); let hunk_end_buffer_offset = hunk.buffer_range.end.to_offset(buffer); @@ -468,28 +434,16 @@ impl DiffMap { let mut should_expand_hunk = was_previously_expanded || self.all_hunks_expanded; - match &operation { - DiffMapOperation::ExpandHunks { ranges } => { - if hunk_is_deletion { - should_expand_hunk = true; - } else { - should_expand_hunk |= ranges.iter().any(|range| { - range - .overlaps(&hunk_anchor_range, &multibuffer_snapshot) - }) - } - } - DiffMapOperation::CollapseHunks { ranges } => { - if hunk_is_deletion { - should_expand_hunk = false; - } else { - should_expand_hunk &= !ranges.iter().any(|range| { - range - .overlaps(&hunk_anchor_range, &multibuffer_snapshot) - }) - } + if let ChangeKind::ExpandOrCollapseHunks { range, expand } = &operation + { + let intersects = hunk_is_deletion + || (hunk_start_buffer_offset < range.end + && hunk_end_buffer_offset > range.start); + if *expand { + should_expand_hunk |= intersects; + } else { + should_expand_hunk &= !intersects; } - _ => {} }; if should_expand_hunk { @@ -574,7 +528,7 @@ impl DiffMap { let diff_edit_old_end = cursor.start().1 + DiffOffset(old_overshoot); let diff_edit_new_end = DiffOffset(new_transforms.summary().diff_map.len); - if is_inlay_edit { + if let ChangeKind::InputEdited = operation { edits.push(DiffEdit { old: diff_edit_old_start..diff_edit_old_end, new: diff_edit_new_start..diff_edit_new_end, @@ -583,7 +537,7 @@ impl DiffMap { if let Some((next_edit, _, _)) = changes.peek() { if next_edit.old.start < cursor.end(&()).0 { - (edit, is_inlay_edit, multibuffer_range) = changes.next().unwrap(); + (edit, multibuffer_range, operation) = changes.next().unwrap(); continue; } } @@ -1857,7 +1811,7 @@ mod tests { } #[gpui::test] - fn test_expand_collapse_hunks(cx: &mut TestAppContext) { + fn test_expand_collapse_at_positions_adjacent_to_hunks(cx: &mut TestAppContext) { cx.update(init_test); let base_text = indoc!( @@ -1884,7 +1838,7 @@ mod tests { let (diff_map, mut snapshot, deps) = build_diff_map(text, Some(base_text), cx); - // Expand at the line right right after a deleted hunk. + // Expand at the line right below a deleted hunk. diff_map.update(cx, |diff_map, cx| { let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); diff_map.expand_diff_hunks(vec![point..point], cx) @@ -1909,7 +1863,7 @@ mod tests { ), ); - // Expand at the line right right after a deleted hunk. + // Collapse at the line right below a deleted hunk. diff_map.update(cx, |diff_map, cx| { let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); diff_map.collapse_diff_hunks(vec![point..point], cx) @@ -1932,7 +1886,7 @@ mod tests { ), ); - // Expand at the line right right above a deleted hunk. + // Expand at the line right above a deleted hunk. diff_map.update(cx, |diff_map, cx| { let point = deps.multibuffer_snapshot.anchor_before(Point::new(1, 0)); diff_map.expand_diff_hunks(vec![point..point], cx) @@ -1956,6 +1910,33 @@ mod tests { " ), ); + + eprintln!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + + // Expand at the line right below a modified hunk. Should not expand anything. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(4, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + - three + - four + five + six; seven + eight + " + ), + ); } #[gpui::test] From 2266b3cb9630cca23be19067e64468ef9ffbf3e9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 13 Dec 2024 17:26:23 -0800 Subject: [PATCH 32/51] Perform the same range expansion in `has_expanded_diff_hunks_in_ranges` --- crates/editor/src/display_map/diff_map.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index f312d2fad1167b..77586df9120168 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -250,14 +250,24 @@ impl DiffMap { ranges: &[Range], ) -> bool { let mut cursor = self.snapshot.transforms.cursor::(&()); + let multibuffer_snapshot = self.snapshot.buffer(); for range in ranges { - let range = range.to_offset(self.snapshot.buffer()); - let inlay_start = self.snapshot.inlay_snapshot.to_inlay_offset(range.start); - let inlay_end = self.snapshot.inlay_snapshot.to_inlay_offset(range.end); + let range = range.to_point(multibuffer_snapshot); + let start = multibuffer_snapshot.point_to_offset(Point::new(range.start.row, 0)); + let end = multibuffer_snapshot.point_to_offset(Point::new(range.end.row + 1, 0)); + let start = start.saturating_sub(1); + let end = multibuffer_snapshot.len().min(end + 1); + let inlay_start = self.snapshot.inlay_snapshot.to_inlay_offset(start); + let inlay_end = self.snapshot.inlay_snapshot.to_inlay_offset(end); cursor.seek(&inlay_start, Bias::Right, &()); while *cursor.start() < inlay_end { - if let Some(DiffTransform::DeletedHunk { .. }) = cursor.item() { - return true; + match cursor.item() { + Some(DiffTransform::DeletedHunk { .. }) + | Some(DiffTransform::BufferContent { + is_inserted_hunk: true, + .. + }) => return true, + _ => {} } cursor.next(&()); } From 1f28437c821975b864f73ecc31ba5cfdf96fe578 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 13 Dec 2024 17:32:39 -0800 Subject: [PATCH 33/51] Sync display map immediately when updating diff state --- crates/editor/src/display_map.rs | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 2c6b0369fa0df1..038a0cb108ffd3 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -401,18 +401,36 @@ impl DisplayMap { } pub fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext) { - self.diff_map - .update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx)) + self.update_diff_map(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx)) } pub fn expand_diff_hunks(&mut self, ranges: Vec>, cx: &mut ModelContext) { - self.diff_map - .update(cx, |diff_map, cx| diff_map.expand_diff_hunks(ranges, cx)) + self.update_diff_map(cx, |diff_map, cx| diff_map.expand_diff_hunks(ranges, cx)) } pub fn collapse_diff_hunks(&mut self, ranges: Vec>, cx: &mut ModelContext) { - self.diff_map - .update(cx, |diff_map, cx| diff_map.collapse_diff_hunks(ranges, cx)) + self.update_diff_map(cx, |diff_map, cx| diff_map.collapse_diff_hunks(ranges, cx)) + } + + fn update_diff_map( + &mut self, + cx: &mut ModelContext, + f: impl FnOnce(&mut DiffMap, &mut ModelContext), + ) { + let snapshot = self.buffer.read(cx).snapshot(cx); + let edits = self.buffer_subscription.consume().into_inner(); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); + let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| { + f(diff_map, cx); + diff_map.sync(snapshot, edits, cx) + }); + let (snapshot, edits) = self.fold_map.read(snapshot, edits); + let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.write(snapshot, edits); } pub fn insert_blocks( From 093c3d36520853ede4337342eb67632d3b8b024e Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 13 Dec 2024 17:49:15 -0800 Subject: [PATCH 34/51] Add failing unit test for editing inside of an insertion hunk --- crates/editor/src/display_map/diff_map.rs | 92 +++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 77586df9120168..602c539298e25d 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -2028,6 +2028,98 @@ mod tests { ); } + #[gpui::test] + fn test_edit_in_insertion_hunk(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = indoc!( + " + one + two + six + seven + " + ); + let text = indoc!( + " + one + two + three + four + five + six + seven + " + ); + + let (diff_map, mut snapshot, mut deps) = build_diff_map(text, Some(base_text), cx); + + // Expand the hunk + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + + three + + four + + five + six + seven + " + ), + ); + + eprintln!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + + let edits = deps.update_buffer(cx, |buffer, cx| { + buffer.edit_via_marked_text( + indoc!( + " + one + two + three« + !» + four + five + six + seven + " + ), + None, + cx, + ) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.inlay_snapshot.clone(), edits, cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + + three + + ! + + four + + five + six + seven + " + ), + ); + } + #[track_caller] fn assert_new_snapshot( snapshot: &mut DiffMapSnapshot, From 472fee6b62fc80c0ce926f352cb747940c64548a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Sun, 15 Dec 2024 20:30:18 -0800 Subject: [PATCH 35/51] When buffer is edited, compute DiffMap edits using deltas --- crates/editor/src/display_map/diff_map.rs | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 602c539298e25d..ba6723ec79dda8 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -360,11 +360,10 @@ impl DiffMap { let mut delta = 0_isize; let mut end_of_current_insert = InlayOffset(0); loop { - let old_overshoot = (edit.old.start - cursor.start().0).0; - let new_overshoot = edit.new.start.0 - new_transforms.summary().inlay_map.len; - let diff_edit_old_start = cursor.start().1 + DiffOffset(old_overshoot); - let diff_edit_new_start = - DiffOffset(new_transforms.summary().diff_map.len + new_overshoot); + let edit_old_start = + cursor.start().1 + DiffOffset((edit.old.start - cursor.start().0).0); + let mut edit_old_end = + cursor.start().1 + DiffOffset((edit.old.end - cursor.start().0).0); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) @@ -526,6 +525,10 @@ impl DiffMap { }; edits.push(edit); } + + edit_old_end = + cursor.start().1 + DiffOffset((edit.old.end - cursor.start().0).0); + cursor.next(&()); } @@ -534,15 +537,17 @@ impl DiffMap { edit.new.end, end_of_current_insert, ); - let old_overshoot = (edit.old.end - cursor.start().0).0; - let diff_edit_old_end = cursor.start().1 + DiffOffset(old_overshoot); - let diff_edit_new_end = DiffOffset(new_transforms.summary().diff_map.len); if let ChangeKind::InputEdited = operation { - edits.push(DiffEdit { - old: diff_edit_old_start..diff_edit_old_end, - new: diff_edit_new_start..diff_edit_new_end, - }) + let edit_new_start = DiffOffset((edit_old_start.0 as isize + delta) as usize); + delta += (edit.new.end - edit.new.start).0 as isize + - (edit.old.end - edit.old.start).0 as isize; + let edit_new_end = DiffOffset((edit_old_end.0 as isize + delta) as usize); + let edit = DiffEdit { + old: edit_old_start..edit_old_end, + new: edit_new_start..edit_new_end, + }; + edits.push(edit); } if let Some((next_edit, _, _)) = changes.peek() { From f6aa7df14fe140996c46b24d94fbc8bfb7a1be78 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Dec 2024 10:34:45 -0800 Subject: [PATCH 36/51] Make EditorTestContext::assert_state_with_diff use new DiffMap Co-authored-by: Conrad Co-authored-by: Agus --- crates/editor/src/display_map.rs | 15 +- crates/editor/src/display_map/diff_map.rs | 12 +- crates/editor/src/editor_tests.rs | 24 ++- crates/editor/src/hunk_diff.rs | 26 +-- crates/editor/src/test/editor_test_context.rs | 155 +++++------------- 5 files changed, 94 insertions(+), 138 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 038a0cb108ffd3..a363cd71a28579 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -36,7 +36,7 @@ pub use block_map::{ use block_map::{BlockRow, BlockSnapshot}; use collections::{HashMap, HashSet}; pub use crease_map::*; -use diff_map::{DiffMap, DiffMapSnapshot}; +use diff_map::{DiffMap, DiffMapSnapshot, DiffOffset, DiffPoint}; pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint}; use fold_map::{FoldMap, FoldSnapshot}; use git::diff::DiffHunkStatus; @@ -890,6 +890,11 @@ impl DisplaySnapshot { .to_offset(self.display_point_to_inlay_point(point, bias)) } + pub fn display_point_to_diff_offset(&self, point: DisplayPoint, bias: Bias) -> DiffOffset { + self.diff_snapshot() + .to_offset(self.display_point_to_diff_point(point, bias)) + } + pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset { self.inlay_snapshot .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot)) @@ -901,12 +906,16 @@ impl DisplaySnapshot { } fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint { + let diff_point = self.display_point_to_diff_point(point, bias); + self.diff_snapshot().to_inlay_point(diff_point) + } + + fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; - let diff_point = fold_point.to_diff_point(&self.fold_snapshot); - self.diff_snapshot().to_inlay_point(diff_point) + fold_point.to_diff_point(&self.fold_snapshot) } pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint { diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index ba6723ec79dda8..a05f94e80ee285 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -312,7 +312,7 @@ impl DiffMap { let multibuffer_end = multibuffer_snapshot.point_to_offset(Point::new(multibuffer_end.row + 1, 0)); let expanded_multibuffer_start = multibuffer_start.saturating_sub(1); - let expanded_multibuffer_end = multibuffer_snapshot.len().min(multibuffer_end + 1); + let expanded_multibuffer_end = multibuffer_snapshot.len().min(multibuffer_end); let inlay_start = self .snapshot .inlay_snapshot @@ -354,7 +354,13 @@ impl DiffMap { let mut changes = changes.into_iter().peekable(); while let Some((mut edit, mut multibuffer_range, mut operation)) = changes.next() { - let to_skip = cursor.slice(&edit.old.start, Bias::Right, &()); + let mut to_skip = cursor.slice(&edit.old.start, Bias::Left, &()); + while cursor.end(&()).0 < edit.old.start + || (cursor.end(&()).0 == edit.old.start && cursor.start().0 < edit.old.start) + { + to_skip.extend(cursor.item().cloned(), &()); + cursor.next(&()); + } self.append_transforms(&mut new_transforms, to_skip); let mut delta = 0_isize; @@ -780,7 +786,7 @@ impl DiffMapSnapshot { self.diffs.values().any(|diff| !diff.diff.is_empty()) } - #[cfg(test)] + #[cfg(any(test, feature = "test-support"))] pub fn text(&self) -> String { self.chunks(DiffOffset(0)..self.len(), false, Highlights::default()) .map(|c| c.text) diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 52a87920213517..c107d444921846 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -11963,12 +11963,34 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test ); cx.update_editor(|editor, cx| { - for _ in 0..3 { + for _ in 0..2 { editor.go_to_next_hunk(&GoToHunk, cx); editor.toggle_hunk_diff(&ToggleHunkDiff, cx); } }); executor.run_until_parked(); + cx.assert_state_with_diff( + r#" + - use some::mod; + + ˇuse some::modified; + + + fn main() { + - println!("hello"); + + println!("hello there"); + + + println!("around the"); + println!("world"); + } + "# + .unindent(), + ); + + cx.update_editor(|editor, cx| { + editor.go_to_next_hunk(&GoToHunk, cx); + editor.toggle_hunk_diff(&ToggleHunkDiff, cx); + }); + executor.run_until_parked(); cx.assert_state_with_diff( r#" - use some::mod; diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 4d83ecb9ea8b0b..11ec893837d3d8 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -746,23 +746,15 @@ impl Editor { } pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool { - if self.diff_map.expand_all { - return false; - } - self.diff_map.hunk_update_tasks.clear(); - self.clear_row_highlights::(); - let to_remove = self - .diff_map - .hunks - .drain(..) - .flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter()) - .collect::>(); - if to_remove.is_empty() { - false - } else { - self.remove_blocks(to_remove, None, cx); - true - } + self.display_map.update(cx, |diff_map, cx| { + let ranges = vec![Anchor::min()..Anchor::max()]; + if diff_map.has_expanded_diff_hunks_in_ranges(&ranges, cx) { + diff_map.collapse_diff_hunks(ranges, cx); + true + } else { + false + } + }) } pub(super) fn sync_expanded_diff_hunks( diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index aecdfa91c27eb7..f9f371cbd2a10a 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -1,6 +1,6 @@ use crate::{ - display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DiffRowHighlight, DisplayPoint, - Editor, MultiBuffer, RowExt, + display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer, + RowExt, }; use collections::BTreeMap; use futures::Future; @@ -11,11 +11,12 @@ use gpui::{ }; use itertools::Itertools; use language::{Buffer, BufferSnapshot, LanguageRegistry}; -use multi_buffer::{ExcerptRange, ToPoint}; +use multi_buffer::ExcerptRange; use parking_lot::RwLock; use project::{FakeFs, Project}; use std::{ any::TypeId, + cmp, ops::{Deref, DerefMut, Range}, path::Path, sync::{ @@ -23,6 +24,7 @@ use std::{ Arc, }, }; +use text::Bias; use util::{ assert_set_eq, test::{generate_marked_text, marked_text_ranges}, @@ -332,83 +334,48 @@ impl EditorTestContext { /// /// Diff hunks are indicated by lines starting with `+` and `-`. #[track_caller] - pub fn assert_state_with_diff(&mut self, expected_diff: String) { - let has_diff_markers = expected_diff - .lines() - .any(|line| line.starts_with("+") || line.starts_with("-")); - let expected_diff_text = expected_diff - .split('\n') - .map(|line| { - let trimmed = line.trim(); - if trimmed.is_empty() { - String::new() - } else if has_diff_markers { - line.to_string() - } else { - format!(" {line}") - } + pub fn assert_state_with_diff(&mut self, expected_diff_text: String) { + let (snapshot, selections) = self + .editor + .update(&mut self.cx, |editor, cx| editor.selections.all_display(cx)); + + let diff_snapshot = snapshot.diff_snapshot(); + let diff_offsets = selections + .into_iter() + .map(|s| { + let start = snapshot.display_point_to_diff_offset(s.start, Bias::Left).0; + let end = snapshot.display_point_to_diff_offset(s.end, Bias::Left).0; + cmp::min(start, end)..cmp::max(start, end) }) - .join("\n"); + .collect::>(); - let actual_selections = self.editor_selections(); - let actual_marked_text = - generate_marked_text(&self.buffer_text(), &actual_selections, true); + let actual_marked_text = generate_marked_text(&diff_snapshot.text(), &diff_offsets, true); // Read the actual diff from the editor's row highlights and block // decorations. - let actual_diff = self.editor.update(&mut self.cx, |editor, cx| { - let snapshot = editor.snapshot(cx); - let insertions = editor - .highlighted_rows::() - .map(|(range, _)| { - let start = range.start.to_point(&snapshot.buffer_snapshot); - let end = range.end.to_point(&snapshot.buffer_snapshot); - start.row..end.row - }) - .collect::>(); - let deletions = editor - .diff_map - .hunks - .iter() - .filter_map(|hunk| { - if hunk.blocks.is_empty() { - return None; - } - let row = hunk - .hunk_range - .start - .to_point(&snapshot.buffer_snapshot) - .row; - let (_, buffer, _) = editor - .buffer() - .read(cx) - .excerpt_containing(hunk.hunk_range.start, cx) - .expect("no excerpt for expanded buffer's hunk start"); - let buffer_id = buffer.read(cx).remote_id(); - let change_set = &editor - .diff_map - .diff_bases - .get(&buffer_id) - .expect("should have a diff base for expanded hunk") - .change_set; - let deleted_text = change_set - .read(cx) - .base_text - .as_ref() - .expect("no base text for expanded hunk") - .read(cx) - .as_rope() - .slice(hunk.diff_base_byte_range.clone()) - .to_string(); - if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status { - Some((row, deleted_text)) - } else { - None + let line_infos = diff_snapshot.row_infos(0).collect::>(); + let has_diff = line_infos.iter().any(|info| info.diff_status.is_some()); + + let actual_diff = actual_marked_text + .split('\n') + .zip(line_infos) + .map(|(line, info)| { + let marker = match info.diff_status { + Some(DiffHunkStatus::Added) => "+ ", + Some(DiffHunkStatus::Removed) => "- ", + Some(DiffHunkStatus::Modified) => unreachable!(), + None => { + if has_diff && !line.trim().is_empty() { + " " + } else { + "" + } } - }) - .collect::>(); - format_diff(actual_marked_text, deletions, insertions) - }); + }; + format!("{marker}{line}") + }) + .collect::>() + .join("\n"); pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state"); } @@ -503,46 +470,6 @@ impl EditorTestContext { } } -fn format_diff( - text: String, - actual_deletions: Vec<(u32, String)>, - actual_insertions: Vec>, -) -> String { - let mut diff = String::new(); - for (row, line) in text.split('\n').enumerate() { - let row = row as u32; - if row > 0 { - diff.push('\n'); - } - if let Some(text) = actual_deletions - .iter() - .find_map(|(deletion_row, deleted_text)| { - if *deletion_row == row { - Some(deleted_text) - } else { - None - } - }) - { - for line in text.lines() { - diff.push('-'); - if !line.is_empty() { - diff.push(' '); - diff.push_str(line); - } - diff.push('\n'); - } - } - let marker = if actual_insertions.iter().any(|range| range.contains(&row)) { - "+ " - } else { - " " - }; - diff.push_str(format!("{marker}{line}").trim_end()); - } - diff -} - impl Deref for EditorTestContext { type Target = gpui::VisualTestContext; From 241c7a6645be82e9175542f169a55802b8b087fe Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Dec 2024 11:56:25 -0800 Subject: [PATCH 37/51] Extract logic around custom text highlights out of InlayChunks iterator Co-Authored-By: Conrad Co-Authored-By: Agus --- crates/editor/src/display_map.rs | 1 + .../src/display_map/custom_highlights.rs | 174 +++++++++++++++++ crates/editor/src/display_map/inlay_map.rs | 177 ++---------------- 3 files changed, 191 insertions(+), 161 deletions(-) create mode 100644 crates/editor/src/display_map/custom_highlights.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a363cd71a28579..a7f801a1aa4693 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -19,6 +19,7 @@ mod block_map; mod crease_map; +mod custom_highlights; mod diff_map; mod fold_map; mod inlay_map; diff --git a/crates/editor/src/display_map/custom_highlights.rs b/crates/editor/src/display_map/custom_highlights.rs new file mode 100644 index 00000000000000..11356586ebf3c5 --- /dev/null +++ b/crates/editor/src/display_map/custom_highlights.rs @@ -0,0 +1,174 @@ +use collections::BTreeMap; +use gpui::HighlightStyle; +use language::Chunk; +use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferSnapshot, ToOffset as _}; +use std::{ + any::TypeId, + cmp, + iter::{self, Peekable}, + ops::Range, + sync::Arc, + vec, +}; +use sum_tree::TreeMap; + +pub struct CustomHighlightsChunks<'a> { + buffer_chunks: MultiBufferChunks<'a>, + buffer_chunk: Option>, + offset: usize, + multibuffer_snapshot: &'a MultiBufferSnapshot, + + highlight_endpoints: Peekable>, + active_highlights: BTreeMap, + text_highlights: Option<&'a TreeMap>)>>>, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +struct HighlightEndpoint { + offset: usize, + is_start: bool, + tag: TypeId, + style: HighlightStyle, +} + +impl<'a> CustomHighlightsChunks<'a> { + pub fn new( + range: Range, + language_aware: bool, + text_highlights: Option<&'a TreeMap>)>>>, + multibuffer_snapshot: &'a MultiBufferSnapshot, + ) -> Self { + Self { + buffer_chunks: multibuffer_snapshot.chunks(range.clone(), language_aware), + buffer_chunk: None, + offset: range.start, + + text_highlights, + highlight_endpoints: create_highlight_endpoints( + &range, + text_highlights, + multibuffer_snapshot, + ), + active_highlights: Default::default(), + multibuffer_snapshot, + } + } + + pub fn seek(&mut self, new_range: Range) { + self.highlight_endpoints = + create_highlight_endpoints(&new_range, self.text_highlights, self.multibuffer_snapshot); + self.offset = new_range.start; + self.buffer_chunks.seek(new_range); + self.buffer_chunk.take(); + self.active_highlights.clear() + } +} + +fn create_highlight_endpoints( + range: &Range, + text_highlights: Option<&TreeMap>)>>>, + buffer: &MultiBufferSnapshot, +) -> iter::Peekable> { + let mut highlight_endpoints = Vec::new(); + if let Some(text_highlights) = text_highlights { + let start = buffer.anchor_after(range.start); + let end = buffer.anchor_after(range.end); + for (&tag, text_highlights) in text_highlights.iter() { + let style = text_highlights.0; + let ranges = &text_highlights.1; + + let start_ix = match ranges.binary_search_by(|probe| { + let cmp = probe.end.cmp(&start, &buffer); + if cmp.is_gt() { + cmp::Ordering::Greater + } else { + cmp::Ordering::Less + } + }) { + Ok(i) | Err(i) => i, + }; + + for range in &ranges[start_ix..] { + if range.start.cmp(&end, &buffer).is_ge() { + break; + } + + highlight_endpoints.push(HighlightEndpoint { + offset: range.start.to_offset(&buffer), + is_start: true, + tag, + style, + }); + highlight_endpoints.push(HighlightEndpoint { + offset: range.end.to_offset(&buffer), + is_start: false, + tag, + style, + }); + } + } + highlight_endpoints.sort(); + } + highlight_endpoints.into_iter().peekable() +} + +impl<'a> Iterator for CustomHighlightsChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + let mut next_highlight_endpoint = usize::MAX; + while let Some(endpoint) = self.highlight_endpoints.peek().copied() { + if endpoint.offset <= self.offset { + if endpoint.is_start { + self.active_highlights.insert(endpoint.tag, endpoint.style); + } else { + self.active_highlights.remove(&endpoint.tag); + } + self.highlight_endpoints.next(); + } else { + next_highlight_endpoint = endpoint.offset; + break; + } + } + + let chunk = self + .buffer_chunk + .get_or_insert_with(|| self.buffer_chunks.next().unwrap()); + if chunk.text.is_empty() { + *chunk = self.buffer_chunks.next().unwrap(); + } + + let (prefix, suffix) = chunk + .text + .split_at(chunk.text.len().min(next_highlight_endpoint - self.offset)); + + chunk.text = suffix; + self.offset += prefix.len(); + let mut prefix = Chunk { + text: prefix, + ..chunk.clone() + }; + if !self.active_highlights.is_empty() { + let mut highlight_style = HighlightStyle::default(); + for active_highlight in self.active_highlights.values() { + highlight_style.highlight(*active_highlight); + } + prefix.highlight_style = Some(highlight_style); + } + Some(prefix) + } +} + +impl PartialOrd for HighlightEndpoint { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for HighlightEndpoint { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.offset + .cmp(&other.offset) + .then_with(|| other.is_start.cmp(&self.is_start)) + } +} diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 8acec1f7150e07..750782c63102b9 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,22 +1,15 @@ use crate::{HighlightStyles, InlayId}; -use collections::{BTreeMap, BTreeSet}; -use gpui::HighlightStyle; +use collections::BTreeSet; use language::{Chunk, Edit, Point, TextSummary}; -use multi_buffer::{ - Anchor, MultiBufferChunks, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset, -}; +use multi_buffer::{Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset}; use std::{ - any::TypeId, cmp, - iter::Peekable, ops::{Add, AddAssign, Range, Sub, SubAssign}, - sync::Arc, - vec, }; -use sum_tree::{Bias, Cursor, SumTree, TreeMap}; +use sum_tree::{Bias, Cursor, SumTree}; use text::{Patch, Rope}; -use super::Highlights; +use super::{custom_highlights::CustomHighlightsChunks, Highlights}; /// Decides where the [`Inlay`]s should be displayed. /// @@ -207,39 +200,15 @@ pub struct InlayBufferRows<'a> { max_buffer_row: MultiBufferRow, } -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct HighlightEndpoint { - offset: InlayOffset, - is_start: bool, - tag: TypeId, - style: HighlightStyle, -} - -impl PartialOrd for HighlightEndpoint { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for HighlightEndpoint { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.offset - .cmp(&other.offset) - .then_with(|| other.is_start.cmp(&self.is_start)) - } -} - pub struct InlayChunks<'a> { transforms: Cursor<'a, Transform, (InlayOffset, usize)>, - buffer_chunks: MultiBufferChunks<'a>, + buffer_chunks: CustomHighlightsChunks<'a>, buffer_chunk: Option>, inlay_chunks: Option>, inlay_chunk: Option<&'a str>, output_offset: InlayOffset, max_output_offset: InlayOffset, highlight_styles: HighlightStyles, - highlight_endpoints: Peekable>, - active_highlights: BTreeMap, highlights: Highlights<'a>, snapshot: &'a InlaySnapshot, } @@ -255,22 +224,6 @@ impl<'a> InlayChunks<'a> { self.buffer_chunk = None; self.output_offset = new_range.start; self.max_output_offset = new_range.end; - - let mut highlight_endpoints = Vec::new(); - if let Some(text_highlights) = self.highlights.text_highlights { - if !text_highlights.is_empty() { - self.snapshot.apply_text_highlights( - &mut self.transforms, - &new_range, - text_highlights, - &mut highlight_endpoints, - ); - self.transforms.seek(&new_range.start, Bias::Right, &()); - highlight_endpoints.sort(); - } - } - self.highlight_endpoints = highlight_endpoints.into_iter().peekable(); - self.active_highlights.clear(); } } @@ -282,21 +235,6 @@ impl<'a> Iterator for InlayChunks<'a> { return None; } - let mut next_highlight_endpoint = InlayOffset(usize::MAX); - while let Some(endpoint) = self.highlight_endpoints.peek().copied() { - if endpoint.offset <= self.output_offset { - if endpoint.is_start { - self.active_highlights.insert(endpoint.tag, endpoint.style); - } else { - self.active_highlights.remove(&endpoint.tag); - } - self.highlight_endpoints.next(); - } else { - next_highlight_endpoint = endpoint.offset; - break; - } - } - let chunk = match self.transforms.item()? { Transform::Isomorphic(_) => { let chunk = self @@ -310,24 +248,15 @@ impl<'a> Iterator for InlayChunks<'a> { chunk .text .len() - .min(self.transforms.end(&()).0 .0 - self.output_offset.0) - .min(next_highlight_endpoint.0 - self.output_offset.0), + .min(self.transforms.end(&()).0 .0 - self.output_offset.0), ); chunk.text = suffix; self.output_offset.0 += prefix.len(); - let mut prefix = Chunk { + Chunk { text: prefix, ..chunk.clone() - }; - if !self.active_highlights.is_empty() { - let mut highlight_style = HighlightStyle::default(); - for active_highlight in self.active_highlights.values() { - highlight_style.highlight(*active_highlight); - } - prefix.highlight_style = Some(highlight_style); } - prefix } Transform::Inlay(inlay) => { let mut inlay_style_and_highlight = None; @@ -381,13 +310,6 @@ impl<'a> Iterator for InlayChunks<'a> { self.output_offset.0 += chunk.len(); - if !self.active_highlights.is_empty() { - for active_highlight in self.active_highlights.values() { - highlight_style - .get_or_insert(Default::default()) - .highlight(*active_highlight); - } - } Chunk { text: chunk, highlight_style, @@ -1056,21 +978,13 @@ impl InlaySnapshot { let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&()); cursor.seek(&range.start, Bias::Right, &()); - let mut highlight_endpoints = Vec::new(); - if let Some(text_highlights) = highlights.text_highlights { - if !text_highlights.is_empty() { - self.apply_text_highlights( - &mut cursor, - &range, - text_highlights, - &mut highlight_endpoints, - ); - cursor.seek(&range.start, Bias::Right, &()); - } - } - highlight_endpoints.sort(); let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); - let buffer_chunks = self.buffer.chunks(buffer_range, language_aware); + let buffer_chunks = CustomHighlightsChunks::new( + buffer_range, + language_aware, + highlights.text_highlights, + &self.buffer, + ); InlayChunks { transforms: cursor, @@ -1081,71 +995,11 @@ impl InlaySnapshot { output_offset: range.start, max_output_offset: range.end, highlight_styles: highlights.styles, - highlight_endpoints: highlight_endpoints.into_iter().peekable(), - active_highlights: Default::default(), highlights, snapshot: self, } } - fn apply_text_highlights( - &self, - cursor: &mut Cursor<'_, Transform, (InlayOffset, usize)>, - range: &Range, - text_highlights: &TreeMap>)>>, - highlight_endpoints: &mut Vec, - ) { - while cursor.start().0 < range.end { - let transform_start = self - .buffer - .anchor_after(self.to_buffer_offset(cmp::max(range.start, cursor.start().0))); - let transform_end = - { - let overshoot = InlayOffset(range.end.0 - cursor.start().0 .0); - self.buffer.anchor_before(self.to_buffer_offset(cmp::min( - cursor.end(&()).0, - cursor.start().0 + overshoot, - ))) - }; - - for (&tag, text_highlights) in text_highlights.iter() { - let style = text_highlights.0; - let ranges = &text_highlights.1; - - let start_ix = match ranges.binary_search_by(|probe| { - let cmp = probe.end.cmp(&transform_start, &self.buffer); - if cmp.is_gt() { - cmp::Ordering::Greater - } else { - cmp::Ordering::Less - } - }) { - Ok(i) | Err(i) => i, - }; - for range in &ranges[start_ix..] { - if range.start.cmp(&transform_end, &self.buffer).is_ge() { - break; - } - - highlight_endpoints.push(HighlightEndpoint { - offset: self.to_inlay_offset(range.start.to_offset(&self.buffer)), - is_start: true, - tag, - style, - }); - highlight_endpoints.push(HighlightEndpoint { - offset: self.to_inlay_offset(range.end.to_offset(&self.buffer)), - is_start: false, - tag, - style, - }); - } - } - - cursor.next(&()); - } - } - #[cfg(test)] pub fn text(&self) -> String { self.chunks(Default::default()..self.len(), false, Highlights::default()) @@ -1201,11 +1055,12 @@ mod tests { hover_links::InlayHighlight, InlayId, MultiBuffer, }; - use gpui::AppContext; + use gpui::{AppContext, HighlightStyle}; use project::{InlayHint, InlayHintLabel, ResolveState}; use rand::prelude::*; use settings::SettingsStore; - use std::{cmp::Reverse, env, sync::Arc}; + use std::{any::TypeId, cmp::Reverse, env, sync::Arc}; + use sum_tree::TreeMap; use text::Patch; use util::post_inc; From 88b4628c001304d39cdce161339dc9900f237d48 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Dec 2024 14:38:39 -0700 Subject: [PATCH 38/51] rewrite diff map to not depend on inlay map --- crates/editor/src/display_map.rs | 8 +- crates/editor/src/display_map/diff_map.rs | 401 +++++++++------------- crates/editor/src/display_map/fold_map.rs | 26 +- crates/editor/src/display_map/tab_map.rs | 2 +- 4 files changed, 187 insertions(+), 250 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index a7f801a1aa4693..1f304a2f730f37 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -811,7 +811,7 @@ impl DisplaySnapshot { let mut fold_point = self.fold_snapshot.to_fold_point(diff_point, Bias::Left); fold_point.0.column = 0; diff_point = fold_point.to_diff_point(&self.fold_snapshot); - inlay_point = self.diff_snapshot().to_inlay_point(diff_point); + inlay_point = self.diff_snapshot().to_multibuffer_point(diff_point); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Left); @@ -831,7 +831,7 @@ impl DisplaySnapshot { let mut fold_point = self.fold_snapshot.to_fold_point(diff_point, Bias::Right); fold_point.0.column = self.fold_snapshot.line_len(fold_point.row()); diff_point = fold_point.to_diff_point(&self.fold_snapshot); - inlay_point = self.diff_snapshot().to_inlay_point(diff_point); + inlay_point = self.diff_snapshot().to_multibuffer_point(diff_point); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Right); @@ -908,7 +908,7 @@ impl DisplaySnapshot { fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint { let diff_point = self.display_point_to_diff_point(point, bias); - self.diff_snapshot().to_inlay_point(diff_point) + self.diff_snapshot().to_multibuffer_point(diff_point) } fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint { @@ -1508,7 +1508,7 @@ impl DisplayPoint { let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; let diff_point = fold_point.to_diff_point(&map.fold_snapshot); - let inlay_point = map.diff_snapshot().to_inlay_point(diff_point); + let inlay_point = map.diff_snapshot().to_multibuffer_point(diff_point); map.inlay_snapshot .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point)) } diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index a05f94e80ee285..aa6b9124c17680 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,15 +1,15 @@ -use super::inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlaySnapshot}; -use crate::{Highlights, InlayOffset, InlayPoint, RowInfo}; +use super::custom_highlights::CustomHighlightsChunks; +use crate::RowInfo; use collections::HashMap; use git::diff::DiffHunkStatus; -use gpui::{AppContext, Context as _, Model, ModelContext, Subscription}; +use gpui::{AppContext, Context as _, HighlightStyle, Model, ModelContext, Subscription}; use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::{ - Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferSnapshot, - ToOffset, ToPoint, + Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferRows, + MultiBufferSnapshot, ToOffset, ToPoint, }; use project::buffer_store::BufferChangeSet; -use std::{mem, ops::Range}; +use std::{any::TypeId, mem, ops::Range, sync::Arc}; use sum_tree::{Cursor, SumTree, TreeMap}; use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; @@ -37,7 +37,7 @@ pub struct DiffMapSnapshot { diffs: TreeMap, transforms: SumTree, pub(crate) version: usize, - inlay_snapshot: InlaySnapshot, + multibuffer_snapshot: MultiBufferSnapshot, } #[derive(Debug, Clone)] @@ -56,16 +56,16 @@ enum DiffTransform { #[derive(Debug, Clone)] struct DiffTransformSummary { - inlay_map: TextSummary, + multibuffer_map: TextSummary, diff_map: TextSummary, } impl DiffTransformSummary { - pub fn inlay_point(&self) -> InlayPoint { - InlayPoint(self.inlay_map.lines) + pub fn multibuffer_point(&self) -> Point { + self.multibuffer_map.lines } - pub fn inlay_offset(&self) -> InlayOffset { - InlayOffset(self.inlay_map.len) + pub fn multibuffer_offset(&self) -> usize { + self.multibuffer_map.len } pub fn diff_point(&self) -> DiffPoint { DiffPoint(self.diff_map.lines) @@ -78,10 +78,10 @@ impl DiffTransformSummary { pub struct DiffMapChunks<'a> { snapshot: &'a DiffMapSnapshot, language_aware: bool, - cursor: Cursor<'a, DiffTransform, (DiffOffset, InlayOffset)>, - inlay_chunks: InlayChunks<'a>, - inlay_chunk: Option>, - inlay_offset: InlayOffset, + cursor: Cursor<'a, DiffTransform, (DiffOffset, usize)>, + multibuffer_chunks: CustomHighlightsChunks<'a>, + multibuffer_chunk: Option>, + multibuffer_offset: usize, offset: DiffOffset, end_offset: DiffOffset, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, @@ -89,9 +89,9 @@ pub struct DiffMapChunks<'a> { #[derive(Clone)] pub struct DiffMapRows<'a> { - cursor: Cursor<'a, DiffTransform, (DiffPoint, InlayPoint)>, + cursor: Cursor<'a, DiffTransform, (DiffPoint, Point)>, diff_point: DiffPoint, - input_buffer_rows: InlayBufferRows<'a>, + input_buffer_rows: MultiBufferRows<'a>, } pub type DiffEdit = text::Edit; @@ -123,21 +123,21 @@ impl DiffPoint { impl DiffMap { pub fn new( - inlay_snapshot: InlaySnapshot, multibuffer: Model, cx: &mut AppContext, ) -> (Model, DiffMapSnapshot) { + let multibuffer_snapshot = multibuffer.read(cx).snapshot(cx); let snapshot = DiffMapSnapshot { diffs: TreeMap::default(), version: 0, transforms: SumTree::from_item( DiffTransform::BufferContent { - summary: inlay_snapshot.text_summary(), + summary: multibuffer_snapshot.text_summary(), is_inserted_hunk: false, }, &(), ), - inlay_snapshot, + multibuffer_snapshot, }; let this = cx.new_model(|_| Self { @@ -169,19 +169,15 @@ impl DiffMap { pub fn sync( &mut self, - inlay_snapshot: InlaySnapshot, - edits: Vec, + multibuffer_snapshot: MultiBufferSnapshot, + edits: Vec>, cx: &mut ModelContext, ) -> (DiffMapSnapshot, Vec) { let changes = edits .iter() - .map(|edit| { - let start = inlay_snapshot.to_buffer_offset(edit.new.start); - let end = inlay_snapshot.to_buffer_offset(edit.new.end); - (edit.clone(), start..end, ChangeKind::InputEdited) - }) + .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::>(); - self.snapshot.inlay_snapshot = inlay_snapshot.clone(); + self.snapshot.multibuffer_snapshot = multibuffer_snapshot.clone(); self.recompute_transforms(changes, cx); @@ -224,20 +220,11 @@ impl DiffMap { .map(|(_, range, _)| { let multibuffer_start = multibuffer_snapshot.point_to_offset(range.start); let multibuffer_end = multibuffer_snapshot.point_to_offset(range.end); - let inlay_start = self - .snapshot - .inlay_snapshot - .to_inlay_offset(multibuffer_start); - let inlay_end = self - .snapshot - .inlay_snapshot - .to_inlay_offset(multibuffer_end); ( - InlayEdit { - old: inlay_start..inlay_end, - new: inlay_start..inlay_end, + text::Edit { + old: multibuffer_start..multibuffer_end, + new: multibuffer_start..multibuffer_end, }, - multibuffer_start..multibuffer_end, ChangeKind::DiffUpdated, ) }) @@ -249,7 +236,7 @@ impl DiffMap { &self, ranges: &[Range], ) -> bool { - let mut cursor = self.snapshot.transforms.cursor::(&()); + let mut cursor = self.snapshot.transforms.cursor::(&()); let multibuffer_snapshot = self.snapshot.buffer(); for range in ranges { let range = range.to_point(multibuffer_snapshot); @@ -257,10 +244,8 @@ impl DiffMap { let end = multibuffer_snapshot.point_to_offset(Point::new(range.end.row + 1, 0)); let start = start.saturating_sub(1); let end = multibuffer_snapshot.len().min(end + 1); - let inlay_start = self.snapshot.inlay_snapshot.to_inlay_offset(start); - let inlay_end = self.snapshot.inlay_snapshot.to_inlay_offset(end); - cursor.seek(&inlay_start, Bias::Right, &()); - while *cursor.start() < inlay_end { + cursor.seek(&start, Bias::Right, &()); + while *cursor.start() < end { match cursor.item() { Some(DiffTransform::DeletedHunk { .. }) | Some(DiffTransform::BufferContent { @@ -311,22 +296,13 @@ impl DiffMap { multibuffer_snapshot.point_to_offset(Point::new(multibuffer_start.row, 0)); let multibuffer_end = multibuffer_snapshot.point_to_offset(Point::new(multibuffer_end.row + 1, 0)); - let expanded_multibuffer_start = multibuffer_start.saturating_sub(1); - let expanded_multibuffer_end = multibuffer_snapshot.len().min(multibuffer_end); - let inlay_start = self - .snapshot - .inlay_snapshot - .to_inlay_offset(expanded_multibuffer_start); - let inlay_end = self - .snapshot - .inlay_snapshot - .to_inlay_offset(expanded_multibuffer_end); + let expanded_start = multibuffer_start.saturating_sub(1); + let expanded_end = multibuffer_snapshot.len().min(multibuffer_end + 1); changes.push(( - InlayEdit { - old: inlay_start..inlay_end, - new: inlay_start..inlay_end, + text::Edit { + old: expanded_start..expanded_end, + new: expanded_start..expanded_end, }, - expanded_multibuffer_start..expanded_multibuffer_end, ChangeKind::ExpandOrCollapseHunks { range: multibuffer_start..multibuffer_end, expand, @@ -339,37 +315,29 @@ impl DiffMap { fn recompute_transforms( &mut self, - changes: Vec<(InlayEdit, Range, ChangeKind)>, + changes: Vec<(text::Edit, ChangeKind)>, cx: &mut ModelContext, ) { let multibuffer = self.multibuffer.read(cx); let multibuffer_snapshot = self.snapshot.buffer(); - let mut cursor = self - .snapshot - .transforms - .cursor::<(InlayOffset, DiffOffset)>(&()); + let mut cursor = self.snapshot.transforms.cursor::<(usize, DiffOffset)>(&()); let mut new_transforms = SumTree::default(); let mut edits = Patch::default(); let mut changes = changes.into_iter().peekable(); - while let Some((mut edit, mut multibuffer_range, mut operation)) = changes.next() { - let mut to_skip = cursor.slice(&edit.old.start, Bias::Left, &()); - while cursor.end(&()).0 < edit.old.start - || (cursor.end(&()).0 == edit.old.start && cursor.start().0 < edit.old.start) - { - to_skip.extend(cursor.item().cloned(), &()); - cursor.next(&()); - } + while let Some((mut edit, mut operation)) = changes.next() { + let to_skip = cursor.slice(&edit.old.start, Bias::Right, &()); self.append_transforms(&mut new_transforms, to_skip); let mut delta = 0_isize; - let mut end_of_current_insert = InlayOffset(0); + let mut end_of_current_insert = 0; loop { + let multibuffer_range = edit.old.clone(); let edit_old_start = - cursor.start().1 + DiffOffset((edit.old.start - cursor.start().0).0); + cursor.start().1 + DiffOffset(edit.old.start - cursor.start().0); let mut edit_old_end = - cursor.start().1 + DiffOffset((edit.old.end - cursor.start().0).0); + cursor.start().1 + DiffOffset(edit.old.end - cursor.start().0); for (buffer, buffer_range, excerpt_id) in multibuffer.range_to_buffer_ranges(multibuffer_range.clone(), cx) @@ -401,20 +369,16 @@ impl DiffMap { let hunk_start_multibuffer_offset = excerpt_range.start + hunk_start_buffer_offset - excerpt_buffer_range_start_offset; - let hunk_start_inlay_offset = self - .snapshot - .inlay_snapshot - .to_inlay_offset(hunk_start_multibuffer_offset); self.push_buffer_content_transform( &mut new_transforms, - hunk_start_inlay_offset, + hunk_start_multibuffer_offset, end_of_current_insert, ); - while cursor.end(&()).0 < hunk_start_inlay_offset - || (cursor.end(&()).0 == hunk_start_inlay_offset - && cursor.start().0 < hunk_start_inlay_offset) + while cursor.end(&()).0 < hunk_start_multibuffer_offset + || (cursor.end(&()).0 == hunk_start_multibuffer_offset + && cursor.start().0 < hunk_start_multibuffer_offset) { let Some(item) = cursor.item() else { break; @@ -434,7 +398,7 @@ impl DiffMap { } let mut was_previously_expanded = false; - if cursor.start().0 == hunk_start_inlay_offset { + if cursor.start().0 == hunk_start_multibuffer_offset { was_previously_expanded = match cursor.item() { Some(DiffTransform::DeletedHunk { .. }) => true, Some(DiffTransform::BufferContent { @@ -467,7 +431,7 @@ impl DiffMap { { if !was_previously_expanded { let hunk_overshoot = - (hunk_start_inlay_offset - cursor.start().0).0; + hunk_start_multibuffer_offset - cursor.start().0; let old_offset = cursor.start().1 + DiffOffset(hunk_overshoot); let new_start = @@ -502,11 +466,7 @@ impl DiffMap { let hunk_end_multibuffer_offset = excerpt_range.start + hunk_end_buffer_offset - excerpt_buffer_range_start_offset; - let hunk_end_inlay_offset = self - .snapshot - .inlay_snapshot - .to_inlay_offset(hunk_end_multibuffer_offset); - end_of_current_insert = hunk_end_inlay_offset; + end_of_current_insert = hunk_end_multibuffer_offset; } if was_previously_expanded { @@ -532,8 +492,7 @@ impl DiffMap { edits.push(edit); } - edit_old_end = - cursor.start().1 + DiffOffset((edit.old.end - cursor.start().0).0); + edit_old_end = cursor.start().1 + DiffOffset(edit.old.end - cursor.start().0); cursor.next(&()); } @@ -546,8 +505,8 @@ impl DiffMap { if let ChangeKind::InputEdited = operation { let edit_new_start = DiffOffset((edit_old_start.0 as isize + delta) as usize); - delta += (edit.new.end - edit.new.start).0 as isize - - (edit.old.end - edit.old.start).0 as isize; + delta += (edit.new.end - edit.new.start) as isize + - (edit.old.end - edit.old.start) as isize; let edit_new_end = DiffOffset((edit_old_end.0 as isize + delta) as usize); let edit = DiffEdit { old: edit_old_start..edit_old_end, @@ -556,15 +515,15 @@ impl DiffMap { edits.push(edit); } - if let Some((next_edit, _, _)) = changes.peek() { + if let Some((next_edit, _)) = changes.peek() { if next_edit.old.start < cursor.end(&()).0 { - (edit, multibuffer_range, operation) = changes.next().unwrap(); + (edit, operation) = changes.next().unwrap(); continue; } } - let suffix = (cursor.end(&()).0 - edit.old.end).0; - let transform_end = InlayOffset(new_transforms.summary().inlay_map.len + suffix); + let suffix = cursor.end(&()).0 - edit.old.end; + let transform_end = new_transforms.summary().multibuffer_map.len + suffix; self.push_buffer_content_transform( &mut new_transforms, transform_end, @@ -615,21 +574,21 @@ impl DiffMap { fn push_buffer_content_transform( &self, new_transforms: &mut SumTree, - end_offset: InlayOffset, - end_of_current_inserted_hunk: InlayOffset, + end_offset: usize, + end_of_current_inserted_hunk: usize, ) { for (end_offset, region_is_inserted_hunk) in [ (end_offset.min(end_of_current_inserted_hunk), true), (end_offset, false), ] { - let start_offset = InlayOffset(new_transforms.summary().inlay_map.len); + let start_offset = new_transforms.summary().multibuffer_map.len; if end_offset <= start_offset { continue; } let summary_to_add = self .snapshot - .inlay_snapshot - .text_summary_for_range(start_offset..end_offset); + .multibuffer_snapshot + .text_summary_for_range::(start_offset..end_offset); if !self.extend_last_buffer_content_transform( new_transforms, @@ -675,11 +634,12 @@ impl DiffMap { #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; - if snapshot.transforms.summary().inlay_map.len != snapshot.inlay_snapshot.len().0 { + if snapshot.transforms.summary().multibuffer_map.len != snapshot.multibuffer_snapshot.len() + { panic!( "incorrect input length. expected {}, got {}. transforms: {:+?}", - snapshot.inlay_snapshot.len().0, - snapshot.transforms.summary().inlay_map.len, + snapshot.multibuffer_snapshot.len(), + snapshot.transforms.summary().multibuffer_map.len, snapshot.transforms.items(&()), ); } @@ -786,9 +746,9 @@ impl DiffMapSnapshot { self.diffs.values().any(|diff| !diff.diff.is_empty()) } - #[cfg(any(test, feature = "test-support"))] + #[cfg(test)] pub fn text(&self) -> String { - self.chunks(DiffOffset(0)..self.len(), false, Highlights::default()) + self.chunks(DiffOffset(0)..self.len(), false, None) .map(|c| c.text) .collect() } @@ -802,27 +762,27 @@ impl DiffMapSnapshot { } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); + let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); cursor.seek(&range.start, Bias::Right, &()); let Some(first_transform) = cursor.item() else { return TextSummary::default(); }; - let (diff_transform_start, inlay_transform_start) = cursor.start().clone(); + let (diff_transform_start, multibuffer_transform_start) = cursor.start().clone(); let (diff_transform_end, _) = cursor.end(&()); let diff_start = range.start; let diff_end = std::cmp::min(range.end, diff_transform_end); let mut result = match first_transform { DiffTransform::BufferContent { .. } => { - let inlay_start = - inlay_transform_start + InlayOffset((diff_start - diff_transform_start).0); - let inlay_end = - inlay_transform_start + InlayOffset((diff_end - diff_transform_start).0); + let multibuffer_start = + multibuffer_transform_start + (diff_start - diff_transform_start).0; + let multibuffer_end = + multibuffer_transform_start + (diff_end - diff_transform_start).0; - self.inlay_snapshot - .text_summary_for_range(inlay_start..inlay_end) + self.multibuffer_snapshot + .text_summary_for_range(multibuffer_start..multibuffer_end) } DiffTransform::DeletedHunk { buffer_id, @@ -852,15 +812,17 @@ impl DiffMapSnapshot { return result; }; - let (diff_transform_start, inlay_transform_start) = cursor.start().clone(); + let (diff_transform_start, multibuffer_transform_start) = cursor.start().clone(); result += match last_transform { DiffTransform::BufferContent { .. } => { - let inlay_end = - inlay_transform_start + InlayOffset((range.end - diff_transform_start).0); + let multibuffer_end = + multibuffer_transform_start + (range.end - diff_transform_start).0; - self.inlay_snapshot - .text_summary_for_range(inlay_transform_start..inlay_end) + self.multibuffer_snapshot + .text_summary_for_range::( + multibuffer_transform_start..multibuffer_end, + ) } DiffTransform::DeletedHunk { base_text_byte_range, @@ -882,7 +844,7 @@ impl DiffMapSnapshot { } pub fn buffer(&self) -> &MultiBufferSnapshot { - &self.inlay_snapshot.buffer + &self.multibuffer_snapshot } pub fn to_point(&self, offset: DiffOffset) -> DiffPoint { @@ -896,10 +858,12 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { - let inlay_offset = start_transform.inlay_offset() + InlayOffset(overshoot.0); - let inlay_point = self.inlay_snapshot.to_point(inlay_offset); + let multibuffer_offset = start_transform.multibuffer_offset() + overshoot.0; + let multibuffer_point = self + .multibuffer_snapshot + .offset_to_point(multibuffer_offset); start_transform.diff_point() - + DiffPoint((inlay_point - start_transform.inlay_point()).0) + + DiffPoint(multibuffer_point - start_transform.multibuffer_point()) } Some(DiffTransform::DeletedHunk { buffer_id, @@ -931,10 +895,10 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { - let inlay_point = start_transform.inlay_point() + InlayPoint(overshoot.0); - let clipped = self.inlay_snapshot.clip_point(inlay_point, bias); + let inlay_point = start_transform.multibuffer_point() + overshoot.0; + let clipped = self.multibuffer_snapshot.clip_point(inlay_point, bias); start_transform.diff_point() - + DiffPoint((clipped - start_transform.inlay_point()).0) + + DiffPoint(clipped - start_transform.multibuffer_point()) } Some(DiffTransform::DeletedHunk { buffer_id, @@ -963,10 +927,11 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { - let inlay_point = start_transform.inlay_point() + InlayPoint(overshoot.0); - let inlay_offset = self.inlay_snapshot.to_offset(inlay_point); + let multibuffer_point = start_transform.multibuffer_point() + overshoot.0; + let multibuffer_offset = + self.multibuffer_snapshot.point_to_offset(multibuffer_point); start_transform.diff_offset() - + DiffOffset((inlay_offset - start_transform.inlay_offset()).0) + + DiffOffset(multibuffer_offset - start_transform.multibuffer_offset()) } Some(DiffTransform::DeletedHunk { buffer_id, @@ -988,101 +953,86 @@ impl DiffMapSnapshot { } } - pub fn to_inlay_offset(&self, offset: DiffOffset) -> InlayOffset { - let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); + pub fn to_multibuffer_offset(&self, offset: DiffOffset) -> usize { + let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); cursor.seek(&offset, Bias::Right, &()); - let mut inlay_offset = cursor.start().1; + let mut multibuffer_offset = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = offset.0 - cursor.start().0 .0; - inlay_offset.0 += overshoot; + multibuffer_offset += overshoot; } - inlay_offset + multibuffer_offset } - pub fn to_inlay_point(&self, point: DiffPoint) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&()); + pub fn to_multibuffer_point(&self, point: DiffPoint) -> Point { + let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); cursor.seek(&point, Bias::Right, &()); let mut inlay_point = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = point.0 - cursor.start().0 .0; - inlay_point.0 += overshoot; + inlay_point += overshoot; } inlay_point } - pub fn to_diff_offset(&self, offset: InlayOffset) -> DiffOffset { - let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&()); - cursor.seek(&offset, Bias::Right, &()); + pub fn to_diff_offset(&self, multibuffer_offset: usize) -> DiffOffset { + let mut cursor = self.transforms.cursor::<(usize, DiffOffset)>(&()); + cursor.seek(&multibuffer_offset, Bias::Right, &()); let mut diff_offset = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { - let overshoot = offset.0 - cursor.start().0 .0; + let overshoot = multibuffer_offset - cursor.start().0; diff_offset.0 += overshoot; } diff_offset } - pub fn to_diff_point(&self, point: InlayPoint) -> DiffPoint { - let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&()); - cursor.seek(&point, Bias::Right, &()); + pub fn to_diff_point(&self, multibuffer_point: Point) -> DiffPoint { + let mut cursor = self.transforms.cursor::<(Point, DiffPoint)>(&()); + cursor.seek(&multibuffer_point, Bias::Right, &()); let mut diff_point = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { - let overshoot = point.0 - cursor.start().0 .0; + let overshoot = multibuffer_point - cursor.start().0; diff_point.0 += overshoot; } diff_point } - pub fn make_diff_offset(&self, buffer_offset: usize) -> DiffOffset { - self.to_diff_offset(self.inlay_snapshot.to_inlay_offset(buffer_offset)) - } - - pub fn make_diff_point(&self, buffer_point: Point) -> DiffPoint { - self.to_diff_point(self.inlay_snapshot.to_inlay_point(buffer_point)) - } - - pub fn to_buffer_offset(&self, diff_offset: DiffOffset) -> usize { - self.inlay_snapshot - .to_buffer_offset(self.to_inlay_offset(diff_offset)) - } - - pub fn to_buffer_point(&self, diff_point: DiffPoint) -> Point { - self.inlay_snapshot - .to_buffer_point(self.to_inlay_point(diff_point)) - } - pub(crate) fn chunks<'a>( &'a self, range: Range, language_aware: bool, - highlights: Highlights<'a>, + text_highlights: Option<&'a TreeMap>)>>>, ) -> DiffMapChunks<'a> { - let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); + let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); cursor.seek(&range.end, Bias::Right, &()); - let mut inlay_end = cursor.start().1; + let mut multibuffer_end = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = range.end.0 - cursor.start().0 .0; - inlay_end.0 += overshoot; + multibuffer_end += overshoot; } cursor.seek(&range.start, Bias::Right, &()); - let mut inlay_start = cursor.start().1; + let mut multibuffer_start = cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { let overshoot = range.start.0 - cursor.start().0 .0; - inlay_start.0 += overshoot; + multibuffer_start += overshoot; } - let inlay_chunks = - self.inlay_snapshot - .chunks(inlay_start..inlay_end, language_aware, highlights); + let multibuffer_chunks = CustomHighlightsChunks::new( + multibuffer_start..multibuffer_end, + language_aware, + text_highlights, + &self.multibuffer_snapshot, + ); DiffMapChunks { snapshot: self, language_aware, cursor, - inlay_chunk: None, - inlay_chunks, - inlay_offset: inlay_start, + multibuffer_chunk: None, + multibuffer_chunks, + multibuffer_offset: multibuffer_start, offset: range.start, diff_base_chunks: None, end_offset: range.end, @@ -1095,7 +1045,7 @@ impl DiffMapSnapshot { } let diff_point = DiffPoint(Point::new(start_row, 0)); - let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&()); + let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); cursor.seek(&diff_point, Bias::Right, &()); let (diff_transform_start, inlay_transform_start) = cursor.start().clone(); @@ -1106,8 +1056,8 @@ impl DiffMapSnapshot { 0 }; let input_buffer_rows = self - .inlay_snapshot - .buffer_rows(inlay_transform_start.row() + overshoot); + .multibuffer_snapshot + .buffer_rows(MultiBufferRow(inlay_transform_start.row + overshoot)); DiffMapRows { diff_point, @@ -1123,19 +1073,19 @@ impl<'a> DiffMapChunks<'a> { let mut inlay_end = self.cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { let overshoot = range.end.0 - self.cursor.start().0 .0; - inlay_end.0 += overshoot; + inlay_end += overshoot; } self.cursor.seek(&range.start, Bias::Right, &()); let mut inlay_start = self.cursor.start().1; if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { let overshoot = range.start.0 - self.cursor.start().0 .0; - inlay_start.0 += overshoot; + inlay_start += overshoot; } - self.inlay_chunks.seek(inlay_start..inlay_end); - self.inlay_chunk.take(); - self.inlay_offset = inlay_start; + self.multibuffer_chunks.seek(inlay_start..inlay_end); + self.multibuffer_chunk.take(); + self.multibuffer_offset = inlay_start; self.offset = range.start; self.end_offset = range.end; } @@ -1161,8 +1111,8 @@ impl<'a> Iterator for DiffMapChunks<'a> { match transform { DiffTransform::BufferContent { summary, .. } => { let chunk = self - .inlay_chunk - .get_or_insert_with(|| self.inlay_chunks.next().unwrap()); + .multibuffer_chunk + .get_or_insert_with(|| self.multibuffer_chunks.next().unwrap()); let chunk_end = self.offset + DiffOffset(chunk.text.len()); let mut transform_end = self.cursor.start().0 + DiffOffset(summary.len); @@ -1181,7 +1131,7 @@ impl<'a> Iterator for DiffMapChunks<'a> { }) } else { self.offset = chunk_end; - self.inlay_chunk.take() + self.multibuffer_chunk.take() } } DiffTransform::DeletedHunk { @@ -1237,7 +1187,7 @@ impl<'a> DiffMapRows<'a> { 0 }; self.input_buffer_rows - .seek(inlay_transform_start.row() + overshoot); + .seek(MultiBufferRow(inlay_transform_start.row + overshoot)); } } @@ -1282,11 +1232,11 @@ impl sum_tree::Item for DiffTransform { fn summary(&self, _: &::Context) -> Self::Summary { match self { DiffTransform::BufferContent { summary, .. } => DiffTransformSummary { - inlay_map: summary.clone(), + multibuffer_map: summary.clone(), diff_map: summary.clone(), }, DiffTransform::DeletedHunk { summary, .. } => DiffTransformSummary { - inlay_map: TextSummary::default(), + multibuffer_map: TextSummary::default(), diff_map: summary.clone(), }, } @@ -1298,24 +1248,24 @@ impl sum_tree::Summary for DiffTransformSummary { fn zero(_: &Self::Context) -> Self { DiffTransformSummary { - inlay_map: TextSummary::default(), + multibuffer_map: TextSummary::default(), diff_map: TextSummary::default(), } } fn add_summary(&mut self, summary: &Self, _: &Self::Context) { - self.inlay_map += &summary.inlay_map; + self.multibuffer_map += &summary.multibuffer_map; self.diff_map += &summary.diff_map; } } -impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayOffset { +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for usize { fn zero(_: &()) -> Self { - InlayOffset(0) + 0 } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.inlay_map.len + *self += summary.multibuffer_map.len } } @@ -1329,13 +1279,13 @@ impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { } } -impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for InlayPoint { +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for Point { fn zero(_: &()) -> Self { - InlayPoint(Point::zero()) + Point::zero() } fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { - self.0 += summary.inlay_map.lines + *self += summary.multibuffer_map.lines } } @@ -1432,7 +1382,6 @@ impl std::ops::Sub for DiffPoint { #[cfg(test)] mod tests { use super::*; - use crate::display_map::inlay_map::InlayMap; use gpui::{AppContext, TestAppContext}; use indoc::indoc; use language::Buffer; @@ -1483,7 +1432,7 @@ mod tests { diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1548,7 +1497,7 @@ mod tests { diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1570,7 +1519,7 @@ mod tests { diff_map.expand_diff_hunks(vec![position..position], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1593,7 +1542,7 @@ mod tests { diff_map.expand_diff_hunks(vec![position..position], cx) }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1631,7 +1580,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), edits, cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), edits, cx) }); assert_new_snapshot( &mut snapshot, @@ -1657,7 +1606,7 @@ mod tests { }); cx.run_until_parked(); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), Vec::new(), cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), Vec::new(), cx) }); assert_new_snapshot( &mut snapshot, @@ -1690,7 +1639,7 @@ mod tests { buffer.edit([(4..5, "a"), (9..11, "k")], None, cx); }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot, edits, cx) + diff_map.sync(deps.multibuffer_snapshot, edits, cx) }); assert_new_snapshot(&mut snapshot, sync, indoc!("hella work")); @@ -1721,7 +1670,7 @@ mod tests { cx.run_until_parked(); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot, vec![], cx) + diff_map.sync(deps.multibuffer_snapshot, vec![], cx) }); assert_new_snapshot( @@ -1866,7 +1815,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1891,7 +1840,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1914,7 +1863,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1941,7 +1890,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -1994,7 +1943,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -2019,7 +1968,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -2071,7 +2020,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), vec![], cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) }); assert_new_snapshot( &mut snapshot, @@ -2111,7 +2060,7 @@ mod tests { }); let sync = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(deps.inlay_snapshot.clone(), edits, cx) + diff_map.sync(deps.multibuffer_snapshot.clone(), edits, cx) }); assert_new_snapshot( &mut snapshot, @@ -2251,9 +2200,7 @@ mod tests { buffer: Model, multibuffer: Model, change_set: Model, - inlay_map: InlayMap, multibuffer_snapshot: MultiBufferSnapshot, - inlay_snapshot: InlaySnapshot, multibuffer_edits: text::Subscription, } @@ -2278,10 +2225,7 @@ mod tests { let (multibuffer_snapshot, multibuffer_edits) = multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); - let (inlay_map, inlay_snapshot) = InlayMap::new(multibuffer_snapshot.clone()); - - let (diff_map, diff_map_snapshot) = - cx.update(|cx| DiffMap::new(inlay_snapshot.clone(), multibuffer.clone(), cx)); + let (diff_map, diff_map_snapshot) = cx.update(|cx| DiffMap::new(multibuffer.clone(), cx)); diff_map.update(cx, |diff_map, cx| { diff_map.add_change_set(change_set.clone(), cx) }); @@ -2294,9 +2238,7 @@ mod tests { buffer, multibuffer, change_set, - inlay_map, multibuffer_snapshot, - inlay_snapshot, multibuffer_edits, }, ) @@ -2307,18 +2249,13 @@ mod tests { &mut self, cx: &mut TestAppContext, f: impl FnOnce(&mut Buffer, &mut ModelContext), - ) -> Vec { + ) -> Vec> { self.buffer.update(cx, f); self.multibuffer_snapshot = self .multibuffer .read_with(cx, |buffer, cx| buffer.snapshot(cx)); - let (inlay_snapshot, edits) = self.inlay_map.sync( - self.multibuffer_snapshot.clone(), - self.multibuffer_edits.consume().into_inner(), - ); - self.inlay_snapshot = inlay_snapshot; - edits + self.multibuffer_edits.consume().into_inner() } } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 425fb3c39298f0..39684b4d4c6e6a 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -163,7 +163,7 @@ impl<'a> FoldMapWriter<'a> { }); let diff_range = - snapshot.make_diff_offset(range.start)..snapshot.make_diff_offset(range.end); + snapshot.to_diff_offset(range.start)..snapshot.to_diff_offset(range.end); edits.push(DiffEdit { old: diff_range.clone(), new: diff_range, @@ -231,8 +231,8 @@ impl<'a> FoldMapWriter<'a> { fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer); if should_unfold(fold) { if offset_range.end > offset_range.start { - let diff_range = snapshot.make_diff_offset(offset_range.start) - ..snapshot.make_diff_offset(offset_range.end); + let diff_range = snapshot.to_diff_offset(offset_range.start) + ..snapshot.to_diff_offset(offset_range.end); edits.push(DiffEdit { old: diff_range.clone(), new: diff_range, @@ -412,7 +412,7 @@ impl FoldMap { let anchor = diff_map_snapshot .buffer() - .anchor_before(diff_map_snapshot.to_buffer_offset(edit.new.start)); + .anchor_before(diff_map_snapshot.to_multibuffer_offset(edit.new.start)); let mut folds_cursor = self .snapshot .folds @@ -432,8 +432,8 @@ impl FoldMap { let buffer_end = fold.range.end.to_offset(&diff_map_snapshot.buffer()); ( fold.clone(), - diff_map_snapshot.make_diff_offset(buffer_start) - ..diff_map_snapshot.make_diff_offset(buffer_end), + diff_map_snapshot.to_diff_offset(buffer_start) + ..diff_map_snapshot.to_diff_offset(buffer_end), ) }); folds_cursor.next(&diff_map_snapshot.buffer()); @@ -690,11 +690,11 @@ impl FoldSnapshot { } pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { - self.to_fold_point(self.diff_map_snapshot.make_diff_point(point), bias) + self.to_fold_point(self.diff_map_snapshot.to_diff_point(point), bias) } pub fn make_fold_offset(&self, buffer_offset: usize, bias: Bias) -> FoldOffset { - self.to_fold_offset(self.diff_map_snapshot.make_diff_offset(buffer_offset), bias) + self.to_fold_offset(self.diff_map_snapshot.to_diff_offset(buffer_offset), bias) } pub fn to_fold_point(&self, point: DiffPoint, bias: Bias) -> FoldPoint { @@ -795,7 +795,7 @@ impl FoldSnapshot { T: ToOffset, { let buffer_offset = offset.to_offset(self.buffer()); - let diff_offset = self.diff_map_snapshot.make_diff_offset(buffer_offset); + let diff_offset = self.diff_map_snapshot.to_diff_offset(buffer_offset); let mut cursor = self.transforms.cursor::(&()); cursor.seek(&diff_offset, Bias::Right, &()); cursor.item().map_or(false, |t| t.placeholder.is_some()) @@ -804,13 +804,13 @@ impl FoldSnapshot { pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool { let mut diff_point = self .diff_map_snapshot - .make_diff_point(Point::new(buffer_row.0, 0)); + .to_diff_point(Point::new(buffer_row.0, 0)); let mut cursor = self.transforms.cursor::(&()); cursor.seek(&diff_point, Bias::Right, &()); loop { match cursor.item() { Some(transform) => { - let buffer_point = self.diff_map_snapshot.to_buffer_point(diff_point); + let buffer_point = self.diff_map_snapshot.to_multibuffer_point(diff_point); if buffer_point.row != buffer_row.0 { return false; } else if transform.placeholder.is_some() { @@ -1761,8 +1761,8 @@ mod tests { let mut expected_text: String = diff_snapshot.text().to_string(); for fold_range in map.merged_folds().into_iter().rev() { - let fold_inlay_start = diff_snapshot.make_diff_offset(fold_range.start); - let fold_inlay_end = diff_snapshot.make_diff_offset(fold_range.end); + let fold_inlay_start = diff_snapshot.to_diff_offset(fold_range.start); + let fold_inlay_end = diff_snapshot.to_diff_offset(fold_range.end); expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯"); } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 98f30c2645f43c..7b1793980f13a9 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -326,7 +326,7 @@ impl TabSnapshot { let inlay_point = fold_point.to_diff_point(&self.fold_snapshot); self.fold_snapshot .diff_map_snapshot - .to_buffer_point(inlay_point) + .to_multibuffer_point(inlay_point) } fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { From 2625719ca2250a6bddbeb1e8a7f80ef0d7a702bf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Dec 2024 14:58:43 -0800 Subject: [PATCH 39/51] Put DiffMap before InlayMap in DisplayMap Co-authored-by: Conrad --- crates/editor/src/display_map.rs | 88 ++- crates/editor/src/display_map/block_map.rs | 95 ++-- crates/editor/src/display_map/diff_map.rs | 85 +-- crates/editor/src/display_map/fold_map.rs | 614 ++++++++++----------- crates/editor/src/display_map/inlay_map.rs | 530 ++++++++++-------- crates/editor/src/display_map/tab_map.rs | 45 +- crates/editor/src/display_map/wrap_map.rs | 23 +- 7 files changed, 741 insertions(+), 739 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 1f304a2f730f37..0927bbedb04f0a 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -146,8 +146,8 @@ impl DisplayMap { let tab_size = Self::tab_size(&buffer, cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); let crease_map = CreaseMap::new(&buffer_snapshot); - let (inlay_map, snapshot) = InlayMap::new(buffer_snapshot); - let (diff_map, snapshot) = DiffMap::new(snapshot, buffer.clone(), cx); + let (diff_map, snapshot) = DiffMap::new(buffer.clone(), cx); + let (inlay_map, snapshot) = InlayMap::new(snapshot); let (fold_map, snapshot) = FoldMap::new(snapshot); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); let (wrap_map, snapshot) = WrapMap::new(snapshot, font, font_size, wrap_width, cx); @@ -182,11 +182,11 @@ impl DisplayMap { pub fn snapshot(&mut self, cx: &mut ModelContext) -> DisplaySnapshot { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); - let (inlay_snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); let (diff_snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), edits, cx) + diff_map.sync(buffer_snapshot.clone(), edits, cx) }); - let (fold_snapshot, edits) = self.fold_map.read(diff_snapshot.clone(), edits); + let (inlay_snapshot, edits) = self.inlay_map.sync(diff_snapshot, edits); + let (fold_snapshot, edits) = self.fold_map.read(inlay_snapshot.clone(), edits); let tab_size = Self::tab_size(&self.buffer, cx); let (tab_snapshot, edits) = self.tab_map.sync(fold_snapshot.clone(), edits, tab_size); let (wrap_snapshot, edits) = self @@ -234,10 +234,10 @@ impl DisplayMap { let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot.clone(), edits); - let (snapshot, edits) = self - .diff_map - .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| { + diff_map.sync(buffer_snapshot.clone(), edits, cx) + }); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -310,10 +310,10 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self .diff_map .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -342,10 +342,10 @@ impl DisplayMap { .collect::>(); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self .diff_map .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (mut fold_map, snapshot, edits) = self.fold_map.write(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -420,11 +420,11 @@ impl DisplayMap { ) { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| { f(diff_map, cx); diff_map.sync(snapshot, edits, cx) }); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); @@ -442,10 +442,10 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self .diff_map .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -463,10 +463,10 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self .diff_map .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -484,10 +484,10 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self .diff_map .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -505,10 +505,10 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); - let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self .diff_map .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -583,10 +583,10 @@ impl DisplayMap { } let buffer_snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); - let (snapshot, edits) = self.inlay_map.sync(buffer_snapshot, edits); - let (snapshot, edits) = self - .diff_map - .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); + let (snapshot, edits) = self.diff_map.update(cx, |diff_map, cx| { + diff_map.sync(buffer_snapshot.clone(), edits, cx) + }); + let (snapshot, edits) = self.inlay_map.sync(snapshot.clone(), edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let tab_size = Self::tab_size(&self.buffer, cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); @@ -596,9 +596,6 @@ impl DisplayMap { self.block_map.read(snapshot, edits); let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert); - let (snapshot, edits) = self - .diff_map - .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); let (snapshot, edits) = self @@ -806,12 +803,10 @@ impl DisplaySnapshot { pub fn prev_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) { loop { - let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); - let mut diff_point = self.diff_snapshot().to_diff_point(inlay_point); - let mut fold_point = self.fold_snapshot.to_fold_point(diff_point, Bias::Left); + let mut inlay_point = self.inlay_snapshot.make_inlay_point(point); + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Left); fold_point.0.column = 0; - diff_point = fold_point.to_diff_point(&self.fold_snapshot); - inlay_point = self.diff_snapshot().to_multibuffer_point(diff_point); + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Left); @@ -826,12 +821,10 @@ impl DisplaySnapshot { pub fn next_line_boundary(&self, mut point: MultiBufferPoint) -> (Point, DisplayPoint) { loop { - let mut inlay_point = self.inlay_snapshot.to_inlay_point(point); - let mut diff_point = self.diff_snapshot().to_diff_point(inlay_point); - let mut fold_point = self.fold_snapshot.to_fold_point(diff_point, Bias::Right); + let mut inlay_point = self.inlay_snapshot.make_inlay_point(point); + let mut fold_point = self.fold_snapshot.to_fold_point(inlay_point, Bias::Right); fold_point.0.column = self.fold_snapshot.line_len(fold_point.row()); - diff_point = fold_point.to_diff_point(&self.fold_snapshot); - inlay_point = self.diff_snapshot().to_multibuffer_point(diff_point); + inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); point = self.inlay_snapshot.to_buffer_point(inlay_point); let mut display_point = self.point_to_display_point(point, Bias::Right); @@ -872,9 +865,8 @@ impl DisplaySnapshot { } pub fn point_to_display_point(&self, point: MultiBufferPoint, bias: Bias) -> DisplayPoint { - let inlay_point = self.inlay_snapshot.to_inlay_point(point); - let diff_point = self.diff_snapshot().to_diff_point(inlay_point); - let fold_point = self.fold_snapshot.to_fold_point(diff_point, bias); + let inlay_point = self.inlay_snapshot.make_inlay_point(point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); let tab_point = self.tab_snapshot.to_tab_point(fold_point); let wrap_point = self.wrap_snapshot.tab_point_to_wrap_point(tab_point); let block_point = self.block_snapshot.to_block_point(wrap_point); @@ -893,12 +885,13 @@ impl DisplaySnapshot { pub fn display_point_to_diff_offset(&self, point: DisplayPoint, bias: Bias) -> DiffOffset { self.diff_snapshot() - .to_offset(self.display_point_to_diff_point(point, bias)) + .point_to_offset(self.display_point_to_diff_point(point, bias)) } pub fn anchor_to_inlay_offset(&self, anchor: Anchor) -> InlayOffset { - self.inlay_snapshot - .to_inlay_offset(anchor.to_offset(&self.buffer_snapshot)) + let multibuffer_offset = anchor.to_offset(&self.buffer_snapshot); + let diff_offset = self.diff_snapshot().to_diff_offset(multibuffer_offset); + self.inlay_snapshot.to_inlay_offset(diff_offset) } pub fn display_point_to_anchor(&self, point: DisplayPoint, bias: Bias) -> Anchor { @@ -907,16 +900,16 @@ impl DisplaySnapshot { } fn display_point_to_inlay_point(&self, point: DisplayPoint, bias: Bias) -> InlayPoint { - let diff_point = self.display_point_to_diff_point(point, bias); - self.diff_snapshot().to_multibuffer_point(diff_point) - } - - fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint { let block_point = point.0; let wrap_point = self.block_snapshot.to_wrap_point(block_point, bias); let tab_point = self.wrap_snapshot.to_tab_point(wrap_point); let fold_point = self.tab_snapshot.to_fold_point(tab_point, bias).0; - fold_point.to_diff_point(&self.fold_snapshot) + fold_point.to_inlay_point(&self.fold_snapshot) + } + + fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint { + self.inlay_snapshot + .to_diff_point(self.display_point_to_inlay_point(point, bias)) } pub fn display_point_to_fold_point(&self, point: DisplayPoint, bias: Bias) -> FoldPoint { @@ -1401,7 +1394,7 @@ impl DisplaySnapshot { } pub(crate) fn diff_snapshot(&self) -> &DiffMapSnapshot { - &self.fold_snapshot.diff_map_snapshot + &self.inlay_snapshot.diff_map_snapshot } } @@ -1507,8 +1500,7 @@ impl DisplayPoint { let wrap_point = map.block_snapshot.to_wrap_point(self.0, bias); let tab_point = map.wrap_snapshot.to_tab_point(wrap_point); let fold_point = map.tab_snapshot.to_fold_point(tab_point, bias).0; - let diff_point = fold_point.to_diff_point(&map.fold_snapshot); - let inlay_point = map.diff_snapshot().to_multibuffer_point(diff_point); + let inlay_point = fold_point.to_inlay_point(&map.fold_snapshot); map.inlay_snapshot .to_buffer_offset(map.inlay_snapshot.to_offset(inlay_point)) } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 866a6e66af579a..dd11f590a8ff9a 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1837,10 +1837,9 @@ mod tests { let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx)); let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (diff_map, diff_snapshot) = - cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); - let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); + let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (wrap_map, wraps_snapshot) = cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx)); @@ -1988,12 +1987,11 @@ mod tests { buffer.snapshot(cx) }); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx) }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, 4.try_into().unwrap()); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -2054,10 +2052,9 @@ mod tests { .width; } - let multi_buffer_snapshot = multi_buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(multi_buffer_snapshot.clone()); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, multi_buffer.clone(), cx); - let (_, fold_snapshot) = FoldMap::new(diff_snapshot); + let (_, diff_snapshot) = DiffMap::new(multi_buffer.clone(), cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = WrapMap::new(tab_snapshot, font, font_size, Some(wrap_width), cx); @@ -2094,10 +2091,9 @@ mod tests { let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx)); let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); let _subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (_inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_diff_map, diff_snapshot) = - cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); - let (_fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); + let (_diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); + let (_inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (_fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 1.try_into().unwrap()); let (_wrap_map, wraps_snapshot) = cx.update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), None, cx)); @@ -2198,9 +2194,9 @@ mod tests { let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx)); let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); - let (_, fold_snapshot) = FoldMap::new(diff_snapshot); + let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wraps_snapshot) = cx.update(|cx| { WrapMap::new(tab_snapshot, font("Helvetica"), px(14.0), Some(px(60.)), cx) @@ -2243,10 +2239,9 @@ mod tests { let buffer = cx.update(|cx| MultiBuffer::build_simple(text, cx)); let buffer_subscription = buffer.update(cx, |buffer, _cx| buffer.subscribe()); let buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (diff_map, diff_snapshot) = - cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); - let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); + let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); let tab_size = 1.try_into().unwrap(); let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, tab_size); let (wrap_map, wraps_snapshot) = @@ -2272,14 +2267,15 @@ mod tests { buffer.edit([(Point::new(2, 0)..Point::new(3, 0), "")], None, cx); buffer.snapshot(cx) }); - let (inlay_snapshot, inlay_edits) = inlay_map.sync( - buffer_snapshot.clone(), - buffer_subscription.consume().into_inner(), - ); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync( + buffer_snapshot, + buffer_subscription.consume().into_inner(), + cx, + ) }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot.clone(), diff_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) @@ -2298,14 +2294,15 @@ mod tests { ); buffer.snapshot(cx) }); - let (inlay_snapshot, inlay_edits) = inlay_map.sync( - buffer_snapshot.clone(), - buffer_subscription.consume().into_inner(), - ); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync( + buffer_snapshot.clone(), + buffer_subscription.consume().into_inner(), + cx, + ) }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) @@ -2403,10 +2400,9 @@ mod tests { }; let mut buffer_snapshot = cx.update(|cx| buffer.read(cx).snapshot(cx)); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (diff_map, diff_snapshot) = - cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); - let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot); + let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); let (mut tab_map, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (wrap_map, wraps_snapshot) = cx .update(|cx| WrapMap::new(tab_snapshot, font("Helvetica"), font_size, wrap_width, cx)); @@ -2468,12 +2464,11 @@ mod tests { }) .collect::>(); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot.clone(), vec![]); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot.clone(), vec![], cx) }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -2496,12 +2491,11 @@ mod tests { .map(|block| block.id) .collect::>(); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot.clone(), vec![]); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot.clone(), vec![], cx) }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { @@ -2522,12 +2516,11 @@ mod tests { } } - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot.clone(), buffer_edits); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx) }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tab_snapshot, tab_edits, cx) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index aa6b9124c17680..7a6c9ff74f5a4d 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -37,7 +37,7 @@ pub struct DiffMapSnapshot { diffs: TreeMap, transforms: SumTree, pub(crate) version: usize, - multibuffer_snapshot: MultiBufferSnapshot, + pub(crate) buffer: MultiBufferSnapshot, } #[derive(Debug, Clone)] @@ -137,7 +137,7 @@ impl DiffMap { }, &(), ), - multibuffer_snapshot, + buffer: multibuffer_snapshot, }; let this = cx.new_model(|_| Self { @@ -170,14 +170,40 @@ impl DiffMap { pub fn sync( &mut self, multibuffer_snapshot: MultiBufferSnapshot, - edits: Vec>, + mut buffer_edits: Vec>, cx: &mut ModelContext, ) -> (DiffMapSnapshot, Vec) { - let changes = edits + let changes = buffer_edits .iter() .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::>(); - self.snapshot.multibuffer_snapshot = multibuffer_snapshot.clone(); + + let snapshot = &mut self.snapshot; + if buffer_edits.is_empty() + && snapshot.buffer.trailing_excerpt_update_count() + != multibuffer_snapshot.trailing_excerpt_update_count() + { + buffer_edits.push(Edit { + old: snapshot.buffer.len()..snapshot.buffer.len(), + new: multibuffer_snapshot.len()..multibuffer_snapshot.len(), + }); + } + + if buffer_edits.is_empty() { + if snapshot.buffer.edit_count() != multibuffer_snapshot.edit_count() + || snapshot.buffer.non_text_state_update_count() + != multibuffer_snapshot.non_text_state_update_count() + || snapshot.buffer.trailing_excerpt_update_count() + != multibuffer_snapshot.trailing_excerpt_update_count() + { + snapshot.version += 1; + } + + snapshot.buffer = multibuffer_snapshot; + return (snapshot.clone(), Vec::new()); + } + + snapshot.buffer = multibuffer_snapshot.clone(); self.recompute_transforms(changes, cx); @@ -587,7 +613,7 @@ impl DiffMap { } let summary_to_add = self .snapshot - .multibuffer_snapshot + .buffer .text_summary_for_range::(start_offset..end_offset); if !self.extend_last_buffer_content_transform( @@ -634,11 +660,10 @@ impl DiffMap { #[cfg(test)] fn check_invariants(&self) { let snapshot = &self.snapshot; - if snapshot.transforms.summary().multibuffer_map.len != snapshot.multibuffer_snapshot.len() - { + if snapshot.transforms.summary().multibuffer_map.len != snapshot.buffer.len() { panic!( "incorrect input length. expected {}, got {}. transforms: {:+?}", - snapshot.multibuffer_snapshot.len(), + snapshot.buffer.len(), snapshot.transforms.summary().multibuffer_map.len, snapshot.transforms.items(&()), ); @@ -746,7 +771,7 @@ impl DiffMapSnapshot { self.diffs.values().any(|diff| !diff.diff.is_empty()) } - #[cfg(test)] + #[cfg(any(test, feature = "test-support"))] pub fn text(&self) -> String { self.chunks(DiffOffset(0)..self.len(), false, None) .map(|c| c.text) @@ -757,6 +782,10 @@ impl DiffMapSnapshot { DiffOffset(self.transforms.summary().diff_map.len) } + pub fn max_point(&self) -> DiffPoint { + DiffPoint(self.transforms.summary().diff_map.lines) + } + pub fn text_summary(&self) -> TextSummary { self.transforms.summary().diff_map.clone() } @@ -781,7 +810,7 @@ impl DiffMapSnapshot { let multibuffer_end = multibuffer_transform_start + (diff_end - diff_transform_start).0; - self.multibuffer_snapshot + self.buffer .text_summary_for_range(multibuffer_start..multibuffer_end) } DiffTransform::DeletedHunk { @@ -819,10 +848,9 @@ impl DiffMapSnapshot { let multibuffer_end = multibuffer_transform_start + (range.end - diff_transform_start).0; - self.multibuffer_snapshot - .text_summary_for_range::( - multibuffer_transform_start..multibuffer_end, - ) + self.buffer.text_summary_for_range::( + multibuffer_transform_start..multibuffer_end, + ) } DiffTransform::DeletedHunk { base_text_byte_range, @@ -844,10 +872,10 @@ impl DiffMapSnapshot { } pub fn buffer(&self) -> &MultiBufferSnapshot { - &self.multibuffer_snapshot + &self.buffer } - pub fn to_point(&self, offset: DiffOffset) -> DiffPoint { + pub fn offset_to_point(&self, offset: DiffOffset) -> DiffPoint { let mut cursor = self.transforms.cursor::(&()); cursor.seek(&offset, Bias::Right, &()); let start_transform = cursor.start(); @@ -859,9 +887,7 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { let multibuffer_offset = start_transform.multibuffer_offset() + overshoot.0; - let multibuffer_point = self - .multibuffer_snapshot - .offset_to_point(multibuffer_offset); + let multibuffer_point = self.buffer.offset_to_point(multibuffer_offset); start_transform.diff_point() + DiffPoint(multibuffer_point - start_transform.multibuffer_point()) } @@ -896,7 +922,7 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { let inlay_point = start_transform.multibuffer_point() + overshoot.0; - let clipped = self.multibuffer_snapshot.clip_point(inlay_point, bias); + let clipped = self.buffer.clip_point(inlay_point, bias); start_transform.diff_point() + DiffPoint(clipped - start_transform.multibuffer_point()) } @@ -916,7 +942,7 @@ impl DiffMapSnapshot { } } - pub fn to_offset(&self, point: DiffPoint) -> DiffOffset { + pub fn point_to_offset(&self, point: DiffPoint) -> DiffOffset { let mut cursor = self.transforms.cursor::(&()); cursor.seek(&point, Bias::Right, &()); let start_transform = cursor.start(); @@ -928,8 +954,7 @@ impl DiffMapSnapshot { match cursor.item() { Some(DiffTransform::BufferContent { .. }) => { let multibuffer_point = start_transform.multibuffer_point() + overshoot.0; - let multibuffer_offset = - self.multibuffer_snapshot.point_to_offset(multibuffer_point); + let multibuffer_offset = self.buffer.point_to_offset(multibuffer_point); start_transform.diff_offset() + DiffOffset(multibuffer_offset - start_transform.multibuffer_offset()) } @@ -1023,7 +1048,7 @@ impl DiffMapSnapshot { multibuffer_start..multibuffer_end, language_aware, text_highlights, - &self.multibuffer_snapshot, + &self.buffer, ); DiffMapChunks { @@ -1056,7 +1081,7 @@ impl DiffMapSnapshot { 0 }; let input_buffer_rows = self - .multibuffer_snapshot + .buffer .buffer_rows(MultiBufferRow(inlay_transform_start.row + overshoot)); DiffMapRows { @@ -1089,10 +1114,6 @@ impl<'a> DiffMapChunks<'a> { self.offset = range.start; self.end_offset = range.end; } - - pub fn offset(&self) -> DiffOffset { - self.offset - } } impl<'a> Iterator for DiffMapChunks<'a> { @@ -1487,9 +1508,9 @@ mod tests { ), (DiffPoint::new(8, 0), DiffOffset(snapshot.text().len())), ] { - let actual = snapshot.to_offset(*point); + let actual = snapshot.point_to_offset(*point); assert_eq!(actual, *offset, "for {:?}", point); - let actual = snapshot.to_point(*offset); + let actual = snapshot.offset_to_point(*offset); assert_eq!(actual, *point, "for {:?}", offset); } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 39684b4d4c6e6a..0ac726a5554e7f 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,7 +1,7 @@ use crate::RowInfo; use super::{ - diff_map::{DiffEdit, DiffMapChunks, DiffMapRows, DiffMapSnapshot, DiffOffset, DiffPoint}, + inlay_map::{InlayBufferRows, InlayChunks, InlayEdit, InlayOffset, InlayPoint, InlaySnapshot}, Highlights, }; use gpui::{AnyElement, ElementId, WindowContext}; @@ -94,11 +94,11 @@ impl FoldPoint { &mut self.0.column } - pub fn to_diff_point(self, snapshot: &FoldSnapshot) -> DiffPoint { - let mut cursor = snapshot.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); + pub fn to_inlay_point(self, snapshot: &FoldSnapshot) -> InlayPoint { + let mut cursor = snapshot.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); cursor.seek(&self, Bias::Right, &()); let overshoot = self.0 - cursor.start().0 .0; - DiffPoint(cursor.start().1 .0 + overshoot) + InlayPoint(cursor.start().1 .0 + overshoot) } pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset { @@ -112,8 +112,8 @@ impl FoldPoint { let transform = cursor.item().expect("display point out of range"); assert!(transform.placeholder.is_none()); let end_inlay_offset = snapshot - .diff_map_snapshot - .to_offset(DiffPoint(cursor.start().1.input.lines + overshoot)); + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1.input.lines + overshoot)); offset += end_inlay_offset.0 - cursor.start().1.input.len; } FoldOffset(offset) @@ -139,9 +139,9 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut folds = Vec::new(); - let snapshot = self.0.snapshot.diff_map_snapshot.clone(); - let buffer = snapshot.buffer(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); for (range, fold_text) in ranges.into_iter() { + let buffer = snapshot.buffer(); let range = range.start.to_offset(buffer)..range.end.to_offset(buffer); // Ignore any empty ranges. @@ -162,14 +162,15 @@ impl<'a> FoldMapWriter<'a> { placeholder: fold_text, }); - let diff_range = - snapshot.to_diff_offset(range.start)..snapshot.to_diff_offset(range.end); - edits.push(DiffEdit { - old: diff_range.clone(), - new: diff_range, + let inlay_range = + snapshot.make_inlay_offset(range.start)..snapshot.make_inlay_offset(range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, }); } + let buffer = snapshot.buffer(); folds.sort_unstable_by(|a, b| sum_tree::SeekTarget::cmp(&a.range, &b.range, buffer)); self.0.snapshot.folds = { @@ -220,7 +221,7 @@ impl<'a> FoldMapWriter<'a> { ) -> (FoldSnapshot, Vec) { let mut edits = Vec::new(); let mut fold_ixs_to_delete = Vec::new(); - let snapshot = self.0.snapshot.diff_map_snapshot.clone(); + let snapshot = self.0.snapshot.inlay_snapshot.clone(); let buffer = snapshot.buffer(); for range in ranges.into_iter() { let range = range.start.to_offset(buffer)..range.end.to_offset(buffer); @@ -231,11 +232,11 @@ impl<'a> FoldMapWriter<'a> { fold.range.start.to_offset(buffer)..fold.range.end.to_offset(buffer); if should_unfold(fold) { if offset_range.end > offset_range.start { - let diff_range = snapshot.to_diff_offset(offset_range.start) - ..snapshot.to_diff_offset(offset_range.end); - edits.push(DiffEdit { - old: diff_range.clone(), - new: diff_range, + let inlay_range = snapshot.make_inlay_offset(offset_range.start) + ..snapshot.make_inlay_offset(offset_range.end); + edits.push(InlayEdit { + old: inlay_range.clone(), + new: inlay_range, }); } fold_ixs_to_delete.push(*folds_cursor.start()); @@ -273,21 +274,21 @@ pub(crate) struct FoldMap { } impl FoldMap { - pub(crate) fn new(diff_map_snapshot: DiffMapSnapshot) -> (Self, FoldSnapshot) { + pub(crate) fn new(inlay_snapshot: InlaySnapshot) -> (Self, FoldSnapshot) { let this = Self { snapshot: FoldSnapshot { - folds: SumTree::new(diff_map_snapshot.buffer()), + folds: SumTree::new(inlay_snapshot.buffer()), transforms: SumTree::from_item( Transform { summary: TransformSummary { - input: diff_map_snapshot.text_summary(), - output: diff_map_snapshot.text_summary(), + input: inlay_snapshot.text_summary(), + output: inlay_snapshot.text_summary(), }, placeholder: None, }, &(), ), - diff_map_snapshot: diff_map_snapshot.clone(), + inlay_snapshot: inlay_snapshot.clone(), version: 0, }, next_fold_id: FoldId::default(), @@ -298,20 +299,20 @@ impl FoldMap { pub fn read( &mut self, - diff_map_snapshot: DiffMapSnapshot, - edits: Vec, + inlay_snapshot: InlaySnapshot, + edits: Vec, ) -> (FoldSnapshot, Vec) { - let edits = self.sync(diff_map_snapshot, edits); + let edits = self.sync(inlay_snapshot, edits); self.check_invariants(); (self.snapshot.clone(), edits) } pub fn write( &mut self, - diff_map_snapshot: DiffMapSnapshot, - edits: Vec, + inlay_snapshot: InlaySnapshot, + edits: Vec, ) -> (FoldMapWriter, FoldSnapshot, Vec) { - let (snapshot, edits) = self.read(diff_map_snapshot, edits); + let (snapshot, edits) = self.read(inlay_snapshot, edits); (FoldMapWriter(self), snapshot, edits) } @@ -319,7 +320,7 @@ impl FoldMap { if cfg!(test) { assert_eq!( self.snapshot.transforms.summary().input.len, - self.snapshot.diff_map_snapshot.len().0, + self.snapshot.inlay_snapshot.len().0, "transform tree does not match inlay snapshot's length" ); @@ -337,9 +338,7 @@ impl FoldMap { let mut folds = self.snapshot.folds.iter().peekable(); while let Some(fold) = folds.next() { if let Some(next_fold) = folds.peek() { - let comparison = fold - .range - .cmp(&next_fold.range, &self.snapshot.diff_map_snapshot.buffer()); + let comparison = fold.range.cmp(&next_fold.range, self.snapshot.buffer()); assert!(comparison.is_le()); } } @@ -348,21 +347,21 @@ impl FoldMap { fn sync( &mut self, - diff_map_snapshot: DiffMapSnapshot, - diff_edits: Vec, + inlay_snapshot: InlaySnapshot, + inlay_edits: Vec, ) -> Vec { - if diff_edits.is_empty() { - if self.snapshot.diff_map_snapshot.version != diff_map_snapshot.version { + if inlay_edits.is_empty() { + if self.snapshot.inlay_snapshot.version != inlay_snapshot.version { self.snapshot.version += 1; } - self.snapshot.diff_map_snapshot = diff_map_snapshot; + self.snapshot.inlay_snapshot = inlay_snapshot; Vec::new() } else { - let mut inlay_edits_iter = diff_edits.iter().cloned().peekable(); + let mut inlay_edits_iter = inlay_edits.iter().cloned().peekable(); let mut new_transforms = SumTree::::default(); - let mut cursor = self.snapshot.transforms.cursor::(&()); - cursor.seek(&DiffOffset(0), Bias::Right, &()); + let mut cursor = self.snapshot.transforms.cursor::(&()); + cursor.seek(&InlayOffset(0), Bias::Right, &()); while let Some(mut edit) = inlay_edits_iter.next() { if let Some(item) = cursor.item() { @@ -408,35 +407,34 @@ impl FoldMap { } edit.new.end = - DiffOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); + InlayOffset(((edit.new.start + edit.old_len()).0 as isize + delta) as usize); - let anchor = diff_map_snapshot + let anchor = inlay_snapshot .buffer() - .anchor_before(diff_map_snapshot.to_multibuffer_offset(edit.new.start)); + .anchor_before(inlay_snapshot.to_buffer_offset(edit.new.start)); let mut folds_cursor = self .snapshot .folds - .cursor::(&diff_map_snapshot.buffer()); + .cursor::(&inlay_snapshot.buffer()); folds_cursor.seek( &FoldRange(anchor..Anchor::max()), Bias::Left, - &diff_map_snapshot.buffer(), + &inlay_snapshot.buffer(), ); let mut folds = iter::from_fn({ - let diff_map_snapshot = &diff_map_snapshot; + let inlay_snapshot = &inlay_snapshot; move || { let item = folds_cursor.item().map(|fold| { - let buffer_start = - fold.range.start.to_offset(&diff_map_snapshot.buffer()); - let buffer_end = fold.range.end.to_offset(&diff_map_snapshot.buffer()); + let buffer_start = fold.range.start.to_offset(&inlay_snapshot.buffer()); + let buffer_end = fold.range.end.to_offset(&inlay_snapshot.buffer()); ( fold.clone(), - diff_map_snapshot.to_diff_offset(buffer_start) - ..diff_map_snapshot.to_diff_offset(buffer_end), + inlay_snapshot.make_inlay_offset(buffer_start) + ..inlay_snapshot.make_inlay_offset(buffer_end), ) }); - folds_cursor.next(&diff_map_snapshot.buffer()); + folds_cursor.next(&inlay_snapshot.buffer()); item } }) @@ -464,8 +462,8 @@ impl FoldMap { } if fold_range.start.0 > sum.input.len { - let text_summary = diff_map_snapshot - .text_summary_for_range(DiffOffset(sum.input.len)..fold_range.start); + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..fold_range.start); push_isomorphic(&mut new_transforms, text_summary); } @@ -477,7 +475,7 @@ impl FoldMap { Transform { summary: TransformSummary { output: TextSummary::from(ELLIPSIS), - input: diff_map_snapshot + input: inlay_snapshot .text_summary_for_range(fold_range.start..fold_range.end), }, placeholder: Some(TransformPlaceholder { @@ -501,29 +499,29 @@ impl FoldMap { let sum = new_transforms.summary(); if sum.input.len < edit.new.end.0 { - let text_summary = diff_map_snapshot - .text_summary_for_range(DiffOffset(sum.input.len)..edit.new.end); + let text_summary = inlay_snapshot + .text_summary_for_range(InlayOffset(sum.input.len)..edit.new.end); push_isomorphic(&mut new_transforms, text_summary); } } new_transforms.append(cursor.suffix(&()), &()); if new_transforms.is_empty() { - let text_summary = diff_map_snapshot.text_summary(); + let text_summary = inlay_snapshot.text_summary(); push_isomorphic(&mut new_transforms, text_summary); } drop(cursor); - let mut fold_edits = Vec::with_capacity(diff_edits.len()); + let mut fold_edits = Vec::with_capacity(inlay_edits.len()); { let mut old_transforms = self .snapshot .transforms - .cursor::<(DiffOffset, FoldOffset)>(&()); - let mut new_transforms = new_transforms.cursor::<(DiffOffset, FoldOffset)>(&()); + .cursor::<(InlayOffset, FoldOffset)>(&()); + let mut new_transforms = new_transforms.cursor::<(InlayOffset, FoldOffset)>(&()); - for mut edit in diff_edits { + for mut edit in inlay_edits { old_transforms.seek(&edit.old.start, Bias::Left, &()); if old_transforms.item().map_or(false, |t| t.is_fold()) { edit.old.start = old_transforms.start().0; @@ -564,7 +562,7 @@ impl FoldMap { } self.snapshot.transforms = new_transforms; - self.snapshot.diff_map_snapshot = diff_map_snapshot; + self.snapshot.inlay_snapshot = inlay_snapshot; self.snapshot.version += 1; fold_edits } @@ -575,11 +573,15 @@ impl FoldMap { pub struct FoldSnapshot { transforms: SumTree, folds: SumTree, - pub diff_map_snapshot: DiffMapSnapshot, + pub inlay_snapshot: InlaySnapshot, pub version: usize, } impl FoldSnapshot { + pub fn buffer(&self) -> &MultiBufferSnapshot { + self.inlay_snapshot.buffer() + } + #[cfg(test)] pub fn text(&self) -> String { self.chunks(FoldOffset(0)..self.len(), false, Highlights::default()) @@ -587,19 +589,15 @@ impl FoldSnapshot { .collect() } - pub fn buffer(&self) -> &MultiBufferSnapshot { - self.diff_map_snapshot.buffer() - } - #[cfg(test)] pub fn fold_count(&self) -> usize { - self.folds.items(self.diff_map_snapshot.buffer()).len() + self.folds.items(self.inlay_snapshot.buffer()).len() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); cursor.seek(&range.start, Bias::Right, &()); if let Some(transform) = cursor.item() { let start_in_transform = range.start.0 - cursor.start().0 .0; @@ -611,13 +609,13 @@ impl FoldSnapshot { ); } else { let inlay_start = self - .diff_map_snapshot - .to_offset(DiffPoint(cursor.start().1 .0 + start_in_transform)); + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + start_in_transform)); let inlay_end = self - .diff_map_snapshot - .to_offset(DiffPoint(cursor.start().1 .0 + end_in_transform)); + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); summary = self - .diff_map_snapshot + .inlay_snapshot .text_summary_for_range(inlay_start..inlay_end); } } @@ -633,54 +631,12 @@ impl FoldSnapshot { summary += TextSummary::from(&placeholder.text[..end_in_transform.column as usize]); } else { - let inlay_start = self.diff_map_snapshot.to_offset(cursor.start().1); + let inlay_start = self.inlay_snapshot.to_offset(cursor.start().1); let inlay_end = self - .diff_map_snapshot - .to_offset(DiffPoint(cursor.start().1 .0 + end_in_transform)); - summary += self - .diff_map_snapshot - .text_summary_for_range(inlay_start..inlay_end); - } - } - } - - summary - } - - pub fn text_summary_for_offset_range(&self, range: Range) -> TextSummary { - let mut summary = TextSummary::default(); - - let mut cursor = self.transforms.cursor::<(FoldOffset, DiffOffset)>(&()); - cursor.seek(&range.start, Bias::Right, &()); - if let Some(transform) = cursor.item() { - let start_in_transform = range.start.0 - cursor.start().0 .0; - let end_in_transform = cmp::min(range.end, cursor.end(&()).0).0 - cursor.start().0 .0; - if let Some(placeholder) = transform.placeholder.as_ref() { - summary = - TextSummary::from(&placeholder.text[start_in_transform..end_in_transform]); - } else { - let inlay_start = DiffOffset(cursor.start().1 .0 + start_in_transform); - let inlay_end = DiffOffset(cursor.start().1 .0 + end_in_transform); - summary = self - .diff_map_snapshot - .text_summary_for_range(inlay_start..inlay_end); - } - } - - if range.end > cursor.end(&()).0 { - cursor.next(&()); - summary += &cursor - .summary::<_, TransformSummary>(&range.end, Bias::Right, &()) - .output; - if let Some(transform) = cursor.item() { - let end_in_transform = range.end.0 - cursor.start().0 .0; - if let Some(placeholder) = transform.placeholder.as_ref() { - summary += TextSummary::from(&placeholder.text[..end_in_transform]); - } else { - let inlay_start = cursor.start().1; - let inlay_end = DiffOffset(cursor.start().1 .0 + end_in_transform); + .inlay_snapshot + .to_offset(InlayPoint(cursor.start().1 .0 + end_in_transform)); summary += self - .diff_map_snapshot + .inlay_snapshot .text_summary_for_range(inlay_start..inlay_end); } } @@ -690,43 +646,43 @@ impl FoldSnapshot { } pub fn make_fold_point(&self, point: Point, bias: Bias) -> FoldPoint { - self.to_fold_point(self.diff_map_snapshot.to_diff_point(point), bias) + self.to_fold_point(self.inlay_snapshot.make_inlay_point(point), bias) } pub fn make_fold_offset(&self, buffer_offset: usize, bias: Bias) -> FoldOffset { - self.to_fold_offset(self.diff_map_snapshot.to_diff_offset(buffer_offset), bias) + self.to_fold_offset(self.inlay_snapshot.make_inlay_offset(buffer_offset), bias) } - pub fn to_fold_point(&self, point: DiffPoint, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(DiffPoint, FoldPoint)>(&()); - cursor.seek(&point, Bias::Right, &()); + pub fn to_fold_offset(&self, inlay_offset: InlayOffset, bias: Bias) -> FoldOffset { + let mut cursor = self.transforms.cursor::<(InlayOffset, FoldOffset)>(&()); + cursor.seek(&inlay_offset, Bias::Right, &()); if cursor.item().map_or(false, |t| t.is_fold()) { - if bias == Bias::Left || point == cursor.start().0 { + if bias == Bias::Left || inlay_offset == cursor.start().0 { cursor.start().1 } else { cursor.end(&()).1 } } else { - let overshoot = point.0 - cursor.start().0 .0; - FoldPoint(cmp::min( + let overshoot = inlay_offset.0 - cursor.start().0 .0; + FoldOffset(cmp::min( cursor.start().1 .0 + overshoot, cursor.end(&()).1 .0, )) } } - pub fn to_fold_offset(&self, inlay_offset: DiffOffset, bias: Bias) -> FoldOffset { - let mut cursor = self.transforms.cursor::<(DiffOffset, FoldOffset)>(&()); - cursor.seek(&inlay_offset, Bias::Right, &()); + pub fn to_fold_point(&self, point: InlayPoint, bias: Bias) -> FoldPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, FoldPoint)>(&()); + cursor.seek(&point, Bias::Right, &()); if cursor.item().map_or(false, |t| t.is_fold()) { - if bias == Bias::Left || inlay_offset == cursor.start().0 { + if bias == Bias::Left || point == cursor.start().0 { cursor.start().1 } else { cursor.end(&()).1 } } else { - let overshoot = inlay_offset.0 - cursor.start().0 .0; - FoldOffset(cmp::min( + let overshoot = point.0 - cursor.start().0 .0; + FoldPoint(cmp::min( cursor.start().1 .0 + overshoot, cursor.end(&()).1 .0, )) @@ -753,12 +709,12 @@ impl FoldSnapshot { } let fold_point = FoldPoint::new(start_row, 0); - let mut cursor = self.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); cursor.seek(&fold_point, Bias::Left, &()); let overshoot = fold_point.0 - cursor.start().0 .0; - let diff_point = DiffPoint(cursor.start().1 .0 + overshoot); - let input_rows = self.diff_map_snapshot.row_infos(diff_point.0.row); + let inlay_point = InlayPoint(cursor.start().1 .0 + overshoot); + let input_rows = self.inlay_snapshot.row_infos(inlay_point.row()); FoldRows { fold_point, @@ -780,9 +736,9 @@ impl FoldSnapshot { where T: ToOffset, { - let buffer = self.buffer(); + let buffer = self.inlay_snapshot.buffer(); let range = range.start.to_offset(buffer)..range.end.to_offset(buffer); - let mut folds = intersecting_folds(&self.diff_map_snapshot, &self.folds, range, false); + let mut folds = intersecting_folds(&self.inlay_snapshot, &self.folds, range, false); iter::from_fn(move || { let item = folds.item(); folds.next(buffer); @@ -794,23 +750,23 @@ impl FoldSnapshot { where T: ToOffset, { - let buffer_offset = offset.to_offset(self.buffer()); - let diff_offset = self.diff_map_snapshot.to_diff_offset(buffer_offset); - let mut cursor = self.transforms.cursor::(&()); - cursor.seek(&diff_offset, Bias::Right, &()); + let buffer_offset = offset.to_offset(self.inlay_snapshot.buffer()); + let inlay_offset = self.inlay_snapshot.make_inlay_offset(buffer_offset); + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&inlay_offset, Bias::Right, &()); cursor.item().map_or(false, |t| t.placeholder.is_some()) } pub fn is_line_folded(&self, buffer_row: MultiBufferRow) -> bool { - let mut diff_point = self - .diff_map_snapshot - .to_diff_point(Point::new(buffer_row.0, 0)); - let mut cursor = self.transforms.cursor::(&()); - cursor.seek(&diff_point, Bias::Right, &()); + let mut inlay_point = self + .inlay_snapshot + .make_inlay_point(Point::new(buffer_row.0, 0)); + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&inlay_point, Bias::Right, &()); loop { match cursor.item() { Some(transform) => { - let buffer_point = self.diff_map_snapshot.to_multibuffer_point(diff_point); + let buffer_point = self.inlay_snapshot.to_buffer_point(inlay_point); if buffer_point.row != buffer_row.0 { return false; } else if transform.placeholder.is_some() { @@ -820,11 +776,11 @@ impl FoldSnapshot { None => return false, } - if cursor.end(&()).0.row == diff_point.0.row { + if cursor.end(&()).row() == inlay_point.row() { cursor.next(&()); } else { - diff_point.0 += Point::new(1, 0); - cursor.seek(&diff_point, Bias::Right, &()); + inlay_point.0 += Point::new(1, 0); + cursor.seek(&inlay_point, Bias::Right, &()); } } } @@ -835,12 +791,12 @@ impl FoldSnapshot { language_aware: bool, highlights: Highlights<'a>, ) -> FoldChunks<'a> { - let mut transform_cursor = self.transforms.cursor::<(FoldOffset, DiffOffset)>(&()); + let mut transform_cursor = self.transforms.cursor::<(FoldOffset, InlayOffset)>(&()); transform_cursor.seek(&range.start, Bias::Right, &()); let inlay_start = { let overshoot = range.start.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + DiffOffset(overshoot) + transform_cursor.start().1 + InlayOffset(overshoot) }; let transform_end = transform_cursor.end(&()); @@ -852,20 +808,20 @@ impl FoldSnapshot { inlay_start } else if range.end < transform_end.0 { let overshoot = range.end.0 - transform_cursor.start().0 .0; - transform_cursor.start().1 + DiffOffset(overshoot) + transform_cursor.start().1 + InlayOffset(overshoot) } else { transform_end.1 }; FoldChunks { transform_cursor, - diff_map_chunks: self.diff_map_snapshot.chunks( + inlay_chunks: self.inlay_snapshot.chunks( inlay_start..inlay_end, language_aware, highlights, ), - diff_chunk: None, - diff_offset: inlay_start, + inlay_chunk: None, + inlay_offset: inlay_start, output_offset: range.start, max_output_offset: range.end, } @@ -890,7 +846,7 @@ impl FoldSnapshot { } pub fn clip_point(&self, point: FoldPoint, bias: Bias) -> FoldPoint { - let mut cursor = self.transforms.cursor::<(FoldPoint, DiffPoint)>(&()); + let mut cursor = self.transforms.cursor::<(FoldPoint, InlayPoint)>(&()); cursor.seek(&point, Bias::Right, &()); if let Some(transform) = cursor.item() { let transform_start = cursor.start().0 .0; @@ -901,10 +857,10 @@ impl FoldSnapshot { FoldPoint(cursor.end(&()).0 .0) } } else { - let overshoot = point.0 - transform_start; - let diff_point = DiffPoint(cursor.start().1 .0 + overshoot); - let clipped_diff_point = self.diff_map_snapshot.clip_point(diff_point, bias); - FoldPoint(cursor.start().0 .0 + (clipped_diff_point - cursor.start().1).0) + let overshoot = InlayPoint(point.0 - transform_start); + let inlay_point = cursor.start().1 + overshoot; + let clipped_inlay_point = self.inlay_snapshot.clip_point(inlay_point, bias); + FoldPoint(cursor.start().0 .0 + (clipped_inlay_point - cursor.start().1).0) } } else { FoldPoint(self.transforms.summary().output.lines) @@ -939,12 +895,12 @@ fn push_isomorphic(transforms: &mut SumTree, summary: TextSummary) { } fn intersecting_folds<'a>( - diff_map_snapshot: &'a DiffMapSnapshot, + inlay_snapshot: &'a InlaySnapshot, folds: &'a SumTree, range: Range, inclusive: bool, ) -> FilterCursor<'a, impl 'a + FnMut(&FoldSummary) -> bool, Fold, usize> { - let buffer = diff_map_snapshot.buffer(); + let buffer = inlay_snapshot.buffer(); let start = buffer.anchor_before(range.start.to_offset(buffer)); let end = buffer.anchor_after(range.end.to_offset(buffer)); let mut cursor = folds.filter::<_, usize>(buffer, move |summary| { @@ -961,7 +917,7 @@ fn intersecting_folds<'a>( cursor } -fn consolidate_inlay_edits(mut edits: Vec) -> Vec { +fn consolidate_inlay_edits(mut edits: Vec) -> Vec { edits.sort_unstable_by(|a, b| { a.old .start @@ -1209,8 +1165,8 @@ impl<'a> sum_tree::Dimension<'a, FoldSummary> for usize { #[derive(Clone)] pub struct FoldRows<'a> { - cursor: Cursor<'a, Transform, (FoldPoint, DiffPoint)>, - input_rows: DiffMapRows<'a>, + cursor: Cursor<'a, Transform, (FoldPoint, InlayPoint)>, + input_rows: InlayBufferRows<'a>, fold_point: FoldPoint, } @@ -1219,8 +1175,8 @@ impl<'a> FoldRows<'a> { let fold_point = FoldPoint::new(row, 0); self.cursor.seek(&fold_point, Bias::Left, &()); let overshoot = fold_point.0 - self.cursor.start().0 .0; - let diff_point = DiffPoint(self.cursor.start().1 .0 + overshoot); - self.input_rows.seek(diff_point.0.row); + let inlay_point = InlayPoint(self.cursor.start().1 .0 + overshoot); + self.input_rows.seek(inlay_point.row()); self.fold_point = fold_point; } } @@ -1252,10 +1208,10 @@ impl<'a> Iterator for FoldRows<'a> { } pub struct FoldChunks<'a> { - transform_cursor: Cursor<'a, Transform, (FoldOffset, DiffOffset)>, - diff_map_chunks: DiffMapChunks<'a>, - diff_chunk: Option<(DiffOffset, Chunk<'a>)>, - diff_offset: DiffOffset, + transform_cursor: Cursor<'a, Transform, (FoldOffset, InlayOffset)>, + inlay_chunks: InlayChunks<'a>, + inlay_chunk: Option<(InlayOffset, Chunk<'a>)>, + inlay_offset: InlayOffset, output_offset: FoldOffset, max_output_offset: FoldOffset, } @@ -1264,29 +1220,29 @@ impl<'a> FoldChunks<'a> { pub(crate) fn seek(&mut self, range: Range) { self.transform_cursor.seek(&range.start, Bias::Right, &()); - let diff_start = { + let inlay_start = { let overshoot = range.start.0 - self.transform_cursor.start().0 .0; - self.transform_cursor.start().1 + DiffOffset(overshoot) + self.transform_cursor.start().1 + InlayOffset(overshoot) }; let transform_end = self.transform_cursor.end(&()); - let diff_end = if self + let inlay_end = if self .transform_cursor .item() .map_or(true, |transform| transform.is_fold()) { - diff_start + inlay_start } else if range.end < transform_end.0 { let overshoot = range.end.0 - self.transform_cursor.start().0 .0; - self.transform_cursor.start().1 + DiffOffset(overshoot) + self.transform_cursor.start().1 + InlayOffset(overshoot) } else { transform_end.1 }; - self.diff_map_chunks.seek(diff_start..diff_end); - self.diff_chunk = None; - self.diff_offset = diff_start; + self.inlay_chunks.seek(inlay_start..inlay_end); + self.inlay_chunk = None; + self.inlay_offset = inlay_start; self.output_offset = range.start; self.max_output_offset = range.end; } @@ -1305,10 +1261,10 @@ impl<'a> Iterator for FoldChunks<'a> { // If we're in a fold, then return the fold's display text and // advance the transform and buffer cursors to the end of the fold. if let Some(placeholder) = transform.placeholder.as_ref() { - self.diff_chunk.take(); - self.diff_offset += DiffOffset(transform.summary.input.len); + self.inlay_chunk.take(); + self.inlay_offset += InlayOffset(transform.summary.input.len); - while self.diff_offset >= self.transform_cursor.end(&()).1 + while self.inlay_offset >= self.transform_cursor.end(&()).1 && self.transform_cursor.item().is_some() { self.transform_cursor.next(&()); @@ -1324,46 +1280,43 @@ impl<'a> Iterator for FoldChunks<'a> { // When we reach a non-fold region, seek the underlying text // chunk iterator to the next unfolded range. - if self.diff_offset == self.transform_cursor.start().1 - && self.diff_map_chunks.offset() != self.diff_offset + if self.inlay_offset == self.transform_cursor.start().1 + && self.inlay_chunks.offset() != self.inlay_offset { let transform_start = self.transform_cursor.start(); let transform_end = self.transform_cursor.end(&()); let inlay_end = if self.max_output_offset < transform_end.0 { let overshoot = self.max_output_offset.0 - transform_start.0 .0; - transform_start.1 + DiffOffset(overshoot) + transform_start.1 + InlayOffset(overshoot) } else { transform_end.1 }; - self.diff_map_chunks.seek(self.diff_offset..inlay_end); + self.inlay_chunks.seek(self.inlay_offset..inlay_end); } // Retrieve a chunk from the current location in the buffer. - if self.diff_chunk.is_none() { - let chunk_offset = self.diff_map_chunks.offset(); - self.diff_chunk = self - .diff_map_chunks - .next() - .map(|chunk| (chunk_offset, chunk)); + if self.inlay_chunk.is_none() { + let chunk_offset = self.inlay_chunks.offset(); + self.inlay_chunk = self.inlay_chunks.next().map(|chunk| (chunk_offset, chunk)); } // Otherwise, take a chunk from the buffer's text. - if let Some((buffer_chunk_start, mut chunk)) = self.diff_chunk.clone() { - let buffer_chunk_end = buffer_chunk_start + DiffOffset(chunk.text.len()); + if let Some((buffer_chunk_start, mut chunk)) = self.inlay_chunk.clone() { + let buffer_chunk_end = buffer_chunk_start + InlayOffset(chunk.text.len()); let transform_end = self.transform_cursor.end(&()).1; let chunk_end = buffer_chunk_end.min(transform_end); chunk.text = &chunk.text - [(self.diff_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; + [(self.inlay_offset - buffer_chunk_start).0..(chunk_end - buffer_chunk_start).0]; if chunk_end == transform_end { self.transform_cursor.next(&()); } else if chunk_end == buffer_chunk_end { - self.diff_chunk.take(); + self.inlay_chunk.take(); } - self.diff_offset = chunk_end; + self.inlay_offset = chunk_end; self.output_offset.0 += chunk.text.len(); return Some(chunk); } @@ -1385,20 +1338,18 @@ impl FoldOffset { Point::new(0, (self.0 - cursor.start().0 .0) as u32) } else { let inlay_offset = cursor.start().1.input.len + self.0 - cursor.start().0 .0; - let inlay_point = snapshot - .diff_map_snapshot - .to_point(DiffOffset(inlay_offset)); + let inlay_point = snapshot.inlay_snapshot.to_point(InlayOffset(inlay_offset)); inlay_point.0 - cursor.start().1.input.lines }; FoldPoint(cursor.start().1.output.lines + overshoot) } #[cfg(test)] - pub fn to_diff_offset(self, snapshot: &FoldSnapshot) -> DiffOffset { - let mut cursor = snapshot.transforms.cursor::<(FoldOffset, DiffOffset)>(&()); + pub fn to_inlay_offset(self, snapshot: &FoldSnapshot) -> InlayOffset { + let mut cursor = snapshot.transforms.cursor::<(FoldOffset, InlayOffset)>(&()); cursor.seek(&self, Bias::Right, &()); let overshoot = self.0 - cursor.start().0 .0; - DiffOffset(cursor.start().1 .0 + overshoot) + InlayOffset(cursor.start().1 .0 + overshoot) } } @@ -1434,7 +1385,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for FoldOffset { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffPoint { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { fn zero(_cx: &()) -> Self { Default::default() } @@ -1444,7 +1395,7 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffPoint { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffOffset { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayOffset { fn zero(_cx: &()) -> Self { Default::default() } @@ -1467,6 +1418,7 @@ mod tests { use rand::prelude::*; use settings::SettingsStore; use std::{env, mem}; + use text::Patch; use util::test::sample_text; use util::RandomCharIter; use Bias::{Left, Right}; @@ -1476,12 +1428,11 @@ mod tests { init_test(cx); let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (diff_map, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let mut map = FoldMap::new(diff_snapshot.clone()).0; + let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(diff_snapshot, vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot, vec![]); let (snapshot2, edits) = writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(2, 4)..Point::new(4, 1), FoldPlaceholder::test()), @@ -1513,12 +1464,15 @@ mod tests { buffer.snapshot(cx) }); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync( + buffer_snapshot.clone(), + subscription.consume().into_inner(), + cx, + ) }); - let (snapshot3, edits) = map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (snapshot3, edits) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot3.text(), "123a⋯c123c⋯eeeee"); assert_eq!( edits, @@ -1538,22 +1492,22 @@ mod tests { buffer.edit([(Point::new(2, 6)..Point::new(4, 3), "456")], None, cx); buffer.snapshot(cx) }); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx) }); - let (snapshot4, _) = map.read(diff_snapshot.clone(), diff_edits); + + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (snapshot4, _) = map.read(inlay_snapshot.clone(), inlay_edits); assert_eq!(snapshot4.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), false); - let (snapshot5, _) = map.read(diff_snapshot.clone(), vec![]); + let (snapshot5, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot5.text(), "123a⋯c123456eee"); - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.unfold_intersecting(Some(Point::new(0, 4)..Point::new(0, 4)), true); - let (snapshot6, _) = map.read(diff_snapshot, vec![]); + let (snapshot6, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot6.text(), "123aaaaa\nbbbbbb\nccc123456eee"); } @@ -1562,47 +1516,46 @@ mod tests { init_test(cx); let buffer = MultiBuffer::build_simple("abcdefghijkl", cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (diff_map, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); { - let mut map = FoldMap::new(diff_snapshot.clone()).0; + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![(5..8, FoldPlaceholder::test())]); - let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "abcde⋯ijkl"); // Create an fold adjacent to the start of the first fold. - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ (0..1, FoldPlaceholder::test()), (2..5, FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯ijkl"); // Create an fold adjacent to the end of the first fold. - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ (11..11, FoldPlaceholder::test()), (8..10, FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "⋯b⋯kl"); } { - let mut map = FoldMap::new(diff_snapshot.clone()).0; + let mut map = FoldMap::new(inlay_snapshot.clone()).0; // Create two adjacent folds. - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ (0..2, FoldPlaceholder::test()), (2..5, FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(diff_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "⋯fghijkl"); // Edit within one of the folds. @@ -1610,12 +1563,11 @@ mod tests { buffer.edit([(0..1, "12345")], None, cx); buffer.snapshot(cx) }); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx) }); - let (snapshot, _) = map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot.text(), "12345⋯fghijkl"); } } @@ -1623,18 +1575,17 @@ mod tests { #[gpui::test] fn test_overlapping_folds(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let mut map = FoldMap::new(diff_snapshot.clone()).0; - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()), (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(diff_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1643,29 +1594,27 @@ mod tests { init_test(cx); let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let subscription = buffer.update(cx, |buffer, _| buffer.subscribe()); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (diff_map, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let mut map = FoldMap::new(diff_snapshot.clone()).0; + let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee"); let buffer_snapshot = buffer.update(cx, |buffer, cx| { buffer.edit([(Point::new(2, 2)..Point::new(3, 1), "")], None, cx); buffer.snapshot(cx) }); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot, subscription.consume().into_inner()); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot, subscription.consume().into_inner(), cx) }); - let (snapshot, _) = map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + let (snapshot, _) = map.read(inlay_snapshot, inlay_edits); assert_eq!(snapshot.text(), "aa⋯eeeee"); } @@ -1673,18 +1622,18 @@ mod tests { fn test_folds_in_range(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx); let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let mut map = FoldMap::new(diff_snapshot.clone()).0; + let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(0, 4)..Point::new(1, 0), FoldPlaceholder::test()), (Point::new(1, 2)..Point::new(3, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(diff_snapshot.clone(), vec![]); + let (snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); let fold_ranges = snapshot .folds_in_range(Point::new(1, 0)..Point::new(1, 3)) .map(|fold| { @@ -1716,53 +1665,53 @@ mod tests { MultiBuffer::build_random(&mut rng, cx) }; let mut buffer_snapshot = buffer.read(cx).snapshot(cx); - let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (diff_map, mut diff_snapshot) = - DiffMap::new(inlay_snapshot.clone(), buffer.clone(), cx); - let mut map = FoldMap::new(diff_snapshot.clone()).0; + let (diff_map, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut initial_snapshot, _) = map.read(diff_snapshot.clone(), vec![]); + let (mut initial_snapshot, _) = map.read(inlay_snapshot.clone(), vec![]); let mut snapshot_edits = Vec::new(); let mut next_inlay_id = 0; for _ in 0..operations { log::info!("text: {:?}", buffer_snapshot.text()); - + let mut buffer_edits = Vec::new(); let mut inlay_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=39 => { snapshot_edits.extend(map.randomly_mutate(&mut rng)); } 40..=59 => { - (inlay_snapshot, inlay_edits) = - inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); - } - _ => { - (inlay_snapshot, inlay_edits) = buffer.update(cx, |buffer, cx| { - let subscription = buffer.subscribe(); - let edit_count = rng.gen_range(1..=5); - buffer.randomly_mutate(&mut rng, edit_count, cx); - buffer_snapshot = buffer.snapshot(cx); - let buffer_edits = subscription.consume().into_inner(); - log::info!("editing {:?}", buffer_edits); - inlay_map.sync(buffer_snapshot.clone(), buffer_edits) - }) + let (_, edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); + inlay_edits = edits; } + _ => buffer.update(cx, |buffer, cx| { + let subscription = buffer.subscribe(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_mutate(&mut rng, edit_count, cx); + buffer_snapshot = buffer.snapshot(cx); + let edits = subscription.consume().into_inner(); + log::info!("editing {:?}", edits); + buffer_edits.extend(edits); + }), }; - log::info!("inlay text {:?}", inlay_snapshot.text()); - let (new_diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot.clone(), inlay_edits, cx) + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx) }); - diff_snapshot = new_diff_snapshot; + let (inlay_snapshot, new_inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + log::info!("inlay text {:?}", inlay_snapshot.text()); - let (snapshot, edits) = map.read(diff_snapshot.clone(), diff_edits); + let inlay_edits = Patch::new(inlay_edits) + .compose(new_inlay_edits) + .into_inner(); + let (snapshot, edits) = map.read(inlay_snapshot.clone(), inlay_edits); snapshot_edits.push((snapshot.clone(), edits)); - let mut expected_text: String = diff_snapshot.text().to_string(); + let mut expected_text: String = inlay_snapshot.text().to_string(); for fold_range in map.merged_folds().into_iter().rev() { - let fold_inlay_start = diff_snapshot.to_diff_offset(fold_range.start); - let fold_inlay_end = diff_snapshot.to_diff_offset(fold_range.end); + let fold_inlay_start = inlay_snapshot.make_inlay_offset(fold_range.start); + let fold_inlay_end = inlay_snapshot.make_inlay_offset(fold_range.end); expected_text.replace_range(fold_inlay_start.0..fold_inlay_end.0, "⋯"); } @@ -1777,19 +1726,19 @@ mod tests { let mut expected_buffer_rows = Vec::new(); for fold_range in map.merged_folds() { let fold_start = inlay_snapshot - .to_point(inlay_snapshot.to_inlay_offset(fold_range.start)) + .to_point(inlay_snapshot.make_inlay_offset(fold_range.start)) .row(); let fold_end = inlay_snapshot - .to_point(inlay_snapshot.to_inlay_offset(fold_range.end)) + .to_point(inlay_snapshot.make_inlay_offset(fold_range.end)) .row(); expected_buffer_rows.extend( inlay_snapshot - .buffer_rows(prev_row) + .row_infos(prev_row) .take((1 + fold_start - prev_row) as usize), ); prev_row = 1 + fold_end; } - expected_buffer_rows.extend(inlay_snapshot.buffer_rows(prev_row)); + expected_buffer_rows.extend(inlay_snapshot.row_infos(prev_row)); assert_eq!( expected_buffer_rows.len(), @@ -1815,19 +1764,19 @@ mod tests { let mut fold_offset = FoldOffset(0); let mut char_column = 0; for c in expected_text.chars() { - let diff_point = fold_point.to_diff_point(&snapshot); - let diff_offset = fold_offset.to_diff_offset(&snapshot); + let inlay_point = fold_point.to_inlay_point(&snapshot); + let inlay_offset = fold_offset.to_inlay_offset(&snapshot); assert_eq!( - snapshot.to_fold_point(diff_point, Right), + snapshot.to_fold_point(inlay_point, Right), fold_point, "{:?} -> fold point", - diff_point, + inlay_point, ); assert_eq!( - diff_snapshot.to_offset(diff_point), - diff_offset, - "diff_map_snapshot.to_offset({:?})", - diff_point, + inlay_snapshot.to_offset(inlay_point), + inlay_offset, + "inlay_snapshot.to_offset({:?})", + inlay_point, ); assert_eq!( fold_point.to_offset(&snapshot), @@ -1878,10 +1827,7 @@ mod tests { let mut fold_row = 0; while fold_row < expected_buffer_rows.len() as u32 { assert_eq!( - snapshot - .row_infos(fold_row) - .map(|row_info| row_info.buffer_row) - .collect::>(), + snapshot.row_infos(fold_row).collect::>(), expected_buffer_rows[(fold_row as usize)..], "wrong buffer rows starting at fold row {}", fold_row, @@ -1944,12 +1890,21 @@ mod tests { let text = snapshot.text(); for _ in 0..5 { - let end = - snapshot.clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Right); - let start = snapshot.clip_offset(FoldOffset(rng.gen_range(0..=end.0)), Left); - let bytes = start..end; + let start_row = rng.gen_range(0..=snapshot.max_point().row()); + let start_column = rng.gen_range(0..=snapshot.line_len(start_row)); + let end_row = rng.gen_range(0..=snapshot.max_point().row()); + let end_column = rng.gen_range(0..=snapshot.line_len(end_row)); + let mut start = + snapshot.clip_point(FoldPoint::new(start_row, start_column), Bias::Left); + let mut end = snapshot.clip_point(FoldPoint::new(end_row, end_column), Bias::Right); + if start > end { + mem::swap(&mut start, &mut end); + } + + let lines = start..end; + let bytes = start.to_offset(&snapshot)..end.to_offset(&snapshot); assert_eq!( - snapshot.text_summary_for_offset_range(bytes.clone()), + snapshot.text_summary_for_range(lines), TextSummary::from(&text[bytes.start.0..bytes.end.0]) ) } @@ -1974,30 +1929,29 @@ mod tests { let text = sample_text(6, 6, 'a') + "\n"; let buffer = MultiBuffer::build_simple(&text, cx); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot.clone(), buffer.clone(), cx); - let mut map = FoldMap::new(diff_snapshot.clone()).0; + let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot); + let mut map = FoldMap::new(inlay_snapshot.clone()).0; - let (mut writer, _, _) = map.write(diff_snapshot.clone(), vec![]); + let (mut writer, _, _) = map.write(inlay_snapshot.clone(), vec![]); writer.fold(vec![ (Point::new(0, 2)..Point::new(2, 2), FoldPlaceholder::test()), (Point::new(3, 1)..Point::new(4, 1), FoldPlaceholder::test()), ]); - let (snapshot, _) = map.read(diff_snapshot, vec![]); + let (snapshot, _) = map.read(inlay_snapshot, vec![]); assert_eq!(snapshot.text(), "aa⋯cccc\nd⋯eeeee\nffffff\n"); assert_eq!( snapshot .row_infos(0) - .map(|row_info| row_info.buffer_row) + .map(|info| info.buffer_row) .collect::>(), [Some(0), Some(3), Some(5), Some(6)] ); assert_eq!( snapshot .row_infos(3) - .map(|row_info| row_info.buffer_row) + .map(|info| info.buffer_row) .collect::>(), [Some(6)] ); @@ -2010,8 +1964,8 @@ mod tests { impl FoldMap { fn merged_folds(&self) -> Vec> { - let diff_map_snapshot = self.snapshot.diff_map_snapshot.clone(); - let buffer = diff_map_snapshot.buffer(); + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = inlay_snapshot.buffer(); let mut folds = self.snapshot.folds.items(buffer); // Ensure sorting doesn't change how folds get merged and displayed. folds.sort_by(|a, b| a.range.cmp(&b.range, buffer)); @@ -2046,8 +2000,8 @@ mod tests { let mut snapshot_edits = Vec::new(); match rng.gen_range(0..=100) { 0..=39 if !self.snapshot.folds.is_empty() => { - let diff_map_snapshot = self.snapshot.diff_map_snapshot.clone(); - let buffer = diff_map_snapshot.buffer(); + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = inlay_snapshot.buffer(); let mut to_unfold = Vec::new(); for _ in 0..rng.gen_range(1..=3) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -2056,14 +2010,14 @@ mod tests { } let inclusive = rng.gen(); log::info!("unfolding {:?} (inclusive: {})", to_unfold, inclusive); - let (mut writer, snapshot, edits) = self.write(diff_map_snapshot, vec![]); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.unfold_intersecting(to_unfold, inclusive); snapshot_edits.push((snapshot, edits)); } _ => { - let diff_map_snapshot = self.snapshot.diff_map_snapshot.clone(); - let buffer = diff_map_snapshot.buffer(); + let inlay_snapshot = self.snapshot.inlay_snapshot.clone(); + let buffer = inlay_snapshot.buffer(); let mut to_fold = Vec::new(); for _ in 0..rng.gen_range(1..=2) { let end = buffer.clip_offset(rng.gen_range(0..=buffer.len()), Right); @@ -2071,7 +2025,7 @@ mod tests { to_fold.push((start..end, FoldPlaceholder::test())); } log::info!("folding {:?}", to_fold); - let (mut writer, snapshot, edits) = self.write(diff_map_snapshot, vec![]); + let (mut writer, snapshot, edits) = self.write(inlay_snapshot, vec![]); snapshot_edits.push((snapshot, edits)); let (snapshot, edits) = writer.fold(to_fold); snapshot_edits.push((snapshot, edits)); diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 750782c63102b9..2fe5d90548c8d3 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,7 +1,7 @@ -use crate::{HighlightStyles, InlayId}; +use crate::{HighlightStyles, InlayId, RowInfo}; use collections::BTreeSet; use language::{Chunk, Edit, Point, TextSummary}; -use multi_buffer::{Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset}; +use multi_buffer::{Anchor, MultiBufferSnapshot, ToOffset}; use std::{ cmp, ops::{Add, AddAssign, Range, Sub, SubAssign}, @@ -9,7 +9,10 @@ use std::{ use sum_tree::{Bias, Cursor, SumTree}; use text::{Patch, Rope}; -use super::{custom_highlights::CustomHighlightsChunks, Highlights}; +use super::{ + diff_map::{DiffEdit, DiffMapChunks, DiffMapRows, DiffMapSnapshot, DiffOffset, DiffPoint}, + Highlights, +}; /// Decides where the [`Inlay`]s should be displayed. /// @@ -21,7 +24,7 @@ pub struct InlayMap { #[derive(Clone)] pub struct InlaySnapshot { - pub buffer: MultiBufferSnapshot, + pub diff_map_snapshot: DiffMapSnapshot, transforms: SumTree, pub version: usize, } @@ -172,37 +175,37 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InlayPoint { } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffOffset { fn zero(_cx: &()) -> Self { - Default::default() + DiffOffset(0) } fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += &summary.input.len; + self.0 += &summary.input.len; } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for DiffPoint { fn zero(_cx: &()) -> Self { Default::default() } fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += &summary.input.lines; + self.0 += &summary.input.lines; } } #[derive(Clone)] pub struct InlayBufferRows<'a> { - transforms: Cursor<'a, Transform, (InlayPoint, Point)>, - buffer_rows: MultiBufferRows<'a>, + transforms: Cursor<'a, Transform, (InlayPoint, DiffPoint)>, + diff_rows: DiffMapRows<'a>, inlay_row: u32, - max_buffer_row: MultiBufferRow, + max_point: DiffPoint, } pub struct InlayChunks<'a> { - transforms: Cursor<'a, Transform, (InlayOffset, usize)>, - buffer_chunks: CustomHighlightsChunks<'a>, + transforms: Cursor<'a, Transform, (InlayOffset, DiffOffset)>, + diff_map_chunks: DiffMapChunks<'a>, buffer_chunk: Option>, inlay_chunks: Option>, inlay_chunk: Option<&'a str>, @@ -214,12 +217,16 @@ pub struct InlayChunks<'a> { } impl<'a> InlayChunks<'a> { + pub fn offset(&self) -> InlayOffset { + self.output_offset + } + pub fn seek(&mut self, new_range: Range) { self.transforms.seek(&new_range.start, Bias::Right, &()); - let buffer_range = self.snapshot.to_buffer_offset(new_range.start) - ..self.snapshot.to_buffer_offset(new_range.end); - self.buffer_chunks.seek(buffer_range); + let diff_range = self.snapshot.to_diff_offset(new_range.start) + ..self.snapshot.to_diff_offset(new_range.end); + self.diff_map_chunks.seek(diff_range); self.inlay_chunks = None; self.buffer_chunk = None; self.output_offset = new_range.start; @@ -239,9 +246,9 @@ impl<'a> Iterator for InlayChunks<'a> { Transform::Isomorphic(_) => { let chunk = self .buffer_chunk - .get_or_insert_with(|| self.buffer_chunks.next().unwrap()); + .get_or_insert_with(|| self.diff_map_chunks.next().unwrap()); if chunk.text.is_empty() { - *chunk = self.buffer_chunks.next().unwrap(); + *chunk = self.diff_map_chunks.next().unwrap(); } let (prefix, suffix) = chunk.text.split_at( @@ -332,33 +339,33 @@ impl<'a> InlayBufferRows<'a> { let inlay_point = InlayPoint::new(row, 0); self.transforms.seek(&inlay_point, Bias::Left, &()); - let mut buffer_point = self.transforms.start().1; - let buffer_row = MultiBufferRow(if row == 0 { + let mut diff_point = self.transforms.start().1; + let row = if row == 0 { 0 } else { match self.transforms.item() { Some(Transform::Isomorphic(_)) => { - buffer_point += inlay_point.0 - self.transforms.start().0 .0; - buffer_point.row + diff_point.0 += inlay_point.0 - self.transforms.start().0 .0; + diff_point.row() } - _ => cmp::min(buffer_point.row + 1, self.max_buffer_row.0), + _ => cmp::min(diff_point.row() + 1, self.max_point.row()), } - }); + }; self.inlay_row = inlay_point.row(); - self.buffer_rows.seek(buffer_row); + self.diff_rows.seek(row); } } impl<'a> Iterator for InlayBufferRows<'a> { - type Item = Option; + type Item = RowInfo; fn next(&mut self) -> Option { - let buffer_row = if self.inlay_row == 0 { - self.buffer_rows.next().unwrap() + let row_info = if self.inlay_row == 0 { + self.diff_rows.next().unwrap() } else { match self.transforms.item()? { - Transform::Inlay(_) => None, - Transform::Isomorphic(_) => self.buffer_rows.next().unwrap(), + Transform::Inlay(_) => Default::default(), + Transform::Isomorphic(_) => self.diff_rows.next().unwrap(), } }; @@ -366,7 +373,7 @@ impl<'a> Iterator for InlayBufferRows<'a> { self.transforms .seek_forward(&InlayPoint::new(self.inlay_row, 0), Bias::Left, &()); - Some(buffer_row) + Some(row_info) } } @@ -381,11 +388,14 @@ impl InlayPoint { } impl InlayMap { - pub fn new(buffer: MultiBufferSnapshot) -> (Self, InlaySnapshot) { + pub fn new(diff_map_snapshot: DiffMapSnapshot) -> (Self, InlaySnapshot) { let version = 0; let snapshot = InlaySnapshot { - buffer: buffer.clone(), - transforms: SumTree::from_iter(Some(Transform::Isomorphic(buffer.text_summary())), &()), + transforms: SumTree::from_iter( + Some(Transform::Isomorphic(diff_map_snapshot.text_summary())), + &(), + ), + diff_map_snapshot, version, }; @@ -400,133 +410,111 @@ impl InlayMap { pub fn sync( &mut self, - buffer_snapshot: MultiBufferSnapshot, - mut buffer_edits: Vec>, + diff_map_snapshot: DiffMapSnapshot, + diff_edits: Vec, ) -> (InlaySnapshot, Vec) { let snapshot = &mut self.snapshot; - if buffer_edits.is_empty() - && snapshot.buffer.trailing_excerpt_update_count() - != buffer_snapshot.trailing_excerpt_update_count() - { - buffer_edits.push(Edit { - old: snapshot.buffer.len()..snapshot.buffer.len(), - new: buffer_snapshot.len()..buffer_snapshot.len(), - }); - } - - if buffer_edits.is_empty() { - if snapshot.buffer.edit_count() != buffer_snapshot.edit_count() - || snapshot.buffer.non_text_state_update_count() - != buffer_snapshot.non_text_state_update_count() - || snapshot.buffer.trailing_excerpt_update_count() - != buffer_snapshot.trailing_excerpt_update_count() - { - snapshot.version += 1; - } - - snapshot.buffer = buffer_snapshot; - (snapshot.clone(), Vec::new()) - } else { - let mut inlay_edits = Patch::default(); - let mut new_transforms = SumTree::default(); - let mut cursor = snapshot.transforms.cursor::<(usize, InlayOffset)>(&()); - let mut buffer_edits_iter = buffer_edits.iter().peekable(); - while let Some(buffer_edit) = buffer_edits_iter.next() { - new_transforms.append(cursor.slice(&buffer_edit.old.start, Bias::Left, &()), &()); - if let Some(Transform::Isomorphic(transform)) = cursor.item() { - if cursor.end(&()).0 == buffer_edit.old.start { - push_isomorphic(&mut new_transforms, transform.clone()); - cursor.next(&()); - } + let mut inlay_edits = Patch::default(); + let mut new_transforms = SumTree::default(); + let mut cursor = snapshot.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); + let mut diff_edits_iter = diff_edits.iter().peekable(); + while let Some(diff_edit) = diff_edits_iter.next() { + new_transforms.append(cursor.slice(&diff_edit.old.start, Bias::Left, &()), &()); + if let Some(Transform::Isomorphic(transform)) = cursor.item() { + if cursor.end(&()).0 == diff_edit.old.start { + push_isomorphic(&mut new_transforms, transform.clone()); + cursor.next(&()); } + } - // Remove all the inlays and transforms contained by the edit. - let old_start = - cursor.start().1 + InlayOffset(buffer_edit.old.start - cursor.start().0); - cursor.seek(&buffer_edit.old.end, Bias::Right, &()); - let old_end = - cursor.start().1 + InlayOffset(buffer_edit.old.end - cursor.start().0); - - // Push the unchanged prefix. - let prefix_start = new_transforms.summary().input.len; - let prefix_end = buffer_edit.new.start; - push_isomorphic( - &mut new_transforms, - buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), - ); - let new_start = InlayOffset(new_transforms.summary().output.len); - - let start_ix = match self.inlays.binary_search_by(|probe| { - probe - .position - .to_offset(&buffer_snapshot) - .cmp(&buffer_edit.new.start) - .then(std::cmp::Ordering::Greater) - }) { - Ok(ix) | Err(ix) => ix, - }; + // Remove all the inlays and transforms contained by the edit. + let old_start = + cursor.start().1 + InlayOffset((diff_edit.old.start - cursor.start().0).0); + cursor.seek(&diff_edit.old.end, Bias::Right, &()); + let old_end = cursor.start().1 + InlayOffset((diff_edit.old.end - cursor.start().0).0); + + // Push the unchanged prefix. + let prefix_start = DiffOffset(new_transforms.summary().input.len); + let prefix_end = DiffOffset(diff_edit.new.start.0); + push_isomorphic( + &mut new_transforms, + diff_map_snapshot.text_summary_for_range(prefix_start..prefix_end), + ); + let new_start = InlayOffset(new_transforms.summary().output.len); - for inlay in &self.inlays[start_ix..] { - let buffer_offset = inlay.position.to_offset(&buffer_snapshot); - if buffer_offset > buffer_edit.new.end { - break; - } + let edit_buffer_range = diff_map_snapshot.to_multibuffer_offset(diff_edit.new.start) + ..diff_map_snapshot.to_multibuffer_offset(diff_edit.new.end); - let prefix_start = new_transforms.summary().input.len; - let prefix_end = buffer_offset; - push_isomorphic( - &mut new_transforms, - buffer_snapshot.text_summary_for_range(prefix_start..prefix_end), - ); + let start_ix = match self.inlays.binary_search_by(|probe| { + probe + .position + .to_offset(diff_map_snapshot.buffer()) + .cmp(&edit_buffer_range.start) + .then(std::cmp::Ordering::Greater) + }) { + Ok(ix) | Err(ix) => ix, + }; - if inlay.position.is_valid(&buffer_snapshot) { - new_transforms.push(Transform::Inlay(inlay.clone()), &()); - } + for inlay in &self.inlays[start_ix..] { + let buffer_offset = inlay.position.to_offset(&diff_map_snapshot.buffer); + let diff_offset = diff_map_snapshot.to_diff_offset(buffer_offset); + if buffer_offset > edit_buffer_range.end { + break; } - // Apply the rest of the edit. - let transform_start = new_transforms.summary().input.len; + let prefix_start = DiffOffset(new_transforms.summary().input.len); + let prefix_end = diff_offset; push_isomorphic( &mut new_transforms, - buffer_snapshot.text_summary_for_range(transform_start..buffer_edit.new.end), + diff_map_snapshot.text_summary_for_range(prefix_start..prefix_end), ); - let new_end = InlayOffset(new_transforms.summary().output.len); - inlay_edits.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - // If the next edit doesn't intersect the current isomorphic transform, then - // we can push its remainder. - if buffer_edits_iter - .peek() - .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) - { - let transform_start = new_transforms.summary().input.len; - let transform_end = - buffer_edit.new.end + (cursor.end(&()).0 - buffer_edit.old.end); - push_isomorphic( - &mut new_transforms, - buffer_snapshot.text_summary_for_range(transform_start..transform_end), - ); - cursor.next(&()); + if inlay.position.is_valid(&diff_map_snapshot.buffer) { + new_transforms.push(Transform::Inlay(inlay.clone()), &()); } } - new_transforms.append(cursor.suffix(&()), &()); - if new_transforms.is_empty() { - new_transforms.push(Transform::Isomorphic(Default::default()), &()); - } + // Apply the rest of the edit. + let transform_start = DiffOffset(new_transforms.summary().input.len); + push_isomorphic( + &mut new_transforms, + diff_map_snapshot.text_summary_for_range(transform_start..diff_edit.new.end), + ); + let new_end = InlayOffset(new_transforms.summary().output.len); + inlay_edits.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); - drop(cursor); - snapshot.transforms = new_transforms; - snapshot.version += 1; - snapshot.buffer = buffer_snapshot; - snapshot.check_invariants(); + // If the next edit doesn't intersect the current isomorphic transform, then + // we can push its remainder. + if diff_edits_iter + .peek() + .map_or(true, |edit| edit.old.start >= cursor.end(&()).0) + { + let transform_start = DiffOffset(new_transforms.summary().input.len); + let transform_end = diff_edit.new.end + (cursor.end(&()).0 - diff_edit.old.end); + push_isomorphic( + &mut new_transforms, + diff_map_snapshot.text_summary_for_range(transform_start..transform_end), + ); + cursor.next(&()); + } + } - (snapshot.clone(), inlay_edits.into_inner()) + new_transforms.append(cursor.suffix(&()), &()); + if new_transforms.is_empty() { + new_transforms.push(Transform::Isomorphic(Default::default()), &()); } + + drop(cursor); + snapshot.transforms = new_transforms; + snapshot.version += 1; + snapshot.diff_map_snapshot = diff_map_snapshot; + snapshot.check_invariants(); + + (snapshot.clone(), inlay_edits.into_inner()) } pub fn splice( @@ -540,7 +528,9 @@ impl InlayMap { self.inlays.retain(|inlay| { let retain = !to_remove.contains(&inlay.id); if !retain { - let offset = inlay.position.to_offset(&snapshot.buffer); + let offset = snapshot + .diff_map_snapshot + .to_diff_offset(inlay.position.to_offset(&snapshot.diff_map_snapshot.buffer)); edits.insert(offset); } retain @@ -552,11 +542,16 @@ impl InlayMap { continue; } - let offset = inlay_to_insert.position.to_offset(&snapshot.buffer); - match self.inlays.binary_search_by(|probe| { - probe + let offset = snapshot.diff_map_snapshot.to_diff_offset( + inlay_to_insert .position - .cmp(&inlay_to_insert.position, &snapshot.buffer) + .to_offset(&snapshot.diff_map_snapshot.buffer), + ); + match self.inlays.binary_search_by(|probe| { + probe.position.cmp( + &inlay_to_insert.position, + &snapshot.diff_map_snapshot.buffer, + ) }) { Ok(ix) | Err(ix) => { self.inlays.insert(ix, inlay_to_insert); @@ -573,7 +568,7 @@ impl InlayMap { new: offset..offset, }) .collect(); - let buffer_snapshot = snapshot.buffer.clone(); + let buffer_snapshot = snapshot.diff_map_snapshot.clone(); let (snapshot, edits) = self.sync(buffer_snapshot, buffer_edits); (snapshot, edits) } @@ -596,7 +591,9 @@ impl InlayMap { let snapshot = &mut self.snapshot; for i in 0..rng.gen_range(1..=5) { if self.inlays.is_empty() || rng.gen() { - let position = snapshot.buffer.random_byte_range(0, rng).start; + let position = snapshot.buffer().random_byte_range(0, rng).start; + let anchor = snapshot.buffer().anchor_at(position, Bias::Left); + let position = snapshot.diff_map_snapshot.to_diff_offset(position); let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let len = if rng.gen_bool(0.01) { 0 @@ -614,7 +611,7 @@ impl InlayMap { InlayId::Suggestion(post_inc(next_inlay_id)) }; log::info!( - "creating inlay {:?} at buffer offset {} with bias {:?} and text {:?}", + "creating inlay {:?} at buffer offset {:?} with bias {:?} and text {:?}", inlay_id, position, bias, @@ -623,7 +620,7 @@ impl InlayMap { to_insert.push(Inlay { id: inlay_id, - position: snapshot.buffer.anchor_at(position, bias), + position: anchor, text: text.into(), }); } else { @@ -647,16 +644,16 @@ impl InlaySnapshot { pub fn to_point(&self, offset: InlayOffset) -> InlayPoint { let mut cursor = self .transforms - .cursor::<(InlayOffset, (InlayPoint, usize))>(&()); + .cursor::<(InlayOffset, (InlayPoint, DiffOffset))>(&()); cursor.seek(&offset, Bias::Right, &()); let overshoot = offset.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { let buffer_offset_start = cursor.start().1 .1; - let buffer_offset_end = buffer_offset_start + overshoot; - let buffer_start = self.buffer.offset_to_point(buffer_offset_start); - let buffer_end = self.buffer.offset_to_point(buffer_offset_end); - InlayPoint(cursor.start().1 .0 .0 + (buffer_end - buffer_start)) + let buffer_offset_end = buffer_offset_start + DiffOffset(overshoot); + let buffer_start = self.diff_map_snapshot.offset_to_point(buffer_offset_start); + let buffer_end = self.diff_map_snapshot.offset_to_point(buffer_offset_end); + cursor.start().1 .0 + InlayPoint(buffer_end.0 - buffer_start.0) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text.offset_to_point(overshoot); @@ -677,51 +674,68 @@ impl InlaySnapshot { pub fn to_offset(&self, point: InlayPoint) -> InlayOffset { let mut cursor = self .transforms - .cursor::<(InlayPoint, (InlayOffset, Point))>(&()); + .cursor::<(InlayPoint, (InlayOffset, DiffPoint))>(&()); cursor.seek(&point, Bias::Right, &()); let overshoot = point.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { let buffer_point_start = cursor.start().1 .1; - let buffer_point_end = buffer_point_start + overshoot; - let buffer_offset_start = self.buffer.point_to_offset(buffer_point_start); - let buffer_offset_end = self.buffer.point_to_offset(buffer_point_end); - InlayOffset(cursor.start().1 .0 .0 + (buffer_offset_end - buffer_offset_start)) + let buffer_point_end = buffer_point_start + DiffPoint(overshoot); + let buffer_offset_start = + self.diff_map_snapshot.point_to_offset(buffer_point_start); + let buffer_offset_end = self.diff_map_snapshot.point_to_offset(buffer_point_end); + cursor.start().1 .0 + InlayOffset((buffer_offset_end - buffer_offset_start).0) } Some(Transform::Inlay(inlay)) => { let overshoot = inlay.text.point_to_offset(overshoot); - InlayOffset(cursor.start().1 .0 .0 + overshoot) + cursor.start().1 .0 + InlayOffset(overshoot) } None => self.len(), } } - pub fn to_buffer_point(&self, point: InlayPoint) -> Point { - let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&()); + + pub fn to_diff_point(&self, point: InlayPoint) -> DiffPoint { + let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&()); cursor.seek(&point, Bias::Right, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { let overshoot = point.0 - cursor.start().0 .0; - cursor.start().1 + overshoot + cursor.start().1 + DiffPoint(overshoot) } Some(Transform::Inlay(_)) => cursor.start().1, - None => self.buffer.max_point(), + None => self.diff_map_snapshot.max_point(), } } - pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize { - let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&()); + + pub fn to_diff_offset(&self, offset: InlayOffset) -> DiffOffset { + let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&()); cursor.seek(&offset, Bias::Right, &()); match cursor.item() { Some(Transform::Isomorphic(_)) => { let overshoot = offset - cursor.start().0; - cursor.start().1 + overshoot.0 + cursor.start().1 + DiffOffset(overshoot.0) } Some(Transform::Inlay(_)) => cursor.start().1, - None => self.buffer.len(), + None => self.diff_map_snapshot.len(), } } - pub fn to_inlay_offset(&self, offset: usize) -> InlayOffset { - let mut cursor = self.transforms.cursor::<(usize, InlayOffset)>(&()); + pub fn buffer(&self) -> &MultiBufferSnapshot { + &self.diff_map_snapshot.buffer + } + + pub fn to_buffer_point(&self, point: InlayPoint) -> Point { + self.diff_map_snapshot + .to_multibuffer_point(self.to_diff_point(point)) + } + + pub fn to_buffer_offset(&self, offset: InlayOffset) -> usize { + self.diff_map_snapshot + .to_multibuffer_offset(self.to_diff_offset(offset)) + } + + pub fn to_inlay_offset(&self, offset: DiffOffset) -> InlayOffset { + let mut cursor = self.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); cursor.seek(&offset, Bias::Left, &()); loop { match cursor.item() { @@ -736,7 +750,7 @@ impl InlaySnapshot { } return cursor.end(&()).1; } else { - let overshoot = offset - cursor.start().0; + let overshoot = offset.0 - cursor.start().0 .0; return InlayOffset(cursor.start().1 .0 + overshoot); } } @@ -753,8 +767,17 @@ impl InlaySnapshot { } } } - pub fn to_inlay_point(&self, point: Point) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(Point, InlayPoint)>(&()); + + pub fn make_inlay_point(&self, multibuffer_point: Point) -> InlayPoint { + self.to_inlay_point(self.diff_map_snapshot.to_diff_point(multibuffer_point)) + } + + pub fn make_inlay_offset(&self, multibuffer_point: usize) -> InlayOffset { + self.to_inlay_offset(self.diff_map_snapshot.to_diff_offset(multibuffer_point)) + } + + pub fn to_inlay_point(&self, point: DiffPoint) -> InlayPoint { + let mut cursor = self.transforms.cursor::<(DiffPoint, InlayPoint)>(&()); cursor.seek(&point, Bias::Left, &()); loop { match cursor.item() { @@ -770,7 +793,7 @@ impl InlaySnapshot { return cursor.end(&()).1; } else { let overshoot = point - cursor.start().0; - return InlayPoint(cursor.start().1 .0 + overshoot); + return InlayPoint(cursor.start().1 .0 + overshoot.0); } } Some(Transform::Inlay(inlay)) => { @@ -788,7 +811,7 @@ impl InlaySnapshot { } pub fn clip_point(&self, mut point: InlayPoint, mut bias: Bias) -> InlayPoint { - let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&()); + let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&()); cursor.seek(&point, Bias::Left, &()); loop { match cursor.item() { @@ -824,10 +847,11 @@ impl InlaySnapshot { } } else { let overshoot = point.0 - cursor.start().0 .0; - let buffer_point = cursor.start().1 + overshoot; - let clipped_buffer_point = self.buffer.clip_point(buffer_point, bias); - let clipped_overshoot = clipped_buffer_point - cursor.start().1; - let clipped_point = InlayPoint(cursor.start().0 .0 + clipped_overshoot); + let diff_point = cursor.start().1 + DiffPoint(overshoot); + let clipped_diff_point = + self.diff_map_snapshot.clip_point(diff_point, bias); + let clipped_overshoot = clipped_diff_point - cursor.start().1; + let clipped_point = cursor.start().0 + InlayPoint(clipped_overshoot.0); if clipped_point == point { return clipped_point; } else { @@ -885,17 +909,19 @@ impl InlaySnapshot { pub fn text_summary_for_range(&self, range: Range) -> TextSummary { let mut summary = TextSummary::default(); - let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&()); + let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&()); cursor.seek(&range.start, Bias::Right, &()); let overshoot = range.start.0 - cursor.start().0 .0; match cursor.item() { Some(Transform::Isomorphic(_)) => { let buffer_start = cursor.start().1; - let suffix_start = buffer_start + overshoot; - let suffix_end = - buffer_start + (cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0); - summary = self.buffer.text_summary_for_range(suffix_start..suffix_end); + let suffix_start = buffer_start + DiffOffset(overshoot); + let suffix_end = buffer_start + + DiffOffset(cmp::min(cursor.end(&()).0, range.end).0 - cursor.start().0 .0); + summary = self + .diff_map_snapshot + .text_summary_for_range(suffix_start..suffix_end); cursor.next(&()); } Some(Transform::Inlay(inlay)) => { @@ -916,10 +942,10 @@ impl InlaySnapshot { match cursor.item() { Some(Transform::Isomorphic(_)) => { let prefix_start = cursor.start().1; - let prefix_end = prefix_start + overshoot; + let prefix_end = prefix_start + DiffOffset(overshoot); summary += self - .buffer - .text_summary_for_range::(prefix_start..prefix_end); + .diff_map_snapshot + .text_summary_for_range(prefix_start..prefix_end); } Some(Transform::Inlay(inlay)) => { let prefix_end = overshoot; @@ -932,30 +958,30 @@ impl InlaySnapshot { summary } - pub fn buffer_rows(&self, row: u32) -> InlayBufferRows<'_> { - let mut cursor = self.transforms.cursor::<(InlayPoint, Point)>(&()); + pub fn row_infos(&self, row: u32) -> InlayBufferRows<'_> { + let mut cursor = self.transforms.cursor::<(InlayPoint, DiffPoint)>(&()); let inlay_point = InlayPoint::new(row, 0); cursor.seek(&inlay_point, Bias::Left, &()); - let max_buffer_row = self.buffer.max_row(); - let mut buffer_point = cursor.start().1; + let max_point = self.diff_map_snapshot.max_point(); + let mut diff_point = cursor.start().1; let buffer_row = if row == 0 { - MultiBufferRow(0) + 0 } else { match cursor.item() { Some(Transform::Isomorphic(_)) => { - buffer_point += inlay_point.0 - cursor.start().0 .0; - MultiBufferRow(buffer_point.row) + diff_point += DiffPoint(inlay_point.0 - cursor.start().0 .0); + diff_point.row() } - _ => cmp::min(MultiBufferRow(buffer_point.row + 1), max_buffer_row), + _ => cmp::min(diff_point.row() + 1, max_point.row()), } }; InlayBufferRows { transforms: cursor, inlay_row: inlay_point.row(), - buffer_rows: self.buffer.buffer_rows(buffer_row), - max_buffer_row, + diff_rows: self.diff_map_snapshot.row_infos(buffer_row), + max_point, } } @@ -975,20 +1001,17 @@ impl InlaySnapshot { language_aware: bool, highlights: Highlights<'a>, ) -> InlayChunks<'a> { - let mut cursor = self.transforms.cursor::<(InlayOffset, usize)>(&()); + let mut cursor = self.transforms.cursor::<(InlayOffset, DiffOffset)>(&()); cursor.seek(&range.start, Bias::Right, &()); - let buffer_range = self.to_buffer_offset(range.start)..self.to_buffer_offset(range.end); - let buffer_chunks = CustomHighlightsChunks::new( - buffer_range, - language_aware, - highlights.text_highlights, - &self.buffer, - ); + let diff_range = self.to_diff_offset(range.start)..self.to_diff_offset(range.end); + let diff_map_chunks = + self.diff_map_snapshot + .chunks(diff_range, language_aware, highlights.text_highlights); InlayChunks { transforms: cursor, - buffer_chunks, + diff_map_chunks, inlay_chunks: None, inlay_chunk: None, buffer_chunk: None, @@ -1010,7 +1033,10 @@ impl InlaySnapshot { fn check_invariants(&self) { #[cfg(any(debug_assertions, feature = "test-support"))] { - assert_eq!(self.transforms.summary().input, self.buffer.text_summary()); + assert_eq!( + self.transforms.summary().input, + self.diff_map_snapshot.text_summary() + ); let mut transforms = self.transforms.iter().peekable(); while let Some(transform) = transforms.next() { let transform_is_isomorphic = matches!(transform, Transform::Isomorphic(_)); @@ -1051,7 +1077,7 @@ fn push_isomorphic(sum_tree: &mut SumTree, summary: TextSummary) { mod tests { use super::*; use crate::{ - display_map::{InlayHighlights, TextHighlights}, + display_map::{DiffMap, InlayHighlights, TextHighlights}, hover_links::InlayHighlight, InlayId, MultiBuffer, }; @@ -1151,7 +1177,8 @@ mod tests { fn test_basic_inlays(cx: &mut AppContext) { let buffer = MultiBuffer::build_simple("abcdefghi", cx); let buffer_edits = buffer.update(cx, |buffer, _| buffer.subscribe()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + let (diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_map_snapshot.clone()); assert_eq!(inlay_snapshot.text(), "abcdefghi"); let mut next_inlay_id = 0; @@ -1165,27 +1192,27 @@ mod tests { ); assert_eq!(inlay_snapshot.text(), "abc|123|defghi"); assert_eq!( - inlay_snapshot.to_inlay_point(Point::new(0, 0)), + inlay_snapshot.make_inlay_point(Point::new(0, 0)), InlayPoint::new(0, 0) ); assert_eq!( - inlay_snapshot.to_inlay_point(Point::new(0, 1)), + inlay_snapshot.make_inlay_point(Point::new(0, 1)), InlayPoint::new(0, 1) ); assert_eq!( - inlay_snapshot.to_inlay_point(Point::new(0, 2)), + inlay_snapshot.make_inlay_point(Point::new(0, 2)), InlayPoint::new(0, 2) ); assert_eq!( - inlay_snapshot.to_inlay_point(Point::new(0, 3)), + inlay_snapshot.make_inlay_point(Point::new(0, 3)), InlayPoint::new(0, 3) ); assert_eq!( - inlay_snapshot.to_inlay_point(Point::new(0, 4)), + inlay_snapshot.make_inlay_point(Point::new(0, 4)), InlayPoint::new(0, 9) ); assert_eq!( - inlay_snapshot.to_inlay_point(Point::new(0, 5)), + inlay_snapshot.make_inlay_point(Point::new(0, 5)), InlayPoint::new(0, 10) ); assert_eq!( @@ -1217,18 +1244,26 @@ mod tests { buffer.update(cx, |buffer, cx| { buffer.edit([(2..3, "x"), (3..3, "y"), (4..4, "z")], None, cx) }); - let (inlay_snapshot, _) = inlay_map.sync( - buffer.read(cx).snapshot(cx), - buffer_edits.consume().into_inner(), - ); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + cx, + ) + }); + let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits); assert_eq!(inlay_snapshot.text(), "abxy|123|dzefghi"); // An edit surrounding the inlay should invalidate it. buffer.update(cx, |buffer, cx| buffer.edit([(4..5, "D")], None, cx)); - let (inlay_snapshot, _) = inlay_map.sync( - buffer.read(cx).snapshot(cx), - buffer_edits.consume().into_inner(), - ); + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + cx, + ) + }); + let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits); assert_eq!(inlay_snapshot.text(), "abxyDzefghi"); let (inlay_snapshot, _) = inlay_map.splice( @@ -1250,10 +1285,16 @@ mod tests { // Edits ending where the inlay starts should not move it if it has a left bias. buffer.update(cx, |buffer, cx| buffer.edit([(3..3, "JKL")], None, cx)); - let (inlay_snapshot, _) = inlay_map.sync( - buffer.read(cx).snapshot(cx), - buffer_edits.consume().into_inner(), - ); + + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync( + buffer.read(cx).snapshot(cx), + buffer_edits.consume().into_inner(), + cx, + ) + }); + + let (inlay_snapshot, _) = inlay_map.sync(diff_snapshot, diff_edits); assert_eq!(inlay_snapshot.text(), "abx|123|JKL|456|yDzefghi"); assert_eq!( @@ -1438,7 +1479,8 @@ mod tests { #[gpui::test] fn test_inlay_buffer_rows(cx: &mut AppContext) { let buffer = MultiBuffer::build_simple("abc\ndef\nghi", cx); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer.read(cx).snapshot(cx)); + let (_diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_map_snapshot); assert_eq!(inlay_snapshot.text(), "abc\ndef\nghi"); let mut next_inlay_id = 0; @@ -1464,7 +1506,10 @@ mod tests { ); assert_eq!(inlay_snapshot.text(), "|123|\nabc\n|456|def\n|567|\n\nghi"); assert_eq!( - inlay_snapshot.buffer_rows(0).collect::>(), + inlay_snapshot + .row_infos(0) + .map(|r| r.buffer_row) + .collect::>(), vec![Some(0), None, Some(1), None, None, Some(2)] ); } @@ -1489,7 +1534,8 @@ mod tests { let mut buffer_snapshot = buffer.read(cx).snapshot(cx); let mut next_inlay_id = 0; log::info!("buffer text: {:?}", buffer_snapshot.text()); - let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (diff_map, diff_map_snapshot) = DiffMap::new(buffer.clone(), cx); + let (mut inlay_map, mut inlay_snapshot) = InlayMap::new(diff_map_snapshot.clone()); for _ in 0..operations { let mut inlay_edits = Patch::default(); @@ -1512,8 +1558,12 @@ mod tests { }), }; + let (new_diff_snapshot, new_diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(buffer.read(cx).snapshot(cx), buffer_edits, cx) + }); + let (new_inlay_snapshot, new_inlay_edits) = - inlay_map.sync(buffer_snapshot.clone(), buffer_edits); + inlay_map.sync(new_diff_snapshot, new_diff_edits); inlay_snapshot = new_inlay_snapshot; inlay_edits = inlay_edits.compose(new_inlay_edits); @@ -1535,7 +1585,7 @@ mod tests { } assert_eq!(inlay_snapshot.text(), expected_text.to_string()); - let expected_buffer_rows = inlay_snapshot.buffer_rows(0).collect::>(); + let expected_buffer_rows = inlay_snapshot.row_infos(0).collect::>(); assert_eq!( expected_buffer_rows.len() as u32, expected_text.max_point().row + 1 @@ -1543,7 +1593,7 @@ mod tests { for row_start in 0..expected_buffer_rows.len() { assert_eq!( inlay_snapshot - .buffer_rows(row_start as u32) + .row_infos(row_start as u32) .collect::>(), &expected_buffer_rows[row_start..], "incorrect buffer rows starting at {}", @@ -1661,14 +1711,14 @@ mod tests { assert_eq!(expected_text.len(), inlay_snapshot.len().0); let mut buffer_point = Point::default(); - let mut inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + let mut inlay_point = inlay_snapshot.make_inlay_point(buffer_point); let mut buffer_chars = buffer_snapshot.chars_at(0); loop { // Ensure conversion from buffer coordinates to inlay coordinates // is consistent. let buffer_offset = buffer_snapshot.point_to_offset(buffer_point); assert_eq!( - inlay_snapshot.to_point(inlay_snapshot.to_inlay_offset(buffer_offset)), + inlay_snapshot.to_point(inlay_snapshot.make_inlay_offset(buffer_offset)), inlay_point ); @@ -1695,7 +1745,7 @@ mod tests { } // Ensure that moving forward in the buffer always moves the inlay point forward as well. - let new_inlay_point = inlay_snapshot.to_inlay_point(buffer_point); + let new_inlay_point = inlay_snapshot.make_inlay_point(buffer_point); assert!(new_inlay_point > inlay_point); inlay_point = new_inlay_point; } else { @@ -1755,7 +1805,7 @@ mod tests { // Ensure the clipped points are at valid buffer locations. assert_eq!( inlay_snapshot - .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_left_point)), + .to_inlay_point(inlay_snapshot.to_diff_point(clipped_left_point)), clipped_left_point, "to_buffer_point({:?}) = {:?}", clipped_left_point, @@ -1763,7 +1813,7 @@ mod tests { ); assert_eq!( inlay_snapshot - .to_inlay_point(inlay_snapshot.to_buffer_point(clipped_right_point)), + .to_inlay_point(inlay_snapshot.to_diff_point(clipped_right_point)), clipped_right_point, "to_buffer_point({:?}) = {:?}", clipped_right_point, diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 7b1793980f13a9..22e923e30eb22d 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -164,7 +164,7 @@ pub struct TabSnapshot { impl TabSnapshot { pub fn buffer_snapshot(&self) -> &MultiBufferSnapshot { - self.fold_snapshot.diff_map_snapshot.buffer() + self.fold_snapshot.inlay_snapshot.buffer() } pub fn line_len(&self, row: u32) -> u32 { @@ -323,10 +323,10 @@ impl TabSnapshot { pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { let fold_point = self.to_fold_point(point, bias).0; - let inlay_point = fold_point.to_diff_point(&self.fold_snapshot); + let inlay_point = fold_point.to_inlay_point(&self.fold_snapshot); self.fold_snapshot - .diff_map_snapshot - .to_multibuffer_point(inlay_point) + .inlay_snapshot + .to_buffer_point(inlay_point) } fn expand_tabs(&self, chars: impl Iterator, column: u32) -> u32 { @@ -609,10 +609,9 @@ mod tests { #[gpui::test] fn test_expand_tabs(cx: &mut gpui::AppContext) { let buffer = MultiBuffer::build_simple("", cx); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let (_, fold_snapshot) = FoldMap::new(diff_snapshot); + let (_, diff_snapshot) = DiffMap::new(buffer, cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!(tab_snapshot.expand_tabs("\t".chars(), 0), 0); @@ -627,10 +626,9 @@ mod tests { let output = "A BC DEF G HI J K L M"; let buffer = MultiBuffer::build_simple(input, cx); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let (_, fold_snapshot) = FoldMap::new(diff_snapshot); + let (_, diff_snapshot) = DiffMap::new(buffer, cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; @@ -675,10 +673,9 @@ mod tests { let input = "abcdefg⋯hij"; let buffer = MultiBuffer::build_simple(input, cx); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let (_, fold_snapshot) = FoldMap::new(diff_snapshot); + let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, mut tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); tab_snapshot.max_expansion_column = max_expansion_column; @@ -690,10 +687,9 @@ mod tests { let input = "\t \thello"; let buffer = MultiBuffer::build_simple(input, cx); - let buffer_snapshot = buffer.read(cx).snapshot(cx); - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); - let (_, fold_snapshot) = FoldMap::new(diff_snapshot); + let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot); + let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); assert_eq!( @@ -752,13 +748,14 @@ mod tests { let buffer_snapshot = buffer.read(cx).snapshot(cx); log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - let (_, diff_snapshot) = DiffMap::new(inlay_snapshot, buffer.clone(), cx); + let (_, diff_snapshot) = DiffMap::new(buffer.clone(), cx); + + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); log::info!("DiffMap text: {:?}", diff_snapshot.text()); - let (mut fold_map, _) = FoldMap::new(diff_snapshot.clone()); + let (mut fold_map, _) = FoldMap::new(inlay_snapshot.clone()); fold_map.randomly_mutate(&mut rng); - let (fold_snapshot, _) = fold_map.read(diff_snapshot, vec![]); + let (fold_snapshot, _) = fold_map.read(inlay_snapshot, vec![]); log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (inlay_snapshot, _) = inlay_map.randomly_mutate(&mut 0, &mut rng); log::info!("InlayMap text: {:?}", inlay_snapshot.text()); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 84afc4654bb305..d7fbec9879a725 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1216,12 +1216,11 @@ mod tests { }); let mut buffer_snapshot = buffer.read_with(cx, |buffer, cx| buffer.snapshot(cx)); log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (mut inlay_map, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); - log::info!("InlayMap text: {:?}", inlay_snapshot.text()); - let (diff_map, diff_snapshot) = - cx.update(|cx| DiffMap::new(inlay_snapshot, buffer.clone(), cx)); + let (diff_map, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); log::info!("DiffMap text: {:?}", diff_snapshot.text()); - let (mut fold_map, fold_snapshot) = FoldMap::new(diff_snapshot.clone()); + let (mut inlay_map, inlay_snapshot) = InlayMap::new(diff_snapshot); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (mut fold_map, fold_snapshot) = FoldMap::new(inlay_snapshot); log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (mut tab_map, _) = TabMap::new(fold_snapshot.clone(), tab_size); let tabs_snapshot = tab_map.set_max_expansion_column(32); @@ -1282,10 +1281,7 @@ mod tests { 40..=59 => { let (inlay_snapshot, inlay_edits) = inlay_map.randomly_mutate(&mut next_inlay_id, &mut rng); - let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) - }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); let (mut snapshot, wrap_edits) = @@ -1306,13 +1302,12 @@ mod tests { } log::info!("Buffer text: {:?}", buffer_snapshot.text()); - let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot.clone(), buffer_edits); - log::info!("InlayMap text: {:?}", inlay_snapshot.text()); let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { - diff_map.sync(inlay_snapshot, inlay_edits, cx) + diff_map.sync(buffer_snapshot.clone(), buffer_edits, cx) }); - let (fold_snapshot, fold_edits) = fold_map.read(diff_snapshot, diff_edits); + let (inlay_snapshot, inlay_edits) = inlay_map.sync(diff_snapshot, diff_edits); + log::info!("InlayMap text: {:?}", inlay_snapshot.text()); + let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); log::info!("FoldMap text: {:?}", fold_snapshot.text()); let (tabs_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); log::info!("TabMap text: {:?}", tabs_snapshot.text()); From 2f5ea92fe146c2538f46235d32f729fb235c53e7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Dec 2024 15:30:45 -0800 Subject: [PATCH 40/51] Fix bugs in DiffMap::sync Co-authored-by: Conrad --- crates/editor/src/display_map/diff_map.rs | 34 +++------------------- crates/editor/src/display_map/inlay_map.rs | 13 +++++++-- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 7a6c9ff74f5a4d..fb53eacc1faeb3 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -170,7 +170,7 @@ impl DiffMap { pub fn sync( &mut self, multibuffer_snapshot: MultiBufferSnapshot, - mut buffer_edits: Vec>, + buffer_edits: Vec>, cx: &mut ModelContext, ) -> (DiffMapSnapshot, Vec) { let changes = buffer_edits @@ -178,33 +178,7 @@ impl DiffMap { .map(|edit| (edit.clone(), ChangeKind::InputEdited)) .collect::>(); - let snapshot = &mut self.snapshot; - if buffer_edits.is_empty() - && snapshot.buffer.trailing_excerpt_update_count() - != multibuffer_snapshot.trailing_excerpt_update_count() - { - buffer_edits.push(Edit { - old: snapshot.buffer.len()..snapshot.buffer.len(), - new: multibuffer_snapshot.len()..multibuffer_snapshot.len(), - }); - } - - if buffer_edits.is_empty() { - if snapshot.buffer.edit_count() != multibuffer_snapshot.edit_count() - || snapshot.buffer.non_text_state_update_count() - != multibuffer_snapshot.non_text_state_update_count() - || snapshot.buffer.trailing_excerpt_update_count() - != multibuffer_snapshot.trailing_excerpt_update_count() - { - snapshot.version += 1; - } - - snapshot.buffer = multibuffer_snapshot; - return (snapshot.clone(), Vec::new()); - } - - snapshot.buffer = multibuffer_snapshot.clone(); - + self.snapshot.buffer = multibuffer_snapshot.clone(); self.recompute_transforms(changes, cx); ( @@ -352,14 +326,14 @@ impl DiffMap { let mut edits = Patch::default(); let mut changes = changes.into_iter().peekable(); + let mut delta = 0_isize; while let Some((mut edit, mut operation)) = changes.next() { let to_skip = cursor.slice(&edit.old.start, Bias::Right, &()); self.append_transforms(&mut new_transforms, to_skip); - let mut delta = 0_isize; let mut end_of_current_insert = 0; loop { - let multibuffer_range = edit.old.clone(); + let multibuffer_range = edit.new.clone(); let edit_old_start = cursor.start().1 + DiffOffset(edit.old.start - cursor.start().0); let mut edit_old_end = diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 2fe5d90548c8d3..6bec8ca3e1905b 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -411,10 +411,20 @@ impl InlayMap { pub fn sync( &mut self, diff_map_snapshot: DiffMapSnapshot, - diff_edits: Vec, + mut diff_edits: Vec, ) -> (InlaySnapshot, Vec) { let snapshot = &mut self.snapshot; + if diff_edits.is_empty() + && snapshot.buffer().trailing_excerpt_update_count() + != diff_map_snapshot.buffer().trailing_excerpt_update_count() + { + diff_edits.push(Edit { + old: snapshot.diff_map_snapshot.len()..snapshot.diff_map_snapshot.len(), + new: diff_map_snapshot.len()..diff_map_snapshot.len(), + }); + } + let mut inlay_edits = Patch::default(); let mut new_transforms = SumTree::default(); let mut cursor = snapshot.transforms.cursor::<(DiffOffset, InlayOffset)>(&()); @@ -593,7 +603,6 @@ impl InlayMap { if self.inlays.is_empty() || rng.gen() { let position = snapshot.buffer().random_byte_range(0, rng).start; let anchor = snapshot.buffer().anchor_at(position, Bias::Left); - let position = snapshot.diff_map_snapshot.to_diff_offset(position); let bias = if rng.gen() { Bias::Left } else { Bias::Right }; let len = if rng.gen_bool(0.01) { 0 From 87d50ee54c32bacf705d726e13720596578a53d2 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Dec 2024 16:32:27 -0800 Subject: [PATCH 41/51] Fix handling of modified empty lines in assert_state_with_diff helper --- crates/editor/src/test/editor_test_context.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index f9f371cbd2a10a..39d3eceb7b78eb 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -360,18 +360,21 @@ impl EditorTestContext { .split('\n') .zip(line_infos) .map(|(line, info)| { - let marker = match info.diff_status { + let mut marker = match info.diff_status { Some(DiffHunkStatus::Added) => "+ ", Some(DiffHunkStatus::Removed) => "- ", Some(DiffHunkStatus::Modified) => unreachable!(), None => { - if has_diff && !line.trim().is_empty() { + if has_diff { " " } else { "" } } }; + if line.is_empty() { + marker = marker.trim(); + } format!("{marker}{line}") }) .collect::>() From 5cc3bd158555b400e4d1f525f89c95144354da70 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Dec 2024 16:34:33 -0800 Subject: [PATCH 42/51] Fix collapsing of hunks when base text changes Co-authored-by: Conrad --- crates/editor/src/display_map/diff_map.rs | 62 +++++++++++++++------- crates/editor/src/display_map/inlay_map.rs | 6 +-- 2 files changed, 46 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index fb53eacc1faeb3..afd0bfcd74dd33 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -30,18 +30,19 @@ struct ChangeSetState { struct DiffSnapshot { diff: git::diff::BufferDiff, base_text: language::BufferSnapshot, + base_text_version: usize, } #[derive(Clone)] pub struct DiffMapSnapshot { diffs: TreeMap, - transforms: SumTree, + pub(crate) transforms: SumTree, pub(crate) version: usize, pub(crate) buffer: MultiBufferSnapshot, } #[derive(Debug, Clone)] -enum DiffTransform { +pub(crate) enum DiffTransform { BufferContent { summary: TextSummary, is_inserted_hunk: bool, @@ -55,7 +56,7 @@ enum DiffTransform { } #[derive(Debug, Clone)] -struct DiffTransformSummary { +pub(crate) struct DiffTransformSummary { multibuffer_map: TextSummary, diff_map: TextSummary, } @@ -97,7 +98,7 @@ pub struct DiffMapRows<'a> { pub type DiffEdit = text::Edit; enum ChangeKind { - DiffUpdated, + DiffUpdated { base_changed: bool }, InputEdited, ExpandOrCollapseHunks { range: Range, expand: bool }, } @@ -199,6 +200,13 @@ impl DiffMap { .base_text .as_ref() .map(|buffer| buffer.read(cx).snapshot()); + let base_text_version_changed = self + .snapshot + .diffs + .get(&buffer_id) + .map_or(true, |snapshot| { + snapshot.base_text_version != change_set.base_text_version + }); if let Some(base_text) = base_text.clone() { self.snapshot.diffs.insert( @@ -206,6 +214,7 @@ impl DiffMap { DiffSnapshot { diff: diff.clone(), base_text, + base_text_version: change_set.base_text_version, }, ); } else { @@ -225,7 +234,9 @@ impl DiffMap { old: multibuffer_start..multibuffer_end, new: multibuffer_start..multibuffer_end, }, - ChangeKind::DiffUpdated, + ChangeKind::DiffUpdated { + base_changed: base_text_version_changed, + }, ) }) .collect(); @@ -328,7 +339,14 @@ impl DiffMap { let mut changes = changes.into_iter().peekable(); let mut delta = 0_isize; while let Some((mut edit, mut operation)) = changes.next() { - let to_skip = cursor.slice(&edit.old.start, Bias::Right, &()); + let mut to_skip = cursor.slice(&edit.old.start, Bias::Left, &()); + while cursor.end(&()).0 < edit.old.start + || (cursor.end(&()).0 == edit.old.start && cursor.start().0 < edit.old.start) + { + to_skip.extend(cursor.item().cloned(), &()); + cursor.next(&()); + } + self.append_transforms(&mut new_transforms, to_skip); let mut end_of_current_insert = 0; @@ -411,18 +429,24 @@ impl DiffMap { let hunk_is_deletion = hunk_start_buffer_offset == hunk_end_buffer_offset; - let mut should_expand_hunk = - was_previously_expanded || self.all_hunks_expanded; - if let ChangeKind::ExpandOrCollapseHunks { range, expand } = &operation - { - let intersects = hunk_is_deletion - || (hunk_start_buffer_offset < range.end - && hunk_end_buffer_offset > range.start); - if *expand { - should_expand_hunk |= intersects; - } else { - should_expand_hunk &= !intersects; + let should_expand_hunk = match &operation { + ChangeKind::DiffUpdated { base_changed: true } => { + self.all_hunks_expanded + } + ChangeKind::ExpandOrCollapseHunks { range, expand } => { + let intersects = hunk_is_deletion + || (hunk_start_buffer_offset < range.end + && hunk_end_buffer_offset > range.start); + if *expand { + was_previously_expanded + || self.all_hunks_expanded + || intersects + } else { + !intersects + && (was_previously_expanded || self.all_hunks_expanded) + } } + _ => was_previously_expanded || self.all_hunks_expanded, }; if should_expand_hunk { @@ -1047,7 +1071,7 @@ impl DiffMapSnapshot { let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); cursor.seek(&diff_point, Bias::Right, &()); - let (diff_transform_start, inlay_transform_start) = cursor.start().clone(); + let (diff_transform_start, buffer_transform_start) = cursor.start().clone(); let overshoot = if matches!(cursor.item(), Some(DiffTransform::BufferContent { .. })) { diff_point.row() - diff_transform_start.row() @@ -1056,7 +1080,7 @@ impl DiffMapSnapshot { }; let input_buffer_rows = self .buffer - .buffer_rows(MultiBufferRow(inlay_transform_start.row + overshoot)); + .buffer_rows(MultiBufferRow(buffer_transform_start.row + overshoot)); DiffMapRows { diff_point, diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 6bec8ca3e1905b..c9634eae1cbca9 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -446,7 +446,7 @@ impl InlayMap { // Push the unchanged prefix. let prefix_start = DiffOffset(new_transforms.summary().input.len); - let prefix_end = DiffOffset(diff_edit.new.start.0); + let prefix_end = diff_edit.new.start; push_isomorphic( &mut new_transforms, diff_map_snapshot.text_summary_for_range(prefix_start..prefix_end), @@ -974,7 +974,7 @@ impl InlaySnapshot { let max_point = self.diff_map_snapshot.max_point(); let mut diff_point = cursor.start().1; - let buffer_row = if row == 0 { + let diff_row = if row == 0 { 0 } else { match cursor.item() { @@ -989,7 +989,7 @@ impl InlaySnapshot { InlayBufferRows { transforms: cursor, inlay_row: inlay_point.row(), - diff_rows: self.diff_map_snapshot.row_infos(buffer_row), + diff_rows: self.diff_map_snapshot.row_infos(diff_row), max_point, } } From 9450de73b30ff2b73c80fca9906f1660dd01118c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 16 Dec 2024 16:48:03 -0800 Subject: [PATCH 43/51] Remove more of the old DiffMap --- crates/editor/src/display_map.rs | 7 ++- crates/editor/src/display_map/diff_map.rs | 9 +++- crates/editor/src/editor.rs | 1 + crates/editor/src/hunk_diff.rs | 60 ++--------------------- 4 files changed, 18 insertions(+), 59 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 0927bbedb04f0a..8525a3a6b9fed1 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -42,7 +42,8 @@ pub use fold_map::{Fold, FoldId, FoldPlaceholder, FoldPoint}; use fold_map::{FoldMap, FoldSnapshot}; use git::diff::DiffHunkStatus; use gpui::{ - AnyElement, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, UnderlineStyle, + AnyElement, AppContext, Font, HighlightStyle, LineLayout, Model, ModelContext, Pixels, + UnderlineStyle, }; pub(crate) use inlay_map::Inlay; use inlay_map::{InlayMap, InlaySnapshot}; @@ -391,6 +392,10 @@ impl DisplayMap { }); } + pub fn has_multiple_hunks(&self, cx: &AppContext) -> bool { + self.diff_map.read(cx).has_multiple_hunks() + } + pub fn has_expanded_diff_hunks_in_ranges( &mut self, ranges: &[Range], diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index afd0bfcd74dd33..a99659e500ff23 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -243,6 +243,13 @@ impl DiffMap { self.recompute_transforms(changes, cx); } + pub(super) fn has_multiple_hunks(&self) -> bool { + self.snapshot + .diff_hunks_in_range(Anchor::min()..Anchor::max()) + .nth(1) + .is_some() + } + pub(super) fn has_expanded_diff_hunks_in_ranges( &self, ranges: &[Range], @@ -386,7 +393,7 @@ impl DiffMap { excerpt_buffer_range.start.to_offset(buffer); let hunk_start_multibuffer_offset = excerpt_range.start + hunk_start_buffer_offset - - excerpt_buffer_range_start_offset; + .saturating_sub(excerpt_buffer_range_start_offset); self.push_buffer_content_transform( &mut new_transforms, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 3adf2105e32200..036aa882e35d8d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -13024,6 +13024,7 @@ pub fn hunks_for_ranges( let query_rows = MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1); for hunk in snapshot + .diff_snapshot() .diff_hunks_in_range(Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0)) { // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index 11ec893837d3d8..ca0edb64e4d803 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -7,11 +7,10 @@ use gpui::{ use language::{Buffer, BufferId, Point}; use multi_buffer::{ Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, - MultiBufferSnapshot, ToOffset, ToPoint, + MultiBufferSnapshot, ToPoint, }; use project::buffer_store::BufferChangeSet; use std::{ops::Range, sync::Arc}; -use sum_tree::TreeMap; use text::OffsetRangeExt; use ui::{ prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement, @@ -38,7 +37,6 @@ pub(super) struct HoveredHunk { pub(super) struct DiffMap { pub(crate) hunks: Vec, pub(crate) diff_bases: HashMap, - pub(crate) snapshot: DiffMapSnapshot, hunk_update_tasks: HashMap, Task<()>>, expand_all: bool, } @@ -52,9 +50,6 @@ pub(super) struct ExpandedHunk { pub folded: bool, } -#[derive(Clone, Debug, Default)] -pub(crate) struct DiffMapSnapshot(TreeMap); - pub(crate) struct DiffBaseState { pub(crate) change_set: Model, pub(crate) last_version: Option, @@ -75,54 +70,7 @@ pub enum DisplayDiffHunk { }, } -impl DiffMapSnapshot { - pub fn diff_hunks<'a>( - &'a self, - buffer_snapshot: &'a MultiBufferSnapshot, - ) -> impl Iterator + 'a { - self.diff_hunks_in_range(0..buffer_snapshot.len(), buffer_snapshot) - } - - pub fn diff_hunks_in_range<'a, T: ToOffset>( - &'a self, - range: Range, - buffer_snapshot: &'a MultiBufferSnapshot, - ) -> impl Iterator + 'a { - let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot); - buffer_snapshot - .excerpts_for_range(range.clone()) - .filter_map(move |excerpt| { - let buffer = excerpt.buffer(); - let buffer_id = buffer.remote_id(); - let diff = self.0.get(&buffer_id)?; - let buffer_range = excerpt.map_range_to_buffer(range.clone()); - let buffer_range = - buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); - Some( - diff.hunks_intersecting_range(buffer_range, excerpt.buffer()) - .map(move |hunk| { - let start = - excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0)); - let end = - excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0)); - MultiBufferDiffHunk { - row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row), - buffer_id, - buffer_range: hunk.buffer_range.clone(), - diff_base_byte_range: hunk.diff_base_byte_range.clone(), - } - }), - ) - }) - .flatten() - } -} - impl Editor { - // pub fn set_expand_all_diff_hunks(&mut self) { - // self.diff_map.expand_all = true; - // } - pub(super) fn toggle_hovered_hunk( &mut self, hovered_hunk: &HoveredHunk, @@ -402,10 +350,8 @@ impl Editor { } } - fn has_multiple_hunks(&self, cx: &AppContext) -> bool { - let snapshot = self.buffer.read(cx).snapshot(cx); - let mut hunks = self.diff_map.snapshot.diff_hunks(&snapshot); - hunks.nth(1).is_some() + fn has_multiple_hunks(&self, cx: &mut WindowContext) -> bool { + self.display_map.read(cx).has_multiple_hunks(cx) } fn hunk_header_block( From c160ce6a2453c03c8a7e5ee1a98654fc5819da0c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Dec 2024 21:30:28 -0700 Subject: [PATCH 44/51] handle diff chunks with no trailing newline --- crates/editor/src/display_map/diff_map.rs | 172 +++++++++++++++++++--- crates/rope/src/rope.rs | 8 + 2 files changed, 159 insertions(+), 21 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index a99659e500ff23..01a04faadca9b2 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -9,7 +9,12 @@ use multi_buffer::{ MultiBufferSnapshot, ToOffset, ToPoint, }; use project::buffer_store::BufferChangeSet; -use std::{any::TypeId, mem, ops::Range, sync::Arc}; +use std::{ + any::TypeId, + mem::{self}, + ops::Range, + sync::Arc, +}; use sum_tree::{Cursor, SumTree, TreeMap}; use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; @@ -48,10 +53,11 @@ pub(crate) enum DiffTransform { is_inserted_hunk: bool, }, DeletedHunk { - summary: TextSummary, + summary_including_newline: TextSummary, buffer_id: BufferId, base_text_byte_range: Range, base_text_start: Point, + needs_newline: bool, }, } @@ -460,6 +466,20 @@ impl DiffMap { if hunk.diff_base_byte_range.len() > 0 && hunk_start_buffer_offset >= change_start_buffer_offset { + let mut text_cursor = base_text.as_rope().cursor(0); + let base_text_start = text_cursor + .summary::(hunk.diff_base_byte_range.start); + let mut base_text_summary = text_cursor + .summary::(hunk.diff_base_byte_range.end); + let mut needs_newline = false; + let mut diff_byte_range = + DiffOffset(hunk.diff_base_byte_range.len()); + if base_text_summary.last_line_chars > 0 { + diff_byte_range.0 += 1; + base_text_summary.add_newline(); + needs_newline = true; + } + if !was_previously_expanded { let hunk_overshoot = hunk_start_multibuffer_offset - cursor.start().0; @@ -467,9 +487,8 @@ impl DiffMap { cursor.start().1 + DiffOffset(hunk_overshoot); let new_start = DiffOffset(new_transforms.summary().diff_map.len); - let new_end = - new_start + DiffOffset(hunk.diff_base_byte_range.len()); - delta += hunk.diff_base_byte_range.len() as isize; + let new_end = new_start + diff_byte_range; + delta += diff_byte_range.0 as isize; let edit = Edit { old: old_offset..old_offset, new: new_start..new_end, @@ -477,17 +496,13 @@ impl DiffMap { edits.push(edit); } - let mut text_cursor = base_text.as_rope().cursor(0); - let base_text_start = text_cursor - .summary::(hunk.diff_base_byte_range.start); - let base_text_summary = text_cursor - .summary::(hunk.diff_base_byte_range.end); new_transforms.push( DiffTransform::DeletedHunk { base_text_byte_range: hunk.diff_base_byte_range.clone(), - summary: base_text_summary, + summary_including_newline: base_text_summary, buffer_id, base_text_start, + needs_newline, }, &(), ); @@ -821,18 +836,30 @@ impl DiffMapSnapshot { DiffTransform::DeletedHunk { buffer_id, base_text_byte_range, + needs_newline, .. } => { let buffer_start = base_text_byte_range.start + (diff_start - diff_transform_start).0; - let buffer_end = base_text_byte_range.start + (diff_end - diff_transform_start).0; + let mut buffer_end = + base_text_byte_range.start + (diff_end - diff_transform_start).0; let Some(buffer_diff) = self.diffs.get(buffer_id) else { panic!("{:?} is in non-extant deleted hunk", range.start) }; - buffer_diff + if *needs_newline && diff_end == diff_transform_end { + buffer_end -= 1; + } + + let mut summary = buffer_diff .base_text - .text_summary_for_range(buffer_start..buffer_end) + .text_summary_for_range::(buffer_start..buffer_end); + + if *needs_newline && diff_end == diff_transform_end { + summary.add_newline(); + } + + summary } }; if range.end < diff_transform_end { @@ -860,6 +887,7 @@ impl DiffMapSnapshot { DiffTransform::DeletedHunk { base_text_byte_range, buffer_id, + needs_newline, .. } => { let buffer_end = base_text_byte_range.start + (range.end - diff_transform_start).0; @@ -867,9 +895,17 @@ impl DiffMapSnapshot { panic!("{:?} is in non-extant deleted hunk", range.end) }; - buffer_diff + let mut result = buffer_diff .base_text - .text_summary_for_range(base_text_byte_range.start..buffer_end) + .text_summary_for_range::( + base_text_byte_range.start..buffer_end, + ); + + if *needs_newline && buffer_end == base_text_byte_range.end + 1 { + result.add_newline(); + } + + result } }; @@ -1163,6 +1199,7 @@ impl<'a> Iterator for DiffMapChunks<'a> { DiffTransform::DeletedHunk { buffer_id, base_text_byte_range, + needs_newline, .. } => { let hunk_start_offset = self.cursor.start().0; @@ -1190,9 +1227,18 @@ impl<'a> Iterator for DiffMapChunks<'a> { ) }; - let chunk = chunks.next()?; - self.offset.0 += chunk.text.len(); - self.diff_base_chunks = Some((*buffer_id, chunks)); + let chunk = if let Some(chunk) = chunks.next() { + self.offset.0 += chunk.text.len(); + self.diff_base_chunks = Some((*buffer_id, chunks)); + chunk + } else { + debug_assert!(needs_newline); + self.offset.0 += "\n".len(); + Chunk { + text: "\n", + ..Default::default() + } + }; Some(chunk) } } @@ -1261,7 +1307,10 @@ impl sum_tree::Item for DiffTransform { multibuffer_map: summary.clone(), diff_map: summary.clone(), }, - DiffTransform::DeletedHunk { summary, .. } => DiffTransformSummary { + DiffTransform::DeletedHunk { + summary_including_newline: summary, + .. + } => DiffTransformSummary { multibuffer_map: TextSummary::default(), diff_map: summary.clone(), }, @@ -1806,6 +1855,87 @@ mod tests { ); } + #[gpui::test] + fn test_expand_no_newline(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = "todo"; + let text = indoc!( + " + one + two + " + ); + + let (diff_map, mut diff_snapshot, deps) = build_diff_map(text, Some(base_text), cx); + assert_eq!(diff_snapshot.text(), "one\ntwo\n"); + assert_eq!( + diff_snapshot + .row_infos(0) + .map(|info| info.buffer_row) + .collect::>(), + [Some(0), Some(1), Some(2)] + ); + + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut diff_snapshot, + sync, + indoc!( + " + - todo + + one + + two + " + ), + ); + + assert_eq!(diff_snapshot.text(), "todo\none\ntwo\n"); + + assert_eq!( + diff_snapshot + .row_infos(0) + .map(|info| info.buffer_row) + .collect::>(), + [None, Some(0), Some(1), Some(2)] + ); + assert_eq!( + diff_snapshot + .row_infos(1) + .map(|info| info.buffer_row) + .collect::>(), + [Some(0), Some(1), Some(2)] + ); + + for (point, offset) in [ + (DiffPoint::new(0, 4), DiffOffset(4)), + (DiffPoint::new(1, 0), DiffOffset(5)), + (DiffPoint::new(1, 1), DiffOffset(6)), + ] { + assert_eq!(diff_snapshot.offset_to_point(offset), point); + assert_eq!(diff_snapshot.point_to_offset(point), offset); + } + + assert_eq!( + diff_snapshot.clip_point(DiffPoint::new(0, 5), Bias::Left), + DiffPoint::new(0, 4) + ); + assert_eq!( + diff_snapshot.to_diff_point(Point::new(0, 0)), + DiffPoint::new(1, 0) + ); + assert_eq!( + diff_snapshot.to_multibuffer_point(DiffPoint::new(0, 4)), + Point::new(0, 0) + ); + } + #[gpui::test] fn test_expand_collapse_at_positions_adjacent_to_hunks(cx: &mut TestAppContext) { cx.update(init_test); @@ -2106,7 +2236,7 @@ mod tests { ); } - #[track_caller] + // #[track_caller] fn assert_new_snapshot( snapshot: &mut DiffMapSnapshot, (new_snapshot, edits): (DiffMapSnapshot, Vec>), diff --git a/crates/rope/src/rope.rs b/crates/rope/src/rope.rs index 89cb1e7b6319f3..4e3323019095bc 100644 --- a/crates/rope/src/rope.rs +++ b/crates/rope/src/rope.rs @@ -989,6 +989,14 @@ impl TextSummary { column: self.last_line_len_utf16, } } + + pub fn add_newline(&mut self) { + self.len += 1; + self.len_utf16 += OffsetUtf16(self.len_utf16.0 + 1); + self.last_line_chars = 0; + self.last_line_len_utf16 = 0; + self.lines += Point::new(1, 0); + } } impl<'a> From<&'a str> for TextSummary { From d600c58d3c76bc3db25984d2872ced172b550f2c Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Dec 2024 22:06:41 -0700 Subject: [PATCH 45/51] fix test toggle diff hunks (again) --- crates/editor/src/display_map/diff_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 01a04faadca9b2..daf4ee67f4799e 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -321,7 +321,7 @@ impl DiffMap { let multibuffer_end = multibuffer_snapshot.point_to_offset(Point::new(multibuffer_end.row + 1, 0)); let expanded_start = multibuffer_start.saturating_sub(1); - let expanded_end = multibuffer_snapshot.len().min(multibuffer_end + 1); + let expanded_end = multibuffer_snapshot.len().min(multibuffer_end); changes.push(( text::Edit { old: expanded_start..expanded_end, From d1239f6cdbe0e3ad9c6b952bde8758bf4627ab0b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Dec 2024 22:32:36 -0700 Subject: [PATCH 46/51] fix test_edits_around_expanded_deletion_hunks --- crates/editor/src/display_map/diff_map.rs | 29 +++++++++++++++++------ crates/editor/src/editor_tests.rs | 20 ++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index daf4ee67f4799e..574b6229651428 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -103,6 +103,7 @@ pub struct DiffMapRows<'a> { pub type DiffEdit = text::Edit; +#[derive(Debug)] enum ChangeKind { DiffUpdated { base_changed: bool }, InputEdited, @@ -429,13 +430,23 @@ impl DiffMap { } let mut was_previously_expanded = false; + let mut previous_expanded_summary = TextSummary::default(); if cursor.start().0 == hunk_start_multibuffer_offset { - was_previously_expanded = match cursor.item() { - Some(DiffTransform::DeletedHunk { .. }) => true, + match cursor.item() { + Some(DiffTransform::DeletedHunk { + summary_including_newline, + .. + }) => { + was_previously_expanded = true; + previous_expanded_summary = + summary_including_newline.clone(); + } Some(DiffTransform::BufferContent { is_inserted_hunk, .. - }) => *is_inserted_hunk, - None => false, + }) => { + was_previously_expanded = *is_inserted_hunk; + } + None => {} }; } @@ -480,17 +491,21 @@ impl DiffMap { needs_newline = true; } - if !was_previously_expanded { + if !was_previously_expanded + || base_text_summary != previous_expanded_summary + { let hunk_overshoot = hunk_start_multibuffer_offset - cursor.start().0; - let old_offset = + let old_start = cursor.start().1 + DiffOffset(hunk_overshoot); + let old_end = + old_start + DiffOffset(previous_expanded_summary.len); let new_start = DiffOffset(new_transforms.summary().diff_map.len); let new_end = new_start + diff_byte_range; delta += diff_byte_range.0 as isize; let edit = Edit { - old: old_offset..old_offset, + old: old_start..old_end, new: new_start..new_end, }; edits.push(edit); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index c107d444921846..9fc70eaf6eb277 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -12601,7 +12601,7 @@ async fn test_edits_around_expanded_insertion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12624,7 +12624,7 @@ async fn test_edits_around_expanded_insertion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12648,7 +12648,7 @@ async fn test_edits_around_expanded_insertion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12673,7 +12673,7 @@ async fn test_edits_around_expanded_insertion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12699,7 +12699,7 @@ async fn test_edits_around_expanded_insertion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12720,7 +12720,7 @@ async fn test_edits_around_expanded_insertion_hunks( println!("world"); } - "# + "# .unindent(), ); } @@ -12792,7 +12792,7 @@ async fn test_edits_around_expanded_deletion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12815,7 +12815,7 @@ async fn test_edits_around_expanded_deletion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12838,7 +12838,7 @@ async fn test_edits_around_expanded_deletion_hunks( println!("world"); } - "# + "# .unindent(), ); @@ -12862,7 +12862,7 @@ async fn test_edits_around_expanded_deletion_hunks( println!("world"); } - "# + "# .unindent(), ); } From 2b68817ed82d4188fcd892e898831cb10d0d24e3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 16 Dec 2024 23:11:16 -0700 Subject: [PATCH 47/51] Fix test_{addition,deletion,modification}_reverts --- crates/editor/src/display_map.rs | 10 +- crates/editor/src/display_map/diff_map.rs | 10 +- crates/editor/src/editor.rs | 123 ++++++++++------------ crates/editor/src/hunk_diff.rs | 9 +- 4 files changed, 75 insertions(+), 77 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 8525a3a6b9fed1..41c34d7a1f2df8 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -71,7 +71,7 @@ use std::{ }; use sum_tree::{Bias, TreeMap}; use tab_map::{TabMap, TabSnapshot}; -use text::LineIndent; +use text::{BufferId, LineIndent}; use ui::{px, SharedString, WindowContext}; use unicode_segmentation::UnicodeSegmentation; use wrap_map::{WrapMap, WrapSnapshot}; @@ -418,6 +418,14 @@ impl DisplayMap { self.update_diff_map(cx, |diff_map, cx| diff_map.collapse_diff_hunks(ranges, cx)) } + pub fn diff_base_for<'a>( + &'a self, + buffer_id: BufferId, + cx: &'a AppContext, + ) -> Option<&'a Model> { + self.diff_map.read(cx).diff_base_for(buffer_id) + } + fn update_diff_map( &mut self, cx: &mut ModelContext, diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 574b6229651428..99293d5e35f0fc 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -27,7 +27,7 @@ pub(crate) struct DiffMap { } struct ChangeSetState { - _change_set: Model, + change_set: Model, _subscription: Subscription, } @@ -170,11 +170,17 @@ impl DiffMap { buffer_id, ChangeSetState { _subscription: cx.observe(&change_set, Self::buffer_diff_changed), - _change_set: change_set, + change_set, }, ); } + pub fn diff_base_for(&self, buffer_id: BufferId) -> Option<&Model> { + self.diff_bases + .get(&buffer_id) + .map(|state| &state.change_set) + } + pub fn sync( &mut self, multibuffer_snapshot: MultiBufferSnapshot, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 036aa882e35d8d..009f96aeeb2620 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5793,10 +5793,9 @@ impl Editor { pub fn revert_file(&mut self, _: &RevertFile, cx: &mut ViewContext) { let mut revert_changes = HashMap::default(); let snapshot = self.snapshot(cx); - for hunk in hunks_for_ranges( - Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter(), - &snapshot, - ) { + for hunk in snapshot + .hunks_for_ranges(Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter()) + { self.prepare_revert_change(&mut revert_changes, &hunk, cx); } if !revert_changes.is_empty() { @@ -5814,7 +5813,12 @@ impl Editor { } pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext) { - let revert_changes = self.gather_revert_changes(&self.selections.all(cx), cx); + let selections = self.selections.all(cx).into_iter().map(|s| s.range()); + let mut revert_changes = HashMap::default(); + let snapshot = self.snapshot(cx); + for hunk in &snapshot.hunks_for_ranges(selections) { + self.prepare_revert_change(&mut revert_changes, &hunk, cx); + } if !revert_changes.is_empty() { self.transact(cx, |editor, cx| { editor.revert(revert_changes, cx); @@ -5851,28 +5855,18 @@ impl Editor { } } - fn gather_revert_changes( - &mut self, - selections: &[Selection], - cx: &mut ViewContext<'_, Editor>, - ) -> HashMap, Rope)>> { - let mut revert_changes = HashMap::default(); - let snapshot = self.snapshot(cx); - for hunk in hunks_for_selections(&snapshot, selections) { - self.prepare_revert_change(&mut revert_changes, &hunk, cx); - } - revert_changes - } - pub fn prepare_revert_change( &mut self, revert_changes: &mut HashMap, Rope)>>, hunk: &MultiBufferDiffHunk, - cx: &AppContext, + cx: &mut WindowContext, ) -> Option<()> { + let change_set = self + .display_map + .read(cx) + .diff_base_for(hunk.buffer_id, cx)?; let buffer = self.buffer.read(cx).buffer(hunk.buffer_id)?; let buffer = buffer.read(cx); - let change_set = &self.diff_map.diff_bases.get(&hunk.buffer_id)?.change_set; let original_text = change_set .read(cx) .base_text @@ -13003,56 +12997,6 @@ fn test_wrap_with_prefix() { ); } -fn hunks_for_selections( - snapshot: &EditorSnapshot, - selections: &[Selection], -) -> Vec { - hunks_for_ranges( - selections.iter().map(|selection| selection.range()), - snapshot, - ) -} - -pub fn hunks_for_ranges( - ranges: impl Iterator>, - snapshot: &EditorSnapshot, -) -> Vec { - let mut hunks = Vec::new(); - let mut processed_buffer_rows: HashMap>> = - HashMap::default(); - for query_range in ranges { - let query_rows = - MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1); - for hunk in snapshot - .diff_snapshot() - .diff_hunks_in_range(Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0)) - { - // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it - // when the caret is just above or just below the deleted hunk. - let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed; - let related_to_selection = if allow_adjacent { - hunk.row_range.overlaps(&query_rows) - || hunk.row_range.start == query_rows.end - || hunk.row_range.end == query_rows.start - } else { - hunk.row_range.overlaps(&query_rows) - }; - if related_to_selection { - if !processed_buffer_rows - .entry(hunk.buffer_id) - .or_default() - .insert(hunk.buffer_range.start..hunk.buffer_range.end) - { - continue; - } - hunks.push(hunk); - } - } - } - - hunks -} - pub trait CollaborationHub { fn collaborators<'a>(&self, cx: &'a AppContext) -> &'a HashMap; fn user_participant_indices<'a>( @@ -13601,6 +13545,45 @@ impl EditorSnapshot { }) } + pub fn hunks_for_ranges( + &self, + ranges: impl Iterator>, + ) -> Vec { + let mut hunks = Vec::new(); + let mut processed_buffer_rows: HashMap>> = + HashMap::default(); + for query_range in ranges { + let query_rows = + MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1); + for hunk in self.diff_snapshot().diff_hunks_in_range( + Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0), + ) { + // Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it + // when the caret is just above or just below the deleted hunk. + let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed; + let related_to_selection = if allow_adjacent { + hunk.row_range.overlaps(&query_rows) + || hunk.row_range.start == query_rows.end + || hunk.row_range.end == query_rows.start + } else { + hunk.row_range.overlaps(&query_rows) + }; + if related_to_selection { + if !processed_buffer_rows + .entry(hunk.buffer_id) + .or_default() + .insert(hunk.buffer_range.start..hunk.buffer_range.end) + { + continue; + } + hunks.push(hunk); + } + } + } + + hunks + } + pub fn language_at(&self, position: T) -> Option<&Arc> { self.display_snapshot.buffer_snapshot.language_at(position) } diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index ca0edb64e4d803..e00deb67f98393 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -20,9 +20,9 @@ use util::RangeExt; use workspace::Item; use crate::{ - editor_settings::CurrentLineHighlight, hunk_status, hunks_for_selections, ApplyAllDiffHunks, - ApplyDiffHunk, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, - DisplayRow, DisplaySnapshot, Editor, EditorElement, GoToHunk, GoToPrevHunk, RevertFile, + editor_settings::CurrentLineHighlight, hunk_status, ApplyAllDiffHunks, ApplyDiffHunk, + BlockPlacement, BlockProperties, BlockStyle, CustomBlockId, DiffRowHighlight, DisplayRow, + DisplaySnapshot, Editor, EditorElement, GoToHunk, GoToPrevHunk, RevertFile, RevertSelectedHunks, ToDisplayPoint, ToggleHunkDiff, }; @@ -326,7 +326,8 @@ impl Editor { cx: &mut ViewContext, ) { let snapshot = self.snapshot(cx); - let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx)); + let ranges = self.selections.all(cx).into_iter().map(|s| s.range()); + let hunks = snapshot.hunks_for_ranges(ranges); let mut ranges_by_buffer = HashMap::default(); self.transact(cx, |editor, cx| { for hunk in hunks { From 0b3715ae44c671bb8f878418d82e0041ea6de082 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Dec 2024 12:20:54 -0700 Subject: [PATCH 48/51] Add DisplayAnchor --- crates/editor/src/display_map.rs | 59 ++++++++++++++ crates/editor/src/display_map/diff_map.rs | 95 ++++++++++++++++++++++- 2 files changed, 151 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 41c34d7a1f2df8..52758ad6cbc7e4 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -920,6 +920,12 @@ impl DisplaySnapshot { fold_point.to_inlay_point(&self.fold_snapshot) } + fn diff_point_to_display_point(&self, diff_point: DiffPoint, bias: Bias) -> DisplayPoint { + let inlay_point = self.inlay_snapshot.to_inlay_point(diff_point); + let fold_point = self.fold_snapshot.to_fold_point(inlay_point, bias); + self.fold_point_to_display_point(fold_point) + } + fn display_point_to_diff_point(&self, point: DisplayPoint, bias: Bias) -> DiffPoint { self.inlay_snapshot .to_diff_point(self.display_point_to_inlay_point(point, bias)) @@ -1162,6 +1168,22 @@ impl DisplaySnapshot { }) } + pub fn anchor_before(&self, point: DisplayPoint) -> DisplayAnchor { + let diff_point = self.display_point_to_diff_point(point, Bias::Left); + self.diff_snapshot().point_to_anchor(diff_point, Bias::Left) + } + + pub fn anchor_after(&self, point: DisplayPoint) -> DisplayAnchor { + let diff_point = self.display_point_to_diff_point(point, Bias::Left); + self.diff_snapshot() + .point_to_anchor(diff_point, Bias::Right) + } + + pub fn anchor_to_point(&self, anchor: DisplayAnchor) -> DisplayPoint { + let diff_point = self.diff_snapshot().anchor_to_point(anchor); + self.diff_point_to_display_point(diff_point, Bias::Left) + } + pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { let mut clipped = self.block_snapshot.clip_point(point.0, bias); if self.clip_at_line_ends { @@ -1411,6 +1433,43 @@ impl DisplaySnapshot { } } +#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] +pub struct DisplayAnchor { + pub anchor: multi_buffer::Anchor, + pub diff_base_anchor: Option, +} + +impl DisplayAnchor { + pub fn min() -> Self { + Self { + anchor: multi_buffer::Anchor::min(), + diff_base_anchor: None, + } + } + + pub fn max() -> Self { + Self { + anchor: multi_buffer::Anchor::max(), + diff_base_anchor: None, + } + } +} + +pub trait DisplayCoordinate { + fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor; +} + +impl DisplayCoordinate for DisplayAnchor { + fn to_display_anchor(self, _: &DisplaySnapshot) -> DisplayAnchor { + self + } +} +impl DisplayCoordinate for DisplayPoint { + fn to_display_anchor(self, map: &DisplaySnapshot) -> DisplayAnchor { + map.anchor_before(self) + } +} + #[derive(Copy, Clone, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct DisplayPoint(BlockPoint); diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 99293d5e35f0fc..9fbfab1c7f53c7 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1,4 +1,4 @@ -use super::custom_highlights::CustomHighlightsChunks; +use super::{custom_highlights::CustomHighlightsChunks, DisplayAnchor}; use crate::RowInfo; use collections::HashMap; use git::diff::DiffHunkStatus; @@ -6,7 +6,7 @@ use gpui::{AppContext, Context as _, HighlightStyle, Model, ModelContext, Subscr use language::{BufferChunks, BufferId, Chunk}; use multi_buffer::{ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferRows, - MultiBufferSnapshot, ToOffset, ToPoint, + MultiBufferSnapshot, ToOffset, ToPoint as _, }; use project::buffer_store::BufferChangeSet; use std::{ @@ -16,7 +16,8 @@ use std::{ sync::Arc, }; use sum_tree::{Cursor, SumTree, TreeMap}; -use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _}; +use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _, ToPoint as _}; +use util::debug_panic; pub(crate) struct DiffMap { snapshot: DiffMapSnapshot, @@ -1040,6 +1041,83 @@ impl DiffMapSnapshot { } } + pub fn point_to_anchor(&self, point: DiffPoint, bias: Bias) -> DisplayAnchor { + let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); + cursor.seek(&point, Bias::Right, &()); + let (diff_start, multibuffer_start) = *cursor.start(); + match cursor.item() { + Some(DiffTransform::BufferContent { .. }) => { + let multibuffer_point = multibuffer_start + (point - diff_start).0; + DisplayAnchor { + anchor: self.buffer.anchor_at(multibuffer_point, bias), + diff_base_anchor: None, + } + } + Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + .. + }) => { + let diff_base_point = *base_text_start + (point - diff_start).0; + let diff_base_anchor = if let Some(diff_base_snapshot) = self.diffs.get(&buffer_id) + { + Some( + diff_base_snapshot + .base_text + .anchor_at(diff_base_point, bias), + ) + } else { + debug_panic!("{} is missing diff base", buffer_id); + None + }; + + DisplayAnchor { + anchor: self.buffer.anchor_at(multibuffer_start, Bias::Left), + diff_base_anchor, + } + } + None => { + panic!("{:?} is out of range", point) + } + } + } + + pub fn anchor_to_point(&self, anchor: DisplayAnchor) -> DiffPoint { + let multibuffer_point = anchor.anchor.to_point(&self.buffer); + + let mut cursor = self.transforms.cursor::<(Point, DiffPoint)>(&()); + cursor.seek(&multibuffer_point, Bias::Left, &()); + + if let Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + .. + }) = cursor.item() + { + if let Some(diff_base_anchor) = anchor.diff_base_anchor { + if let Some(diff_base_snapshot) = self.diffs.get(&buffer_id) { + if diff_base_anchor.buffer_id == Some(diff_base_snapshot.base_text.remote_id()) + { + let (_, diff_start) = *cursor.start(); + let base_text_point = + diff_base_anchor.to_point(&diff_base_snapshot.base_text); + return diff_start + DiffPoint(base_text_point - base_text_start); + } + } + } else { + cursor.next(&()) + } + } + + debug_assert!(matches!( + cursor.item(), + Some(DiffTransform::BufferContent { .. }) + )); + + let (multibuffer_start, diff_start) = *cursor.start(); + diff_start + DiffPoint(multibuffer_point - multibuffer_start) + } + pub fn to_multibuffer_offset(&self, offset: DiffOffset) -> usize { let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); cursor.seek(&offset, Bias::Right, &()); @@ -1943,6 +2021,17 @@ mod tests { assert_eq!(diff_snapshot.point_to_offset(point), offset); } + for point in [ + DiffPoint::new(0, 4), + DiffPoint::new(1, 0), + DiffPoint::new(1, 1), + ] { + let anchor = diff_snapshot.point_to_anchor(point, Bias::Left); + dbg!(&anchor); + let actual = diff_snapshot.anchor_to_point(anchor); + assert_eq!(point, actual); + } + assert_eq!( diff_snapshot.clip_point(DiffPoint::new(0, 5), Bias::Left), DiffPoint::new(0, 4) From 2cca833d7b594b9a80f2b41a52ef1e2bb9834ca8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Dec 2024 12:50:36 -0700 Subject: [PATCH 49/51] Add cmp --- crates/editor/src/display_map.rs | 4 ++++ crates/editor/src/display_map/diff_map.rs | 27 +++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 52758ad6cbc7e4..dc605d6ecf3cbb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1453,6 +1453,10 @@ impl DisplayAnchor { diff_base_anchor: None, } } + + pub fn cmp(&self, other: &DisplayAnchor, map: &DisplaySnapshot) -> std::cmp::Ordering { + map.diff_snapshot().compare_anchors(self, other) + } } pub trait DisplayCoordinate { diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index 9fbfab1c7f53c7..a494a2de993302 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1118,6 +1118,33 @@ impl DiffMapSnapshot { diff_start + DiffPoint(multibuffer_point - multibuffer_start) } + pub fn compare_anchors(&self, a: &DisplayAnchor, b: &DisplayAnchor) -> std::cmp::Ordering { + let buffer_cmp = a.anchor.cmp(&b.anchor, self.buffer()); + if buffer_cmp != std::cmp::Ordering::Equal { + return buffer_cmp; + } + + match (a.diff_base_anchor.is_some(), b.diff_base_anchor.is_some()) { + (false, true) => return std::cmp::Ordering::Greater, + (true, false) => return std::cmp::Ordering::Less, + (false, false) => return std::cmp::Ordering::Equal, + (true, true) => {} + } + + let diff_anchor_a = a.diff_base_anchor.unwrap(); + let diff_anchor_b = b.diff_base_anchor.unwrap(); + + if diff_anchor_a.buffer_id != diff_anchor_b.buffer_id { + return std::cmp::Ordering::Equal; + } + + let Some(diff_base_snapshot) = self.diffs.get(diff_anchor_a.buffer_id) else { + return std::cmp::Ordering::Equal; + }; + + diff_anchor_a.cmp(&diff_anchor_b, &diff_base_snapshot.base_text) + } + pub fn to_multibuffer_offset(&self, offset: DiffOffset) -> usize { let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); cursor.seek(&offset, Bias::Right, &()); From 7adcf903cccbf0a6373bd73128819725635771ee Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Dec 2024 14:55:25 -0700 Subject: [PATCH 50/51] fix merge conflicts --- crates/editor/src/display_map.rs | 6 +++ crates/editor/src/display_map/block_map.rs | 46 +++++++++++++++++----- crates/editor/src/display_map/diff_map.rs | 4 +- crates/editor/src/display_map/inlay_map.rs | 9 +---- crates/editor/src/editor.rs | 2 +- crates/editor/src/element.rs | 4 +- crates/multi_buffer/src/multi_buffer.rs | 1 + 7 files changed, 48 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 79054688c7d265..58d1b2f4f06ce8 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -368,6 +368,9 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); @@ -382,6 +385,9 @@ impl DisplayMap { let snapshot = self.buffer.read(cx).snapshot(cx); let edits = self.buffer_subscription.consume().into_inner(); let tab_size = Self::tab_size(&self.buffer, cx); + let (snapshot, edits) = self + .diff_map + .update(cx, |diff_map, cx| diff_map.sync(snapshot, edits, cx)); let (snapshot, edits) = self.inlay_map.sync(snapshot, edits); let (snapshot, edits) = self.fold_map.read(snapshot, edits); let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 40e138f6804c08..8648f6aab4029c 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -2542,7 +2542,8 @@ mod tests { let buffer_id_2 = buffer_ids[1]; let buffer_id_3 = buffer_ids[2]; - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wrap_snapshot) = @@ -2555,7 +2556,10 @@ mod tests { "\n\n\n111\n\n\n\n\n222\n\n\n333\n\n\n444\n\n\n\n\n555\n\n\n666\n" ); assert_eq!( - blocks_snapshot.buffer_rows(BlockRow(0)).collect::>(), + blocks_snapshot + .row_infos(BlockRow(0)) + .map(|i| i.buffer_row) + .collect::>(), vec![ None, None, @@ -2631,7 +2635,10 @@ mod tests { "\n\n\n111\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n" ); assert_eq!( - blocks_snapshot.buffer_rows(BlockRow(0)).collect::>(), + blocks_snapshot + .row_infos(BlockRow(0)) + .map(|i| i.buffer_row) + .collect::>(), vec![ None, None, @@ -2706,7 +2713,10 @@ mod tests { "\n\n\n\n\n\n222\n\n\n\n333\n\n\n444\n\n\n\n\n\n\n555\n\n\n666\n\n" ); assert_eq!( - blocks_snapshot.buffer_rows(BlockRow(0)).collect::>(), + blocks_snapshot + .row_infos(BlockRow(0)) + .map(|i| i.buffer_row) + .collect::>(), vec![ None, None, @@ -2771,7 +2781,10 @@ mod tests { ); assert_eq!(blocks_snapshot.text(), "\n\n\n\n\n\n\n\n555\n\n\n666\n\n"); assert_eq!( - blocks_snapshot.buffer_rows(BlockRow(0)).collect::>(), + blocks_snapshot + .row_infos(BlockRow(0)) + .map(|i| i.buffer_row) + .collect::>(), vec![ None, None, @@ -2825,7 +2838,10 @@ mod tests { "Should have extra newline for 111 buffer, due to a new block added when it was folded" ); assert_eq!( - blocks_snapshot.buffer_rows(BlockRow(0)).collect::>(), + blocks_snapshot + .row_infos(BlockRow(0)) + .map(|i| i.buffer_row) + .collect::>(), vec![ None, None, @@ -2879,7 +2895,10 @@ mod tests { "Should have a single, first buffer left after folding" ); assert_eq!( - blocks_snapshot.buffer_rows(BlockRow(0)).collect::>(), + blocks_snapshot + .row_infos(BlockRow(0)) + .map(|i| i.buffer_row) + .collect::>(), vec![ None, None, @@ -2913,7 +2932,8 @@ mod tests { assert_eq!(buffer_ids.len(), 1); let buffer_id = buffer_ids[0]; - let (_, inlay_snapshot) = InlayMap::new(buffer_snapshot.clone()); + let (_, diff_snapshot) = cx.update(|cx| DiffMap::new(buffer.clone(), cx)); + let (_, inlay_snapshot) = InlayMap::new(diff_snapshot.clone()); let (_, fold_snapshot) = FoldMap::new(inlay_snapshot); let (_, tab_snapshot) = TabMap::new(fold_snapshot, 4.try_into().unwrap()); let (_, wrap_snapshot) = @@ -2949,7 +2969,10 @@ mod tests { ); assert_eq!(blocks_snapshot.text(), "\n"); assert_eq!( - blocks_snapshot.buffer_rows(BlockRow(0)).collect::>(), + blocks_snapshot + .row_infos(BlockRow(0)) + .map(|i| i.buffer_row) + .collect::>(), vec![None, None], "When fully folded, should be no buffer rows" ); @@ -3117,8 +3140,11 @@ mod tests { log::info!("Noop fold/unfold operation on a singleton buffer"); continue; } + let (diff_snapshot, diff_edits) = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(buffer_snapshot.clone(), vec![], cx) + }); let (inlay_snapshot, inlay_edits) = - inlay_map.sync(buffer_snapshot.clone(), vec![]); + inlay_map.sync(diff_snapshot.clone(), diff_edits); let (fold_snapshot, fold_edits) = fold_map.read(inlay_snapshot, inlay_edits); let (tab_snapshot, tab_edits) = tab_map.sync(fold_snapshot, fold_edits, tab_size); diff --git a/crates/editor/src/display_map/diff_map.rs b/crates/editor/src/display_map/diff_map.rs index a494a2de993302..0c4c32e67e0f2a 100644 --- a/crates/editor/src/display_map/diff_map.rs +++ b/crates/editor/src/display_map/diff_map.rs @@ -1134,11 +1134,11 @@ impl DiffMapSnapshot { let diff_anchor_a = a.diff_base_anchor.unwrap(); let diff_anchor_b = b.diff_base_anchor.unwrap(); - if diff_anchor_a.buffer_id != diff_anchor_b.buffer_id { + if diff_anchor_a.buffer_id != diff_anchor_b.buffer_id || diff_anchor_a.buffer_id.is_none() { return std::cmp::Ordering::Equal; } - let Some(diff_base_snapshot) = self.diffs.get(diff_anchor_a.buffer_id) else { + let Some(diff_base_snapshot) = self.diffs.get(&diff_anchor_a.buffer_id.unwrap()) else { return std::cmp::Ordering::Equal; }; diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 6403f06d48d85b..bbf1270d9df5cf 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,21 +1,14 @@ use crate::{HighlightStyles, InlayId, RowInfo}; use collections::BTreeSet; use language::{Chunk, Edit, Point, TextSummary}; -use multi_buffer::{Anchor, MultiBufferRow, MultiBufferRows, MultiBufferSnapshot, ToOffset}; +use multi_buffer::{Anchor, MultiBufferSnapshot, ToOffset}; use std::{ cmp, ops::{Add, AddAssign, Range, Sub, SubAssign}, }; -use std::{ - cmp, - ops::{Add, AddAssign, Range, Sub, SubAssign}, -}; -use sum_tree::{Bias, Cursor, SumTree}; use sum_tree::{Bias, Cursor, SumTree}; use text::{Patch, Rope}; -use text::{Patch, Rope}; -use super::{custom_highlights::CustomHighlightsChunks, Highlights}; use super::{ diff_map::{DiffEdit, DiffMapChunks, DiffMapRows, DiffMapSnapshot, DiffOffset, DiffPoint}, Highlights, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index fc888a1251bfc0..207f5823b9f0e7 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5085,7 +5085,7 @@ impl Editor { })) } - #[cfg(feature = "test-support")] + #[cfg(any(test, feature = "test-support"))] pub fn context_menu_visible(&self) -> bool { self.context_menu .borrow() diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 4d640d9748c713..3d7caf9926668b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -22,7 +22,7 @@ use crate::{ EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowInfo, RowRangeExt, SelectPhase, - Selection, SoftWrap, ToPoint, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, + Selection, SoftWrap, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT, }; use client::ParticipantIndex; @@ -6199,8 +6199,6 @@ impl Element for EditorElement { if show_code_actions { let newest_selection_point = newest_selection_head.to_point(&snapshot.display_snapshot); - let newest_selection_display_row = - newest_selection_point.to_display_point(&snapshot).row(); if !snapshot .is_line_folded(MultiBufferRow(newest_selection_point.row)) { diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index cdcdd726131dcc..f840dc4357c0c2 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1576,6 +1576,7 @@ impl MultiBuffer { } ranges } + pub fn excerpt_ranges_for_buffer( &self, buffer_id: BufferId, From df81a91bf9b8446b6d2c9f080643c1dc11504330 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 17 Dec 2024 15:33:38 -0700 Subject: [PATCH 51/51] WIPWIPWIPWIPW --- Cargo.lock | 2 + crates/editor/src/display_map.rs | 8 +- crates/multi_buffer/Cargo.toml | 2 + crates/multi_buffer/src/diff_map.rs | 2563 +++++++++++++++++++++++ crates/multi_buffer/src/multi_buffer.rs | 37 + 5 files changed, 2605 insertions(+), 7 deletions(-) create mode 100644 crates/multi_buffer/src/diff_map.rs diff --git a/Cargo.lock b/Cargo.lock index a7f690078f21ad..83472cf28317d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7702,11 +7702,13 @@ dependencies = [ "ctor", "env_logger 0.11.5", "futures 0.3.31", + "git", "gpui", "itertools 0.13.0", "language", "log", "parking_lot", + "project", "rand 0.8.5", "serde", "settings", diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 58d1b2f4f06ce8..87ea4425e5bd24 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -56,7 +56,7 @@ use language::{ use lsp::DiagnosticSeverity; use multi_buffer::{ Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow, - MultiBufferSnapshot, ToOffset, ToPoint, + MultiBufferSnapshot, RowInfo, ToOffset, ToPoint, }; use project::buffer_store::BufferChangeSet; use serde::Deserialize; @@ -82,12 +82,6 @@ pub enum FoldStatus { Foldable, } -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] -pub struct RowInfo { - pub buffer_row: Option, - pub diff_status: Option, -} - pub type RenderFoldToggle = Arc AnyElement>; pub trait ToDisplayPoint { diff --git a/crates/multi_buffer/Cargo.toml b/crates/multi_buffer/Cargo.toml index 444fe3c75c6cfb..e3912e45eac1e9 100644 --- a/crates/multi_buffer/Cargo.toml +++ b/crates/multi_buffer/Cargo.toml @@ -27,11 +27,13 @@ collections.workspace = true ctor.workspace = true env_logger.workspace = true futures.workspace = true +git.workspace = true gpui.workspace = true itertools.workspace = true language.workspace = true log.workspace = true parking_lot.workspace = true +project.workspace = true rand.workspace = true settings.workspace = true serde.workspace = true diff --git a/crates/multi_buffer/src/diff_map.rs b/crates/multi_buffer/src/diff_map.rs new file mode 100644 index 00000000000000..7dc09d643cf94a --- /dev/null +++ b/crates/multi_buffer/src/diff_map.rs @@ -0,0 +1,2563 @@ +use crate::MultiBufferChunks; + +use super::{ + Anchor, AnchorRangeExt, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow, MultiBufferRows, + MultiBufferSnapshot, ToOffset, ToPoint as _, +}; +use collections::HashMap; +use git::diff::DiffHunkStatus; +use gpui::{AppContext, Context as _, HighlightStyle, Model, ModelContext, Subscription}; +use language::{BufferChunks, BufferId, Chunk}; +use project::buffer_store::BufferChangeSet; +use std::{ + any::TypeId, + mem::{self}, + ops::Range, + sync::Arc, +}; +use sum_tree::{Cursor, SumTree, TreeMap}; +use text::{Bias, Edit, Patch, Point, TextSummary, ToOffset as _, ToPoint as _}; +use util::debug_panic; + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct RowInfo { + pub buffer_row: Option, + pub diff_status: Option, +} + +pub(crate) struct DiffMap { + snapshot: DiffMapSnapshot, + diff_bases: HashMap, + all_hunks_expanded: bool, + edits_since_sync: Patch, +} + +struct ChangeSetState { + change_set: Model, + _subscription: Subscription, +} + +#[derive(Clone)] +struct DiffSnapshot { + diff: git::diff::BufferDiff, + base_text: language::BufferSnapshot, + base_text_version: usize, +} + +#[derive(Clone)] +pub struct DiffMapSnapshot { + diffs: TreeMap, + pub(crate) transforms: SumTree, + pub(crate) version: usize, +} + +#[derive(Debug, Clone)] +pub(crate) enum DiffTransform { + BufferContent { + summary: TextSummary, + is_inserted_hunk: bool, + }, + DeletedHunk { + summary_including_newline: TextSummary, + buffer_id: BufferId, + base_text_byte_range: Range, + base_text_start: Point, + needs_newline: bool, + }, +} + +#[derive(Debug, Clone)] +pub(crate) struct DiffTransformSummary { + multibuffer_map: TextSummary, + diff_map: TextSummary, +} + +impl DiffTransformSummary { + pub fn multibuffer_point(&self) -> Point { + self.multibuffer_map.lines + } + pub fn multibuffer_offset(&self) -> usize { + self.multibuffer_map.len + } + pub fn diff_point(&self) -> DiffPoint { + DiffPoint(self.diff_map.lines) + } + pub fn diff_offset(&self) -> DiffOffset { + DiffOffset(self.diff_map.len) + } +} + +pub struct DiffMapChunks<'a> { + snapshot: &'a DiffMapSnapshot, + language_aware: bool, + cursor: Cursor<'a, DiffTransform, (DiffOffset, usize)>, + multibuffer_chunks: MultiBufferChunks<'a>, + multibuffer_chunk: Option>, + multibuffer_offset: usize, + offset: DiffOffset, + end_offset: DiffOffset, + diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, +} + +#[derive(Clone)] +pub struct DiffMapRows<'a> { + cursor: Cursor<'a, DiffTransform, (DiffPoint, Point)>, + diff_point: DiffPoint, + input_buffer_rows: MultiBufferRows<'a>, +} + +pub type DiffEdit = text::Edit; + +#[derive(Debug)] +enum ChangeKind { + DiffUpdated { base_changed: bool }, + InputEdited, + ExpandOrCollapseHunks { range: Range, expand: bool }, +} + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DiffOffset(pub usize); + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct DiffPoint(pub Point); + +impl DiffPoint { + pub fn new(row: u32, col: u32) -> Self { + DiffPoint(Point::new(row, col)) + } + + pub fn row(&self) -> u32 { + self.0.row + } + pub fn column(&self) -> u32 { + self.0.column + } +} + +impl DiffMap { + pub fn new( + multibuffer_snapshot: &MultiBufferSnapshot, + cx: &mut AppContext, + ) -> (Model, DiffMapSnapshot) { + let snapshot = DiffMapSnapshot { + diffs: TreeMap::default(), + version: 0, + transforms: SumTree::from_item( + DiffTransform::BufferContent { + summary: multibuffer_snapshot.text_summary(), + is_inserted_hunk: false, + }, + &(), + ), + }; + + let this = cx.new_model(|_| Self { + snapshot: snapshot.clone(), + all_hunks_expanded: false, + diff_bases: HashMap::default(), + edits_since_sync: Patch::default(), + }); + + (this, snapshot) + } + + pub fn add_change_set( + &mut self, + change_set: Model, + cx: &mut ModelContext, + ) { + let buffer_id = change_set.read(cx).buffer_id; + self.buffer_diff_changed(change_set.clone(), cx); + self.diff_bases.insert( + buffer_id, + ChangeSetState { + _subscription: cx.observe(&change_set, Self::buffer_diff_changed), + change_set, + }, + ); + } + + pub fn diff_base_for(&self, buffer_id: BufferId) -> Option<&Model> { + self.diff_bases + .get(&buffer_id) + .map(|state| &state.change_set) + } + + pub fn sync( + &mut self, + multibuffer_snapshot: &MultiBufferSnapshot, + buffer_edits: Vec>, + cx: &mut ModelContext, + ) -> (DiffMapSnapshot, Vec) { + let changes = buffer_edits + .iter() + .map(|edit| (edit.clone(), ChangeKind::InputEdited)) + .collect::>(); + + self.snapshot.buffer = multibuffer_snapshot.clone(); + self.recompute_transforms(changes, multibuffer_snapshot, cx); + + ( + self.snapshot.clone(), + mem::take(&mut self.edits_since_sync).into_inner(), + ) + } + + fn buffer_diff_changed( + &mut self, + change_set: Model, + cx: &mut ModelContext, + ) { + let change_set = change_set.read(cx); + let buffer_id = change_set.buffer_id; + let diff = change_set.diff_to_buffer.clone(); + let base_text = change_set + .base_text + .as_ref() + .map(|buffer| buffer.read(cx).snapshot()); + let base_text_version_changed = self + .snapshot + .diffs + .get(&buffer_id) + .map_or(true, |snapshot| { + snapshot.base_text_version != change_set.base_text_version + }); + + if let Some(base_text) = base_text.clone() { + self.snapshot.diffs.insert( + buffer_id, + DiffSnapshot { + diff: diff.clone(), + base_text, + base_text_version: change_set.base_text_version, + }, + ); + } else { + self.snapshot.diffs.remove(&buffer_id); + } + + let multibuffer = self.multibuffer.read(cx); + let multibuffer_snapshot = self.snapshot.buffer(); + let changes = multibuffer + .ranges_for_buffer(buffer_id, cx) + .into_iter() + .map(|(_, range, _)| { + let multibuffer_start = multibuffer_snapshot.point_to_offset(range.start); + let multibuffer_end = multibuffer_snapshot.point_to_offset(range.end); + ( + text::Edit { + old: multibuffer_start..multibuffer_end, + new: multibuffer_start..multibuffer_end, + }, + ChangeKind::DiffUpdated { + base_changed: base_text_version_changed, + }, + ) + }) + .collect(); + self.recompute_transforms(changes, cx); + } + + pub(super) fn has_multiple_hunks(&self) -> bool { + self.snapshot + .diff_hunks_in_range(Anchor::min()..Anchor::max()) + .nth(1) + .is_some() + } + + pub(super) fn has_expanded_diff_hunks_in_ranges(&self, ranges: &[Range]) -> bool { + let mut cursor = self.snapshot.transforms.cursor::(&()); + let multibuffer_snapshot = self.snapshot.buffer(); + for range in ranges { + let range = range.to_point(multibuffer_snapshot); + let start = multibuffer_snapshot.point_to_offset(Point::new(range.start.row, 0)); + let end = multibuffer_snapshot.point_to_offset(Point::new(range.end.row + 1, 0)); + let start = start.saturating_sub(1); + let end = multibuffer_snapshot.len().min(end + 1); + cursor.seek(&start, Bias::Right, &()); + while *cursor.start() < end { + match cursor.item() { + Some(DiffTransform::DeletedHunk { .. }) + | Some(DiffTransform::BufferContent { + is_inserted_hunk: true, + .. + }) => return true, + _ => {} + } + cursor.next(&()); + } + } + false + } + + pub(super) fn expand_diff_hunks( + &mut self, + ranges: Vec>, + cx: &mut ModelContext, + ) { + self.expand_or_collapse_diff_hunks(ranges, true, cx); + } + + pub(super) fn collapse_diff_hunks( + &mut self, + ranges: Vec>, + cx: &mut ModelContext, + ) { + self.expand_or_collapse_diff_hunks(ranges, false, cx); + } + + pub(super) fn set_all_hunks_expanded(&mut self, cx: &mut ModelContext) { + self.all_hunks_expanded = true; + self.expand_or_collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], true, cx); + } + + fn expand_or_collapse_diff_hunks( + &mut self, + ranges: Vec>, + expand: bool, + cx: &mut ModelContext, + ) { + let multibuffer_snapshot = self.snapshot.buffer(); + let mut changes = Vec::new(); + for range in ranges.iter() { + let multibuffer_start = range.start.to_point(multibuffer_snapshot); + let multibuffer_end = range.end.to_point(multibuffer_snapshot); + let multibuffer_start = + multibuffer_snapshot.point_to_offset(Point::new(multibuffer_start.row, 0)); + let multibuffer_end = + multibuffer_snapshot.point_to_offset(Point::new(multibuffer_end.row + 1, 0)); + let expanded_start = multibuffer_start.saturating_sub(1); + let expanded_end = multibuffer_snapshot.len().min(multibuffer_end); + changes.push(( + text::Edit { + old: expanded_start..expanded_end, + new: expanded_start..expanded_end, + }, + ChangeKind::ExpandOrCollapseHunks { + range: multibuffer_start..multibuffer_end, + expand, + }, + )); + } + + self.recompute_transforms(changes, cx); + } + + fn recompute_transforms( + &mut self, + changes: Vec<(text::Edit, ChangeKind)>, + multibuffer_snapshot: &MultiBufferSnapshot, + cx: &mut ModelContext, + ) { + let multibuffer = self.multibuffer.read(cx); + let multibuffer_snapshot = self.snapshot.buffer(); + + let mut cursor = self.snapshot.transforms.cursor::<(usize, DiffOffset)>(&()); + let mut new_transforms = SumTree::default(); + let mut edits = Patch::default(); + + let mut changes = changes.into_iter().peekable(); + let mut delta = 0_isize; + while let Some((mut edit, mut operation)) = changes.next() { + let mut to_skip = cursor.slice(&edit.old.start, Bias::Left, &()); + while cursor.end(&()).0 < edit.old.start + || (cursor.end(&()).0 == edit.old.start && cursor.start().0 < edit.old.start) + { + to_skip.extend(cursor.item().cloned(), &()); + cursor.next(&()); + } + + self.append_transforms(&mut new_transforms, to_skip); + + let mut end_of_current_insert = 0; + loop { + let multibuffer_range = edit.new.clone(); + let edit_old_start = + cursor.start().1 + DiffOffset(edit.old.start - cursor.start().0); + let mut edit_old_end = + cursor.start().1 + DiffOffset(edit.old.end - cursor.start().0); + + for (buffer, buffer_range, excerpt_id) in + multibuffer.range_to_buffer_ranges(multibuffer_range.clone()) + { + let excerpt_range = multibuffer_snapshot + .range_for_excerpt::(excerpt_id) + .unwrap(); + let excerpt_buffer_range = multibuffer_snapshot + .buffer_range_for_excerpt(excerpt_id) + .unwrap(); + let buffer_id = buffer.remote_id(); + let diff_state = self.snapshot.diffs.get(&buffer_id); + + let buffer_anchor_range = buffer.anchor_before(buffer_range.start) + ..buffer.anchor_after(buffer_range.end); + let change_start_buffer_offset = buffer_range.start; + if let Some(diff_state) = diff_state { + let diff = &diff_state.diff; + let base_text = &diff_state.base_text; + + for hunk in diff.hunks_intersecting_range(buffer_anchor_range, buffer) { + let hunk_start_buffer_offset = + hunk.buffer_range.start.to_offset(buffer); + let hunk_end_buffer_offset = hunk.buffer_range.end.to_offset(buffer); + + let excerpt_buffer_range_start_offset = + excerpt_buffer_range.start.to_offset(buffer); + let hunk_start_multibuffer_offset = excerpt_range.start + + hunk_start_buffer_offset + .saturating_sub(excerpt_buffer_range_start_offset); + + self.push_buffer_content_transform( + &mut new_transforms, + hunk_start_multibuffer_offset, + end_of_current_insert, + ); + + while cursor.end(&()).0 < hunk_start_multibuffer_offset + || (cursor.end(&()).0 == hunk_start_multibuffer_offset + && cursor.start().0 < hunk_start_multibuffer_offset) + { + let Some(item) = cursor.item() else { + break; + }; + if let DiffTransform::DeletedHunk { .. } = item { + let old_range = cursor.start().1..cursor.end(&()).1; + let new_offset = + DiffOffset((old_range.start.0 as isize + delta) as usize); + delta -= (old_range.end - old_range.start).0 as isize; + let edit = Edit { + old: old_range, + new: new_offset..new_offset, + }; + edits.push(edit); + } + cursor.next(&()); + } + + let mut was_previously_expanded = false; + let mut previous_expanded_summary = TextSummary::default(); + if cursor.start().0 == hunk_start_multibuffer_offset { + match cursor.item() { + Some(DiffTransform::DeletedHunk { + summary_including_newline, + .. + }) => { + was_previously_expanded = true; + previous_expanded_summary = + summary_including_newline.clone(); + } + Some(DiffTransform::BufferContent { + is_inserted_hunk, .. + }) => { + was_previously_expanded = *is_inserted_hunk; + } + None => {} + }; + } + + let hunk_is_deletion = + hunk_start_buffer_offset == hunk_end_buffer_offset; + + let should_expand_hunk = match &operation { + ChangeKind::DiffUpdated { base_changed: true } => { + self.all_hunks_expanded + } + ChangeKind::ExpandOrCollapseHunks { range, expand } => { + let intersects = hunk_is_deletion + || (hunk_start_buffer_offset < range.end + && hunk_end_buffer_offset > range.start); + if *expand { + was_previously_expanded + || self.all_hunks_expanded + || intersects + } else { + !intersects + && (was_previously_expanded || self.all_hunks_expanded) + } + } + _ => was_previously_expanded || self.all_hunks_expanded, + }; + + if should_expand_hunk { + if hunk.diff_base_byte_range.len() > 0 + && hunk_start_buffer_offset >= change_start_buffer_offset + { + let mut text_cursor = base_text.as_rope().cursor(0); + let base_text_start = text_cursor + .summary::(hunk.diff_base_byte_range.start); + let mut base_text_summary = text_cursor + .summary::(hunk.diff_base_byte_range.end); + let mut needs_newline = false; + let mut diff_byte_range = + DiffOffset(hunk.diff_base_byte_range.len()); + if base_text_summary.last_line_chars > 0 { + diff_byte_range.0 += 1; + base_text_summary.add_newline(); + needs_newline = true; + } + + if !was_previously_expanded + || base_text_summary != previous_expanded_summary + { + let hunk_overshoot = + hunk_start_multibuffer_offset - cursor.start().0; + let old_start = + cursor.start().1 + DiffOffset(hunk_overshoot); + let old_end = + old_start + DiffOffset(previous_expanded_summary.len); + let new_start = + DiffOffset(new_transforms.summary().diff_map.len); + let new_end = new_start + diff_byte_range; + delta += diff_byte_range.0 as isize; + let edit = Edit { + old: old_start..old_end, + new: new_start..new_end, + }; + edits.push(edit); + } + + new_transforms.push( + DiffTransform::DeletedHunk { + base_text_byte_range: hunk.diff_base_byte_range.clone(), + summary_including_newline: base_text_summary, + buffer_id, + base_text_start, + needs_newline, + }, + &(), + ); + } + + if hunk_end_buffer_offset > hunk_start_buffer_offset { + let hunk_end_multibuffer_offset = excerpt_range.start + + hunk_end_buffer_offset + - excerpt_buffer_range_start_offset; + end_of_current_insert = hunk_end_multibuffer_offset; + } + + if was_previously_expanded { + cursor.next(&()); + } + } + } + } + } + + while cursor.end(&()).0 <= edit.old.end { + let Some(item) = cursor.item() else { + break; + }; + if let DiffTransform::DeletedHunk { .. } = item { + let old_range = cursor.start().1..cursor.end(&()).1; + let new_offset = DiffOffset((old_range.start.0 as isize + delta) as usize); + delta -= (old_range.end - old_range.start).0 as isize; + let edit = Edit { + old: old_range, + new: new_offset..new_offset, + }; + edits.push(edit); + } + + edit_old_end = cursor.start().1 + DiffOffset(edit.old.end - cursor.start().0); + + cursor.next(&()); + } + + self.push_buffer_content_transform( + &mut new_transforms, + edit.new.end, + end_of_current_insert, + ); + + if let ChangeKind::InputEdited = operation { + let edit_new_start = DiffOffset((edit_old_start.0 as isize + delta) as usize); + delta += (edit.new.end - edit.new.start) as isize + - (edit.old.end - edit.old.start) as isize; + let edit_new_end = DiffOffset((edit_old_end.0 as isize + delta) as usize); + let edit = DiffEdit { + old: edit_old_start..edit_old_end, + new: edit_new_start..edit_new_end, + }; + edits.push(edit); + } + + if let Some((next_edit, _)) = changes.peek() { + if next_edit.old.start < cursor.end(&()).0 { + (edit, operation) = changes.next().unwrap(); + continue; + } + } + + let suffix = cursor.end(&()).0 - edit.old.end; + let transform_end = new_transforms.summary().multibuffer_map.len + suffix; + self.push_buffer_content_transform( + &mut new_transforms, + transform_end, + end_of_current_insert, + ); + cursor.next(&()); + break; + } + } + + self.append_transforms(&mut new_transforms, cursor.suffix(&())); + self.edits_since_sync = self.edits_since_sync.compose(edits); + + drop(cursor); + self.snapshot.transforms = new_transforms; + self.snapshot.version += 1; + cx.notify(); + + #[cfg(test)] + self.check_invariants(); + } + + fn append_transforms( + &self, + new_transforms: &mut SumTree, + subtree: SumTree, + ) { + if let Some(DiffTransform::BufferContent { + is_inserted_hunk, + summary, + }) = subtree.first() + { + if self.extend_last_buffer_content_transform( + new_transforms, + *is_inserted_hunk, + summary.clone(), + ) { + let mut cursor = subtree.cursor::<()>(&()); + cursor.next(&()); + cursor.next(&()); + new_transforms.append(cursor.suffix(&()), &()); + return; + } + } + new_transforms.append(subtree, &()); + } + + fn push_buffer_content_transform( + &self, + new_transforms: &mut SumTree, + end_offset: usize, + end_of_current_inserted_hunk: usize, + ) { + for (end_offset, region_is_inserted_hunk) in [ + (end_offset.min(end_of_current_inserted_hunk), true), + (end_offset, false), + ] { + let start_offset = new_transforms.summary().multibuffer_map.len; + if end_offset <= start_offset { + continue; + } + let summary_to_add = self + .snapshot + .buffer + .text_summary_for_range::(start_offset..end_offset); + + if !self.extend_last_buffer_content_transform( + new_transforms, + region_is_inserted_hunk, + summary_to_add.clone(), + ) { + new_transforms.push( + DiffTransform::BufferContent { + summary: summary_to_add, + is_inserted_hunk: region_is_inserted_hunk, + }, + &(), + ) + } + } + } + + fn extend_last_buffer_content_transform( + &self, + new_transforms: &mut SumTree, + region_is_inserted_hunk: bool, + summary_to_add: TextSummary, + ) -> bool { + let mut did_extend = false; + new_transforms.update_last( + |last_transform| { + if let DiffTransform::BufferContent { + summary, + is_inserted_hunk, + } = last_transform + { + if *is_inserted_hunk == region_is_inserted_hunk { + *summary += summary_to_add.clone(); + did_extend = true; + } + } + }, + &(), + ); + did_extend + } + + #[cfg(test)] + fn check_invariants(&self) { + let snapshot = &self.snapshot; + if snapshot.transforms.summary().multibuffer_map.len != snapshot.buffer.len() { + panic!( + "incorrect input length. expected {}, got {}. transforms: {:+?}", + snapshot.buffer.len(), + snapshot.transforms.summary().multibuffer_map.len, + snapshot.transforms.items(&()), + ); + } + + let mut prev_transform: Option<&DiffTransform> = None; + for item in snapshot.transforms.iter() { + if let DiffTransform::BufferContent { + summary, + is_inserted_hunk, + } = item + { + if let Some(DiffTransform::BufferContent { + is_inserted_hunk: prev_is_inserted_hunk, + .. + }) = prev_transform + { + if *is_inserted_hunk == *prev_is_inserted_hunk { + panic!( + "multiple adjacent buffer content transforms with is_inserted_hunk = {is_inserted_hunk}. transforms: {:+?}", + snapshot.transforms.items(&())); + } + } + if summary.len == 0 && !snapshot.buffer().is_empty() { + panic!("empty buffer content transform"); + } + } + prev_transform = Some(item); + } + } +} + +impl DiffMapSnapshot { + pub fn diff_hunks_in_range<'a, T: ToOffset>( + &'a self, + multibuffer_snapshot: &'a MultiBufferSnapshot, + range: Range, + ) -> impl Iterator + 'a { + let range = + range.start.to_offset(multibuffer_snapshot)..range.end.to_offset(multibuffer_snapshot); + multibuffer_snapshot + .excerpts_for_range(range.clone()) + .filter_map(move |excerpt| { + let buffer = excerpt.buffer(); + let buffer_id = buffer.remote_id(); + let diff = &self.diffs.get(&buffer_id)?.diff; + let buffer_range = excerpt.map_range_to_buffer(range.clone()); + let buffer_range = + buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); + Some( + diff.hunks_intersecting_range(buffer_range, excerpt.buffer()) + .map(move |hunk| { + let start = + excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0)); + let end = + excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0)); + MultiBufferDiffHunk { + row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row), + buffer_id, + buffer_range: hunk.buffer_range.clone(), + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + } + }), + ) + }) + .flatten() + } + + pub fn diff_hunks_in_range_rev<'a, T: ToOffset>( + &'a self, + multibuffer_snapshot: &'a MultiBufferSnapshot, + range: Range, + ) -> impl Iterator + 'a { + let multibuffer_snapshot = self.buffer(); + let range = + range.start.to_offset(multibuffer_snapshot)..range.end.to_offset(multibuffer_snapshot); + multibuffer_snapshot + .excerpts_for_range_rev(range.clone()) + .filter_map(move |excerpt| { + let buffer = excerpt.buffer(); + let buffer_id = buffer.remote_id(); + let diff = &self.diffs.get(&buffer_id)?.diff; + let buffer_range = excerpt.map_range_to_buffer(range.clone()); + let buffer_range = + buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end); + Some( + diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer()) + .map(move |hunk| { + let start_row = excerpt + .map_point_from_buffer(Point::new(hunk.row_range.start, 0)) + .row; + let end_row = excerpt + .map_point_from_buffer(Point::new(hunk.row_range.end, 0)) + .row; + MultiBufferDiffHunk { + row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row), + buffer_id, + buffer_range: hunk.buffer_range.clone(), + diff_base_byte_range: hunk.diff_base_byte_range.clone(), + } + }), + ) + }) + .flatten() + } + + pub fn has_diff_hunks(&self) -> bool { + self.diffs.values().any(|diff| !diff.diff.is_empty()) + } + + #[cfg(any(test, feature = "test-support"))] + pub fn text(&self) -> String { + self.chunks(DiffOffset(0)..self.len(), false, None) + .map(|c| c.text) + .collect() + } + + pub fn len(&self) -> DiffOffset { + DiffOffset(self.transforms.summary().diff_map.len) + } + + pub fn max_point(&self) -> DiffPoint { + DiffPoint(self.transforms.summary().diff_map.lines) + } + + pub fn text_summary(&self) -> TextSummary { + self.transforms.summary().diff_map.clone() + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); + cursor.seek(&range.start, Bias::Right, &()); + + let Some(first_transform) = cursor.item() else { + return TextSummary::default(); + }; + + let (diff_transform_start, multibuffer_transform_start) = cursor.start().clone(); + let (diff_transform_end, _) = cursor.end(&()); + let diff_start = range.start; + let diff_end = std::cmp::min(range.end, diff_transform_end); + + let mut result = match first_transform { + DiffTransform::BufferContent { .. } => { + let multibuffer_start = + multibuffer_transform_start + (diff_start - diff_transform_start).0; + let multibuffer_end = + multibuffer_transform_start + (diff_end - diff_transform_start).0; + + self.buffer + .text_summary_for_range(multibuffer_start..multibuffer_end) + } + DiffTransform::DeletedHunk { + buffer_id, + base_text_byte_range, + needs_newline, + .. + } => { + let buffer_start = + base_text_byte_range.start + (diff_start - diff_transform_start).0; + let mut buffer_end = + base_text_byte_range.start + (diff_end - diff_transform_start).0; + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", range.start) + }; + + if *needs_newline && diff_end == diff_transform_end { + buffer_end -= 1; + } + + let mut summary = buffer_diff + .base_text + .text_summary_for_range::(buffer_start..buffer_end); + + if *needs_newline && diff_end == diff_transform_end { + summary.add_newline(); + } + + summary + } + }; + if range.end < diff_transform_end { + return result; + } + + cursor.next(&()); + result = result + cursor.summary(&range.end, Bias::Right, &()); + + let Some(last_transform) = cursor.item() else { + return result; + }; + + let (diff_transform_start, multibuffer_transform_start) = cursor.start().clone(); + + result += match last_transform { + DiffTransform::BufferContent { .. } => { + let multibuffer_end = + multibuffer_transform_start + (range.end - diff_transform_start).0; + + self.buffer.text_summary_for_range::( + multibuffer_transform_start..multibuffer_end, + ) + } + DiffTransform::DeletedHunk { + base_text_byte_range, + buffer_id, + needs_newline, + .. + } => { + let buffer_end = base_text_byte_range.start + (range.end - diff_transform_start).0; + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", range.end) + }; + + let mut result = buffer_diff + .base_text + .text_summary_for_range::( + base_text_byte_range.start..buffer_end, + ); + + if *needs_newline && buffer_end == base_text_byte_range.end + 1 { + result.add_newline(); + } + + result + } + }; + + result + } + + pub fn buffer(&self) -> &MultiBufferSnapshot { + &self.buffer + } + + pub fn offset_to_point(&self, offset: DiffOffset) -> DiffPoint { + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&offset, Bias::Right, &()); + let start_transform = cursor.start(); + let overshoot = offset - start_transform.diff_offset(); + if overshoot.0 == 0 { + return start_transform.diff_point(); + } + + match cursor.item() { + Some(DiffTransform::BufferContent { .. }) => { + let multibuffer_offset = start_transform.multibuffer_offset() + overshoot.0; + let multibuffer_point = self.buffer.offset_to_point(multibuffer_offset); + start_transform.diff_point() + + DiffPoint(multibuffer_point - start_transform.multibuffer_point()) + } + Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + base_text_byte_range, + .. + }) => { + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", offset) + }; + let buffer_offset = base_text_byte_range.start + overshoot.0; + let buffer_point = buffer_diff.base_text.offset_to_point(buffer_offset); + start_transform.diff_point() + DiffPoint(buffer_point - base_text_start) + } + None => { + panic!("{:?} is past end of buffer", offset) + } + } + } + + pub fn clip_point(&self, point: DiffPoint, bias: Bias) -> DiffPoint { + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&point, Bias::Right, &()); + let start_transform = cursor.start(); + let overshoot = point - start_transform.diff_point(); + if overshoot.0.is_zero() { + return start_transform.diff_point(); + } + + match cursor.item() { + Some(DiffTransform::BufferContent { .. }) => { + let inlay_point = start_transform.multibuffer_point() + overshoot.0; + let clipped = self.buffer.clip_point(inlay_point, bias); + start_transform.diff_point() + + DiffPoint(clipped - start_transform.multibuffer_point()) + } + Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + .. + }) => { + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", point) + }; + let buffer_point = *base_text_start + overshoot.0; + let clipped = buffer_diff.base_text.clip_point(buffer_point, bias); + start_transform.diff_point() + DiffPoint(clipped - base_text_start) + } + None => cursor.end(&()).diff_point(), + } + } + + pub fn point_to_offset(&self, point: DiffPoint) -> DiffOffset { + let mut cursor = self.transforms.cursor::(&()); + cursor.seek(&point, Bias::Right, &()); + let start_transform = cursor.start(); + let overshoot = point - start_transform.diff_point(); + if overshoot.0.is_zero() { + return start_transform.diff_offset(); + } + + match cursor.item() { + Some(DiffTransform::BufferContent { .. }) => { + let multibuffer_point = start_transform.multibuffer_point() + overshoot.0; + let multibuffer_offset = self.buffer.point_to_offset(multibuffer_point); + start_transform.diff_offset() + + DiffOffset(multibuffer_offset - start_transform.multibuffer_offset()) + } + Some(DiffTransform::DeletedHunk { + buffer_id, + base_text_start, + base_text_byte_range, + .. + }) => { + let Some(buffer_diff) = self.diffs.get(buffer_id) else { + panic!("{:?} is in non-extant deleted hunk", point) + }; + let buffer_point = *base_text_start + overshoot.0; + let buffer_offset = buffer_diff.base_text.point_to_offset(buffer_point); + start_transform.diff_offset() + + DiffOffset(buffer_offset - base_text_byte_range.start) + } + None => { + panic!("{:?} is past end of buffer", point) + } + } + } + + // pub fn point_to_anchor(&self, point: DiffPoint, bias: Bias) -> DisplayAnchor { + // let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); + // cursor.seek(&point, Bias::Right, &()); + // let (diff_start, multibuffer_start) = *cursor.start(); + // match cursor.item() { + // Some(DiffTransform::BufferContent { .. }) => { + // let multibuffer_point = multibuffer_start + (point - diff_start).0; + // DisplayAnchor { + // anchor: self.buffer.anchor_at(multibuffer_point, bias), + // diff_base_anchor: None, + // } + // } + // Some(DiffTransform::DeletedHunk { + // buffer_id, + // base_text_start, + // .. + // }) => { + // let diff_base_point = *base_text_start + (point - diff_start).0; + // let diff_base_anchor = if let Some(diff_base_snapshot) = self.diffs.get(&buffer_id) + // { + // Some( + // diff_base_snapshot + // .base_text + // .anchor_at(diff_base_point, bias), + // ) + // } else { + // debug_panic!("{} is missing diff base", buffer_id); + // None + // }; + + // DisplayAnchor { + // anchor: self.buffer.anchor_at(multibuffer_start, Bias::Left), + // diff_base_anchor, + // } + // } + // None => { + // panic!("{:?} is out of range", point) + // } + // } + // } + + // pub fn anchor_to_point(&self, anchor: DisplayAnchor) -> DiffPoint { + // let multibuffer_point = anchor.anchor.to_point(&self.buffer); + + // let mut cursor = self.transforms.cursor::<(Point, DiffPoint)>(&()); + // cursor.seek(&multibuffer_point, Bias::Left, &()); + + // if let Some(DiffTransform::DeletedHunk { + // buffer_id, + // base_text_start, + // .. + // }) = cursor.item() + // { + // if let Some(diff_base_anchor) = anchor.diff_base_anchor { + // if let Some(diff_base_snapshot) = self.diffs.get(&buffer_id) { + // if diff_base_anchor.buffer_id == Some(diff_base_snapshot.base_text.remote_id()) + // { + // let (_, diff_start) = *cursor.start(); + // let base_text_point = + // diff_base_anchor.to_point(&diff_base_snapshot.base_text); + // return diff_start + DiffPoint(base_text_point - base_text_start); + // } + // } + // } else { + // cursor.next(&()) + // } + // } + + // debug_assert!(matches!( + // cursor.item(), + // Some(DiffTransform::BufferContent { .. }) + // )); + + // let (multibuffer_start, diff_start) = *cursor.start(); + // diff_start + DiffPoint(multibuffer_point - multibuffer_start) + // } + + // pub fn compare_anchors(&self, a: &DisplayAnchor, b: &DisplayAnchor) -> std::cmp::Ordering { + // let buffer_cmp = a.anchor.cmp(&b.anchor, self.buffer()); + // if buffer_cmp != std::cmp::Ordering::Equal { + // return buffer_cmp; + // } + + // match (a.diff_base_anchor.is_some(), b.diff_base_anchor.is_some()) { + // (false, true) => return std::cmp::Ordering::Greater, + // (true, false) => return std::cmp::Ordering::Less, + // (false, false) => return std::cmp::Ordering::Equal, + // (true, true) => {} + // } + + // let diff_anchor_a = a.diff_base_anchor.unwrap(); + // let diff_anchor_b = b.diff_base_anchor.unwrap(); + + // if diff_anchor_a.buffer_id != diff_anchor_b.buffer_id || diff_anchor_a.buffer_id.is_none() { + // return std::cmp::Ordering::Equal; + // } + + // let Some(diff_base_snapshot) = self.diffs.get(&diff_anchor_a.buffer_id.unwrap()) else { + // return std::cmp::Ordering::Equal; + // }; + + // diff_anchor_a.cmp(&diff_anchor_b, &diff_base_snapshot.base_text) + // } + + pub fn to_multibuffer_offset(&self, offset: DiffOffset) -> usize { + let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); + cursor.seek(&offset, Bias::Right, &()); + let mut multibuffer_offset = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = offset.0 - cursor.start().0 .0; + multibuffer_offset += overshoot; + } + multibuffer_offset + } + + pub fn to_multibuffer_point(&self, point: DiffPoint) -> Point { + let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); + cursor.seek(&point, Bias::Right, &()); + let mut inlay_point = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = point.0 - cursor.start().0 .0; + inlay_point += overshoot; + } + inlay_point + } + + pub fn to_diff_offset(&self, multibuffer_offset: usize) -> DiffOffset { + let mut cursor = self.transforms.cursor::<(usize, DiffOffset)>(&()); + cursor.seek(&multibuffer_offset, Bias::Right, &()); + let mut diff_offset = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = multibuffer_offset - cursor.start().0; + diff_offset.0 += overshoot; + } + diff_offset + } + + pub fn to_diff_point(&self, multibuffer_point: Point) -> DiffPoint { + let mut cursor = self.transforms.cursor::<(Point, DiffPoint)>(&()); + cursor.seek(&multibuffer_point, Bias::Right, &()); + let mut diff_point = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = multibuffer_point - cursor.start().0; + diff_point.0 += overshoot; + } + diff_point + } + + pub(crate) fn chunks<'a>( + &'a self, + range: Range, + language_aware: bool, + text_highlights: Option<&'a TreeMap>)>>>, + ) -> DiffMapChunks<'a> { + let mut cursor = self.transforms.cursor::<(DiffOffset, usize)>(&()); + + cursor.seek(&range.end, Bias::Right, &()); + let mut multibuffer_end = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = range.end.0 - cursor.start().0 .0; + multibuffer_end += overshoot; + } + + cursor.seek(&range.start, Bias::Right, &()); + let mut multibuffer_start = cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = cursor.item() { + let overshoot = range.start.0 - cursor.start().0 .0; + multibuffer_start += overshoot; + } + + let multibuffer_chunks = CustomHighlightsChunks::new( + multibuffer_start..multibuffer_end, + language_aware, + text_highlights, + &self.buffer, + ); + + DiffMapChunks { + snapshot: self, + language_aware, + cursor, + multibuffer_chunk: None, + multibuffer_chunks, + multibuffer_offset: multibuffer_start, + offset: range.start, + diff_base_chunks: None, + end_offset: range.end, + } + } + + pub fn row_infos(&self, start_row: u32) -> DiffMapRows { + if start_row > self.transforms.summary().diff_map.lines.row { + panic!("invalid diff map row {}", start_row); + } + + let diff_point = DiffPoint(Point::new(start_row, 0)); + let mut cursor = self.transforms.cursor::<(DiffPoint, Point)>(&()); + cursor.seek(&diff_point, Bias::Right, &()); + + let (diff_transform_start, buffer_transform_start) = cursor.start().clone(); + + let overshoot = if matches!(cursor.item(), Some(DiffTransform::BufferContent { .. })) { + diff_point.row() - diff_transform_start.row() + } else { + 0 + }; + let input_buffer_rows = self + .buffer + .buffer_rows(MultiBufferRow(buffer_transform_start.row + overshoot)); + + DiffMapRows { + diff_point, + input_buffer_rows, + cursor, + } + } +} + +impl<'a> DiffMapChunks<'a> { + pub fn seek(&mut self, range: Range) { + self.cursor.seek(&range.end, Bias::Right, &()); + let mut inlay_end = self.cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { + let overshoot = range.end.0 - self.cursor.start().0 .0; + inlay_end += overshoot; + } + + self.cursor.seek(&range.start, Bias::Right, &()); + let mut inlay_start = self.cursor.start().1; + if let Some(DiffTransform::BufferContent { .. }) = self.cursor.item() { + let overshoot = range.start.0 - self.cursor.start().0 .0; + inlay_start += overshoot; + } + + self.multibuffer_chunks.seek(inlay_start..inlay_end); + self.multibuffer_chunk.take(); + self.multibuffer_offset = inlay_start; + self.offset = range.start; + self.end_offset = range.end; + } +} + +impl<'a> Iterator for DiffMapChunks<'a> { + type Item = Chunk<'a>; + + fn next(&mut self) -> Option { + if self.offset >= self.end_offset { + return None; + } + if self.offset == self.cursor.end(&()).0 { + self.cursor.next(&()); + } + + let transform = self.cursor.item()?; + + match transform { + DiffTransform::BufferContent { summary, .. } => { + let chunk = self + .multibuffer_chunk + .get_or_insert_with(|| self.multibuffer_chunks.next().unwrap()); + + let chunk_end = self.offset + DiffOffset(chunk.text.len()); + let mut transform_end = self.cursor.start().0 + DiffOffset(summary.len); + + if transform_end > self.end_offset { + transform_end = self.end_offset + } + + if transform_end < chunk_end { + let (before, after) = chunk.text.split_at(transform_end.0 - self.offset.0); + self.offset = transform_end; + chunk.text = after; + Some(Chunk { + text: before, + ..chunk.clone() + }) + } else { + self.offset = chunk_end; + self.multibuffer_chunk.take() + } + } + DiffTransform::DeletedHunk { + buffer_id, + base_text_byte_range, + needs_newline, + .. + } => { + let hunk_start_offset = self.cursor.start().0; + let base_text_start_offset = base_text_byte_range.start; + let base_text_end_offset = base_text_byte_range.end; + let diff_base_end_offset = base_text_end_offset + .min(base_text_start_offset + self.end_offset.0 - hunk_start_offset.0); + let diff_base_start_offset = + base_text_start_offset + self.offset.0 - hunk_start_offset.0; + + let mut chunks = if let Some((_, mut chunks)) = self + .diff_base_chunks + .take() + .filter(|(id, _)| id == buffer_id) + { + if chunks.offset() != diff_base_start_offset { + chunks.seek(diff_base_start_offset..diff_base_end_offset); + } + chunks + } else { + let base_buffer = &self.snapshot.diffs.get(&buffer_id)?.base_text; + base_buffer.chunks( + diff_base_start_offset..diff_base_end_offset, + self.language_aware, + ) + }; + + let chunk = if let Some(chunk) = chunks.next() { + self.offset.0 += chunk.text.len(); + self.diff_base_chunks = Some((*buffer_id, chunks)); + chunk + } else { + debug_assert!(needs_newline); + self.offset.0 += "\n".len(); + Chunk { + text: "\n", + ..Default::default() + } + }; + Some(chunk) + } + } + } +} + +impl<'a> DiffMapRows<'a> { + pub fn seek(&mut self, row: u32) { + self.diff_point = DiffPoint::new(row, 0); + self.cursor.seek(&self.diff_point, Bias::Right, &()); + let (diff_transform_start, inlay_transform_start) = self.cursor.start().clone(); + let overshoot = if matches!( + self.cursor.item(), + Some(DiffTransform::BufferContent { .. }) + ) { + self.diff_point.row() - diff_transform_start.row() + } else { + 0 + }; + self.input_buffer_rows + .seek(MultiBufferRow(inlay_transform_start.row + overshoot)); + } +} + +impl<'a> Iterator for DiffMapRows<'a> { + type Item = RowInfo; + + fn next(&mut self) -> Option { + let result = match self.cursor.item() { + Some(DiffTransform::DeletedHunk { .. }) => Some(RowInfo { + buffer_row: None, + diff_status: Some(DiffHunkStatus::Removed), + }), + Some(DiffTransform::BufferContent { + is_inserted_hunk, .. + }) => { + let row = self.input_buffer_rows.next(); + row.map(|row| RowInfo { + buffer_row: row, + diff_status: if *is_inserted_hunk { + Some(DiffHunkStatus::Added) + } else { + None + }, + }) + } + None => self.input_buffer_rows.next().map(|row| RowInfo { + buffer_row: row, + diff_status: None, + }), + }; + self.diff_point.0 += Point::new(1, 0); + if self.diff_point >= self.cursor.end(&()).0 { + self.cursor.next(&()); + } + result + } +} + +impl sum_tree::Item for DiffTransform { + type Summary = DiffTransformSummary; + + fn summary(&self, _: &::Context) -> Self::Summary { + match self { + DiffTransform::BufferContent { summary, .. } => DiffTransformSummary { + multibuffer_map: summary.clone(), + diff_map: summary.clone(), + }, + DiffTransform::DeletedHunk { + summary_including_newline: summary, + .. + } => DiffTransformSummary { + multibuffer_map: TextSummary::default(), + diff_map: summary.clone(), + }, + } + } +} + +impl sum_tree::Summary for DiffTransformSummary { + type Context = (); + + fn zero(_: &Self::Context) -> Self { + DiffTransformSummary { + multibuffer_map: TextSummary::default(), + diff_map: TextSummary::default(), + } + } + + fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + self.multibuffer_map += &summary.multibuffer_map; + self.diff_map += &summary.diff_map; + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for usize { + fn zero(_: &()) -> Self { + 0 + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + *self += summary.multibuffer_map.len + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffOffset { + fn zero(_: &()) -> Self { + DiffOffset(0) + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + self.0 += summary.diff_map.len + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for Point { + fn zero(_: &()) -> Self { + Point::zero() + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + *self += summary.multibuffer_map.lines + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for DiffPoint { + fn zero(_: &()) -> Self { + DiffPoint(Point::zero()) + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + self.0 += summary.diff_map.lines + } +} + +impl<'a> sum_tree::Dimension<'a, DiffTransformSummary> for TextSummary { + fn zero(_cx: &()) -> Self { + Default::default() + } + + fn add_summary(&mut self, summary: &'a DiffTransformSummary, _: &()) { + *self += &summary.diff_map; + } +} + +impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for DiffPoint { + fn cmp( + &self, + cursor_location: &DiffTransformSummary, + _: &::Context, + ) -> std::cmp::Ordering { + Ord::cmp(self, &cursor_location.diff_point()) + } +} + +impl<'a> sum_tree::SeekTarget<'a, DiffTransformSummary, DiffTransformSummary> for DiffOffset { + fn cmp( + &self, + cursor_location: &DiffTransformSummary, + _: &::Context, + ) -> std::cmp::Ordering { + Ord::cmp(self, &cursor_location.diff_offset()) + } +} + +impl std::ops::Add for DiffOffset { + type Output = DiffOffset; + + fn add(self, rhs: DiffOffset) -> Self::Output { + DiffOffset(self.0 + rhs.0) + } +} + +impl std::ops::AddAssign for DiffOffset { + fn add_assign(&mut self, rhs: DiffOffset) { + self.0 += rhs.0; + } +} + +impl std::ops::Sub for DiffOffset { + type Output = DiffOffset; + + fn sub(self, rhs: DiffOffset) -> Self::Output { + DiffOffset(self.0 - rhs.0) + } +} + +impl std::ops::SubAssign for DiffOffset { + fn sub_assign(&mut self, rhs: DiffOffset) { + self.0 -= rhs.0; + } +} + +impl std::ops::Add for DiffPoint { + type Output = DiffPoint; + + fn add(self, rhs: DiffPoint) -> Self::Output { + DiffPoint(self.0 + rhs.0) + } +} + +impl std::ops::AddAssign for DiffPoint { + fn add_assign(&mut self, rhs: DiffPoint) { + self.0 += rhs.0; + } +} + +impl std::ops::Sub for DiffPoint { + type Output = DiffPoint; + + fn sub(self, rhs: DiffPoint) -> Self::Output { + DiffPoint(self.0 - rhs.0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use gpui::{AppContext, TestAppContext}; + use indoc::indoc; + use language::Buffer; + use multi_buffer::{Anchor, MultiBuffer}; + use project::Project; + use settings::SettingsStore; + use text::OffsetUtf16; + + #[gpui::test] + fn test_basic_diff_map_updates(cx: &mut TestAppContext) { + cx.update(init_test); + + let text = indoc!( + " + ZERO + one + TWO + three + six + " + ); + let base_text = indoc!( + " + one + two + three + four + five + six + " + ); + + let (diff_map, mut snapshot, mut deps) = build_diff_map(text, Some(base_text), cx); + assert_eq!( + snapshot.text(), + indoc!( + " + ZERO + one + TWO + three + six + " + ), + ); + + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + + ZERO + one + - two + + TWO + three + - four + - five + six + " + ), + ); + + assert_eq!( + snapshot + .row_infos(0) + .map(|info| info.buffer_row) + .collect::>(), + vec![ + Some(0), + Some(1), + None, + Some(2), + Some(3), + None, + None, + Some(4), + Some(5) + ] + ); + + assert_chunks_in_ranges(&snapshot); + assert_consistent_line_numbers(&snapshot); + + for (point, offset) in &[ + ( + DiffPoint::new(0, 0), + DiffOffset(snapshot.text().find("ZERO").unwrap()), + ), + ( + DiffPoint::new(2, 2), + DiffOffset(snapshot.text().find("two").unwrap() + 2), + ), + ( + DiffPoint::new(4, 3), + DiffOffset(snapshot.text().find("three").unwrap() + 3), + ), + (DiffPoint::new(8, 0), DiffOffset(snapshot.text().len())), + ] { + let actual = snapshot.point_to_offset(*point); + assert_eq!(actual, *offset, "for {:?}", point); + let actual = snapshot.offset_to_point(*offset); + assert_eq!(actual, *point, "for {:?}", offset); + } + + diff_map.update(cx, |diff_map, cx| { + diff_map.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one + TWO + three + six + " + ), + ); + + // Expand the first diff hunk + diff_map.update(cx, |diff_map, cx| { + let position = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.expand_diff_hunks(vec![position..position], cx) + }); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one + - two + + TWO + three + six + " + ), + ); + + // Expand the second diff hunk + diff_map.update(cx, |diff_map, cx| { + let position = deps.multibuffer_snapshot.anchor_before(Point::new(3, 0)); + diff_map.expand_diff_hunks(vec![position..position], cx) + }); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one + - two + + TWO + three + - four + - five + six + " + ), + ); + + // Edit the buffer before the first hunk + let edits = deps.update_buffer(cx, |buffer, cx| { + buffer.edit_via_marked_text( + indoc!( + " + ZERO + one« hundred + thousand» + TWO + three + six + " + ), + None, + cx, + ); + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), edits, cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one hundred + thousand + - two + + TWO + three + - four + - five + six + " + ), + ); + + // Recalculate the diff, changing the first diff hunk. + let _ = deps.change_set.update(cx, |change_set, cx| { + change_set.recalculate_diff(deps.buffer.read(cx).text_snapshot(), cx) + }); + cx.run_until_parked(); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), Vec::new(), cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + ZERO + one hundred + thousand + TWO + three + - four + - five + six + " + ), + ); + } + + #[gpui::test] + fn test_diff_map_multiple_buffer_edits(cx: &mut TestAppContext) { + cx.update(init_test); + + let text = "hello world"; + + let (diff_map, mut snapshot, mut deps) = build_diff_map(text, None, cx); + assert_eq!(snapshot.text(), "hello world"); + + let edits = deps.update_buffer(cx, |buffer, cx| { + buffer.edit([(4..5, "a"), (9..11, "k")], None, cx); + }); + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot, edits, cx) + }); + + assert_new_snapshot(&mut snapshot, sync, indoc!("hella work")); + } + + #[gpui::test] + fn test_diff_map_clipping(cx: &mut TestAppContext) { + cx.update(init_test); + + let text = indoc!( + "₀ + ₃ + ₄ + ₅ + " + ); + let base_text = indoc!( + "₀ + ₁ + ₂ + ₃ + ₄ + " + ); + + let (diff_map, mut diff_snapshot, deps) = build_diff_map(text, Some(base_text), cx); + diff_map.update(cx, |diff_map, cx| diff_map.set_all_hunks_expanded(cx)); + cx.run_until_parked(); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot, vec![], cx) + }); + + assert_new_snapshot( + &mut diff_snapshot, + sync, + indoc! {" + ₀ + - ₁ + - ₂ + ₃ + ₄ + + ₅ + "}, + ); + + for (point, (left, right)) in [ + ( + DiffPoint::new(0, 0), // start + (DiffPoint::new(0, 0), DiffPoint::new(0, 0)), + ), + ( + DiffPoint::new(1, 1), // deleted + (DiffPoint::new(1, 0), DiffPoint::new(1, 3)), + ), + ( + DiffPoint::new(3, 1), // unchanged + (DiffPoint::new(3, 0), DiffPoint::new(3, 3)), + ), + ( + DiffPoint::new(5, 2), // inserted + (DiffPoint::new(5, 0), DiffPoint::new(5, 3)), + ), + ( + DiffPoint::new(6, 0), // end + (DiffPoint::new(6, 0), DiffPoint::new(6, 0)), + ), + ( + DiffPoint::new(7, 7), // beyond + (DiffPoint::new(6, 0), DiffPoint::new(6, 0)), + ), + ] { + assert_eq!(left, diff_snapshot.clip_point(point, Bias::Left)); + assert_eq!(right, diff_snapshot.clip_point(point, Bias::Right)); + } + + assert_eq!( + diff_snapshot.text_summary_for_range(DiffOffset(0)..DiffOffset(0)), + TextSummary::default() + ); + assert_eq!( + diff_snapshot.text_summary_for_range(diff_snapshot.len()..diff_snapshot.len()), + TextSummary::default() + ); + let full_summary = TextSummary { + len: 24, + len_utf16: OffsetUtf16(12), + lines: Point { row: 6, column: 0 }, + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 0, + longest_row_chars: 1, + }; + let partial_summary = TextSummary { + len: 8, + len_utf16: OffsetUtf16(4), + lines: Point { row: 2, column: 0 }, + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 0, + longest_row_chars: 1, + }; + + let two = DiffOffset(diff_snapshot.text().find("₂").unwrap()); + let four = DiffOffset(diff_snapshot.text().find("₄").unwrap()); + + assert_eq!( + diff_snapshot.text_summary_for_range(DiffOffset(0)..diff_snapshot.len()), + full_summary + ); + assert_eq!( + diff_snapshot.text_summary_for_range(DiffOffset(0)..two), + partial_summary + ); + assert_eq!( + diff_snapshot.text_summary_for_range(two..four), + partial_summary + ); + assert_eq!( + diff_snapshot.text_summary_for_range(four..diff_snapshot.len()), + partial_summary + ); + } + + #[gpui::test] + fn test_empty_diff_map(cx: &mut TestAppContext) { + cx.update(init_test); + + let (_diff_map, diff_snapshot, _deps) = build_diff_map("", None, cx); + assert_eq!( + diff_snapshot + .row_infos(0) + .map(|info| info.buffer_row) + .collect::>(), + [Some(0)] + ); + } + + #[gpui::test] + fn test_expand_no_newline(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = "todo"; + let text = indoc!( + " + one + two + " + ); + + let (diff_map, mut diff_snapshot, deps) = build_diff_map(text, Some(base_text), cx); + assert_eq!(diff_snapshot.text(), "one\ntwo\n"); + assert_eq!( + diff_snapshot + .row_infos(0) + .map(|info| info.buffer_row) + .collect::>(), + [Some(0), Some(1), Some(2)] + ); + + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut diff_snapshot, + sync, + indoc!( + " + - todo + + one + + two + " + ), + ); + + assert_eq!(diff_snapshot.text(), "todo\none\ntwo\n"); + + assert_eq!( + diff_snapshot + .row_infos(0) + .map(|info| info.buffer_row) + .collect::>(), + [None, Some(0), Some(1), Some(2)] + ); + assert_eq!( + diff_snapshot + .row_infos(1) + .map(|info| info.buffer_row) + .collect::>(), + [Some(0), Some(1), Some(2)] + ); + + for (point, offset) in [ + (DiffPoint::new(0, 4), DiffOffset(4)), + (DiffPoint::new(1, 0), DiffOffset(5)), + (DiffPoint::new(1, 1), DiffOffset(6)), + ] { + assert_eq!(diff_snapshot.offset_to_point(offset), point); + assert_eq!(diff_snapshot.point_to_offset(point), offset); + } + + for point in [ + DiffPoint::new(0, 4), + DiffPoint::new(1, 0), + DiffPoint::new(1, 1), + ] { + let anchor = diff_snapshot.point_to_anchor(point, Bias::Left); + dbg!(&anchor); + let actual = diff_snapshot.anchor_to_point(anchor); + assert_eq!(point, actual); + } + + assert_eq!( + diff_snapshot.clip_point(DiffPoint::new(0, 5), Bias::Left), + DiffPoint::new(0, 4) + ); + assert_eq!( + diff_snapshot.to_diff_point(Point::new(0, 0)), + DiffPoint::new(1, 0) + ); + assert_eq!( + diff_snapshot.to_multibuffer_point(DiffPoint::new(0, 4)), + Point::new(0, 0) + ); + } + + #[gpui::test] + fn test_expand_collapse_at_positions_adjacent_to_hunks(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = indoc!( + " + one + two + three + four + five + six + seven + eight + " + ); + let text = indoc!( + " + one + two + five + six; seven + eight + " + ); + + let (diff_map, mut snapshot, deps) = build_diff_map(text, Some(base_text), cx); + + // Expand at the line right below a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + - three + - four + five + six; seven + eight + " + ), + ); + + // Collapse at the line right below a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.collapse_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + five + six; seven + eight + " + ), + ); + + // Expand at the line right above a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(1, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + - three + - four + five + six; seven + eight + " + ), + ); + + eprintln!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + + // Expand at the line right below a modified hunk. Should not expand anything. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(4, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + - three + - four + five + six; seven + eight + " + ), + ); + } + + #[gpui::test] + fn test_expand_collapse_insertion_hunk(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = indoc!( + " + one + two + seven + eight + " + ); + let text = indoc!( + " + one + two + three + four + five + six + seven + eight + " + ); + + let (diff_map, mut snapshot, deps) = build_diff_map(text, Some(base_text), cx); + + // Expand at the line right right after a deleted hunk. + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.expand_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + + three + + four + + five + + six + seven + eight + " + ), + ); + + diff_map.update(cx, |diff_map, cx| { + let point = deps.multibuffer_snapshot.anchor_before(Point::new(2, 0)); + diff_map.collapse_diff_hunks(vec![point..point], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + three + four + five + six + seven + eight + " + ), + ); + } + + #[gpui::test] + fn test_edit_in_insertion_hunk(cx: &mut TestAppContext) { + cx.update(init_test); + + let base_text = indoc!( + " + one + two + six + seven + " + ); + let text = indoc!( + " + one + two + three + four + five + six + seven + " + ); + + let (diff_map, mut snapshot, mut deps) = build_diff_map(text, Some(base_text), cx); + + // Expand the hunk + diff_map.update(cx, |diff_map, cx| { + diff_map.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), vec![], cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + + three + + four + + five + six + seven + " + ), + ); + + eprintln!(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); + + let edits = deps.update_buffer(cx, |buffer, cx| { + buffer.edit_via_marked_text( + indoc!( + " + one + two + three« + !» + four + five + six + seven + " + ), + None, + cx, + ) + }); + + let sync = diff_map.update(cx, |diff_map, cx| { + diff_map.sync(deps.multibuffer_snapshot.clone(), edits, cx) + }); + assert_new_snapshot( + &mut snapshot, + sync, + indoc!( + " + one + two + + three + + ! + + four + + five + six + seven + " + ), + ); + } + + // #[track_caller] + fn assert_new_snapshot( + snapshot: &mut DiffMapSnapshot, + (new_snapshot, edits): (DiffMapSnapshot, Vec>), + expected_diff: &str, + ) { + let actual_text = new_snapshot.text(); + let line_infos = new_snapshot.row_infos(0).collect::>(); + let has_diff = line_infos.iter().any(|info| info.diff_status.is_some()); + let actual_diff = actual_text + .split('\n') + .zip(line_infos) + .map(|(line, info)| { + let marker = match info.diff_status { + Some(DiffHunkStatus::Added) => "+ ", + Some(DiffHunkStatus::Removed) => "- ", + Some(DiffHunkStatus::Modified) => unreachable!(), + None => { + if has_diff { + " " + } else { + "" + } + } + }; + if line.is_empty() { + String::new() + } else { + format!("{marker}{line}") + } + }) + .collect::>() + .join("\n"); + pretty_assertions::assert_eq!(actual_diff, expected_diff); + check_edits(snapshot, &new_snapshot, &edits); + *snapshot = new_snapshot; + } + + #[track_caller] + fn check_edits( + old_snapshot: &DiffMapSnapshot, + new_snapshot: &DiffMapSnapshot, + edits: &[DiffEdit], + ) { + let mut text = old_snapshot.text(); + let new_text = new_snapshot.text(); + for edit in edits.iter().rev() { + if !text.is_char_boundary(edit.old.start.0) + || !text.is_char_boundary(edit.old.end.0) + || !new_text.is_char_boundary(edit.new.start.0) + || !new_text.is_char_boundary(edit.new.end.0) + { + panic!( + "invalid edits: {:?}\nold text: {:?}\nnew text: {:?}", + edits, text, new_text + ); + } + + text.replace_range( + edit.old.start.0..edit.old.end.0, + &new_text[edit.new.start.0..edit.new.end.0], + ); + } + + pretty_assertions::assert_eq!(text, new_text, "invalid edits: {:?}", edits); + } + + #[track_caller] + fn assert_chunks_in_ranges(snapshot: &DiffMapSnapshot) { + let full_text = snapshot.text(); + for ix in 0..full_text.len() { + let offset = DiffOffset(ix); + let mut chunks = + snapshot.chunks(DiffOffset(0)..snapshot.len(), false, Default::default()); + chunks.seek(offset..snapshot.len()); + let tail = chunks.map(|chunk| chunk.text).collect::(); + assert_eq!(tail, &full_text[ix..], "seek to range: {:?}", ix..); + + let tail = snapshot + .chunks(offset..snapshot.len(), false, Default::default()) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!(tail, &full_text[ix..], "start from range: {:?}", ix..); + + let head = snapshot + .chunks(DiffOffset(0)..offset, false, Default::default()) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!(head, &full_text[..ix], "start with range: {:?}", ..ix); + } + } + + #[track_caller] + fn assert_consistent_line_numbers(snapshot: &DiffMapSnapshot) { + let all_line_numbers = snapshot.row_infos(0).collect::>(); + for start_row in 1..all_line_numbers.len() { + let line_numbers = snapshot.row_infos(start_row as u32).collect::>(); + assert_eq!( + line_numbers, + all_line_numbers[start_row..], + "start_row: {start_row}" + ); + + for seek_row in 0..all_line_numbers.len() { + let mut numbers = snapshot.row_infos(start_row as u32); + numbers.seek(seek_row as u32); + let line_numbers = numbers.collect::>(); + assert_eq!( + line_numbers, + all_line_numbers[seek_row..], + "seek_row: {seek_row}, start_row: {start_row}" + ); + } + } + } + + struct DiffMapDeps { + buffer: Model, + multibuffer: Model, + change_set: Model, + multibuffer_snapshot: MultiBufferSnapshot, + multibuffer_edits: text::Subscription, + } + + fn build_diff_map( + text: &str, + base_text: Option<&str>, + cx: &mut TestAppContext, + ) -> (Model, DiffMapSnapshot, DiffMapDeps) { + let buffer = cx.new_model(|cx| Buffer::local(text, cx)); + + let change_set = cx.new_model(|cx| { + let text_snapshot = buffer.read(cx).text_snapshot(); + let mut change_set = BufferChangeSet::new(&text_snapshot); + if let Some(base_text) = base_text { + let _ = change_set.set_base_text(base_text.to_string(), text_snapshot, cx); + } + change_set + }); + + let multibuffer = cx.new_model(|cx| MultiBuffer::singleton(buffer.clone(), cx)); + + let (multibuffer_snapshot, multibuffer_edits) = + multibuffer.update(cx, |buffer, cx| (buffer.snapshot(cx), buffer.subscribe())); + + let (diff_map, diff_map_snapshot) = cx.update(|cx| DiffMap::new(multibuffer.clone(), cx)); + diff_map.update(cx, |diff_map, cx| { + diff_map.add_change_set(change_set.clone(), cx) + }); + cx.run_until_parked(); + + ( + diff_map, + diff_map_snapshot, + DiffMapDeps { + buffer, + multibuffer, + change_set, + multibuffer_snapshot, + multibuffer_edits, + }, + ) + } + + impl DiffMapDeps { + fn update_buffer( + &mut self, + cx: &mut TestAppContext, + f: impl FnOnce(&mut Buffer, &mut ModelContext), + ) -> Vec> { + self.buffer.update(cx, f); + + self.multibuffer_snapshot = self + .multibuffer + .read_with(cx, |buffer, cx| buffer.snapshot(cx)); + self.multibuffer_edits.consume().into_inner() + } + } + + fn init_test(cx: &mut AppContext) { + let settings = SettingsStore::test(cx); + cx.set_global(settings); + language::init(cx); + crate::init(cx); + Project::init_settings(cx); + } +} diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index f840dc4357c0c2..3f8d8f87df34ba 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -1,9 +1,11 @@ mod anchor; +mod diff_map; pub use anchor::{Anchor, AnchorRangeExt, Offset}; use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; +pub use diff_map::*; use futures::{channel::mpsc, SinkExt}; use gpui::{AppContext, EntityId, EventEmitter, Model, ModelContext, Task}; use itertools::Itertools; @@ -69,6 +71,7 @@ pub struct MultiBuffer { history: History, title: Option, capability: Capability, + diff_map: Model, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -179,6 +182,7 @@ struct BufferState { pub struct MultiBufferSnapshot { singleton: bool, excerpts: SumTree, + // diff_map_snapshot: DiffMapSnapshot, excerpt_ids: SumTree, trailing_excerpt_update_count: usize, non_text_state_update_count: usize, @@ -2559,6 +2563,39 @@ impl MultiBufferSnapshot { .eq(needle.bytes()) } + pub fn range_to_buffer_ranges( + &self, + range: Range, + ) -> Vec<(BufferSnapshot, Range, ExcerptId)> { + 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::(&()); + 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()); + result.push((excerpt.buffer.clone(), start..end, excerpt.id)); + cursor.next(&()); + } + + result + } + pub fn surrounding_word( &self, start: T,