History

Merge branch 'main' into modeline

Conrad Irwin committed 9 months ago

Commit af834782 parents 8d6d510 8273e6d675 files · +80785 −27108Browse files

Showing the changes to crates/buffer_diff/src/buffer_diff.rs (1 of 675 files). Show all

Unified · Split

crates/buffer_diff/src/buffer_diff.rs+1974−228
@@ -1,28 +1,52 @@
11 use futures::channel::oneshot;
22 use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch};
3−use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task, TaskLabel};
3+use gpui::{App, AppContext as _, Context, Entity, EventEmitter, Task};
44 use language::{
5− BufferRow, Capability, DiffOptions, Language, LanguageName, LanguageRegistry,
5+ Capability, Diff, DiffOptions, File, Language, LanguageName, LanguageRegistry,
66 language_settings::LanguageSettings, word_diff_ranges,
77 };
88 use rope::Rope;
9−use std::{
10− cmp::Ordering,
11− iter,
12− ops::Range,
13− sync::{Arc, LazyLock},
14−};
9+use std::{cmp::Ordering, future::Future, iter, ops::Range, sync::Arc};
1510 use sum_tree::SumTree;
16−use text::{Anchor, Bias, BufferId, OffsetRangeExt, Point, ToOffset as _, ToPoint as _};
11+use text::{
12+ Anchor, Bias, BufferId, Edit, OffsetRangeExt, Patch, Point, ToOffset as _, ToPoint as _,
13+};
1714 use util::ResultExt;
1815
19−pub static CALCULATE_DIFF_TASK: LazyLock<TaskLabel> = LazyLock::new(TaskLabel::new);
16+fn translate_point_through_patch(
17+ patch: &Patch<Point>,
18+ point: Point,
19+) -> (Range<Point>, Range<Point>) {
20+ let edits = patch.edits();
21+
22+ let ix = match edits.binary_search_by(|probe| probe.old.start.cmp(&point)) {
23+ Ok(ix) => ix,
24+ Err(ix) => {
25+ if ix == 0 {
26+ return (point..point, point..point);
27+ } else {
28+ ix - 1
29+ }
30+ }
31+ };
32+
33+ if let Some(edit) = edits.get(ix) {
34+ if point > edit.old.end {
35+ let translated = edit.new.end + (point - edit.old.end);
36+ (translated..translated, point..point)
37+ } else {
38+ (edit.new.start..edit.new.end, edit.old.start..edit.old.end)
39+ }
40+ } else {
41+ (point..point, point..point)
42+ }
43+}
44+
2045 pub const MAX_WORD_DIFF_LINE_COUNT: usize = 5;
2146
2247 pub struct BufferDiff {
2348 pub buffer_id: BufferId,
2449 inner: BufferDiffInner<Entity<language::Buffer>>,
25− // diff of the index vs head
2650 secondary_diff: Option<Entity<BufferDiff>>,
2751 }
2852
@@ -43,8 +67,10 @@ impl std::fmt::Debug for BufferDiffSnapshot { · 14 unmodified lines
4367
4468 #[derive(Clone)]
4569 pub struct BufferDiffUpdate {
46− base_text_changed: bool,
4770 inner: BufferDiffInner<Arc<str>>,
71+ buffer_snapshot: text::BufferSnapshot,
72+ base_text_edits: Option<Diff>,
73+ base_text_changed: bool,
4874 }
4975
5076 #[derive(Clone)]
@@ -53,6 +79,13 @@ struct BufferDiffInner<BaseText> { · 2 unmodified lines
5379 pending_hunks: SumTree<PendingHunk>,
5480 base_text: BaseText,
5581 base_text_exists: bool,
82+ buffer_snapshot: text::BufferSnapshot,
83+}
84+
85+impl<BaseText> BufferDiffInner<BaseText> {
86+ fn buffer_version(&self) -> &clock::Global {
87+ self.buffer_snapshot.version()
88+ }
5689 }
5790
5891 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@@ -188,6 +221,18 @@ impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for Anchor { · 129 unmodified lines
188221 }
189222 }
190223
224+impl sum_tree::SeekTarget<'_, DiffHunkSummary, DiffHunkSummary> for usize {
225+ fn cmp(&self, cursor_location: &DiffHunkSummary, _cx: &text::BufferSnapshot) -> Ordering {
226+ if *self < cursor_location.diff_base_byte_range.start {
227+ Ordering::Less
228+ } else if *self > cursor_location.diff_base_byte_range.end {
229+ Ordering::Greater
230+ } else {
231+ Ordering::Equal
232+ }
233+ }
234+}
235+
191236 impl std::fmt::Debug for BufferDiffInner<language::BufferSnapshot> {
192237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193238 f.debug_struct("BufferDiffSnapshot")
@@ -222,6 +267,15 @@ impl BufferDiffSnapshot { · 28 unmodified lines
222267 self.secondary_diff.as_deref()
223268 }
224269
270+ pub fn buffer_version(&self) -> &clock::Global {
271+ self.inner.buffer_version()
272+ }
273+
274+ pub fn original_buffer_snapshot(&self) -> &text::BufferSnapshot {
275+ &self.inner.buffer_snapshot
276+ }
277+
278+ #[ztracing::instrument(skip_all)]
225279 pub fn hunks_intersecting_range<'a>(
226280 &'a self,
227281 range: Range<Anchor>,
@@ -331,63 +385,182 @@ impl BufferDiffSnapshot { · 103 unmodified lines
331385 (new_id == old_id && new_version == old_version) || (new_empty && old_empty)
332386 }
333387
334− pub fn row_to_base_text_row(
388+ #[allow(unused)]
389+ fn hunk_before_base_text_offset<'a>(
335390 &self,
336− row: BufferRow,
337− bias: Bias,
338− buffer: &text::BufferSnapshot,
339− ) -> u32 {
340− // TODO(split-diff) expose a parameter to reuse a cursor to avoid repeatedly seeking from the start
341− let target = buffer.anchor_before(Point::new(row, 0));
342− // Find the last hunk that starts before the target.
343− let mut cursor = self.inner.hunks.cursor::<DiffHunkSummary>(buffer);
344− cursor.seek(&target, Bias::Left);
391+ target: usize,
392+ cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
393+ ) -> Option<&'a InternalDiffHunk> {
394+ cursor.seek_forward(&target, Bias::Left);
345395 if cursor
346396 .item()
347− .is_none_or(|hunk| hunk.buffer_range.start.cmp(&target, buffer).is_gt())
397+ .is_none_or(|hunk| target < hunk.diff_base_byte_range.start)
348398 {
349399 cursor.prev();
350400 }
401+ let result = cursor
402+ .item()
403+ .filter(|hunk| target >= hunk.diff_base_byte_range.start);
404+ if cursor.item().is_none() {
405+ cursor.reset();
406+ }
407+ result
408+ }
351409
352− let unclipped_point = if let Some(hunk) = cursor.item()
353− && hunk.buffer_range.start.cmp(&target, buffer).is_le()
410+ #[allow(unused)]
411+ fn hunk_before_buffer_anchor<'a>(
412+ &self,
413+ target: Anchor,
414+ cursor: &mut sum_tree::Cursor<'a, '_, InternalDiffHunk, DiffHunkSummary>,
415+ buffer: &text::BufferSnapshot,
416+ ) -> Option<&'a InternalDiffHunk> {
417+ cursor.seek_forward(&target, Bias::Left);
418+ if cursor
419+ .item()
420+ .is_none_or(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_lt())
354421 {
355− // Found a hunk that starts before the target.
356− let hunk_base_text_end = cursor.end().diff_base_byte_range.end;
357− let unclipped_point = if target.cmp(&cursor.end().buffer_range.end, buffer).is_ge() {
358− // Target falls strictly between two hunks.
359− let mut unclipped_point = hunk_base_text_end.to_point(self.base_text());
360− unclipped_point +=
361− Point::new(row, 0) - cursor.end().buffer_range.end.to_point(buffer);
362− unclipped_point
363− } else if bias == Bias::Right {
364− hunk_base_text_end.to_point(self.base_text())
422+ cursor.prev();
423+ }
424+ let result = cursor
425+ .item()
426+ .filter(|hunk| target.cmp(&hunk.buffer_range.start, buffer).is_ge());
427+ if cursor.item().is_none() {
428+ cursor.reset();
429+ }
430+ result
431+ }
432+
433+ pub fn points_to_base_text_points<'a>(
434+ &'a self,
435+ points: impl IntoIterator<Item = Point> + 'a,
436+ buffer: &'a text::BufferSnapshot,
437+ ) -> (
438+ impl 'a + Iterator<Item = Range<Point>>,
439+ Option<Range<Point>>,
440+ Option<(Point, Range<Point>)>,
441+ ) {
442+ let original_snapshot = self.original_buffer_snapshot();
443+
444+ let edits_since: Vec<Edit<Point>> = buffer
445+ .edits_since::<Point>(original_snapshot.version())
446+ .collect();
447+ let mut inverted_edits_since = Patch::new(edits_since);
448+ inverted_edits_since.invert();
449+
450+ let composed = inverted_edits_since.compose(
451+ self.inner
452+ .hunks
453+ .iter()
454+ .map(|hunk| {
455+ let old_start = hunk.buffer_range.start.to_point(original_snapshot);
456+ let old_end = hunk.buffer_range.end.to_point(original_snapshot);
457+ let new_start = self
458+ .base_text()
459+ .offset_to_point(hunk.diff_base_byte_range.start);
460+ let new_end = self
461+ .base_text()
462+ .offset_to_point(hunk.diff_base_byte_range.end);
463+ Edit {
464+ old: old_start..old_end,
465+ new: new_start..new_end,
466+ }
467+ })
468+ .chain(
469+ if !self.inner.base_text_exists && self.inner.hunks.is_empty() {
470+ Some(Edit {
471+ old: Point::zero()..original_snapshot.max_point(),
472+ new: Point::zero()..Point::zero(),
473+ })
474+ } else {
475+ None
476+ },
477+ ),
478+ );
479+
480+ let mut points = points.into_iter().peekable();
481+
482+ let first_group = points.peek().map(|point| {
483+ let (_, old_range) = translate_point_through_patch(&composed, *point);
484+ old_range
485+ });
486+
487+ let prev_boundary = points.peek().and_then(|first_point| {
488+ if first_point.row > 0 {
489+ let prev_point = Point::new(first_point.row - 1, 0);
490+ let (range, _) = translate_point_through_patch(&composed, prev_point);
491+ Some((prev_point, range))
365492 } else {
366− hunk.diff_base_byte_range.start.to_point(self.base_text())
367− };
368− // Move the cursor so that at the next step we can clip with the start of the next hunk.
369− cursor.next();
370− unclipped_point
371− } else {
372− // Target is before the added region for the first hunk.
373− debug_assert!(self.inner.hunks.first().is_none_or(|first_hunk| {
374− target.cmp(&first_hunk.buffer_range.start, buffer).is_le()
375− }));
376− Point::new(row, 0)
377− };
493+ None
494+ }
495+ });
378496
379− // If the target falls in the region between two hunks, we added an overshoot above.
380− // There may be changes in the main buffer that are not reflected in the hunks,
381− // so we need to ensure this overshoot keeps us in the corresponding base text region.
382− let max_point = if let Some(next_hunk) = cursor.item() {
383− next_hunk
384− .diff_base_byte_range
385− .start
386− .to_point(self.base_text())
387− } else {
388− self.base_text().max_point()
389− };
390− unclipped_point.min(max_point).row
497+ let iter = points.map(move |point| {
498+ let (range, _) = translate_point_through_patch(&composed, point);
499+ range
500+ });
501+
502+ (iter, first_group, prev_boundary)
503+ }
504+
505+ pub fn base_text_points_to_points<'a>(
506+ &'a self,
507+ points: impl IntoIterator<Item = Point> + 'a,
508+ buffer: &'a text::BufferSnapshot,
509+ ) -> (
510+ impl 'a + Iterator<Item = Range<Point>>,
511+ Option<Range<Point>>,
512+ Option<(Point, Range<Point>)>,
513+ ) {
514+ let original_snapshot = self.original_buffer_snapshot();
515+
516+ let mut hunk_edits: Vec<Edit<Point>> = Vec::new();
517+ for hunk in self.inner.hunks.iter() {
518+ let old_start = self
519+ .base_text()
520+ .offset_to_point(hunk.diff_base_byte_range.start);
521+ let old_end = self
522+ .base_text()
523+ .offset_to_point(hunk.diff_base_byte_range.end);
524+ let new_start = hunk.buffer_range.start.to_point(original_snapshot);
525+ let new_end = hunk.buffer_range.end.to_point(original_snapshot);
526+ hunk_edits.push(Edit {
527+ old: old_start..old_end,
528+ new: new_start..new_end,
529+ });
530+ }
531+ if !self.inner.base_text_exists && hunk_edits.is_empty() {
532+ hunk_edits.push(Edit {
533+ old: Point::zero()..Point::zero(),
534+ new: Point::zero()..original_snapshot.max_point(),
535+ })
536+ }
537+ let hunk_patch = Patch::new(hunk_edits);
538+
539+ let composed = hunk_patch.compose(buffer.edits_since::<Point>(original_snapshot.version()));
540+
541+ let mut points = points.into_iter().peekable();
542+
543+ let first_group = points.peek().map(|point| {
544+ let (_, result) = translate_point_through_patch(&composed, *point);
545+ result
546+ });
547+
548+ let prev_boundary = points.peek().and_then(|first_point| {
549+ if first_point.row > 0 {
550+ let prev_point = Point::new(first_point.row - 1, 0);
551+ let (range, _) = translate_point_through_patch(&composed, prev_point);
552+ Some((prev_point, range))
553+ } else {
554+ None
555+ }
556+ });
557+
558+ let iter = points.map(move |point| {
559+ let (range, _) = translate_point_through_patch(&composed, point);
560+ range
561+ });
562+
563+ (iter, first_group, prev_boundary)
391564 }
392565 }
393566
@@ -541,7 +714,9 @@ impl BufferDiffInner<Entity<language::Buffer>> { · 147 unmodified lines
541714 let next_pending_hunk_offset_range =
542715 next_pending_hunk.buffer_range.to_offset(buffer);
543716 if next_pending_hunk_offset_range.start <= buffer_offset_range.end {
544− buffer_offset_range.end = next_pending_hunk_offset_range.end;
717+ buffer_offset_range.end = buffer_offset_range
718+ .end
719+ .max(next_pending_hunk_offset_range.end);
545720 pending_hunks_iter.next();
546721 continue;
547722 }
@@ -864,8 +1039,9 @@ fn compute_hunks( · 316 unmodified lines
8641039 fn compare_hunks(
8651040 new_hunks: &SumTree<InternalDiffHunk>,
8661041 old_hunks: &SumTree<InternalDiffHunk>,
1042+ old_snapshot: &text::BufferSnapshot,
8671043 new_snapshot: &text::BufferSnapshot,
868−) -> (Option<Range<Anchor>>, Option<Range<usize>>) {
1044+) -> DiffChanged {
8691045 let mut new_cursor = new_hunks.cursor::<()>(new_snapshot);
8701046 let mut old_cursor = old_hunks.cursor::<()>(new_snapshot);
8711047 old_cursor.next();
@@ -875,6 +1051,10 @@ fn compare_hunks( · 3 unmodified lines
8751051 let mut base_text_start = None;
8761052 let mut base_text_end = None;
8771053
1054+ let mut last_unchanged_new_hunk_end: Option<text::Anchor> = None;
1055+ let mut has_changes = false;
1056+ let mut extended_end_candidate: Option<text::Anchor> = None;
1057+
8781058 loop {
8791059 match (new_cursor.item(), old_cursor.item()) {
8801060 (Some(new_hunk), Some(old_hunk)) => {
@@ -884,6 +1064,8 @@ fn compare_hunks( · 3 unmodified lines
8841064 .cmp(&old_hunk.buffer_range.start, new_snapshot)
8851065 {
8861066 Ordering::Less => {
1067+ has_changes = true;
1068+ extended_end_candidate = None;
8871069 start.get_or_insert(new_hunk.buffer_range.start);
8881070 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
8891071 end.replace(new_hunk.buffer_range.end);
@@ -892,6 +1074,8 @@ fn compare_hunks( · 2 unmodified lines
8921074 }
8931075 Ordering::Equal => {
8941076 if new_hunk != old_hunk {
1077+ has_changes = true;
1078+ extended_end_candidate = None;
8951079 start.get_or_insert(new_hunk.buffer_range.start);
8961080 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
8971081 if old_hunk
@@ -911,12 +1095,20 @@ fn compare_hunks( · 13 unmodified lines
9111095 .end
9121096 .max(new_hunk.diff_base_byte_range.end),
9131097 );
1098+ } else {
1099+ if !has_changes {
1100+ last_unchanged_new_hunk_end = Some(new_hunk.buffer_range.end);
1101+ } else if extended_end_candidate.is_none() {
1102+ extended_end_candidate = Some(new_hunk.buffer_range.start);
1103+ }
9141104 }
9151105
9161106 new_cursor.next();
9171107 old_cursor.next();
9181108 }
9191109 Ordering::Greater => {
1110+ has_changes = true;
1111+ extended_end_candidate = None;
9201112 start.get_or_insert(old_hunk.buffer_range.start);
9211113 base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
9221114 end.replace(old_hunk.buffer_range.end);
@@ -926,18 +1118,26 @@ fn compare_hunks( · 3 unmodified lines
9261118 }
9271119 }
9281120 (Some(new_hunk), None) => {
1121+ has_changes = true;
1122+ extended_end_candidate = None;
9291123 start.get_or_insert(new_hunk.buffer_range.start);
9301124 base_text_start.get_or_insert(new_hunk.diff_base_byte_range.start);
931− // TODO(cole) it seems like this could move end backward?
932− end.replace(new_hunk.buffer_range.end);
1125+ if end.is_none_or(|end| end.cmp(&new_hunk.buffer_range.end, &new_snapshot).is_le())
1126+ {
1127+ end.replace(new_hunk.buffer_range.end);
1128+ }
9331129 base_text_end = base_text_end.max(Some(new_hunk.diff_base_byte_range.end));
9341130 new_cursor.next();
9351131 }
9361132 (None, Some(old_hunk)) => {
1133+ has_changes = true;
1134+ extended_end_candidate = None;
9371135 start.get_or_insert(old_hunk.buffer_range.start);
9381136 base_text_start.get_or_insert(old_hunk.diff_base_byte_range.start);
939− // TODO(cole) it seems like this could move end backward?
940− end.replace(old_hunk.buffer_range.end);
1137+ if end.is_none_or(|end| end.cmp(&old_hunk.buffer_range.end, &new_snapshot).is_le())
1138+ {
1139+ end.replace(old_hunk.buffer_range.end);
1140+ }
9411141 base_text_end = base_text_end.max(Some(old_hunk.diff_base_byte_range.end));
9421142 old_cursor.next();
9431143 }
@@ -945,12 +1145,46 @@ fn compare_hunks( · 1 unmodified line
9451145 }
9461146 }
9471147
948− (
949− start.zip(end).map(|(start, end)| start..end),
950− base_text_start
951− .zip(base_text_end)
952− .map(|(start, end)| start..end),
953− )
1148+ let changed_range = start.zip(end).map(|(start, end)| start..end);
1149+ let base_text_changed_range = base_text_start
1150+ .zip(base_text_end)
1151+ .map(|(start, end)| start..end);
1152+
1153+ let extended_range = if has_changes && let Some(changed_range) = changed_range.clone() {
1154+ let extended_start = *last_unchanged_new_hunk_end
1155+ .unwrap_or(text::Anchor::min_for_buffer(new_snapshot.remote_id()))
1156+ .min(&changed_range.start, new_snapshot);
1157+ let extended_start = new_snapshot
1158+ .anchored_edits_since_in_range::<usize>(
1159+ &old_snapshot.version(),
1160+ extended_start..changed_range.start,
1161+ )
1162+ .map(|(_, anchors)| anchors.start)
1163+ .min_by(|a, b| a.cmp(b, new_snapshot))
1164+ .unwrap_or(changed_range.start);
1165+
1166+ let extended_end = *extended_end_candidate
1167+ .unwrap_or(text::Anchor::max_for_buffer(new_snapshot.remote_id()))
1168+ .max(&changed_range.end, new_snapshot);
1169+ let extended_end = new_snapshot
1170+ .anchored_edits_since_in_range::<usize>(
1171+ &old_snapshot.version(),
1172+ changed_range.end..extended_end,
1173+ )
1174+ .map(|(_, anchors)| anchors.end)
1175+ .max_by(|a, b| a.cmp(b, new_snapshot))
1176+ .unwrap_or(changed_range.end);
1177+
1178+ Some(extended_start..extended_end)
1179+ } else {
1180+ None
1181+ };
1182+
1183+ DiffChanged {
1184+ changed_range,
1185+ base_text_changed_range,
1186+ extended_range,
1187+ }
9541188 }
9551189
9561190 fn process_patch_hunk(
@@ -1078,12 +1312,16 @@ impl std::fmt::Debug for BufferDiff { · 121 unmodified lines
10781312 }
10791313 }
10801314
1315+#[derive(Clone, Debug, Default)]
1316+pub struct DiffChanged {
1317+ pub changed_range: Option<Range<text::Anchor>>,
1318+ pub base_text_changed_range: Option<Range<usize>>,
1319+ pub extended_range: Option<Range<text::Anchor>>,
1320+}
1321+
10811322 #[derive(Clone, Debug)]
10821323 pub enum BufferDiffEvent {
1083− DiffChanged {
1084− changed_range: Option<Range<text::Anchor>>,
1085− base_text_changed_range: Option<Range<usize>>,
1086− },
1324+ DiffChanged(DiffChanged),
10871325 LanguageChanged,
10881326 HunksStagedOrUnstaged(Option<Rope>),
10891327 }
@@ -1105,6 +1343,7 @@ impl BufferDiff { · 15 unmodified lines
11051343 hunks: SumTree::new(buffer),
11061344 pending_hunks: SumTree::new(buffer),
11071345 base_text_exists: false,
1346+ buffer_snapshot: buffer.clone(),
11081347 },
11091348 secondary_diff: None,
11101349 }
@@ -1125,6 +1364,7 @@ impl BufferDiff { · 14 unmodified lines
11251364 hunks: SumTree::new(buffer),
11261365 pending_hunks: SumTree::new(buffer),
11271366 base_text_exists: true,
1367+ buffer_snapshot: buffer.clone(),
11281368 },
11291369 secondary_diff: None,
11301370 }
@@ -1137,13 +1377,12 @@ impl BufferDiff { · 6 unmodified lines
11371377 cx: &mut Context<Self>,
11381378 ) -> Self {
11391379 let mut this = BufferDiff::new(&buffer, cx);
1140− let executor = cx.background_executor().clone();
11411380 let mut base_text = base_text.to_owned();
11421381 text::LineEnding::normalize(&mut base_text);
1143− let inner = executor.block(this.update_diff(
1382+ let inner = cx.foreground_executor().block_on(this.update_diff(
11441383 buffer.clone(),
11451384 Some(Arc::from(base_text)),
1146− true,
1385+ Some(false),
11471386 None,
11481387 cx,
11491388 ));
@@ -1165,10 +1404,13 @@ impl BufferDiff { · 15 unmodified lines
11651404 buffer_range: Anchor::min_min_range_for_buffer(self.buffer_id),
11661405 diff_base_byte_range: 0..0,
11671406 });
1168− cx.emit(BufferDiffEvent::DiffChanged {
1169− changed_range: Some(Anchor::min_max_range_for_buffer(self.buffer_id)),
1170− base_text_changed_range: Some(0..self.base_text(cx).len()),
1171− });
1407+ let changed_range = Some(Anchor::min_max_range_for_buffer(self.buffer_id));
1408+ let base_text_range = Some(0..self.base_text(cx).len());
1409+ cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1410+ changed_range: changed_range.clone(),
1411+ base_text_changed_range: base_text_range,
1412+ extended_range: changed_range,
1413+ }));
11721414 }
11731415 }
11741416
@@ -1198,13 +1440,14 @@ impl BufferDiff { · 23 unmodified lines
11981440 new_index_text.clone(),
11991441 ));
12001442 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1201− let changed_range = first.buffer_range.start..last.buffer_range.end;
1443+ let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
12021444 let base_text_changed_range =
1203− first.diff_base_byte_range.start..last.diff_base_byte_range.end;
1204− cx.emit(BufferDiffEvent::DiffChanged {
1205− changed_range: Some(changed_range),
1206− base_text_changed_range: Some(base_text_changed_range),
1207− });
1445+ Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1446+ cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1447+ changed_range: changed_range.clone(),
1448+ base_text_changed_range,
1449+ extended_range: changed_range,
1450+ }));
12081451 }
12091452 new_index_text
12101453 }
@@ -1227,13 +1470,14 @@ impl BufferDiff { · 16 unmodified lines
12271470 self.inner
12281471 .stage_or_unstage_hunks_impl(&secondary, stage, &hunks, buffer, file_exists, cx);
12291472 if let Some((first, last)) = hunks.first().zip(hunks.last()) {
1230− let changed_range = first.buffer_range.start..last.buffer_range.end;
1473+ let changed_range = Some(first.buffer_range.start..last.buffer_range.end);
12311474 let base_text_changed_range =
1232− first.diff_base_byte_range.start..last.diff_base_byte_range.end;
1233− cx.emit(BufferDiffEvent::DiffChanged {
1234− changed_range: Some(changed_range),
1235− base_text_changed_range: Some(base_text_changed_range),
1236− });
1475+ Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
1476+ cx.emit(BufferDiffEvent::DiffChanged(DiffChanged {
1477+ changed_range: changed_range.clone(),
1478+ base_text_changed_range,
1479+ extended_range: changed_range,
1480+ }));
12371481 }
12381482 }
12391483
@@ -1241,19 +1485,31 @@ impl BufferDiff { · 1 unmodified line
12411485 &self,
12421486 buffer: text::BufferSnapshot,
12431487 base_text: Option<Arc<str>>,
1244− base_text_changed: bool,
1488+ base_text_change: Option<bool>,
12451489 language: Option<Arc<Language>>,
12461490 cx: &App,
12471491 ) -> Task<BufferDiffUpdate> {
12481492 let prev_base_text = self.base_text(cx).as_rope().clone();
1493+ let base_text_changed = base_text_change.is_some();
1494+ let compute_base_text_edits = base_text_change == Some(true);
12491495 let diff_options = build_diff_options(
12501496 language.as_ref().map(|l| l.name()),
12511497 language.as_ref().map(|l| l.default_scope()),
12521498 cx,
12531499 );
1500+ let buffer_snapshot = buffer.clone();
1501+
1502+ let base_text_diff_task = if base_text_changed && compute_base_text_edits {
1503+ base_text
1504+ .as_ref()
1505+ .map(|new_text| self.inner.base_text.read(cx).diff(new_text.clone(), cx))
1506+ } else {
1507+ None
1508+ };
12541509
1255− cx.background_executor()
1256− .spawn_labeled(*CALCULATE_DIFF_TASK, async move {
1510+ let hunk_task = cx.background_executor().spawn({
1511+ let buffer_snapshot = buffer_snapshot.clone();
1512+ async move {
12571513 let base_text_rope = if let Some(base_text) = &base_text {
12581514 if base_text_changed {
12591515 Rope::from(base_text.as_ref())
@@ -1272,19 +1528,35 @@ impl BufferDiff { · 12 unmodified lines
12721528 diff_options,
12731529 );
12741530 let base_text = base_text.unwrap_or_default();
1275− let inner = BufferDiffInner {
1531+ BufferDiffInner {
12761532 base_text,
12771533 hunks,
12781534 base_text_exists,
12791535 pending_hunks: SumTree::new(&buffer),
1280− };
1281− BufferDiffUpdate {
1282− inner,
1283− base_text_changed,
1536+ buffer_snapshot,
12841537 }
1285− })
1538+ }
1539+ });
1540+
1541+ cx.background_executor().spawn(async move {
1542+ let (inner, base_text_edits) = match base_text_diff_task {
1543+ Some(diff_task) => {
1544+ let (inner, diff) = futures::join!(hunk_task, diff_task);
1545+ (inner, Some(diff))
1546+ }
1547+ None => (hunk_task.await, None),
1548+ };
1549+
1550+ BufferDiffUpdate {
1551+ inner,
1552+ buffer_snapshot,
1553+ base_text_edits,
1554+ base_text_changed,
1555+ }
1556+ })
12861557 }
12871558
1559+ #[ztracing::instrument(skip_all)]
12881560 pub fn language_changed(
12891561 &mut self,
12901562 language: Option<Arc<Language>>,
@@ -1315,23 +1587,37 @@ impl BufferDiff { · 24 unmodified lines
13151587 secondary_diff_change: Option<Range<Anchor>>,
13161588 clear_pending_hunks: bool,
13171589 cx: &mut Context<Self>,
1318− ) -> impl Future<Output = (Option<Range<Anchor>>, Option<Range<usize>>)> + use<> {
1590+ ) -> impl Future<Output = DiffChanged> + use<> {
13191591 log::debug!("set snapshot with secondary {secondary_diff_change:?}");
13201592
13211593 let old_snapshot = self.snapshot(cx);
13221594 let state = &mut self.inner;
13231595 let new_state = update.inner;
1324− let (mut changed_range, mut base_text_changed_range) =
1325− match (state.base_text_exists, new_state.base_text_exists) {
1326− (false, false) => (None, None),
1327− (true, true) if !update.base_text_changed => {
1328− compare_hunks(&new_state.hunks, &old_snapshot.inner.hunks, buffer)
1596+ let base_text_changed = update.base_text_changed;
1597+
1598+ let old_buffer_snapshot = &old_snapshot.inner.buffer_snapshot;
1599+ let DiffChanged {
1600+ mut changed_range,
1601+ mut base_text_changed_range,
1602+ mut extended_range,
1603+ } = match (state.base_text_exists, new_state.base_text_exists) {
1604+ (false, false) => DiffChanged::default(),
1605+ (true, true) if !base_text_changed => compare_hunks(
1606+ &new_state.hunks,
1607+ &old_snapshot.inner.hunks,
1608+ old_buffer_snapshot,
1609+ buffer,
1610+ ),
1611+ _ => {
1612+ let full_range = text::Anchor::min_max_range_for_buffer(self.buffer_id);
1613+ let full_base_range = 0..new_state.base_text.len();
1614+ DiffChanged {
1615+ changed_range: Some(full_range.clone()),
1616+ base_text_changed_range: Some(full_base_range),
1617+ extended_range: Some(full_range),
13291618 }
1330− _ => (
1331− Some(text::Anchor::min_max_range_for_buffer(self.buffer_id)),
1332− Some(0..new_state.base_text.len()),
1333− ),
1334− };
1619+ }
1620+ };
13351621
13361622 if let Some(secondary_changed_range) = secondary_diff_change
13371623 && let (Some(secondary_hunk_range), Some(secondary_base_range)) =
@@ -1341,20 +1627,34 @@ impl BufferDiff { · 3 unmodified lines
13411627 range.start = *secondary_hunk_range.start.min(&range.start, buffer);
13421628 range.end = *secondary_hunk_range.end.max(&range.end, buffer);
13431629 } else {
1344− changed_range = Some(secondary_hunk_range);
1630+ changed_range = Some(secondary_hunk_range.clone());
13451631 }
13461632
1347− if let Some(base_text_range) = &mut base_text_changed_range {
1633+ if let Some(base_text_range) = base_text_changed_range.as_mut() {
13481634 base_text_range.start = secondary_base_range.start.min(base_text_range.start);
13491635 base_text_range.end = secondary_base_range.end.max(base_text_range.end);
13501636 } else {
13511637 base_text_changed_range = Some(secondary_base_range);
13521638 }
1639+
1640+ if let Some(ext) = &mut extended_range {
1641+ ext.start = *ext.start.min(&secondary_hunk_range.start, buffer);
1642+ ext.end = *ext.end.max(&secondary_hunk_range.end, buffer);
1643+ } else {
1644+ extended_range = Some(secondary_hunk_range);
1645+ }
13531646 }
13541647
13551648 let state = &mut self.inner;
13561649 state.base_text_exists = new_state.base_text_exists;
1357− let parsing_idle = if update.base_text_changed {
1650+ let parsing_idle = if let Some(diff) = update.base_text_edits {
1651+ state.base_text.update(cx, |base_text, cx| {
1652+ base_text.set_capability(Capability::ReadWrite, cx);
1653+ base_text.apply_diff(diff, cx);
1654+ base_text.set_capability(Capability::ReadOnly, cx);
1655+ Some(base_text.parsing_idle())
1656+ })
1657+ } else if update.base_text_changed {
13581658 state.base_text.update(cx, |base_text, cx| {
13591659 base_text.set_capability(Capability::ReadWrite, cx);
13601660 base_text.set_text(new_state.base_text.clone(), cx);
@@ -1365,17 +1665,19 @@ impl BufferDiff { · 4 unmodified lines
13651665 None
13661666 };
13671667 state.hunks = new_state.hunks;
1368− if update.base_text_changed || clear_pending_hunks {
1668+ state.buffer_snapshot = update.buffer_snapshot;
1669+ if base_text_changed || clear_pending_hunks {
13691670 if let Some((first, last)) = state.pending_hunks.first().zip(state.pending_hunks.last())
13701671 {
1672+ let pending_range = first.buffer_range.start..last.buffer_range.end;
13711673 if let Some(range) = &mut changed_range {
1372− range.start = *range.start.min(&first.buffer_range.start, buffer);
1373− range.end = *range.end.max(&last.buffer_range.end, buffer);
1674+ range.start = *range.start.min(&pending_range.start, buffer);
1675+ range.end = *range.end.max(&pending_range.end, buffer);
13741676 } else {
1375− changed_range = Some(first.buffer_range.start..last.buffer_range.end);
1677+ changed_range = Some(pending_range.clone());
13761678 }
13771679
1378− if let Some(base_text_range) = &mut base_text_changed_range {
1680+ if let Some(base_text_range) = base_text_changed_range.as_mut() {
13791681 base_text_range.start =
13801682 base_text_range.start.min(first.diff_base_byte_range.start);
13811683 base_text_range.end = base_text_range.end.max(last.diff_base_byte_range.end);
@@ -1383,6 +1685,13 @@ impl BufferDiff { · 1 unmodified line
13831685 base_text_changed_range =
13841686 Some(first.diff_base_byte_range.start..last.diff_base_byte_range.end);
13851687 }
1688+
1689+ if let Some(ext) = &mut extended_range {
1690+ ext.start = *ext.start.min(&pending_range.start, buffer);
1691+ ext.end = *ext.end.max(&pending_range.end, buffer);
1692+ } else {
1693+ extended_range = Some(pending_range);
1694+ }
13861695 }
13871696 state.pending_hunks = SumTree::new(buffer);
13881697 }
@@ -1391,7 +1700,11 @@ impl BufferDiff { · 2 unmodified lines
13911700 if let Some(parsing_idle) = parsing_idle {
13921701 parsing_idle.await;
13931702 }
1394− (changed_range, base_text_changed_range)
1703+ DiffChanged {
1704+ changed_range,
1705+ base_text_changed_range,
1706+ extended_range,
1707+ }
13951708 }
13961709 }
13971710
@@ -1421,15 +1734,12 @@ impl BufferDiff { · 23 unmodified lines
14211734 );
14221735
14231736 cx.spawn(async move |this, cx| {
1424− let (changed_range, base_text_changed_range) = fut.await;
1737+ let change = fut.await;
14251738 this.update(cx, |_, cx| {
1426− cx.emit(BufferDiffEvent::DiffChanged {
1427− changed_range: changed_range.clone(),
1428− base_text_changed_range,
1429− });
1739+ cx.emit(BufferDiffEvent::DiffChanged(change.clone()));
14301740 })
14311741 .ok();
1432− changed_range
1742+ change.changed_range
14331743 })
14341744 }
14351745
@@ -1448,6 +1758,7 @@ impl BufferDiff { · 12 unmodified lines
14481758 pending_hunks: self.inner.pending_hunks.clone(),
14491759 base_text: self.inner.base_text.read(cx).snapshot(),
14501760 base_text_exists: self.inner.base_text_exists,
1761+ buffer_snapshot: self.inner.buffer_snapshot.clone(),
14511762 },
14521763 secondary_diff: self
14531764 .secondary_diff
@@ -1471,7 +1782,7 @@ impl BufferDiff { · 17 unmodified lines
14711782 cx.spawn(async move |this, cx| {
14721783 let Some(state) = this
14731784 .update(cx, |this, cx| {
1474− this.update_diff(buffer.clone(), base_text, true, language, cx)
1785+ this.update_diff(buffer.clone(), base_text, Some(false), language, cx)
14751786 })
14761787 .log_err()
14771788 else {
@@ -1500,15 +1811,12 @@ impl BufferDiff { · 22 unmodified lines
15001811 pub fn recalculate_diff_sync(&mut self, buffer: &text::BufferSnapshot, cx: &mut Context<Self>) {
15011812 let language = self.base_text(cx).language().cloned();
15021813 let base_text = self.base_text_string(cx).map(|s| s.as_str().into());
1503− let fut = self.update_diff(buffer.clone(), base_text, false, language, cx);
1504− let executor = cx.background_executor().clone();
1505− let snapshot = executor.block(fut);
1814+ let fut = self.update_diff(buffer.clone(), base_text, None, language, cx);
1815+ let fg_executor = cx.foreground_executor().clone();
1816+ let snapshot = fg_executor.block_on(fut);
15061817 let fut = self.set_snapshot_with_secondary_inner(snapshot, buffer, None, false, cx);
1507− let (changed_range, base_text_changed_range) = executor.block(fut);
1508− cx.emit(BufferDiffEvent::DiffChanged {
1509− changed_range,
1510− base_text_changed_range,
1511− })
1818+ let change = fg_executor.block_on(fut);
1819+ cx.emit(BufferDiffEvent::DiffChanged(change));
15121820 }
15131821
15141822 pub fn base_text_buffer(&self) -> Entity<language::Buffer> {
@@ -2144,6 +2452,72 @@ mod tests { · 629 unmodified lines
21442452 }
21452453 }
21462454
2455+ #[gpui::test]
2456+ async fn test_stage_all_with_nested_hunks(cx: &mut TestAppContext) {
2457+ // This test reproduces a crash where staging all hunks would cause an underflow
2458+ // when there's one large unstaged hunk containing multiple uncommitted hunks.
2459+ let head_text = "
2460+ aaa
2461+ bbb
2462+ ccc
2463+ ddd
2464+ eee
2465+ fff
2466+ ggg
2467+ hhh
2468+ iii
2469+ jjj
2470+ kkk
2471+ lll
2472+ "
2473+ .unindent();
2474+
2475+ let index_text = "
2476+ aaa
2477+ bbb
2478+ CCC-index
2479+ DDD-index
2480+ EEE-index
2481+ FFF-index
2482+ GGG-index
2483+ HHH-index
2484+ III-index
2485+ JJJ-index
2486+ kkk
2487+ lll
2488+ "
2489+ .unindent();
2490+
2491+ let buffer_text = "
2492+ aaa
2493+ bbb
2494+ ccc-modified
2495+ ddd
2496+ eee-modified
2497+ fff
2498+ ggg
2499+ hhh-modified
2500+ iii
2501+ jjj
2502+ kkk
2503+ lll
2504+ "
2505+ .unindent();
2506+
2507+ let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2508+
2509+ let unstaged_diff = cx.new(|cx| BufferDiff::new_with_base_text(&index_text, &buffer, cx));
2510+ let uncommitted_diff = cx.new(|cx| {
2511+ let mut diff = BufferDiff::new_with_base_text(&head_text, &buffer, cx);
2512+ diff.set_secondary_diff(unstaged_diff);
2513+ diff
2514+ });
2515+
2516+ uncommitted_diff.update(cx, |diff, cx| {
2517+ diff.stage_or_unstage_all_hunks(true, &buffer, true, cx);
2518+ });
2519+ }
2520+
21472521 #[gpui::test]
21482522 async fn test_toggling_stage_and_unstage_same_hunk(cx: &mut TestAppContext) {
21492523 let head_text = "
@@ -2233,11 +2607,19 @@ mod tests { · 83 unmodified lines
22332607
22342608 let empty_diff = cx.update(|cx| BufferDiff::new(&buffer, cx).snapshot(cx));
22352609 let diff_1 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2236− let (range, base_text_range) =
2237− compare_hunks(&diff_1.inner.hunks, &empty_diff.inner.hunks, &buffer);
2238− let range = range.unwrap();
2610+ let DiffChanged {
2611+ changed_range,
2612+ base_text_changed_range,
2613+ extended_range: _,
2614+ } = compare_hunks(
2615+ &diff_1.inner.hunks,
2616+ &empty_diff.inner.hunks,
2617+ &buffer,
2618+ &buffer,
2619+ );
2620+ let range = changed_range.unwrap();
22392621 assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0));
2240− let base_text_range = base_text_range.unwrap();
2622+ let base_text_range = base_text_changed_range.unwrap();
22412623 assert_eq!(
22422624 base_text_range.to_point(diff_1.base_text()),
22432625 Point::new(0, 0)..Point::new(10, 0)
@@ -2258,14 +2640,19 @@ mod tests { · 14 unmodified lines
22582640 .unindent(),
22592641 );
22602642 let diff_2 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2261− let (range, base_text_range) =
2262− compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer);
2643+ let DiffChanged {
2644+ changed_range,
2645+ base_text_changed_range,
2646+ extended_range: _,
2647+ } = compare_hunks(&diff_2.inner.hunks, &diff_1.inner.hunks, &buffer, &buffer);
22632648 assert_eq!(
2264− range.unwrap().to_point(&buffer),
2649+ changed_range.unwrap().to_point(&buffer),
22652650 Point::new(4, 0)..Point::new(5, 0),
22662651 );
22672652 assert_eq!(
2268− base_text_range.unwrap().to_point(diff_2.base_text()),
2653+ base_text_changed_range
2654+ .unwrap()
2655+ .to_point(diff_2.base_text()),
22692656 Point::new(6, 0)..Point::new(7, 0),
22702657 );
22712658
@@ -2284,11 +2671,14 @@ mod tests { · 12 unmodified lines
22842671 .unindent(),
22852672 );
22862673 let diff_3 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2287− let (range, base_text_range) =
2288− compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer);
2289− let range = range.unwrap();
2674+ let DiffChanged {
2675+ changed_range,
2676+ base_text_changed_range,
2677+ extended_range: _,
2678+ } = compare_hunks(&diff_3.inner.hunks, &diff_2.inner.hunks, &buffer, &buffer);
2679+ let range = changed_range.unwrap();
22902680 assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0));
2291− let base_text_range = base_text_range.unwrap();
2681+ let base_text_range = base_text_changed_range.unwrap();
22922682 assert_eq!(
22932683 base_text_range.to_point(diff_3.base_text()),
22942684 Point::new(2, 0)..Point::new(4, 0)
@@ -2308,11 +2698,14 @@ mod tests { · 13 unmodified lines
23082698 .unindent(),
23092699 );
23102700 let diff_4 = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
2311− let (range, base_text_range) =
2312− compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer);
2313− let range = range.unwrap();
2701+ let DiffChanged {
2702+ changed_range,
2703+ base_text_changed_range,
2704+ extended_range: _,
2705+ } = compare_hunks(&diff_4.inner.hunks, &diff_3.inner.hunks, &buffer, &buffer);
2706+ let range = changed_range.unwrap();
23142707 assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0));
2315− let base_text_range = base_text_range.unwrap();
2708+ let base_text_range = base_text_changed_range.unwrap();
23162709 assert_eq!(
23172710 base_text_range.to_point(diff_4.base_text()),
23182711 Point::new(6, 0)..Point::new(7, 0)
@@ -2333,11 +2726,14 @@ mod tests { · 14 unmodified lines
23332726 .unindent(),
23342727 );
23352728 let diff_5 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text.clone(), cx);
2336− let (range, base_text_range) =
2337− compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer);
2338− let range = range.unwrap();
2729+ let DiffChanged {
2730+ changed_range,
2731+ base_text_changed_range,
2732+ extended_range: _,
2733+ } = compare_hunks(&diff_5.inner.hunks, &diff_4.inner.hunks, &buffer, &buffer);
2734+ let range = changed_range.unwrap();
23392735 assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0));
2340− let base_text_range = base_text_range.unwrap();
2736+ let base_text_range = base_text_changed_range.unwrap();
23412737 assert_eq!(
23422738 base_text_range.to_point(diff_5.base_text()),
23432739 Point::new(5, 0)..Point::new(5, 0)
@@ -2358,11 +2754,14 @@ mod tests { · 14 unmodified lines
23582754 .unindent(),
23592755 );
23602756 let diff_6 = BufferDiffSnapshot::new_sync(buffer.snapshot(), base_text, cx);
2361− let (range, base_text_range) =
2362− compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer);
2363− let range = range.unwrap();
2757+ let DiffChanged {
2758+ changed_range,
2759+ base_text_changed_range,
2760+ extended_range: _,
2761+ } = compare_hunks(&diff_6.inner.hunks, &diff_5.inner.hunks, &buffer, &buffer);
2762+ let range = changed_range.unwrap();
23642763 assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0));
2365− let base_text_range = base_text_range.unwrap();
2764+ let base_text_range = base_text_changed_range.unwrap();
23662765 assert_eq!(
23672766 base_text_range.to_point(diff_6.base_text()),
23682767 Point::new(9, 0)..Point::new(10, 0)
@@ -2534,69 +2933,6 @@ mod tests { · 165 unmodified lines
25342933 }
25352934 }
25362935
2537− #[gpui::test]
2538− async fn test_row_to_base_text_row(cx: &mut TestAppContext) {
2539− let base_text = "
2540− zero
2541− one
2542− two
2543− three
2544− four
2545− five
2546− six
2547− seven
2548− eight
2549− "
2550− .unindent();
2551− let buffer_text = "
2552− zero
2553− ONE
2554− two
2555− NINE
2556− five
2557− seven
2558− "
2559− .unindent();
2560−
2561− // zero
2562− // - one
2563− // + ONE
2564− // two
2565− // - three
2566− // - four
2567− // + NINE
2568− // five
2569− // - six
2570− // seven
2571− // + eight
2572−
2573− let buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
2574− let buffer_snapshot = buffer.snapshot();
2575− let diff = BufferDiffSnapshot::new_sync(buffer_snapshot.clone(), base_text, cx);
2576− let expected_results = [
2577− // main buffer row, base text row (right bias), base text row (left bias)
2578− (0, 0, 0),
2579− (1, 2, 1),
2580− (2, 2, 2),
2581− (3, 5, 3),
2582− (4, 5, 5),
2583− (5, 7, 7),
2584− (6, 9, 9),
2585− ];
2586− for (buffer_row, expected_right, expected_left) in expected_results {
2587− assert_eq!(
2588− diff.row_to_base_text_row(buffer_row, Bias::Right, &buffer_snapshot),
2589− expected_right,
2590− "{buffer_row}"
2591− );
2592− assert_eq!(
2593− diff.row_to_base_text_row(buffer_row, Bias::Left, &buffer_snapshot),
2594− expected_left,
2595− "{buffer_row}"
2596− );
2597− }
2598− }
2599−
26002936 #[gpui::test]
26012937 async fn test_changed_ranges(cx: &mut gpui::TestAppContext) {
26022938 let base_text = "
@@ -2646,7 +2982,7 @@ mod tests { · 43 unmodified lines
26462982 diff.update_diff(
26472983 snapshot.clone(),
26482984 Some(base_text.as_str().into()),
2649− false,
2985+ None,
26502986 None,
26512987 cx,
26522988 )
@@ -2659,10 +2995,11 @@ mod tests { · 6 unmodified lines
26592995 let events = rx.into_iter().collect::<Vec<_>>();
26602996 match events.as_slice() {
26612997 [
2662− BufferDiffEvent::DiffChanged {
2998+ BufferDiffEvent::DiffChanged(DiffChanged {
26632999 changed_range: _,
26643000 base_text_changed_range,
2665− },
3001+ extended_range: _,
3002+ }),
26663003 ] => {
26673004 // TODO(cole) this seems like it should pass but currently fails (see compare_hunks)
26683005 // assert_eq!(
@@ -2676,4 +3013,1413 @@ mod tests { · 7 unmodified lines
26763013 _ => panic!("unexpected events: {:?}", events),
26773014 }
26783015 }
3016+
3017+ #[gpui::test]
3018+ async fn test_extended_range(cx: &mut TestAppContext) {
3019+ let base_text = "
3020+ aaa
3021+ bbb
3022+
3023+
3024+
3025+
3026+
3027+ ccc
3028+ ddd
3029+ "
3030+ .unindent();
3031+
3032+ let buffer_text = "
3033+ aaa
3034+ bbb
3035+
3036+
3037+
3038+
3039+
3040+ CCC
3041+ ddd
3042+ "
3043+ .unindent();
3044+
3045+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), buffer_text);
3046+ let old_buffer = buffer.snapshot();
3047+ let diff_a = BufferDiffSnapshot::new_sync(buffer.clone(), base_text.clone(), cx);
3048+
3049+ buffer.edit([(Point::new(1, 3)..Point::new(1, 3), "\n")]);
3050+ let diff_b = BufferDiffSnapshot::new_sync(buffer.clone(), base_text, cx);
3051+
3052+ let DiffChanged {
3053+ changed_range,
3054+ base_text_changed_range: _,
3055+ extended_range,
3056+ } = compare_hunks(
3057+ &diff_b.inner.hunks,
3058+ &diff_a.inner.hunks,
3059+ &old_buffer,
3060+ &buffer,
3061+ );
3062+
3063+ let changed_range = changed_range.unwrap();
3064+ assert_eq!(
3065+ changed_range.to_point(&buffer),
3066+ Point::new(7, 0)..Point::new(9, 0),
3067+ "changed_range should span from old hunk position to new hunk end"
3068+ );
3069+
3070+ let extended_range = extended_range.unwrap();
3071+ assert_eq!(
3072+ extended_range.start.to_point(&buffer),
3073+ Point::new(1, 3),
3074+ "extended_range.start should extend to include the edit outside changed_range"
3075+ );
3076+ assert_eq!(
3077+ extended_range.end.to_point(&buffer),
3078+ Point::new(9, 0),
3079+ "extended_range.end should collapse to changed_range.end when no edits in end margin"
3080+ );
3081+
3082+ let base_text_2 = "
3083+ one
3084+ two
3085+ three
3086+ four
3087+ five
3088+ six
3089+ seven
3090+ eight
3091+ "
3092+ .unindent();
3093+
3094+ let buffer_text_2 = "
3095+ ONE
3096+ two
3097+ THREE
3098+ four
3099+ FIVE
3100+ six
3101+ SEVEN
3102+ eight
3103+ "
3104+ .unindent();
3105+
3106+ let mut buffer_2 = Buffer::new(ReplicaId::LOCAL, BufferId::new(2).unwrap(), buffer_text_2);
3107+ let old_buffer_2 = buffer_2.snapshot();
3108+ let diff_2a = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2.clone(), cx);
3109+
3110+ buffer_2.edit([(Point::new(4, 0)..Point::new(4, 4), "FIVE_CHANGED")]);
3111+ let diff_2b = BufferDiffSnapshot::new_sync(buffer_2.clone(), base_text_2, cx);
3112+
3113+ let DiffChanged {
3114+ changed_range,
3115+ base_text_changed_range: _,
3116+ extended_range,
3117+ } = compare_hunks(
3118+ &diff_2b.inner.hunks,
3119+ &diff_2a.inner.hunks,
3120+ &old_buffer_2,
3121+ &buffer_2,
3122+ );
3123+
3124+ let changed_range = changed_range.unwrap();
3125+ assert_eq!(
3126+ changed_range.to_point(&buffer_2),
3127+ Point::new(4, 0)..Point::new(5, 0),
3128+ "changed_range should be just the hunk that changed (FIVE)"
3129+ );
3130+
3131+ let extended_range = extended_range.unwrap();
3132+ assert_eq!(
3133+ extended_range.to_point(&buffer_2),
3134+ Point::new(4, 0)..Point::new(5, 0),
3135+ "extended_range should equal changed_range when edit is within the hunk"
3136+ );
3137+ }
3138+
3139+ fn assert_rows_to_base_text_rows_visual(
3140+ buffer: &Entity<language::Buffer>,
3141+ diff: &Entity<BufferDiff>,
3142+ source_text: &str,
3143+ annotated_target: &str,
3144+ cx: &mut gpui::TestAppContext,
3145+ ) {
3146+ let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3147+
3148+ let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3149+ let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3150+
3151+ assert_eq!(
3152+ buffer.text(),
3153+ source_text,
3154+ "buffer text does not match source text"
3155+ );
3156+
3157+ assert_eq!(
3158+ diff.base_text_string().unwrap_or_default(),
3159+ target_text,
3160+ "base text does not match stripped annotated target"
3161+ );
3162+
3163+ let num_rows = source_text.lines().count() as u32;
3164+ let max_point = buffer.max_point();
3165+ let points = (0..=num_rows).map(move |row| {
3166+ if row == num_rows && max_point.column > 0 {
3167+ max_point
3168+ } else {
3169+ Point::new(row, 0)
3170+ }
3171+ });
3172+ let actual_ranges: Vec<_> = diff.points_to_base_text_points(points, &buffer).0.collect();
3173+
3174+ assert_eq!(
3175+ actual_ranges, expected_ranges,
3176+ "\nsource (buffer):\n{}\ntarget (base):\n{}\nexpected: {:?}\nactual: {:?}",
3177+ source_text, target_text, expected_ranges, actual_ranges
3178+ );
3179+ }
3180+
3181+ fn assert_base_text_rows_to_rows_visual(
3182+ buffer: &Entity<language::Buffer>,
3183+ diff: &Entity<BufferDiff>,
3184+ source_text: &str,
3185+ annotated_target: &str,
3186+ cx: &mut gpui::TestAppContext,
3187+ ) {
3188+ let (target_text, expected_ranges) = parse_row_annotations(annotated_target);
3189+
3190+ let buffer = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
3191+ let diff = diff.update(cx, |diff, cx| diff.snapshot(cx));
3192+
3193+ assert_eq!(
3194+ diff.base_text_string().unwrap_or_default(),
3195+ source_text,
3196+ "base text does not match source text"
3197+ );
3198+
3199+ assert_eq!(
3200+ buffer.text(),
3201+ target_text,
3202+ "buffer text does not match stripped annotated target"
3203+ );
3204+
3205+ let num_rows = source_text.lines().count() as u32;
3206+ let base_max_point = diff.base_text().max_point();
3207+ let points = (0..=num_rows).map(move |row| {
3208+ if row == num_rows && base_max_point.column > 0 {
3209+ base_max_point
3210+ } else {
3211+ Point::new(row, 0)
3212+ }
3213+ });
3214+ let actual_ranges: Vec<_> = diff.base_text_points_to_points(points, &buffer).0.collect();
3215+
3216+ assert_eq!(
3217+ actual_ranges, expected_ranges,
3218+ "\nsource (base):\n{}\ntarget (buffer):\n{}\nexpected: {:?}\nactual: {:?}",
3219+ source_text, target_text, expected_ranges, actual_ranges
3220+ );
3221+ }
3222+
3223+ fn parse_row_annotations(annotated_text: &str) -> (String, Vec<Range<Point>>) {
3224+ let mut starts: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3225+ let mut ends: std::collections::HashMap<u32, Point> = std::collections::HashMap::new();
3226+
3227+ let mut clean_text = String::new();
3228+ let mut current_point = Point::new(0, 0);
3229+ let mut chars = annotated_text.chars().peekable();
3230+
3231+ while let Some(c) = chars.next() {
3232+ if c == '<' {
3233+ let mut num_str = String::new();
3234+ while let Some(&next) = chars.peek() {
3235+ if next.is_ascii_digit() {
3236+ num_str.push(chars.next().unwrap());
3237+ } else {
3238+ break;
3239+ }
3240+ }
3241+ if !num_str.is_empty() {
3242+ let row_num: u32 = num_str.parse().unwrap();
3243+ starts.insert(row_num, current_point);
3244+
3245+ if chars.peek() == Some(&'>') {
3246+ chars.next();
3247+ ends.insert(row_num, current_point);
3248+ }
3249+ } else {
3250+ clean_text.push(c);
3251+ current_point.column += 1;
3252+ }
3253+ } else if c.is_ascii_digit() {
3254+ let mut num_str = String::from(c);
3255+ while let Some(&next) = chars.peek() {
3256+ if next.is_ascii_digit() {
3257+ num_str.push(chars.next().unwrap());
3258+ } else {
3259+ break;
3260+ }
3261+ }
3262+ if chars.peek() == Some(&'>') {
3263+ chars.next();
3264+ let row_num: u32 = num_str.parse().unwrap();
3265+ ends.insert(row_num, current_point);
3266+ } else {
3267+ for ch in num_str.chars() {
3268+ clean_text.push(ch);
3269+ current_point.column += 1;
3270+ }
3271+ }
3272+ } else if c == '\n' {
3273+ clean_text.push(c);
3274+ current_point.row += 1;
3275+ current_point.column = 0;
3276+ } else {
3277+ clean_text.push(c);
3278+ current_point.column += 1;
3279+ }
3280+ }
3281+
3282+ let max_row = starts.keys().chain(ends.keys()).max().copied().unwrap_or(0);
3283+ let mut ranges: Vec<Range<Point>> = Vec::new();
3284+ for row in 0..=max_row {
3285+ let start = starts.get(&row).copied().unwrap_or(Point::new(0, 0));
3286+ let end = ends.get(&row).copied().unwrap_or(start);
3287+ ranges.push(start..end);
3288+ }
3289+
3290+ (clean_text, ranges)
3291+ }
3292+
3293+ fn make_diff(
3294+ base_text: &str,
3295+ buffer_text: &str,
3296+ cx: &mut gpui::TestAppContext,
3297+ ) -> (Entity<language::Buffer>, Entity<BufferDiff>) {
3298+ let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
3299+ let diff = cx.new(|cx| {
3300+ BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
3301+ });
3302+ (buffer, diff)
3303+ }
3304+
3305+ #[gpui::test]
3306+ async fn test_row_translation_visual(cx: &mut gpui::TestAppContext) {
3307+ use unindent::Unindent;
3308+
3309+ {
3310+ let buffer_text = "
3311+ aaa
3312+ bbb
3313+ ccc
3314+ "
3315+ .unindent();
3316+ let annotated_base = "
3317+ <0>aaa
3318+ <1>bbb
3319+ <2>ccc
3320+ <3>"
3321+ .unindent();
3322+ let (base_text, _) = parse_row_annotations(&annotated_base);
3323+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3324+ assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3325+ }
3326+
3327+ {
3328+ let base_text = "
3329+ aaa
3330+ bbb
3331+ ccc
3332+ "
3333+ .unindent();
3334+ let annotated_buffer = "
3335+ <0>aaa
3336+ <1>bbb
3337+ <2>ccc
3338+ <3>"
3339+ .unindent();
3340+ let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3341+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3342+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3343+ }
3344+
3345+ {
3346+ let buffer_text = "
3347+ XXX
3348+ bbb
3349+ ccc
3350+ "
3351+ .unindent();
3352+ let annotated_base = "
3353+ <0<1aaa
3354+ 0>1>bbb
3355+ <2>ccc
3356+ <3>"
3357+ .unindent();
3358+ let (base_text, _) = parse_row_annotations(&annotated_base);
3359+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3360+ assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3361+ }
3362+
3363+ {
3364+ let buffer_text = "
3365+ aaa
3366+ NEW
3367+ ccc
3368+ "
3369+ .unindent();
3370+ let annotated_base = "
3371+ <0>aaa
3372+ <1><2>ccc
3373+ <3>"
3374+ .unindent();
3375+ let (base_text, _) = parse_row_annotations(&annotated_base);
3376+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3377+ assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3378+ }
3379+
3380+ {
3381+ let base_text = "
3382+ aaa
3383+ ccc
3384+ "
3385+ .unindent();
3386+ let annotated_buffer = "
3387+ <0>aaa
3388+ <1NEW
3389+ 1>ccc
3390+ <2>"
3391+ .unindent();
3392+ let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3393+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3394+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3395+ }
3396+
3397+ {
3398+ let buffer_text = "aaa\nbbb";
3399+ let annotated_base = "<0>aaa\n<1>bbb<2>";
3400+ let (base_text, _) = parse_row_annotations(annotated_base);
3401+ let (buffer, diff) = make_diff(&base_text, buffer_text, cx);
3402+ assert_rows_to_base_text_rows_visual(&buffer, &diff, buffer_text, annotated_base, cx);
3403+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, annotated_base, cx);
3404+ }
3405+
3406+ {
3407+ let base_text = "
3408+ aaa
3409+ bbb
3410+ ccc
3411+ "
3412+ .unindent();
3413+ let annotated_buffer = "
3414+ <0<1XXX
3415+ 0>1>bbb
3416+ <2>ccc
3417+ <3>"
3418+ .unindent();
3419+ let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3420+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3421+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3422+ }
3423+
3424+ {
3425+ let buffer_text = "
3426+ aaa
3427+ bbb
3428+ XXX
3429+ "
3430+ .unindent();
3431+ let annotated_base = "
3432+ <0>aaa
3433+ <1>bbb
3434+ <2<3ccc
3435+ 2>3>"
3436+ .unindent();
3437+ let (base_text, _) = parse_row_annotations(&annotated_base);
3438+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3439+ assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3440+ }
3441+
3442+ {
3443+ let base_text = "
3444+ aaa
3445+ bbb
3446+ ccc
3447+ "
3448+ .unindent();
3449+ let annotated_buffer = "
3450+ <0>aaa
3451+ <1>bbb
3452+ <2<3XXX
3453+ 2>3>"
3454+ .unindent();
3455+ let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3456+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3457+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3458+ }
3459+
3460+ {
3461+ let buffer_text = "
3462+ aaa
3463+ ccc
3464+ "
3465+ .unindent();
3466+ let annotated_base = "
3467+ <0>aaa
3468+ <1DELETED
3469+ 1>ccc
3470+ <2>"
3471+ .unindent();
3472+ let (base_text, _) = parse_row_annotations(&annotated_base);
3473+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3474+ assert_rows_to_base_text_rows_visual(&buffer, &diff, &buffer_text, &annotated_base, cx);
3475+ }
3476+
3477+ {
3478+ let base_text = "
3479+ aaa
3480+ DELETED
3481+ ccc
3482+ "
3483+ .unindent();
3484+ let annotated_buffer = "
3485+ <0>aaa
3486+ <1><2>ccc
3487+ <3>"
3488+ .unindent();
3489+ let (buffer_text, _) = parse_row_annotations(&annotated_buffer);
3490+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3491+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3492+ }
3493+ }
3494+
3495+ #[gpui::test]
3496+ async fn test_row_translation_with_edits_since_diff(cx: &mut gpui::TestAppContext) {
3497+ use unindent::Unindent;
3498+
3499+ {
3500+ let base_text = "
3501+ aaa
3502+ bbb
3503+ ccc
3504+ "
3505+ .unindent();
3506+ let buffer_text = base_text.clone();
3507+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3508+
3509+ buffer.update(cx, |buffer, cx| {
3510+ buffer.edit([(4..7, "XXX")], None, cx);
3511+ });
3512+
3513+ let new_buffer_text = "
3514+ aaa
3515+ XXX
3516+ ccc
3517+ "
3518+ .unindent();
3519+ let annotated_base = "
3520+ <0>aaa
3521+ <1bbb1>
3522+ <2>ccc
3523+ <3>"
3524+ .unindent();
3525+ assert_rows_to_base_text_rows_visual(
3526+ &buffer,
3527+ &diff,
3528+ &new_buffer_text,
3529+ &annotated_base,
3530+ cx,
3531+ );
3532+ }
3533+
3534+ {
3535+ let base_text = "
3536+ aaa
3537+ bbb
3538+ ccc
3539+ "
3540+ .unindent();
3541+ let buffer_text = base_text.clone();
3542+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3543+
3544+ buffer.update(cx, |buffer, cx| {
3545+ buffer.edit([(4..7, "XXX")], None, cx);
3546+ });
3547+
3548+ let annotated_buffer = "
3549+ <0>aaa
3550+ <1XXX1>
3551+ <2>ccc
3552+ <3>"
3553+ .unindent();
3554+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3555+ }
3556+
3557+ {
3558+ let base_text = "
3559+ aaa
3560+ bbb
3561+ ccc
3562+ "
3563+ .unindent();
3564+ let buffer_text = base_text.clone();
3565+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3566+
3567+ buffer.update(cx, |buffer, cx| {
3568+ buffer.edit([(4..4, "NEW\n")], None, cx);
3569+ });
3570+
3571+ let new_buffer_text = "
3572+ aaa
3573+ NEW
3574+ bbb
3575+ ccc
3576+ "
3577+ .unindent();
3578+ let annotated_base = "
3579+ <0>aaa
3580+ <1><2>bbb
3581+ <3>ccc
3582+ <4>"
3583+ .unindent();
3584+ assert_rows_to_base_text_rows_visual(
3585+ &buffer,
3586+ &diff,
3587+ &new_buffer_text,
3588+ &annotated_base,
3589+ cx,
3590+ );
3591+ }
3592+
3593+ {
3594+ let base_text = "
3595+ aaa
3596+ bbb
3597+ ccc
3598+ "
3599+ .unindent();
3600+ let buffer_text = base_text.clone();
3601+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3602+
3603+ buffer.update(cx, |buffer, cx| {
3604+ buffer.edit([(4..4, "NEW\n")], None, cx);
3605+ });
3606+
3607+ let annotated_buffer = "
3608+ <0>aaa
3609+ <1NEW
3610+ 1>bbb
3611+ <2>ccc
3612+ <3>"
3613+ .unindent();
3614+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3615+ }
3616+
3617+ {
3618+ let base_text = "
3619+ aaa
3620+ bbb
3621+ ccc
3622+ "
3623+ .unindent();
3624+ let buffer_text = base_text.clone();
3625+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3626+
3627+ buffer.update(cx, |buffer, cx| {
3628+ buffer.edit([(4..8, "")], None, cx);
3629+ });
3630+
3631+ let new_buffer_text = "
3632+ aaa
3633+ ccc
3634+ "
3635+ .unindent();
3636+ let annotated_base = "
3637+ <0>aaa
3638+ <1bbb
3639+ 1>ccc
3640+ <2>"
3641+ .unindent();
3642+ assert_rows_to_base_text_rows_visual(
3643+ &buffer,
3644+ &diff,
3645+ &new_buffer_text,
3646+ &annotated_base,
3647+ cx,
3648+ );
3649+ }
3650+
3651+ {
3652+ let base_text = "
3653+ aaa
3654+ bbb
3655+ ccc
3656+ "
3657+ .unindent();
3658+ let buffer_text = base_text.clone();
3659+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3660+
3661+ buffer.update(cx, |buffer, cx| {
3662+ buffer.edit([(4..8, "")], None, cx);
3663+ });
3664+
3665+ let annotated_buffer = "
3666+ <0>aaa
3667+ <1><2>ccc
3668+ <3>"
3669+ .unindent();
3670+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3671+ }
3672+
3673+ {
3674+ let base_text = "
3675+ aaa
3676+ bbb
3677+ ccc
3678+ ddd
3679+ eee
3680+ "
3681+ .unindent();
3682+ let buffer_text = "
3683+ aaa
3684+ XXX
3685+ ccc
3686+ ddd
3687+ eee
3688+ "
3689+ .unindent();
3690+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3691+
3692+ buffer.update(cx, |buffer, cx| {
3693+ buffer.edit([(12..15, "YYY")], None, cx);
3694+ });
3695+
3696+ let new_buffer_text = "
3697+ aaa
3698+ XXX
3699+ ccc
3700+ YYY
3701+ eee
3702+ "
3703+ .unindent();
3704+ let annotated_base = "
3705+ <0>aaa
3706+ <1<2bbb
3707+ 1>2>ccc
3708+ <3ddd3>
3709+ <4>eee
3710+ <5>"
3711+ .unindent();
3712+ assert_rows_to_base_text_rows_visual(
3713+ &buffer,
3714+ &diff,
3715+ &new_buffer_text,
3716+ &annotated_base,
3717+ cx,
3718+ );
3719+ }
3720+
3721+ {
3722+ let base_text = "
3723+ aaa
3724+ bbb
3725+ ccc
3726+ ddd
3727+ eee
3728+ "
3729+ .unindent();
3730+ let buffer_text = "
3731+ aaa
3732+ XXX
3733+ ccc
3734+ ddd
3735+ eee
3736+ "
3737+ .unindent();
3738+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3739+
3740+ buffer.update(cx, |buffer, cx| {
3741+ buffer.edit([(12..15, "YYY")], None, cx);
3742+ });
3743+
3744+ let annotated_buffer = "
3745+ <0>aaa
3746+ <1<2XXX
3747+ 1>2>ccc
3748+ <3YYY3>
3749+ <4>eee
3750+ <5>"
3751+ .unindent();
3752+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3753+ }
3754+
3755+ {
3756+ let base_text = "
3757+ aaa
3758+ bbb
3759+ ccc
3760+ "
3761+ .unindent();
3762+ let buffer_text = base_text.clone();
3763+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3764+
3765+ buffer.update(cx, |buffer, cx| {
3766+ buffer.edit([(0..0, "NEW\n")], None, cx);
3767+ });
3768+
3769+ let new_buffer_text = "
3770+ NEW
3771+ aaa
3772+ bbb
3773+ ccc
3774+ "
3775+ .unindent();
3776+ let annotated_base = "
3777+ <0><1>aaa
3778+ <2>bbb
3779+ <3>ccc
3780+ <4>"
3781+ .unindent();
3782+ assert_rows_to_base_text_rows_visual(
3783+ &buffer,
3784+ &diff,
3785+ &new_buffer_text,
3786+ &annotated_base,
3787+ cx,
3788+ );
3789+ }
3790+
3791+ {
3792+ let base_text = "
3793+ aaa
3794+ bbb
3795+ ccc
3796+ "
3797+ .unindent();
3798+ let buffer_text = base_text.clone();
3799+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3800+
3801+ buffer.update(cx, |buffer, cx| {
3802+ buffer.edit([(0..0, "NEW\n")], None, cx);
3803+ });
3804+
3805+ let annotated_buffer = "
3806+ <0NEW
3807+ 0>aaa
3808+ <1>bbb
3809+ <2>ccc
3810+ <3>"
3811+ .unindent();
3812+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3813+ }
3814+
3815+ {
3816+ let base_text = "
3817+ aaa
3818+ bbb
3819+ ccc
3820+ "
3821+ .unindent();
3822+ let buffer_text = base_text.clone();
3823+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3824+
3825+ buffer.update(cx, |buffer, cx| {
3826+ buffer.edit([(12..12, "NEW\n")], None, cx);
3827+ });
3828+
3829+ let new_buffer_text = "
3830+ aaa
3831+ bbb
3832+ ccc
3833+ NEW
3834+ "
3835+ .unindent();
3836+ let annotated_base = "
3837+ <0>aaa
3838+ <1>bbb
3839+ <2>ccc
3840+ <3><4>"
3841+ .unindent();
3842+ assert_rows_to_base_text_rows_visual(
3843+ &buffer,
3844+ &diff,
3845+ &new_buffer_text,
3846+ &annotated_base,
3847+ cx,
3848+ );
3849+ }
3850+
3851+ {
3852+ let base_text = "
3853+ aaa
3854+ bbb
3855+ ccc
3856+ "
3857+ .unindent();
3858+ let buffer_text = base_text.clone();
3859+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3860+
3861+ buffer.update(cx, |buffer, cx| {
3862+ buffer.edit([(12..12, "NEW\n")], None, cx);
3863+ });
3864+
3865+ let annotated_buffer = "
3866+ <0>aaa
3867+ <1>bbb
3868+ <2>ccc
3869+ <3NEW
3870+ 3>"
3871+ .unindent();
3872+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
3873+ }
3874+
3875+ {
3876+ let base_text = "";
3877+ let buffer_text = "aaa\n";
3878+ let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3879+
3880+ buffer.update(cx, |buffer, cx| {
3881+ buffer.edit([(4..4, "bbb\n")], None, cx);
3882+ });
3883+
3884+ let new_buffer_text = "
3885+ aaa
3886+ bbb
3887+ "
3888+ .unindent();
3889+ let annotated_base = "<0><1><2>";
3890+ assert_rows_to_base_text_rows_visual(
3891+ &buffer,
3892+ &diff,
3893+ &new_buffer_text,
3894+ &annotated_base,
3895+ cx,
3896+ );
3897+ }
3898+
3899+ {
3900+ let base_text = "aaa\n";
3901+ let buffer_text = "";
3902+ let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3903+
3904+ buffer.update(cx, |buffer, cx| {
3905+ buffer.edit([(0..0, "bbb\n")], None, cx);
3906+ });
3907+
3908+ let new_buffer_text = "bbb\n";
3909+ let annotated_base = "
3910+ <0<1aaa
3911+ 0>1>"
3912+ .unindent();
3913+ assert_rows_to_base_text_rows_visual(
3914+ &buffer,
3915+ &diff,
3916+ &new_buffer_text,
3917+ &annotated_base,
3918+ cx,
3919+ );
3920+ }
3921+
3922+ {
3923+ let base_text = "";
3924+ let buffer_text = "";
3925+ let (buffer, diff) = make_diff(base_text, buffer_text, cx);
3926+
3927+ buffer.update(cx, |buffer, cx| {
3928+ buffer.edit([(0..0, "aaa\n")], None, cx);
3929+ });
3930+
3931+ let new_buffer_text = "aaa\n";
3932+ let annotated_base = "<0><1>";
3933+ assert_rows_to_base_text_rows_visual(
3934+ &buffer,
3935+ &diff,
3936+ &new_buffer_text,
3937+ &annotated_base,
3938+ cx,
3939+ );
3940+ }
3941+
3942+ {
3943+ let base_text = "
3944+ aaa
3945+ bbb
3946+ ccc
3947+ "
3948+ .unindent();
3949+ let buffer_text = "
3950+ aaa
3951+ XXX
3952+ ccc
3953+ "
3954+ .unindent();
3955+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3956+
3957+ buffer.update(cx, |buffer, cx| {
3958+ buffer.edit([(4..7, "YYY")], None, cx);
3959+ });
3960+
3961+ let new_buffer_text = "
3962+ aaa
3963+ YYY
3964+ ccc
3965+ "
3966+ .unindent();
3967+ let annotated_base = "
3968+ <0>aaa
3969+ <1<2bbb
3970+ 1>2>ccc
3971+ <3>"
3972+ .unindent();
3973+ assert_rows_to_base_text_rows_visual(
3974+ &buffer,
3975+ &diff,
3976+ &new_buffer_text,
3977+ &annotated_base,
3978+ cx,
3979+ );
3980+ }
3981+
3982+ {
3983+ let base_text = "
3984+ aaa
3985+ bbb
3986+ ccc
3987+ "
3988+ .unindent();
3989+ let buffer_text = "
3990+ aaa
3991+ XXX
3992+ ccc
3993+ "
3994+ .unindent();
3995+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
3996+
3997+ buffer.update(cx, |buffer, cx| {
3998+ buffer.edit([(4..7, "YYY")], None, cx);
3999+ });
4000+
4001+ let annotated_buffer = "
4002+ <0>aaa
4003+ <1<2YYY
4004+ 1>2>ccc
4005+ <3>"
4006+ .unindent();
4007+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4008+ }
4009+
4010+ {
4011+ let base_text = "
4012+ aaa
4013+ bbb
4014+ ccc
4015+ "
4016+ .unindent();
4017+ let buffer_text = "
4018+ aaa
4019+ XXXX
4020+ ccc
4021+ "
4022+ .unindent();
4023+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4024+
4025+ buffer.update(cx, |buffer, cx| {
4026+ buffer.edit([(4..6, "YY")], None, cx);
4027+ });
4028+
4029+ let new_buffer_text = "
4030+ aaa
4031+ YYXX
4032+ ccc
4033+ "
4034+ .unindent();
4035+ let annotated_base = "
4036+ <0>aaa
4037+ <1<2bbb
4038+ 1>2>ccc
4039+ <3>"
4040+ .unindent();
4041+ assert_rows_to_base_text_rows_visual(
4042+ &buffer,
4043+ &diff,
4044+ &new_buffer_text,
4045+ &annotated_base,
4046+ cx,
4047+ );
4048+ }
4049+
4050+ {
4051+ let base_text = "
4052+ aaa
4053+ bbb
4054+ ccc
4055+ "
4056+ .unindent();
4057+ let buffer_text = "
4058+ aaa
4059+ XXXX
4060+ ccc
4061+ "
4062+ .unindent();
4063+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4064+
4065+ buffer.update(cx, |buffer, cx| {
4066+ buffer.edit([(6..8, "YY")], None, cx);
4067+ });
4068+
4069+ let new_buffer_text = "
4070+ aaa
4071+ XXYY
4072+ ccc
4073+ "
4074+ .unindent();
4075+ let annotated_base = "
4076+ <0>aaa
4077+ <1<2bbb
4078+ 1>2>ccc
4079+ <3>"
4080+ .unindent();
4081+ assert_rows_to_base_text_rows_visual(
4082+ &buffer,
4083+ &diff,
4084+ &new_buffer_text,
4085+ &annotated_base,
4086+ cx,
4087+ );
4088+ }
4089+
4090+ {
4091+ let base_text = "
4092+ aaa
4093+ bbb
4094+ ccc
4095+ "
4096+ .unindent();
4097+ let buffer_text = "
4098+ aaa
4099+ XXX
4100+ ccc
4101+ "
4102+ .unindent();
4103+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4104+
4105+ buffer.update(cx, |buffer, cx| {
4106+ buffer.edit([(4..4, "NEW")], None, cx);
4107+ });
4108+
4109+ let new_buffer_text = "
4110+ aaa
4111+ NEWXXX
4112+ ccc
4113+ "
4114+ .unindent();
4115+ let annotated_base = "
4116+ <0>aaa
4117+ <1<2bbb
4118+ 1>2>ccc
4119+ <3>"
4120+ .unindent();
4121+ assert_rows_to_base_text_rows_visual(
4122+ &buffer,
4123+ &diff,
4124+ &new_buffer_text,
4125+ &annotated_base,
4126+ cx,
4127+ );
4128+ }
4129+
4130+ {
4131+ let base_text = "
4132+ aaa
4133+ bbb
4134+ ccc
4135+ "
4136+ .unindent();
4137+ let buffer_text = "
4138+ aaa
4139+ XXX
4140+ ccc
4141+ "
4142+ .unindent();
4143+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4144+
4145+ buffer.update(cx, |buffer, cx| {
4146+ buffer.edit([(7..7, "NEW")], None, cx);
4147+ });
4148+
4149+ let new_buffer_text = "
4150+ aaa
4151+ XXXNEW
4152+ ccc
4153+ "
4154+ .unindent();
4155+ let annotated_base = "
4156+ <0>aaa
4157+ <1<2bbb
4158+ 1>2>ccc
4159+ <3>"
4160+ .unindent();
4161+ assert_rows_to_base_text_rows_visual(
4162+ &buffer,
4163+ &diff,
4164+ &new_buffer_text,
4165+ &annotated_base,
4166+ cx,
4167+ );
4168+ }
4169+
4170+ {
4171+ let base_text = "
4172+ aaa
4173+ bbb
4174+ ccc
4175+ "
4176+ .unindent();
4177+ let buffer_text = "
4178+ aaa
4179+ ccc
4180+ "
4181+ .unindent();
4182+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4183+
4184+ buffer.update(cx, |buffer, cx| {
4185+ buffer.edit([(4..4, "NEW\n")], None, cx);
4186+ });
4187+
4188+ let new_buffer_text = "
4189+ aaa
4190+ NEW
4191+ ccc
4192+ "
4193+ .unindent();
4194+ let annotated_base = "
4195+ <0>aaa
4196+ <1<2bbb
4197+ 1>2>ccc
4198+ <3>"
4199+ .unindent();
4200+ assert_rows_to_base_text_rows_visual(
4201+ &buffer,
4202+ &diff,
4203+ &new_buffer_text,
4204+ &annotated_base,
4205+ cx,
4206+ );
4207+ }
4208+
4209+ {
4210+ let base_text = "
4211+ aaa
4212+ bbb
4213+ ccc
4214+ "
4215+ .unindent();
4216+ let buffer_text = "
4217+ aaa
4218+ ccc
4219+ "
4220+ .unindent();
4221+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4222+
4223+ buffer.update(cx, |buffer, cx| {
4224+ buffer.edit([(4..4, "NEW\n")], None, cx);
4225+ });
4226+
4227+ let annotated_buffer = "
4228+ <0>aaa
4229+ <1<2NEW
4230+ 1>2>ccc
4231+ <3>"
4232+ .unindent();
4233+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4234+ }
4235+
4236+ {
4237+ let base_text = "
4238+ aaa
4239+ bbb
4240+ ccc
4241+ ddd
4242+ "
4243+ .unindent();
4244+ let buffer_text = "
4245+ aaa
4246+ ddd
4247+ "
4248+ .unindent();
4249+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4250+
4251+ buffer.update(cx, |buffer, cx| {
4252+ buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4253+ });
4254+
4255+ let new_buffer_text = "
4256+ aaa
4257+ XXX
4258+ YYY
4259+ ddd
4260+ "
4261+ .unindent();
4262+ let annotated_base = "
4263+ <0>aaa
4264+ <1<2<3bbb
4265+ ccc
4266+ 1>2>3>ddd
4267+ <4>"
4268+ .unindent();
4269+ assert_rows_to_base_text_rows_visual(
4270+ &buffer,
4271+ &diff,
4272+ &new_buffer_text,
4273+ &annotated_base,
4274+ cx,
4275+ );
4276+ }
4277+
4278+ {
4279+ let base_text = "
4280+ aaa
4281+ bbb
4282+ ccc
4283+ ddd
4284+ "
4285+ .unindent();
4286+ let buffer_text = "
4287+ aaa
4288+ ddd
4289+ "
4290+ .unindent();
4291+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4292+
4293+ buffer.update(cx, |buffer, cx| {
4294+ buffer.edit([(4..4, "XXX\nYYY\n")], None, cx);
4295+ });
4296+
4297+ let annotated_buffer = "
4298+ <0>aaa
4299+ <1<2<3XXX
4300+ YYY
4301+ 1>2>3>ddd
4302+ <4>"
4303+ .unindent();
4304+ assert_base_text_rows_to_rows_visual(&buffer, &diff, &base_text, &annotated_buffer, cx);
4305+ }
4306+
4307+ {
4308+ let base_text = "
4309+ aaa
4310+ bbb
4311+ ccc
4312+ "
4313+ .unindent();
4314+ let buffer_text = "
4315+ aaa
4316+ XXXX
4317+ ccc
4318+ "
4319+ .unindent();
4320+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4321+
4322+ buffer.update(cx, |buffer, cx| {
4323+ buffer.edit([(2..10, "YY\nZZ")], None, cx);
4324+ });
4325+
4326+ let new_buffer_text = "
4327+ aaYY
4328+ ZZcc
4329+ "
4330+ .unindent();
4331+ let annotated_base = "
4332+ <0>aa<1a
4333+ bbb
4334+ c1>cc
4335+ <2>"
4336+ .unindent();
4337+ assert_rows_to_base_text_rows_visual(
4338+ &buffer,
4339+ &diff,
4340+ &new_buffer_text,
4341+ &annotated_base,
4342+ cx,
4343+ );
4344+ }
4345+
4346+ {
4347+ let base_text = "
4348+ aaa
4349+ bbb
4350+ ccc
4351+ "
4352+ .unindent();
4353+ let buffer_text = "
4354+ aaa
4355+ XXXX
4356+ ccc
4357+ "
4358+ .unindent();
4359+ let (buffer, diff) = make_diff(&base_text, &buffer_text, cx);
4360+
4361+ buffer.update(cx, |buffer, cx| {
4362+ buffer.edit([(0..9, "ZZ\n")], None, cx);
4363+ });
4364+
4365+ let new_buffer_text = "
4366+ ZZ
4367+ ccc
4368+ "
4369+ .unindent();
4370+ let annotated_base = "
4371+ <0<1aaa
4372+ bbb
4373+ 0>1>ccc
4374+ <2>"
4375+ .unindent();
4376+ assert_rows_to_base_text_rows_visual(
4377+ &buffer,
4378+ &diff,
4379+ &new_buffer_text,
4380+ &annotated_base,
4381+ cx,
4382+ );
4383+ }
4384+ }
4385+
4386+ #[gpui::test]
4387+ async fn test_row_translation_no_base_text(cx: &mut gpui::TestAppContext) {
4388+ let buffer_text = "aaa\nbbb\nccc\n";
4389+ let buffer = cx.new(|cx| language::Buffer::local(buffer_text, cx));
4390+ let diff = cx.new(|cx| BufferDiff::new(&buffer.read(cx).text_snapshot(), cx));
4391+
4392+ let buffer_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot());
4393+ let diff_snapshot = diff.update(cx, |diff, cx| diff.snapshot(cx));
4394+
4395+ let points = vec![
4396+ Point::new(0, 0),
4397+ Point::new(1, 0),
4398+ Point::new(2, 0),
4399+ Point::new(3, 0),
4400+ ];
4401+ let base_rows: Vec<_> = diff_snapshot
4402+ .points_to_base_text_points(points, &buffer_snapshot)
4403+ .0
4404+ .collect();
4405+
4406+ let zero = Point::new(0, 0);
4407+ assert_eq!(
4408+ base_rows,
4409+ vec![zero..zero, zero..zero, zero..zero, zero..zero],
4410+ "all buffer rows should map to point 0,0 in empty base text"
4411+ );
4412+
4413+ let base_points = vec![Point::new(0, 0)];
4414+ let (rows_iter, _, _) =
4415+ diff_snapshot.base_text_points_to_points(base_points, &buffer_snapshot);
4416+ let buffer_rows: Vec<_> = rows_iter.collect();
4417+
4418+ let max_point = buffer_snapshot.max_point();
4419+ assert_eq!(
4420+ buffer_rows,
4421+ vec![zero..max_point],
4422+ "base text row 0 should map to entire buffer range"
4423+ );
4424+ }
26794425 }