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:
parent
3c91184726
commit
b0a16a7601
4 changed files with 128 additions and 61 deletions
|
@ -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, "}}")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue