Compare commits

...

2 Commits

Author SHA1 Message Date
Cole Miller
c8fe49b372 wip 2025-10-31 15:17:11 -04:00
Cole Miller
f1c36ba9ae wip 2025-10-30 23:39:42 -04:00
8 changed files with 316 additions and 36 deletions

View File

@@ -565,6 +565,15 @@ impl DisplayMap {
self.inlay_map.current_inlays()
}
// pub(crate) fn sync_unsplittable_groups(&mut self) {
// let group_boundaries = /* read diff hunk info from self */;
// self.splice_unsplittable_groups(group_boundaries);
// }
// fn splice_unsplittable_groups(&mut self, group_boundaries: Vec<usize>) {
// }
pub(crate) fn splice_inlays(
&mut self,
to_remove: &[InlayId],

View File

@@ -507,6 +507,55 @@ impl BlockMap {
BlockMapWriter(self)
}
// current problem: how do we know how many spacer lines?
// q: which buffer row if any does this wrap row end? log(n)
// iterate over buffer rows; for each one, translate into a wrap row and seek to that wrap row
// q: for a given buffer row, what is the wrap row count? log(n)
// q: which diff hunk if any does this wrap row end?
// q: for a given diff hunk, what is the wrap row count of the _other side_?
// struct DiffLineMapping {
// group_boundaries: Vec<usize>, // group_boundaries[x] would be the start phys row for the `x`th group
// // group_boundaries[x + 1] would be the end phsy ...
// row_boundaries: Vec<usize>, // row_boundaries[x] would be the start wrapped row for the `x`th phys line
// // row_boundaries[x + 1] would be the end wrapped_row ...
// }
// group is either:
// - a physical line that is not in a diff
// - an entire diff
//
// we never split up a group with a spacer
// impl DiffLineMapping {
// // modulo off-by-one errors
// fn which_buffer_row_end(&self, wrap_row: usize) -> Option<usize> {
// self.row_boundaries.binary_search(wrap_row).ok()
// }
// fn number_of_spacers_needed(&self, physical_line: usize, other: &DiffLineMapping) -> isize {
// (self.row_boundaries[physical_line + 1] - self.row_boundaries[physical_line]) - (other.row_boundaries[physical_line + 1] - other.row_boundaries[physical_line])
// }
// // left[x].end - left[x.start] - (right[x].end - right[x].start)
// }
// - we get some wrap row edits
// - compute the affected range in the same way
//
// old:
// loop {
// find the next block in the affected region for the edit
// insert an isomorphic transform leading up to that block
// insert block
// }
//
// new:
// for each wrap row in the affected region {
//
// }
fn sync(&self, wrap_snapshot: &WrapSnapshot, mut edits: Patch<u32>) {
let buffer = wrap_snapshot.buffer_snapshot();

View File

@@ -107,6 +107,8 @@ impl FoldPoint {
}
pub fn to_offset(self, snapshot: &FoldSnapshot) -> FoldOffset {
dbg!(&self);
dbg!(snapshot.max_point());
let (start, _, item) = snapshot
.transforms
.find::<Dimensions<FoldPoint, TransformSummary>, _>((), &self, Bias::Right);

View File

@@ -590,6 +590,7 @@ impl InlayMap {
.is_none_or(|edit| edit.old.start >= cursor.end().0)
{
let transform_start = new_transforms.summary().input.len;
// FIXME: attempted to subtract with overflow
let transform_end =
buffer_edit.new.end + (cursor.end().0 - buffer_edit.old.end);
push_isomorphic(

View File

@@ -834,6 +834,25 @@ impl WrapSnapshot {
None
}
// Editor calls DisplayMap::sync()
// - that calls all ther other WhateverMap::sync()s
//
// BlockMap::sync()
// - WrapSnapshot
// - GroupBoundary (for the other Editor's DisplayMap)
// - the other Editor's BlockMap::sync() must have been called
// - which requires this Editor's BlockMap::sync() to have been called
//
// MultiBuffer::sync() <- InlayMap::sync() <- ... <- WrapMap::sync() (us)
// MultiBuffer::sync() <- InlayMap::sync() <- ... <- WrapMap::sync() (them)
//
// BlockMap::sync(our wrap map, their wrap map) (us)
// BlockMap::sync(their wrap map, our wrap map) (them)
// pub fn next_group_boundary(&self, point: WrapPoint) -> Option<(u32, Option<(added_lines: u32, deleted_lines: u32)>)> {
// // ...
// }
#[cfg(test)]
pub fn text(&self) -> String {
self.text_chunks(0).collect()

View File

@@ -209,6 +209,27 @@ struct BufferState {
_subscriptions: [gpui::Subscription; 2],
}
// struct DiffModeQuery<T, const Mode: DiffMode>(T);
// struct InlaySnapshot {
// multibuffer: EditorMultibufferSnapshot,
// }
// struct EditorMultibuffer {
// mode: DiffMode,
// multibuffer: Entity<MultiBuffer>,
// }
// trait ToPointGitAware {
// fn to_point(&self, snapshot: ..., mode: DiffMode) -> Point;
// }
// impl<T: ToPointGitAware> ToPoint for (T, DiffMode) {
// fn to_point(&self, snapshot: ..., mode: DiffMode) -> (Point, DiffMode) {
// self.0.to_point(snapshot, self.1)
// }
// }
struct DiffState {
diff: Entity<BufferDiff>,
_subscription: gpui::Subscription,
@@ -265,6 +286,7 @@ enum DiffTransform {
base_text_byte_range: Range<usize>,
has_trailing_newline: bool,
},
SkippedHunk(TextSummary),
}
#[derive(Clone, Copy, Debug)]
@@ -486,6 +508,7 @@ impl<'a, D: TextDimension> Dimension<'a, DiffTransformSummary> for DiffTransform
#[derive(Clone)]
struct MultiBufferCursor<'a, D: TextDimension> {
excerpts: Cursor<'a, 'static, Excerpt, ExcerptDimension<D>>,
// DONE
diff_transforms: Cursor<'a, 'static, DiffTransform, DiffTransforms<D>>,
diffs: &'a TreeMap<BufferId, BufferDiffSnapshot>,
cached_region: Option<MultiBufferRegion<'a, D>>,
@@ -2461,6 +2484,7 @@ impl MultiBuffer {
}
let mut excerpts = snapshot.excerpts.cursor::<ExcerptOffset>(());
dbg!(snapshot.diff_transforms.iter().collect::<Vec<_>>());
let mut old_diff_transforms = snapshot
.diff_transforms
.cursor::<Dimensions<ExcerptOffset, usize>>(());
@@ -2471,6 +2495,7 @@ impl MultiBuffer {
let mut at_transform_boundary = true;
let mut end_of_current_insert = None;
dbg!("------------------", excerpt_edits.len());
let mut excerpt_edits = excerpt_edits.into_iter().peekable();
while let Some(edit) = excerpt_edits.next() {
excerpts.seek_forward(&edit.new.start, Bias::Right);
@@ -2510,10 +2535,17 @@ impl MultiBuffer {
// Compute the end of the edit in output coordinates.
let edit_old_end_overshoot = edit.old.end - old_diff_transforms.start().0;
let edit_new_end_overshoot = edit.new.end - new_diff_transforms.summary().excerpt_len();
dbg!(new_diff_transforms.iter().collect::<Vec<_>>());
// text between new_diff_transforms.summary().excerpt_len() and edit.new.end is
// bone fide inserted, but it might be part of an added region.
let edit_new_end_overshoot =
dbg!(edit.new.end) - dbg!(new_diff_transforms.summary().excerpt_len());
let edit_old_end = old_diff_transforms.start().1 + edit_old_end_overshoot.value;
let edit_new_end =
new_diff_transforms.summary().output.len + edit_new_end_overshoot.value;
let mut edit_new_end = dbg!(new_diff_transforms.summary().output.len);
if true && end_of_current_insert.is_none() {
// FIXME
edit_new_end += dbg!(edit_new_end_overshoot.value);
}
let output_edit = Edit {
old: edit_old_start..edit_old_end,
new: edit_new_start..edit_new_end,
@@ -2532,6 +2564,7 @@ impl MultiBuffer {
.peek()
.is_none_or(|next_edit| next_edit.old.start >= old_diff_transforms.end().0)
{
dbg!("SHOULD BE HERE");
let keep_next_old_transform = (old_diff_transforms.start().0 >= edit.old.end)
&& match old_diff_transforms.item() {
Some(DiffTransform::BufferContent {
@@ -2579,7 +2612,17 @@ impl MultiBuffer {
snapshot.diff_transforms = new_diff_transforms;
snapshot.edit_count += 1;
#[cfg(any(test, feature = "test-support"))]
// FIXME
for edit in &output_edits {
dbg!(edit.new.end, snapshot.len());
assert!(
edit.new.end <= snapshot.len(),
"transforms: {:?}",
snapshot.diff_transforms.iter().collect::<Vec<_>>()
)
}
// #[cfg(any(test, feature = "test-support"))]
snapshot.check_invariants();
output_edits
}
@@ -2672,7 +2715,7 @@ impl MultiBuffer {
Self::push_buffer_content_transform(
snapshot,
new_diff_transforms,
hunk_excerpt_start,
dbg!(hunk_excerpt_start),
*end_of_current_insert,
);
@@ -2732,8 +2775,11 @@ impl MultiBuffer {
}
if !hunk_buffer_range.is_empty() {
dbg!("yep");
*end_of_current_insert =
Some((hunk_excerpt_end.min(excerpt_end), hunk_info));
} else {
dbg!("nope");
}
}
}
@@ -2801,8 +2847,10 @@ impl MultiBuffer {
for (end_offset, inserted_hunk_info) in inserted_region.into_iter().chain(unchanged_region)
{
dbg!(&inserted_hunk_info);
let start_offset = new_transforms.summary().excerpt_len();
if end_offset <= start_offset {
dbg!();
continue;
}
let summary_to_add = old_snapshot
@@ -2813,13 +2861,15 @@ impl MultiBuffer {
inserted_hunk_info,
summary_to_add,
) {
new_transforms.push(
DiffTransform::BufferContent {
let transform = if inserted_hunk_info.is_some() {
dbg!(DiffTransform::SkippedHunk(summary_to_add))
} else {
dbg!(DiffTransform::BufferContent {
summary: summary_to_add,
inserted_hunk_info,
},
(),
)
})
};
new_transforms.push(transform, ())
}
}
}
@@ -4091,6 +4141,7 @@ impl MultiBufferSnapshot {
summary
}
DiffTransform::SkippedHunk(_) => Default::default(),
};
if range.end < diff_transform_end {
return result;
@@ -4129,6 +4180,7 @@ impl MultiBufferSnapshot {
}
suffix
}
DiffTransform::SkippedHunk(_) => Default::default(),
};
result.add_assign(&suffix);
@@ -5769,15 +5821,16 @@ impl MultiBufferSnapshot {
}
}
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
impl MultiBufferSnapshot {
#[cfg(any(test, feature = "test-support"))]
pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range<usize> {
let end = self.clip_offset(rng.random_range(start_offset..=self.len()), Bias::Right);
let start = self.clip_offset(rng.random_range(start_offset..=end), Bias::Right);
start..end
}
#[cfg(any(test, feature = "test-support"))]
// #[cfg(any(test, feature = "test-support"))]
fn check_invariants(&self) {
let excerpts = self.excerpts.items(());
let excerpt_ids = self.excerpt_ids.items(());
@@ -5838,10 +5891,46 @@ impl MultiBufferSnapshot {
}
}
fn next_non_skipped_diff_transform<'a, D: sum_tree::Dimension<'a, DiffTransformSummary>>(
cursor: &mut Cursor<'a, 'static, DiffTransform, D>,
) {
cursor.next();
if let Some(item) = cursor.item()
&& item.is_skipped_hunk()
{
cursor.next();
}
// Can't have consecutive skipped hunks
}
fn prev_non_skipped_diff_transform<'a, D: sum_tree::Dimension<'a, DiffTransformSummary>>(
cursor: &mut Cursor<'a, 'static, DiffTransform, D>,
) {
cursor.prev();
if let Some(item) = cursor.item()
&& item.is_skipped_hunk()
{
cursor.prev();
}
// Can't have consecutive skipped hunks
}
impl<'a, D> MultiBufferCursor<'a, D>
where
D: TextDimension + Ord + Sub<D, Output = D>,
{
// fn seek_forward_past_skipped_hunks(&mut self) {
// while let Some(DiffTransform::SkippedHunk(_)) = self.diff_transforms.item() {
// self.diff_transforms.next();
// }
// }
// fn seek_backward_past_skipped_hunks(&mut self) {
// while let Some(DiffTransform::SkippedHunk(_)) = self.diff_transforms.item() {
// self.diff_transforms.prev();
// }
// }
fn seek(&mut self, position: &D) {
self.cached_region.take();
self.diff_transforms
@@ -5849,7 +5938,7 @@ where
if self.diff_transforms.item().is_none()
&& *position == self.diff_transforms.start().output_dimension.0
{
self.diff_transforms.prev();
prev_non_skipped_diff_transform(&mut self.diff_transforms);
}
let mut excerpt_position = self.diff_transforms.start().excerpt_dimension.0;
@@ -5872,7 +5961,7 @@ where
if self.diff_transforms.item().is_none()
&& *position == self.diff_transforms.start().output_dimension.0
{
self.diff_transforms.prev();
prev_non_skipped_diff_transform(&mut self.diff_transforms);
}
let overshoot = *position - self.diff_transforms.start().output_dimension.0;
@@ -5905,7 +5994,8 @@ where
&& self.diff_transforms.start().excerpt_dimension < *self.excerpts.start()
&& self.diff_transforms.next_item().is_some()
{
self.diff_transforms.next();
// FIXME by the excerpt dimension comparison, there must be a non-skipped diff transform to find
next_non_skipped_diff_transform(&mut self.diff_transforms);
}
}
@@ -5917,10 +6007,12 @@ where
.excerpt_dimension
.cmp(&self.excerpts.end())
{
cmp::Ordering::Less => self.diff_transforms.next(),
cmp::Ordering::Less => {
next_non_skipped_diff_transform(&mut self.diff_transforms);
}
cmp::Ordering::Greater => self.excerpts.next(),
cmp::Ordering::Equal => {
self.diff_transforms.next();
next_non_skipped_diff_transform(&mut self.diff_transforms);
if self.diff_transforms.end().excerpt_dimension > self.excerpts.end()
|| self.diff_transforms.item().is_none()
{
@@ -5947,9 +6039,11 @@ where
.cmp(self.excerpts.start())
{
cmp::Ordering::Less => self.excerpts.prev(),
cmp::Ordering::Greater => self.diff_transforms.prev(),
cmp::Ordering::Greater => {
next_non_skipped_diff_transform(&mut self.diff_transforms);
}
cmp::Ordering::Equal => {
self.diff_transforms.prev();
prev_non_skipped_diff_transform(&mut self.diff_transforms);
if self.diff_transforms.start().excerpt_dimension < *self.excerpts.start()
|| self.diff_transforms.item().is_none()
{
@@ -5978,7 +6072,10 @@ where
self.diff_transforms.next();
prev_transform.is_none_or(|next_transform| {
matches!(next_transform, DiffTransform::BufferContent { .. })
matches!(
next_transform,
DiffTransform::BufferContent { .. } | DiffTransform::SkippedHunk(_)
)
})
}
@@ -5993,7 +6090,7 @@ where
let next_transform = self.diff_transforms.next_item();
next_transform.is_none_or(|next_transform| match next_transform {
DiffTransform::BufferContent { .. } => true,
DiffTransform::BufferContent { .. } | DiffTransform::SkippedHunk(_) => true,
DiffTransform::DeletedHunk { hunk_info, .. } => self
.excerpts
.item()
@@ -6090,6 +6187,10 @@ where
range: start..end,
})
}
_ => {
log::error!("cursor parked on skipped hunk");
None
}
}
}
@@ -6397,6 +6498,14 @@ impl DiffTransform {
DiffTransform::BufferContent {
inserted_hunk_info, ..
} => *inserted_hunk_info,
DiffTransform::SkippedHunk(_) => None,
}
}
fn is_skipped_hunk(&self) -> bool {
match self {
DiffTransform::SkippedHunk(_) => true,
_ => false,
}
}
}
@@ -6405,6 +6514,10 @@ impl sum_tree::Item for DiffTransform {
type Summary = DiffTransformSummary;
fn summary(&self, _: <Self::Summary as sum_tree::Summary>::Context<'_>) -> Self::Summary {
// in "no deletions" mode,
// DiffTransform::DeletedHunk doesn't add to output either
// in "no additions" mode,
// DiffTransform::BufferContent doesn't add to output if it's an insertion
match self {
DiffTransform::BufferContent { summary, .. } => DiffTransformSummary {
input: *summary,
@@ -6414,6 +6527,10 @@ impl sum_tree::Item for DiffTransform {
input: TextSummary::default(),
output: *summary,
},
DiffTransform::SkippedHunk(summary) => DiffTransformSummary {
input: *summary,
output: TextSummary::default(),
},
}
}
}
@@ -6852,7 +6969,7 @@ impl<'a> Iterator for ReversedMultiBufferChunks<'a> {
}
}
}
// multi_buffer.chunks().map(|chunk| chunk.text()).collect::<String>() = "what the user sees"
impl<'a> Iterator for MultiBufferChunks<'a> {
type Item = Chunk<'a>;
@@ -6861,9 +6978,17 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
return None;
}
if self.range.start == self.diff_transforms.end().0 {
self.diff_transforms.next();
next_non_skipped_diff_transform(&mut self.diff_transforms);
}
debug_assert!(self.range.start == self.diff_transforms.end().0);
// self.diff_t.. |--------------|
// self.range |-----------------------------|
// |------| |-skipped hunk-| |-----------|
// ^ ^
// figure out which transform we're going to operate on (it should be a non-skipped transform)
dbg!(self.diff_transforms.item());
let diff_transform_start = self.diff_transforms.start().0;
let diff_transform_end = self.diff_transforms.end().0;
debug_assert!(self.range.start < diff_transform_end);
@@ -6892,7 +7017,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
chunk.text = after;
chunk.chars = chunk.chars >> split_idx;
chunk.tabs = chunk.tabs >> split_idx;
Some(Chunk {
text: before,
chars,
@@ -6946,6 +7071,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> {
};
Some(chunk)
}
DiffTransform::SkippedHunk(_) => unreachable!("two skipped hunks in a row"),
}
}
}

View File

@@ -1377,6 +1377,45 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) {
);
}
#[gpui::test]
async fn test_insertion(cx: &mut TestAppContext) {
let text = indoc!(
"
a
b
c
"
);
let base_text = "";
let buffer = cx.new(|cx| Buffer::local(text, cx));
let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
cx.run_until_parked();
let multibuffer = cx.new(|cx| {
let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx);
multibuffer.add_diff(diff.clone(), cx);
multibuffer
});
multibuffer.update(cx, |multibuffer, cx| {
multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx);
});
cx.run_until_parked();
let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| {
(multibuffer.snapshot(cx), multibuffer.subscribe())
});
assert_eq!(
snapshot.text(),
indoc!(
"
a
b
c
"
),
);
}
#[gpui::test]
fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) {
let text = indoc!(
@@ -2328,18 +2367,18 @@ impl ReferenceMultibuffer {
}
// Add the inserted text for the hunk.
if hunk_range.end > offset {
let len = text.len();
text.extend(buffer.text_for_range(offset..hunk_range.end));
regions.push(ReferenceRegion {
buffer_id: Some(buffer.remote_id()),
range: len..text.len(),
buffer_start: Some(buffer.offset_to_point(offset)),
status: Some(DiffHunkStatus::added(hunk.secondary_status)),
excerpt_id: Some(excerpt.id),
});
offset = hunk_range.end;
}
// if hunk_range.end > offset {
// let len = text.len();
// text.extend(buffer.text_for_range(offset..hunk_range.end));
// regions.push(ReferenceRegion {
// buffer_id: Some(buffer.remote_id()),
// range: len..text.len(),
// buffer_start: Some(buffer.offset_to_point(offset)),
// status: Some(DiffHunkStatus::added(hunk.secondary_status)),
// excerpt_id: Some(excerpt.id),
// });
// offset = hunk_range.end;
// }
}
// Add the buffer text for the rest of the excerpt.
@@ -3962,3 +4001,28 @@ fn test_random_chunk_bitmaps_with_diffs(cx: &mut App, mut rng: StdRng) {
}
}
}
#[gpui::test]
async fn test_seeking_with_skipped_hunks() {
let first_part = "one\n";
let transforms = SumTree::from_iter(
[
DiffTransform::BufferContent {
summary: TextSummary::from(first_part),
inserted_hunk_info: None,
},
DiffTransform::SkippedHunk(TextSummary::from("2\n")),
DiffTransform::SkippedHunk(TextSummary::from("22\n")),
DiffTransform::BufferContent {
summary: TextSummary::from("3!!!\n"),
inserted_hunk_info: None,
},
],
(),
);
let mut cursor = transforms.cursor::<usize>(());
cursor.seek(&first_part.len(), Bias::Left);
dbg!(cursor.item());
cursor.seek(&first_part.len(), Bias::Right);
dbg!(cursor.item());
}

View File

@@ -169,6 +169,16 @@ impl ConflictSet {
cx.emit(update);
}
// Vec<(Range<usize>)>
// Vec<(Range<usize>, &str)>
//
// [(1..2, ""), (6..7, "")]
// {"hello": "world"}
// {hello: "world"}
//
// foo(bar);
// }
pub fn parse(buffer: &text::BufferSnapshot) -> ConflictSetSnapshot {
let mut conflicts = Vec::new();