Fix bugs with applying hunks from branch buffers (#18721)

Release Notes:

- N/A

---------

Co-authored-by: Marshall <marshall@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-10-07 16:28:33 -07:00 committed by GitHub
parent 3c91184726
commit b0a16a7601
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 128 additions and 61 deletions

View file

@ -216,10 +216,11 @@ impl fmt::Debug for Global {
if timestamp.replica_id > 0 { if timestamp.replica_id > 0 {
write!(f, ", ")?; write!(f, ", ")?;
} }
write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?; if timestamp.replica_id == LOCAL_BRANCH_REPLICA_ID {
} write!(f, "<branch>: {}", timestamp.value)?;
if self.local_branch_value > 0 { } else {
write!(f, "<branch>: {}", self.local_branch_value)?; write!(f, "{}: {}", timestamp.replica_id, timestamp.value)?;
}
} }
write!(f, "}}") write!(f, "}}")
} }

View file

@ -18,6 +18,7 @@ use crate::{
}; };
use anyhow::{anyhow, Context, Result}; use anyhow::{anyhow, Context, Result};
use async_watch as watch; use async_watch as watch;
use clock::Lamport;
pub use clock::ReplicaId; pub use clock::ReplicaId;
use futures::channel::oneshot; use futures::channel::oneshot;
use gpui::{ use gpui::{
@ -90,7 +91,7 @@ enum BufferDiffBase {
PastBufferVersion { PastBufferVersion {
buffer: Model<Buffer>, buffer: Model<Buffer>,
rope: Rope, rope: Rope,
operations_to_ignore: Vec<clock::Lamport>, merged_operations: Vec<Lamport>,
}, },
} }
@ -802,7 +803,7 @@ impl Buffer {
diff_base: Some(BufferDiffBase::PastBufferVersion { diff_base: Some(BufferDiffBase::PastBufferVersion {
buffer: this.clone(), buffer: this.clone(),
rope: self.as_rope().clone(), rope: self.as_rope().clone(),
operations_to_ignore: Vec::new(), merged_operations: Default::default(),
}), }),
language: self.language.clone(), language: self.language.clone(),
has_conflict: self.has_conflict, has_conflict: self.has_conflict,
@ -834,34 +835,32 @@ impl Buffer {
return; return;
}; };
base_buffer.update(cx, |base_buffer, cx| { let mut edits = Vec::new();
let edits = self for edit in self.edits_since::<usize>(&base_buffer.read(cx).version()) {
.edits_since::<usize>(&base_buffer.version) if let Some(range) = &range {
.filter_map(|edit| { if range.start > edit.new.end || edit.new.start > range.end {
if range continue;
.as_ref() }
.map_or(true, |range| range.overlaps(&edit.new)) }
{ edits.push((
Some((edit.old, self.text_for_range(edit.new).collect::<String>())) edit.old.clone(),
} else { self.text_for_range(edit.new.clone()).collect::<String>(),
None ));
} }
})
.collect::<Vec<_>>();
let operation = base_buffer.edit(edits, None, cx); let operation = base_buffer.update(cx, |base_buffer, cx| {
cx.emit(BufferEvent::DiffBaseChanged);
base_buffer.edit(edits, None, cx)
});
// Prevent this operation from being reapplied to the branch. if let Some(operation) = operation {
if let Some(BufferDiffBase::PastBufferVersion { if let Some(BufferDiffBase::PastBufferVersion {
operations_to_ignore, merged_operations, ..
..
}) = &mut self.diff_base }) = &mut self.diff_base
{ {
operations_to_ignore.extend(operation); merged_operations.push(operation);
} }
}
cx.emit(BufferEvent::DiffBaseChanged);
});
} }
fn on_base_buffer_event( fn on_base_buffer_event(
@ -870,31 +869,34 @@ impl Buffer {
event: &BufferEvent, event: &BufferEvent,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
if let BufferEvent::Operation { operation, .. } = event { let BufferEvent::Operation { operation, .. } = event else {
if let Some(BufferDiffBase::PastBufferVersion { return;
operations_to_ignore, };
.. let Some(BufferDiffBase::PastBufferVersion {
}) = &mut self.diff_base merged_operations, ..
{ }) = &mut self.diff_base
let mut is_ignored = false; else {
if let Operation::Buffer(text::Operation::Edit(buffer_operation)) = &operation { return;
operations_to_ignore.retain(|operation_to_ignore| { };
match buffer_operation.timestamp.cmp(&operation_to_ignore) {
Ordering::Less => true, let mut operation_to_undo = None;
Ordering::Equal => { if let Operation::Buffer(text::Operation::Edit(operation)) = &operation {
is_ignored = true; if let Ok(ix) = merged_operations.binary_search(&operation.timestamp) {
false merged_operations.remove(ix);
} operation_to_undo = Some(operation.timestamp);
Ordering::Greater => false,
}
});
}
if !is_ignored {
self.apply_ops([operation.clone()], cx);
self.diff_base_version += 1;
}
} }
} }
self.apply_ops([operation.clone()], cx);
if let Some(timestamp) = operation_to_undo {
let operation = self
.text
.undo_operations([(timestamp, u32::MAX)].into_iter().collect());
self.send_operation(Operation::Buffer(operation), true, cx);
}
self.diff_base_version += 1;
} }
#[cfg(test)] #[cfg(test)]

View file

@ -2485,15 +2485,73 @@ fn test_branch_and_merge(cx: &mut TestAppContext) {
} }
#[gpui::test] #[gpui::test]
fn test_merge_into_base(cx: &mut AppContext) { fn test_merge_into_base(cx: &mut TestAppContext) {
init_settings(cx, |_| {}); cx.update(|cx| init_settings(cx, |_| {}));
let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx)); let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
let branch = base.update(cx, |buffer, cx| buffer.branch(cx)); let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
// Make 3 edits, merge one into the base.
branch.update(cx, |branch, cx| { branch.update(cx, |branch, cx| {
branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx); branch.edit([(0..3, "ABC"), (7..9, "HI"), (11..11, "LMN")], None, cx);
branch.merge_into_base(Some(5..8), cx); branch.merge_into_base(Some(5..8), cx);
}); });
assert_eq!(base.read(cx).text(), "abcdefgHIjk");
branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjkLMN"));
base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
// Undo the one already-merged edit. Merge that into the base.
branch.update(cx, |branch, cx| {
branch.edit([(7..9, "hi")], None, cx);
branch.merge_into_base(Some(5..8), cx);
});
base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
// Merge an insertion into the base.
branch.update(cx, |branch, cx| {
branch.merge_into_base(Some(11..11), cx);
});
branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefghijkLMN"));
base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijkLMN"));
// Deleted the inserted text and merge that into the base.
branch.update(cx, |branch, cx| {
branch.edit([(11..14, "")], None, cx);
branch.merge_into_base(Some(10..11), cx);
});
base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
}
#[gpui::test]
fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
cx.update(|cx| init_settings(cx, |_| {}));
let base = cx.new_model(|cx| Buffer::local("abcdefghijk", cx));
let branch = base.update(cx, |buffer, cx| buffer.branch(cx));
// Make 2 edits, merge one into the base.
branch.update(cx, |branch, cx| {
branch.edit([(0..3, "ABC"), (7..9, "HI")], None, cx);
branch.merge_into_base(Some(7..7), cx);
});
base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
// Undo the merge in the base buffer.
base.update(cx, |base, cx| {
base.undo(cx);
});
base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefghijk"));
branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
// Merge that operation into the base again.
branch.update(cx, |branch, cx| {
branch.merge_into_base(Some(7..7), cx);
});
base.read_with(cx, |base, _| assert_eq!(base.text(), "abcdefgHIjk"));
branch.read_with(cx, |branch, _| assert_eq!(branch.text(), "ABCdefgHIjk"));
} }
fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) { fn start_recalculating_diff(buffer: &Model<Buffer>, cx: &mut TestAppContext) {

View file

@ -1430,16 +1430,22 @@ impl Buffer {
counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1); counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1);
} }
let operation = self.undo_operations(counts);
self.history.push(operation.clone());
operation
}
pub fn undo_operations(&mut self, counts: HashMap<clock::Lamport, u32>) -> Operation {
let timestamp = self.lamport_clock.tick();
let version = self.version();
self.snapshot.version.observe(timestamp);
let undo = UndoOperation { let undo = UndoOperation {
timestamp: self.lamport_clock.tick(), timestamp,
version: self.version(), version,
counts, counts,
}; };
self.apply_undo(&undo); self.apply_undo(&undo);
self.snapshot.version.observe(undo.timestamp); Operation::Undo(undo)
let operation = Operation::Undo(undo);
self.history.push(operation.clone());
operation
} }
pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) { pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) {