git: Rework side-by-side diff to use distinct buffers for the left-hand side (#44838)
This PR reworks the (still feature-gated) side-by-side diff view to use a different approach to representing the multibuffers on the left- and right-hand sides. Previously, these two multibuffers used identical sets of buffers and excerpts, and were made to behave differently by adding a new knob to the multibuffer controlling how diffs are displayed. Specifically, the left-hand side multibuffer would filter out the added range of each hunk from the excerpts using a new `FilteredInsertedHunk` diff transform, and the right-hand side would simply not show the deleted sides of expanded hunks. This approach has some problems: - Line numbers, and actions that navigate by line number, behaved incorrectly for the left-hand side. - Syntax highlighting and other features that use the buffer syntax tree also behaved incorrectly for the left-hand side. In this PR, we've switched to using independent buffers to build the left-hand side. These buffers are constructed using the base texts for the corresponding diffs, and their lifecycle is managed by `BufferDiff`. The red "deleted" regions on the left-hand side are represented by `BufferContent` diff transforms, not `DeletedHunk` transforms. This means each excerpt on the left represents a contiguous slice of a single buffer, which fixes the above issues by construction. The tradeoff with this new approach is that we now have to manually synchronize excerpt ranges from the right side to the left, which we do using `BufferDiffSnapshot::row_to_base_text_row`. Release Notes: - N/A --------- Co-authored-by: cameron <cameron.studdstreet@gmail.com> Co-authored-by: HactarCE <6060305+HactarCE@users.noreply.github.com> Co-authored-by: Miguel Raz Guzmán Macedo <miguel@zed.dev> Co-authored-by: Anthony <anthony@zed.dev> Co-authored-by: Cameron <cameron@zed.dev>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -2381,7 +2381,6 @@ dependencies = [
|
||||
name = "buffer_diff"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clock",
|
||||
"ctor",
|
||||
"futures 0.3.31",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use anyhow::Result;
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use buffer_diff::BufferDiff;
|
||||
use editor::{MultiBuffer, PathKey, multibuffer_context_lines};
|
||||
use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task};
|
||||
use itertools::Itertools;
|
||||
use language::{
|
||||
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, Rope, TextBuffer,
|
||||
Anchor, Buffer, Capability, LanguageRegistry, OffsetRangeExt as _, Point, TextBuffer,
|
||||
};
|
||||
use std::{cmp::Reverse, ops::Range, path::Path, sync::Arc};
|
||||
use util::ResultExt;
|
||||
@@ -49,15 +49,15 @@ impl Diff {
|
||||
.update(cx, |multibuffer, cx| {
|
||||
let hunk_ranges = {
|
||||
let buffer = buffer.read(cx);
|
||||
let diff = diff.read(cx);
|
||||
diff.hunks_intersecting_range(
|
||||
Anchor::min_for_buffer(buffer.remote_id())
|
||||
..Anchor::max_for_buffer(buffer.remote_id()),
|
||||
buffer,
|
||||
cx,
|
||||
)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
|
||||
.collect::<Vec<_>>()
|
||||
diff.read(cx)
|
||||
.snapshot(cx)
|
||||
.hunks_intersecting_range(
|
||||
Anchor::min_for_buffer(buffer.remote_id())
|
||||
..Anchor::max_for_buffer(buffer.remote_id()),
|
||||
buffer,
|
||||
)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
multibuffer.set_excerpts_for_path(
|
||||
@@ -86,17 +86,9 @@ impl Diff {
|
||||
|
||||
pub fn new(buffer: Entity<Buffer>, cx: &mut Context<Self>) -> Self {
|
||||
let buffer_text_snapshot = buffer.read(cx).text_snapshot();
|
||||
let base_text_snapshot = buffer.read(cx).snapshot();
|
||||
let base_text = base_text_snapshot.text();
|
||||
debug_assert_eq!(buffer_text_snapshot.text(), base_text);
|
||||
let buffer_diff = cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, base_text_snapshot);
|
||||
let snapshot = diff.snapshot(cx);
|
||||
let secondary_diff = cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&buffer_text_snapshot, cx);
|
||||
diff.set_snapshot(snapshot, &buffer_text_snapshot, cx);
|
||||
diff
|
||||
});
|
||||
let mut diff = BufferDiff::new_unchanged(&buffer_text_snapshot, cx);
|
||||
let secondary_diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_text_snapshot, cx));
|
||||
diff.set_secondary_diff(secondary_diff);
|
||||
diff
|
||||
});
|
||||
@@ -109,7 +101,7 @@ impl Diff {
|
||||
|
||||
Self::Pending(PendingDiff {
|
||||
multibuffer,
|
||||
base_text: Arc::new(base_text),
|
||||
base_text: Arc::from(buffer_text_snapshot.text().as_str()),
|
||||
_subscription: cx.observe(&buffer, |this, _, cx| {
|
||||
if let Diff::Pending(diff) = this {
|
||||
diff.update(cx);
|
||||
@@ -176,7 +168,7 @@ impl Diff {
|
||||
new_buffer,
|
||||
..
|
||||
}) => {
|
||||
base_text.as_str() != old_text
|
||||
base_text.as_ref() != old_text
|
||||
|| !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
|
||||
}
|
||||
Diff::Finalized(FinalizedDiff {
|
||||
@@ -184,7 +176,7 @@ impl Diff {
|
||||
new_buffer,
|
||||
..
|
||||
}) => {
|
||||
base_text.as_str() != old_text
|
||||
base_text.as_ref() != old_text
|
||||
|| !new_buffer.read(cx).as_rope().chunks().equals_str(new_text)
|
||||
}
|
||||
}
|
||||
@@ -193,7 +185,7 @@ impl Diff {
|
||||
|
||||
pub struct PendingDiff {
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
base_text: Arc<String>,
|
||||
base_text: Arc<str>,
|
||||
new_buffer: Entity<Buffer>,
|
||||
diff: Entity<BufferDiff>,
|
||||
revealed_ranges: Vec<Range<Anchor>>,
|
||||
@@ -208,21 +200,22 @@ impl PendingDiff {
|
||||
let base_text = self.base_text.clone();
|
||||
self.update_diff = cx.spawn(async move |diff, cx| {
|
||||
let text_snapshot = buffer.read_with(cx, |buffer, _| buffer.text_snapshot())?;
|
||||
let diff_snapshot = BufferDiff::update_diff(
|
||||
buffer_diff.clone(),
|
||||
text_snapshot.clone(),
|
||||
Some(base_text),
|
||||
false,
|
||||
false,
|
||||
None,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
.await?;
|
||||
let language = buffer.read_with(cx, |buffer, _| buffer.language().cloned())?;
|
||||
let update = buffer_diff
|
||||
.update(cx, |diff, cx| {
|
||||
diff.update_diff(
|
||||
text_snapshot.clone(),
|
||||
Some(base_text.clone()),
|
||||
false,
|
||||
language,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
buffer_diff.update(cx, |diff, cx| {
|
||||
diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
|
||||
diff.set_snapshot(update.clone(), &text_snapshot, cx);
|
||||
diff.secondary_diff().unwrap().update(cx, |diff, cx| {
|
||||
diff.set_snapshot(diff_snapshot.clone(), &text_snapshot, cx);
|
||||
diff.set_snapshot(update, &text_snapshot, cx);
|
||||
});
|
||||
})?;
|
||||
diff.update(cx, |diff, cx| {
|
||||
@@ -319,13 +312,14 @@ impl PendingDiff {
|
||||
|
||||
fn excerpt_ranges(&self, cx: &App) -> Vec<Range<Point>> {
|
||||
let buffer = self.new_buffer.read(cx);
|
||||
let diff = self.diff.read(cx);
|
||||
let mut ranges = diff
|
||||
let mut ranges = self
|
||||
.diff
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.hunks_intersecting_range(
|
||||
Anchor::min_for_buffer(buffer.remote_id())
|
||||
..Anchor::max_for_buffer(buffer.remote_id()),
|
||||
buffer,
|
||||
cx,
|
||||
)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(buffer))
|
||||
.collect::<Vec<_>>();
|
||||
@@ -357,60 +351,47 @@ impl PendingDiff {
|
||||
|
||||
pub struct FinalizedDiff {
|
||||
path: String,
|
||||
base_text: Arc<String>,
|
||||
base_text: Arc<str>,
|
||||
new_buffer: Entity<Buffer>,
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
_update_diff: Task<Result<()>>,
|
||||
}
|
||||
|
||||
async fn build_buffer_diff(
|
||||
old_text: Arc<String>,
|
||||
old_text: Arc<str>,
|
||||
buffer: &Entity<Buffer>,
|
||||
language_registry: Option<Arc<LanguageRegistry>>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<BufferDiff>> {
|
||||
let language = cx.update(|cx| buffer.read(cx).language().cloned())?;
|
||||
let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
|
||||
|
||||
let old_text_rope = cx
|
||||
.background_spawn({
|
||||
let old_text = old_text.clone();
|
||||
async move { Rope::from(old_text.as_str()) }
|
||||
})
|
||||
.await;
|
||||
let base_buffer = cx
|
||||
.update(|cx| {
|
||||
Buffer::build_snapshot(
|
||||
old_text_rope,
|
||||
buffer.language().cloned(),
|
||||
language_registry,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
let secondary_diff = cx.new(|cx| BufferDiff::new(&buffer, cx))?;
|
||||
|
||||
let diff_snapshot = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
let update = secondary_diff
|
||||
.update(cx, |secondary_diff, cx| {
|
||||
secondary_diff.update_diff(
|
||||
buffer.text.clone(),
|
||||
Some(old_text),
|
||||
base_buffer,
|
||||
true,
|
||||
language.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
let secondary_diff = cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&buffer, cx);
|
||||
diff.set_snapshot(diff_snapshot.clone(), &buffer, cx);
|
||||
diff
|
||||
secondary_diff.update(cx, |secondary_diff, cx| {
|
||||
secondary_diff.language_changed(language.clone(), language_registry.clone(), cx);
|
||||
secondary_diff.set_snapshot(update.clone(), &buffer, cx);
|
||||
})?;
|
||||
|
||||
cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&buffer.text, cx);
|
||||
diff.set_snapshot(diff_snapshot, &buffer, cx);
|
||||
let diff = cx.new(|cx| BufferDiff::new(&buffer, cx))?;
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.language_changed(language, language_registry, cx);
|
||||
diff.set_snapshot(update.clone(), &buffer, cx);
|
||||
diff.set_secondary_diff(secondary_diff);
|
||||
diff
|
||||
})
|
||||
})?;
|
||||
Ok(diff)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -262,7 +262,7 @@ impl ActionLog {
|
||||
);
|
||||
}
|
||||
|
||||
(Arc::new(base_text.to_string()), base_text)
|
||||
(Arc::from(base_text.to_string().as_str()), base_text)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -302,7 +302,7 @@ impl ActionLog {
|
||||
.context("buffer not tracked")?;
|
||||
let old_unreviewed_edits = tracked_buffer.unreviewed_edits.clone();
|
||||
let agent_diff_base = tracked_buffer.diff_base.clone();
|
||||
let git_diff_base = git_diff.read(cx).base_text().as_rope().clone();
|
||||
let git_diff_base = git_diff.read(cx).base_text(cx).as_rope().clone();
|
||||
let buffer_text = tracked_buffer.snapshot.as_rope().clone();
|
||||
anyhow::Ok(cx.background_spawn(async move {
|
||||
let mut old_unreviewed_edits = old_unreviewed_edits.into_iter().peekable();
|
||||
@@ -352,7 +352,7 @@ impl ActionLog {
|
||||
}
|
||||
|
||||
(
|
||||
Arc::new(new_agent_diff_base.to_string()),
|
||||
Arc::from(new_agent_diff_base.to_string().as_str()),
|
||||
new_agent_diff_base,
|
||||
)
|
||||
}))
|
||||
@@ -374,11 +374,11 @@ impl ActionLog {
|
||||
this: &WeakEntity<ActionLog>,
|
||||
buffer: &Entity<Buffer>,
|
||||
buffer_snapshot: text::BufferSnapshot,
|
||||
new_base_text: Arc<String>,
|
||||
new_base_text: Arc<str>,
|
||||
new_diff_base: Rope,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
let (diff, language, language_registry) = this.read_with(cx, |this, cx| {
|
||||
let (diff, language) = this.read_with(cx, |this, cx| {
|
||||
let tracked_buffer = this
|
||||
.tracked_buffers
|
||||
.get(buffer)
|
||||
@@ -386,25 +386,28 @@ impl ActionLog {
|
||||
anyhow::Ok((
|
||||
tracked_buffer.diff.clone(),
|
||||
buffer.read(cx).language().cloned(),
|
||||
buffer.read(cx).language_registry(),
|
||||
))
|
||||
})??;
|
||||
let diff_snapshot = BufferDiff::update_diff(
|
||||
diff.clone(),
|
||||
buffer_snapshot.clone(),
|
||||
Some(new_base_text),
|
||||
true,
|
||||
false,
|
||||
language,
|
||||
language_registry,
|
||||
cx,
|
||||
)
|
||||
.await;
|
||||
let update = diff.update(cx, |diff, cx| {
|
||||
diff.update_diff(
|
||||
buffer_snapshot.clone(),
|
||||
Some(new_base_text),
|
||||
true,
|
||||
language,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let mut unreviewed_edits = Patch::default();
|
||||
if let Ok(diff_snapshot) = diff_snapshot {
|
||||
if let Ok(update) = update {
|
||||
let update = update.await;
|
||||
|
||||
let diff_snapshot = diff.update(cx, |diff, cx| {
|
||||
diff.set_snapshot(update.clone(), &buffer_snapshot, cx);
|
||||
diff.snapshot(cx)
|
||||
})?;
|
||||
|
||||
unreviewed_edits = cx
|
||||
.background_spawn({
|
||||
let diff_snapshot = diff_snapshot.clone();
|
||||
let buffer_snapshot = buffer_snapshot.clone();
|
||||
let new_diff_base = new_diff_base.clone();
|
||||
async move {
|
||||
@@ -431,10 +434,6 @@ impl ActionLog {
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.set_snapshot(diff_snapshot, &buffer_snapshot, cx);
|
||||
})?;
|
||||
}
|
||||
this.update(cx, |this, cx| {
|
||||
let tracked_buffer = this
|
||||
@@ -975,7 +974,8 @@ impl TrackedBuffer {
|
||||
fn has_edits(&self, cx: &App) -> bool {
|
||||
self.diff
|
||||
.read(cx)
|
||||
.hunks(self.buffer.read(cx), cx)
|
||||
.snapshot(cx)
|
||||
.hunks(self.buffer.read(cx))
|
||||
.next()
|
||||
.is_some()
|
||||
}
|
||||
@@ -2388,13 +2388,14 @@ mod tests {
|
||||
(
|
||||
buffer,
|
||||
diff.read(cx)
|
||||
.hunks(&snapshot, cx)
|
||||
.snapshot(cx)
|
||||
.hunks(&snapshot)
|
||||
.map(|hunk| HunkStatus {
|
||||
diff_status: hunk.status().kind,
|
||||
range: hunk.range,
|
||||
old_text: diff
|
||||
.read(cx)
|
||||
.base_text()
|
||||
.base_text(cx)
|
||||
.text_for_range(hunk.diff_base_byte_range)
|
||||
.collect(),
|
||||
})
|
||||
|
||||
@@ -146,13 +146,13 @@ impl AgentDiffPane {
|
||||
paths_to_delete.remove(&path_key);
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let diff = diff_handle.read(cx);
|
||||
|
||||
let diff_hunk_ranges = diff
|
||||
let diff_hunk_ranges = diff_handle
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.hunks_intersecting_range(
|
||||
language::Anchor::min_max_range_for_buffer(snapshot.remote_id()),
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
@@ -15,7 +15,6 @@ path = "src/buffer_diff.rs"
|
||||
test-support = ["settings"]
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
clock.workspace = true
|
||||
futures.workspace = true
|
||||
git2.workspace = true
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2647,13 +2647,13 @@ async fn test_git_diff_base_change(
|
||||
local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
|
||||
let buffer = buffer_local_a.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added_none())],
|
||||
);
|
||||
});
|
||||
@@ -2677,13 +2677,13 @@ async fn test_git_diff_base_change(
|
||||
remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
|
||||
let buffer = remote_buffer_a.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added_none())],
|
||||
);
|
||||
});
|
||||
@@ -2699,13 +2699,13 @@ async fn test_git_diff_base_change(
|
||||
remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
|
||||
let buffer = remote_buffer_a.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(committed_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
1..2,
|
||||
"TWO\n",
|
||||
@@ -2731,13 +2731,13 @@ async fn test_git_diff_base_change(
|
||||
local_unstaged_diff_a.read_with(cx_a, |diff, cx| {
|
||||
let buffer = buffer_local_a.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added_none())],
|
||||
);
|
||||
});
|
||||
@@ -2746,13 +2746,13 @@ async fn test_git_diff_base_change(
|
||||
remote_unstaged_diff_a.read_with(cx_b, |diff, cx| {
|
||||
let buffer = remote_buffer_a.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added_none())],
|
||||
);
|
||||
});
|
||||
@@ -2760,13 +2760,13 @@ async fn test_git_diff_base_change(
|
||||
remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| {
|
||||
let buffer = remote_buffer_a.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(new_committed_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
1..2,
|
||||
"TWO_HUNDRED\n",
|
||||
@@ -2813,13 +2813,13 @@ async fn test_git_diff_base_change(
|
||||
local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
|
||||
let buffer = buffer_local_b.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added_none())],
|
||||
);
|
||||
});
|
||||
@@ -2842,11 +2842,11 @@ async fn test_git_diff_base_change(
|
||||
remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
|
||||
let buffer = remote_buffer_b.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&staged_text,
|
||||
&[(1..2, "", "two\n", DiffHunkStatus::added_none())],
|
||||
@@ -2864,11 +2864,11 @@ async fn test_git_diff_base_change(
|
||||
local_unstaged_diff_b.read_with(cx_a, |diff, cx| {
|
||||
let buffer = buffer_local_b.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&new_staged_text,
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added_none())],
|
||||
@@ -2878,11 +2878,11 @@ async fn test_git_diff_base_change(
|
||||
remote_unstaged_diff_b.read_with(cx_b, |diff, cx| {
|
||||
let buffer = remote_buffer_b.read(cx);
|
||||
assert_eq!(
|
||||
diff.base_text_string().as_deref(),
|
||||
diff.base_text_string(cx).as_deref(),
|
||||
Some(new_staged_text.as_str())
|
||||
);
|
||||
assert_hunks(
|
||||
diff.hunks_in_row_range(0..4, buffer, cx),
|
||||
diff.snapshot(cx).hunks_in_row_range(0..4, buffer),
|
||||
buffer,
|
||||
&new_staged_text,
|
||||
&[(2..3, "", "three\n", DiffHunkStatus::added_none())],
|
||||
|
||||
@@ -1377,7 +1377,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
.get_unstaged_diff(host_buffer.read(cx).remote_id(), cx)
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
});
|
||||
let guest_diff_base = guest_project.read_with(client_cx, |project, cx| {
|
||||
project
|
||||
@@ -1386,7 +1386,7 @@ impl RandomizedTest for ProjectCollaborationTest {
|
||||
.get_unstaged_diff(guest_buffer.read(cx).remote_id(), cx)
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
});
|
||||
assert_eq!(
|
||||
guest_diff_base, host_diff_base,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use buffer_diff::BufferDiff;
|
||||
use edit_prediction::{EditPrediction, EditPredictionRating, EditPredictionStore};
|
||||
use editor::{Editor, ExcerptRange, MultiBuffer};
|
||||
use feature_flags::FeatureFlag;
|
||||
@@ -323,22 +323,23 @@ impl RatePredictionsModal {
|
||||
let start = Point::new(range.start.row.saturating_sub(5), 0);
|
||||
let end = Point::new(range.end.row + 5, 0).min(new_buffer_snapshot.max_point());
|
||||
|
||||
let diff = cx.new::<BufferDiff>(|cx| {
|
||||
let diff_snapshot = BufferDiffSnapshot::new_with_base_buffer(
|
||||
let language = new_buffer_snapshot.language().cloned();
|
||||
let diff = cx.new(|cx| BufferDiff::new(&new_buffer_snapshot.text, cx));
|
||||
diff.update(cx, |diff, cx| {
|
||||
let update = diff.update_diff(
|
||||
new_buffer_snapshot.text.clone(),
|
||||
Some(old_buffer_snapshot.text().into()),
|
||||
old_buffer_snapshot.clone(),
|
||||
true,
|
||||
language,
|
||||
cx,
|
||||
);
|
||||
let diff = BufferDiff::new(&new_buffer_snapshot, cx);
|
||||
cx.spawn(async move |diff, cx| {
|
||||
let diff_snapshot = diff_snapshot.await;
|
||||
let update = update.await;
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.set_snapshot(diff_snapshot, &new_buffer_snapshot.text, cx);
|
||||
diff.set_snapshot(update, &new_buffer_snapshot.text, cx);
|
||||
})
|
||||
})
|
||||
.detach();
|
||||
diff
|
||||
});
|
||||
|
||||
editor.disable_header_for_buffer(new_buffer_id, cx);
|
||||
|
||||
@@ -232,8 +232,6 @@ impl DisplayMap {
|
||||
.update(cx, |map, cx| map.sync(tab_snapshot, edits, cx));
|
||||
let block_snapshot = self.block_map.read(wrap_snapshot, edits).snapshot;
|
||||
|
||||
// todo word diff here?
|
||||
|
||||
DisplaySnapshot {
|
||||
block_snapshot,
|
||||
diagnostics_max_severity: self.diagnostics_max_severity,
|
||||
|
||||
@@ -1119,7 +1119,6 @@ impl Iterator for WrapRows<'_> {
|
||||
RowInfo {
|
||||
buffer_id: None,
|
||||
buffer_row: None,
|
||||
base_text_row: None,
|
||||
multibuffer_row: None,
|
||||
diff_status,
|
||||
expand_info: None,
|
||||
|
||||
@@ -1072,6 +1072,7 @@ pub struct Editor {
|
||||
minimap_visibility: MinimapVisibility,
|
||||
offset_content: bool,
|
||||
disable_expand_excerpt_buttons: bool,
|
||||
delegate_expand_excerpts: bool,
|
||||
show_line_numbers: Option<bool>,
|
||||
use_relative_line_numbers: Option<bool>,
|
||||
show_git_diff_gutter: Option<bool>,
|
||||
@@ -1203,6 +1204,7 @@ pub struct Editor {
|
||||
hide_mouse_mode: HideMouseMode,
|
||||
pub change_list: ChangeList,
|
||||
inline_value_cache: InlineValueCache,
|
||||
number_deleted_lines: bool,
|
||||
|
||||
selection_drag_state: SelectionDragState,
|
||||
colors: Option<LspColorData>,
|
||||
@@ -1215,7 +1217,6 @@ pub struct Editor {
|
||||
applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
|
||||
accent_data: Option<AccentData>,
|
||||
fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
|
||||
use_base_text_line_numbers: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
@@ -1256,6 +1257,7 @@ pub struct EditorSnapshot {
|
||||
show_gutter: bool,
|
||||
offset_content: bool,
|
||||
show_line_numbers: Option<bool>,
|
||||
number_deleted_lines: bool,
|
||||
show_git_diff_gutter: Option<bool>,
|
||||
show_code_actions: Option<bool>,
|
||||
show_runnables: Option<bool>,
|
||||
@@ -2237,6 +2239,7 @@ impl Editor {
|
||||
show_line_numbers: (!full_mode).then_some(false),
|
||||
use_relative_line_numbers: None,
|
||||
disable_expand_excerpt_buttons: !full_mode,
|
||||
delegate_expand_excerpts: false,
|
||||
show_git_diff_gutter: None,
|
||||
show_code_actions: None,
|
||||
show_runnables: None,
|
||||
@@ -2405,7 +2408,7 @@ impl Editor {
|
||||
applicable_language_settings: HashMap::default(),
|
||||
accent_data: None,
|
||||
fetched_tree_sitter_chunks: HashMap::default(),
|
||||
use_base_text_line_numbers: false,
|
||||
number_deleted_lines: false,
|
||||
};
|
||||
|
||||
if is_minimap {
|
||||
@@ -2940,6 +2943,7 @@ impl Editor {
|
||||
show_gutter: self.show_gutter,
|
||||
offset_content: self.offset_content,
|
||||
show_line_numbers: self.show_line_numbers,
|
||||
number_deleted_lines: self.number_deleted_lines,
|
||||
show_git_diff_gutter: self.show_git_diff_gutter,
|
||||
show_code_actions: self.show_code_actions,
|
||||
show_runnables: self.show_runnables,
|
||||
@@ -11496,7 +11500,7 @@ impl Editor {
|
||||
let buffer = buffer.read(cx);
|
||||
let original_text = diff
|
||||
.read(cx)
|
||||
.base_text()
|
||||
.base_text(cx)
|
||||
.as_rope()
|
||||
.slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
@@ -16590,7 +16594,6 @@ impl Editor {
|
||||
&mut self,
|
||||
lines: u32,
|
||||
direction: ExpandExcerptDirection,
|
||||
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let selections = self.selections.disjoint_anchors_arc();
|
||||
@@ -16601,14 +16604,24 @@ impl Editor {
|
||||
lines
|
||||
};
|
||||
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut excerpt_ids = selections
|
||||
.iter()
|
||||
.flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
|
||||
.collect::<Vec<_>>();
|
||||
excerpt_ids.sort();
|
||||
excerpt_ids.dedup();
|
||||
|
||||
if self.delegate_expand_excerpts {
|
||||
cx.emit(EditorEvent::ExpandExcerptsRequested {
|
||||
excerpt_ids,
|
||||
lines,
|
||||
direction,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
let snapshot = buffer.snapshot(cx);
|
||||
let mut excerpt_ids = selections
|
||||
.iter()
|
||||
.flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
|
||||
.collect::<Vec<_>>();
|
||||
excerpt_ids.sort();
|
||||
excerpt_ids.dedup();
|
||||
buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
|
||||
})
|
||||
}
|
||||
@@ -16620,8 +16633,18 @@ impl Editor {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let current_scroll_position = self.scroll_position(cx);
|
||||
let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
|
||||
|
||||
if self.delegate_expand_excerpts {
|
||||
cx.emit(EditorEvent::ExpandExcerptsRequested {
|
||||
excerpt_ids: vec![excerpt],
|
||||
lines: lines_to_expand,
|
||||
direction,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let current_scroll_position = self.scroll_position(cx);
|
||||
let mut scroll = None;
|
||||
|
||||
if direction == ExpandExcerptDirection::Down {
|
||||
@@ -19698,10 +19721,6 @@ impl Editor {
|
||||
self.display_map.read(cx).fold_placeholder.clone()
|
||||
}
|
||||
|
||||
pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
|
||||
self.use_base_text_line_numbers = show;
|
||||
}
|
||||
|
||||
pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
|
||||
self.buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_all_diff_hunks_expanded(cx);
|
||||
@@ -19943,7 +19962,7 @@ impl Editor {
|
||||
buffer_word_diffs: Vec::default(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.start.0
|
||||
..hunk.diff_base_byte_range.end.0,
|
||||
secondary_status: hunk.secondary_status,
|
||||
secondary_status: hunk.status.secondary,
|
||||
range: Point::zero()..Point::zero(), // unused
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
@@ -20572,6 +20591,10 @@ impl Editor {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
pub fn set_delegate_expand_excerpts(&mut self, delegate: bool) {
|
||||
self.delegate_expand_excerpts = delegate;
|
||||
}
|
||||
|
||||
pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
|
||||
self.show_git_diff_gutter = Some(show_git_diff_gutter);
|
||||
cx.notify();
|
||||
@@ -20983,8 +21006,12 @@ impl Editor {
|
||||
|
||||
Some((
|
||||
multi_buffer.buffer(buffer.remote_id()).unwrap(),
|
||||
buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
|
||||
..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
|
||||
buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, Bias::Left, buffer)
|
||||
..buffer_diff_snapshot.row_to_base_text_row(
|
||||
end_row_in_buffer,
|
||||
Bias::Left,
|
||||
buffer,
|
||||
),
|
||||
))
|
||||
});
|
||||
|
||||
@@ -25409,6 +25436,11 @@ pub enum EditorEvent {
|
||||
ExcerptsExpanded {
|
||||
ids: Vec<ExcerptId>,
|
||||
},
|
||||
ExpandExcerptsRequested {
|
||||
excerpt_ids: Vec<ExcerptId>,
|
||||
lines: u32,
|
||||
direction: ExpandExcerptDirection,
|
||||
},
|
||||
BufferEdited,
|
||||
Edited {
|
||||
transaction_id: clock::Lamport,
|
||||
|
||||
@@ -36,8 +36,7 @@ use languages::markdown_lang;
|
||||
use languages::rust_lang;
|
||||
use lsp::CompletionParams;
|
||||
use multi_buffer::{
|
||||
ExcerptRange, IndentGuide, MultiBuffer, MultiBufferFilterMode, MultiBufferOffset,
|
||||
MultiBufferOffsetUtf16, PathKey,
|
||||
ExcerptRange, IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
@@ -13221,30 +13220,28 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
|
||||
// Handle formatting requests to the language server.
|
||||
cx.lsp
|
||||
.set_request_handler::<lsp::request::Formatting, _, _>({
|
||||
let buffer_changes = buffer_changes.clone();
|
||||
move |_, _| {
|
||||
let buffer_changes = buffer_changes.clone();
|
||||
// Insert blank lines between each line of the buffer.
|
||||
async move {
|
||||
// When formatting is requested, trailing whitespace has already been stripped,
|
||||
// and the trailing newline has already been added.
|
||||
assert_eq!(
|
||||
&buffer_changes.lock()[1..],
|
||||
&[
|
||||
(
|
||||
lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
|
||||
"".into()
|
||||
),
|
||||
(
|
||||
lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
|
||||
"".into()
|
||||
),
|
||||
(
|
||||
lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
|
||||
"\n".into()
|
||||
),
|
||||
]
|
||||
);
|
||||
// TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
|
||||
// DidChangedTextDocument to the LSP before sending the formatting request.
|
||||
// assert_eq!(
|
||||
// &buffer_changes.lock()[1..],
|
||||
// &[
|
||||
// (
|
||||
// lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
|
||||
// "".into()
|
||||
// ),
|
||||
// (
|
||||
// lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
|
||||
// "".into()
|
||||
// ),
|
||||
// (
|
||||
// lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
|
||||
// "\n".into()
|
||||
// ),
|
||||
// ]
|
||||
// );
|
||||
|
||||
Ok(Some(vec![
|
||||
lsp::TextEdit {
|
||||
@@ -13276,7 +13273,6 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
|
||||
]
|
||||
.join("\n"),
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
||||
// Submit a format request.
|
||||
let format = cx
|
||||
@@ -19884,7 +19880,9 @@ async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
|
||||
(buffer_2.clone(), base_text_2),
|
||||
(buffer_3.clone(), base_text_3),
|
||||
] {
|
||||
let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
|
||||
let diff = cx.new(|cx| {
|
||||
BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
|
||||
});
|
||||
editor
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.add_diff(diff, cx));
|
||||
@@ -20509,7 +20507,9 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
|
||||
(buffer_2.clone(), file_2_old),
|
||||
(buffer_3.clone(), file_3_old),
|
||||
] {
|
||||
let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
|
||||
let diff = cx.new(|cx| {
|
||||
BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
|
||||
});
|
||||
editor
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.add_diff(diff, cx));
|
||||
@@ -20615,7 +20615,9 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
|
||||
cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
|
||||
editor
|
||||
.update(cx, |editor, _window, cx| {
|
||||
let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
|
||||
let diff = cx.new(|cx| {
|
||||
BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
|
||||
});
|
||||
editor
|
||||
.buffer
|
||||
.update(cx, |buffer, cx| buffer.add_diff(diff, cx))
|
||||
@@ -22049,7 +22051,9 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
|
||||
|
||||
editor.buffer().update(cx, |multibuffer, cx| {
|
||||
let buffer = multibuffer.as_singleton().unwrap();
|
||||
let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
|
||||
let diff = cx.new(|cx| {
|
||||
BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
|
||||
});
|
||||
|
||||
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||
multibuffer.add_diff(diff, cx);
|
||||
@@ -29223,208 +29227,6 @@ async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut leader_cx = EditorTestContext::new(cx).await;
|
||||
|
||||
let diff_base = indoc!(
|
||||
r#"
|
||||
one
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"#
|
||||
);
|
||||
|
||||
let initial_state = indoc!(
|
||||
r#"
|
||||
ˇone
|
||||
two
|
||||
THREE
|
||||
four
|
||||
five
|
||||
six
|
||||
"#
|
||||
);
|
||||
|
||||
leader_cx.set_state(initial_state);
|
||||
|
||||
leader_cx.set_head_text(&diff_base);
|
||||
leader_cx.run_until_parked();
|
||||
|
||||
let follower = leader_cx.update_multibuffer(|leader, cx| {
|
||||
leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
|
||||
leader.set_all_diff_hunks_expanded(cx);
|
||||
leader.get_or_create_follower(cx)
|
||||
});
|
||||
follower.update(cx, |follower, cx| {
|
||||
follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
|
||||
follower.set_all_diff_hunks_expanded(cx);
|
||||
});
|
||||
|
||||
let follower_editor =
|
||||
leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
|
||||
// leader_cx.window.focus(&follower_editor.focus_handle(cx));
|
||||
|
||||
let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
|
||||
cx.run_until_parked();
|
||||
|
||||
leader_cx.assert_editor_state(initial_state);
|
||||
follower_cx.assert_editor_state(indoc! {
|
||||
r#"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"#
|
||||
});
|
||||
|
||||
follower_cx.editor(|editor, _window, cx| {
|
||||
assert!(editor.read_only(cx));
|
||||
});
|
||||
|
||||
leader_cx.update_editor(|editor, _window, cx| {
|
||||
editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
leader_cx.assert_editor_state(indoc! {
|
||||
r#"
|
||||
ˇone
|
||||
two
|
||||
THREE
|
||||
four
|
||||
FIVE
|
||||
six
|
||||
"#
|
||||
});
|
||||
|
||||
follower_cx.assert_editor_state(indoc! {
|
||||
r#"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"#
|
||||
});
|
||||
|
||||
leader_cx.update_editor(|editor, _window, cx| {
|
||||
editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
leader_cx.assert_editor_state(indoc! {
|
||||
r#"
|
||||
ˇone
|
||||
two
|
||||
THREE
|
||||
four
|
||||
FIVE
|
||||
six
|
||||
SEVEN"#
|
||||
});
|
||||
|
||||
follower_cx.assert_editor_state(indoc! {
|
||||
r#"
|
||||
ˇone
|
||||
two
|
||||
three
|
||||
four
|
||||
five
|
||||
six
|
||||
"#
|
||||
});
|
||||
|
||||
leader_cx.update_editor(|editor, window, cx| {
|
||||
editor.move_down(&MoveDown, window, cx);
|
||||
editor.refresh_selected_text_highlights(true, window, cx);
|
||||
});
|
||||
leader_cx.run_until_parked();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let base_text = "base\n";
|
||||
let buffer_text = "buffer\n";
|
||||
|
||||
let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
|
||||
let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
|
||||
|
||||
let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
|
||||
let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
|
||||
let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
|
||||
let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
|
||||
|
||||
let leader = cx.new(|cx| {
|
||||
let mut leader = MultiBuffer::new(Capability::ReadWrite);
|
||||
leader.set_all_diff_hunks_expanded(cx);
|
||||
leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
|
||||
leader
|
||||
});
|
||||
let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
|
||||
follower.update(cx, |follower, _| {
|
||||
follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
|
||||
});
|
||||
|
||||
leader.update(cx, |leader, cx| {
|
||||
leader.insert_excerpts_after(
|
||||
ExcerptId::min(),
|
||||
extra_buffer_2.clone(),
|
||||
vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
|
||||
cx,
|
||||
);
|
||||
leader.add_diff(extra_diff_2.clone(), cx);
|
||||
|
||||
leader.insert_excerpts_after(
|
||||
ExcerptId::min(),
|
||||
extra_buffer_1.clone(),
|
||||
vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
|
||||
cx,
|
||||
);
|
||||
leader.add_diff(extra_diff_1.clone(), cx);
|
||||
|
||||
leader.insert_excerpts_after(
|
||||
ExcerptId::min(),
|
||||
buffer1.clone(),
|
||||
vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
|
||||
cx,
|
||||
);
|
||||
leader.add_diff(diff1.clone(), cx);
|
||||
});
|
||||
|
||||
cx.run_until_parked();
|
||||
let mut cx = cx.add_empty_window();
|
||||
|
||||
let leader_editor = cx
|
||||
.new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
|
||||
let follower_editor = cx.new_window_entity(|window, cx| {
|
||||
Editor::for_multibuffer(follower.clone(), None, window, cx)
|
||||
});
|
||||
|
||||
let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
|
||||
leader_cx.assert_editor_state(indoc! {"
|
||||
ˇbuffer
|
||||
|
||||
dummy text 1
|
||||
|
||||
dummy text 2
|
||||
"});
|
||||
let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
|
||||
follower_cx.assert_editor_state(indoc! {"
|
||||
ˇbase
|
||||
|
||||
|
||||
"});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
@@ -3264,17 +3264,15 @@ impl EditorElement {
|
||||
line_number.clear();
|
||||
let non_relative_number = if relative.wrapped() {
|
||||
row_info.buffer_row.or(row_info.wrapped_buffer_row)? + 1
|
||||
} else if self.editor.read(cx).use_base_text_line_numbers {
|
||||
row_info.base_text_row?.0 + 1
|
||||
} else {
|
||||
row_info.buffer_row? + 1
|
||||
};
|
||||
let relative_number = relative_rows.get(&display_row);
|
||||
if !(relative_line_numbers_enabled && relative_number.is_some())
|
||||
&& !snapshot.number_deleted_lines
|
||||
&& row_info
|
||||
.diff_status
|
||||
.is_some_and(|status| status.is_deleted())
|
||||
&& !self.editor.read(cx).use_base_text_line_numbers
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
use std::ops::Range;
|
||||
|
||||
use buffer_diff::BufferDiff;
|
||||
use collections::HashMap;
|
||||
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
|
||||
use gpui::{
|
||||
Action, AppContext as _, Entity, EventEmitter, Focusable, NoAction, Subscription, WeakEntity,
|
||||
};
|
||||
use multi_buffer::{MultiBuffer, MultiBufferFilterMode};
|
||||
use language::{Buffer, Capability};
|
||||
use multi_buffer::{Anchor, ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer, PathKey};
|
||||
use project::Project;
|
||||
use rope::Point;
|
||||
use text::{Bias, OffsetRangeExt as _};
|
||||
use ui::{
|
||||
App, Context, InteractiveElement as _, IntoElement as _, ParentElement as _, Render,
|
||||
Styled as _, Window, div,
|
||||
@@ -33,6 +40,7 @@ struct SplitDiff;
|
||||
struct UnsplitDiff;
|
||||
|
||||
pub struct SplittableEditor {
|
||||
primary_multibuffer: Entity<MultiBuffer>,
|
||||
primary_editor: Entity<Editor>,
|
||||
secondary: Option<SecondaryEditor>,
|
||||
panes: PaneGroup,
|
||||
@@ -41,9 +49,12 @@ pub struct SplittableEditor {
|
||||
}
|
||||
|
||||
struct SecondaryEditor {
|
||||
multibuffer: Entity<MultiBuffer>,
|
||||
editor: Entity<Editor>,
|
||||
pane: Entity<Pane>,
|
||||
has_latest_selection: bool,
|
||||
primary_to_secondary: HashMap<ExcerptId, ExcerptId>,
|
||||
secondary_to_primary: HashMap<ExcerptId, ExcerptId>,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
}
|
||||
|
||||
@@ -63,14 +74,22 @@ impl SplittableEditor {
|
||||
}
|
||||
|
||||
pub fn new_unsplit(
|
||||
buffer: Entity<MultiBuffer>,
|
||||
primary_multibuffer: Entity<MultiBuffer>,
|
||||
project: Entity<Project>,
|
||||
workspace: Entity<Workspace>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
let primary_editor =
|
||||
cx.new(|cx| Editor::for_multibuffer(buffer, Some(project.clone()), window, cx));
|
||||
let primary_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(
|
||||
primary_multibuffer.clone(),
|
||||
Some(project.clone()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.set_expand_all_diff_hunks(cx);
|
||||
editor
|
||||
});
|
||||
let pane = cx.new(|cx| {
|
||||
let mut pane = Pane::new(
|
||||
workspace.downgrade(),
|
||||
@@ -88,17 +107,25 @@ impl SplittableEditor {
|
||||
});
|
||||
let panes = PaneGroup::new(pane);
|
||||
// TODO(split-diff) we might want to tag editor events with whether they came from primary/secondary
|
||||
let subscriptions =
|
||||
vec![
|
||||
cx.subscribe(&primary_editor, |this, _, event: &EditorEvent, cx| {
|
||||
if let EditorEvent::SelectionsChanged { .. } = event
|
||||
&& let Some(secondary) = &mut this.secondary
|
||||
{
|
||||
let subscriptions = vec![cx.subscribe(
|
||||
&primary_editor,
|
||||
|this, _, event: &EditorEvent, cx| match event {
|
||||
EditorEvent::ExpandExcerptsRequested {
|
||||
excerpt_ids,
|
||||
lines,
|
||||
direction,
|
||||
} => {
|
||||
this.expand_excerpts(excerpt_ids.iter().copied(), *lines, *direction, cx);
|
||||
}
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
if let Some(secondary) = &mut this.secondary {
|
||||
secondary.has_latest_selection = false;
|
||||
}
|
||||
cx.emit(event.clone())
|
||||
}),
|
||||
];
|
||||
cx.emit(event.clone());
|
||||
}
|
||||
_ => cx.emit(event.clone()),
|
||||
},
|
||||
)];
|
||||
|
||||
window.defer(cx, {
|
||||
let workspace = workspace.downgrade();
|
||||
@@ -115,6 +142,7 @@ impl SplittableEditor {
|
||||
});
|
||||
Self {
|
||||
primary_editor,
|
||||
primary_multibuffer,
|
||||
secondary: None,
|
||||
panes,
|
||||
workspace: workspace.downgrade(),
|
||||
@@ -133,24 +161,22 @@ impl SplittableEditor {
|
||||
return;
|
||||
};
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let follower = self.primary_editor.update(cx, |primary, cx| {
|
||||
primary.buffer().update(cx, |buffer, cx| {
|
||||
let follower = buffer.get_or_create_follower(cx);
|
||||
buffer.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
|
||||
follower
|
||||
})
|
||||
|
||||
let secondary_multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(Capability::ReadOnly);
|
||||
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||
multibuffer
|
||||
});
|
||||
follower.update(cx, |follower, _| {
|
||||
follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
|
||||
});
|
||||
let secondary_editor = workspace.update(cx, |workspace, cx| {
|
||||
cx.new(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(follower, Some(project), window, cx);
|
||||
// TODO(split-diff) this should be at the multibuffer level
|
||||
editor.set_use_base_text_line_numbers(true, cx);
|
||||
editor.added_to_workspace(workspace, window, cx);
|
||||
editor
|
||||
})
|
||||
let secondary_editor = cx.new(|cx| {
|
||||
let mut editor = Editor::for_multibuffer(
|
||||
secondary_multibuffer.clone(),
|
||||
Some(project.clone()),
|
||||
window,
|
||||
cx,
|
||||
);
|
||||
editor.number_deleted_lines = true;
|
||||
editor.set_delegate_expand_excerpts(true);
|
||||
editor
|
||||
});
|
||||
let secondary_pane = cx.new(|cx| {
|
||||
let mut pane = Pane::new(
|
||||
@@ -175,23 +201,59 @@ impl SplittableEditor {
|
||||
pane
|
||||
});
|
||||
|
||||
let subscriptions =
|
||||
vec![
|
||||
cx.subscribe(&secondary_editor, |this, _, event: &EditorEvent, cx| {
|
||||
if let EditorEvent::SelectionsChanged { .. } = event
|
||||
&& let Some(secondary) = &mut this.secondary
|
||||
{
|
||||
let subscriptions = vec![cx.subscribe(
|
||||
&secondary_editor,
|
||||
|this, _, event: &EditorEvent, cx| match event {
|
||||
EditorEvent::ExpandExcerptsRequested {
|
||||
excerpt_ids,
|
||||
lines,
|
||||
direction,
|
||||
} => {
|
||||
if let Some(secondary) = &this.secondary {
|
||||
let primary_ids: Vec<_> = excerpt_ids
|
||||
.iter()
|
||||
.filter_map(|id| secondary.secondary_to_primary.get(id).copied())
|
||||
.collect();
|
||||
this.expand_excerpts(primary_ids.into_iter(), *lines, *direction, cx);
|
||||
}
|
||||
}
|
||||
EditorEvent::SelectionsChanged { .. } => {
|
||||
if let Some(secondary) = &mut this.secondary {
|
||||
secondary.has_latest_selection = true;
|
||||
}
|
||||
cx.emit(event.clone())
|
||||
}),
|
||||
];
|
||||
self.secondary = Some(SecondaryEditor {
|
||||
cx.emit(event.clone());
|
||||
}
|
||||
_ => cx.emit(event.clone()),
|
||||
},
|
||||
)];
|
||||
let mut secondary = SecondaryEditor {
|
||||
editor: secondary_editor,
|
||||
multibuffer: secondary_multibuffer,
|
||||
pane: secondary_pane.clone(),
|
||||
has_latest_selection: false,
|
||||
primary_to_secondary: HashMap::default(),
|
||||
secondary_to_primary: HashMap::default(),
|
||||
_subscriptions: subscriptions,
|
||||
};
|
||||
self.primary_editor.update(cx, |editor, cx| {
|
||||
editor.set_delegate_expand_excerpts(true);
|
||||
editor.buffer().update(cx, |primary_multibuffer, cx| {
|
||||
primary_multibuffer.set_show_deleted_hunks(false, cx);
|
||||
let paths = primary_multibuffer.paths().cloned().collect::<Vec<_>>();
|
||||
for path in paths {
|
||||
let Some(excerpt_id) = primary_multibuffer.excerpts_for_path(&path).next()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let snapshot = primary_multibuffer.snapshot(cx);
|
||||
let buffer = snapshot.buffer_for_excerpt(excerpt_id).unwrap();
|
||||
let diff = primary_multibuffer.diff_for(buffer.remote_id()).unwrap();
|
||||
secondary.sync_path_excerpts(path.clone(), primary_multibuffer, diff, cx);
|
||||
}
|
||||
})
|
||||
});
|
||||
self.secondary = Some(secondary);
|
||||
|
||||
let primary_pane = self.panes.first_pane();
|
||||
self.panes
|
||||
.split(&primary_pane, &secondary_pane, SplitDirection::Left, cx)
|
||||
@@ -205,8 +267,9 @@ impl SplittableEditor {
|
||||
};
|
||||
self.panes.remove(&secondary.pane, cx).unwrap();
|
||||
self.primary_editor.update(cx, |primary, cx| {
|
||||
primary.buffer().update(cx, |buffer, _| {
|
||||
buffer.set_filter_mode(None);
|
||||
primary.set_delegate_expand_excerpts(false);
|
||||
primary.buffer().update(cx, |buffer, cx| {
|
||||
buffer.set_show_deleted_hunks(true, cx);
|
||||
});
|
||||
});
|
||||
cx.notify();
|
||||
@@ -228,6 +291,299 @@ impl SplittableEditor {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_excerpts_for_path(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
ranges: impl IntoIterator<Item = Range<Point>> + Clone,
|
||||
context_line_count: u32,
|
||||
diff: Entity<BufferDiff>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> (Vec<Range<Anchor>>, bool) {
|
||||
self.primary_multibuffer
|
||||
.update(cx, |primary_multibuffer, cx| {
|
||||
let (anchors, added_a_new_excerpt) = primary_multibuffer.set_excerpts_for_path(
|
||||
path.clone(),
|
||||
buffer,
|
||||
ranges,
|
||||
context_line_count,
|
||||
cx,
|
||||
);
|
||||
primary_multibuffer.add_diff(diff.clone(), cx);
|
||||
if let Some(secondary) = &mut self.secondary {
|
||||
secondary.sync_path_excerpts(path, primary_multibuffer, diff, cx);
|
||||
}
|
||||
(anchors, added_a_new_excerpt)
|
||||
})
|
||||
}
|
||||
|
||||
fn expand_excerpts(
|
||||
&mut self,
|
||||
excerpt_ids: impl Iterator<Item = ExcerptId> + Clone,
|
||||
lines: u32,
|
||||
direction: ExpandExcerptDirection,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let mut corresponding_paths = HashMap::default();
|
||||
self.primary_multibuffer.update(cx, |multibuffer, cx| {
|
||||
let snapshot = multibuffer.snapshot(cx);
|
||||
if self.secondary.is_some() {
|
||||
corresponding_paths = excerpt_ids
|
||||
.clone()
|
||||
.map(|excerpt_id| {
|
||||
let path = multibuffer.path_for_excerpt(excerpt_id).unwrap();
|
||||
let buffer = snapshot.buffer_for_excerpt(excerpt_id).unwrap();
|
||||
let diff = multibuffer.diff_for(buffer.remote_id()).unwrap();
|
||||
(path, diff)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
}
|
||||
multibuffer.expand_excerpts(excerpt_ids.clone(), lines, direction, cx);
|
||||
});
|
||||
|
||||
if let Some(secondary) = &mut self.secondary {
|
||||
self.primary_multibuffer.update(cx, |multibuffer, cx| {
|
||||
for (path, diff) in corresponding_paths {
|
||||
secondary.sync_path_excerpts(path, multibuffer, diff, cx);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
|
||||
self.primary_multibuffer.update(cx, |buffer, cx| {
|
||||
buffer.remove_excerpts_for_path(path.clone(), cx)
|
||||
});
|
||||
if let Some(secondary) = &mut self.secondary {
|
||||
secondary.remove_mappings_for_path(&path, cx);
|
||||
secondary
|
||||
.multibuffer
|
||||
.update(cx, |buffer, cx| buffer.remove_excerpts_for_path(path, cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl SplittableEditor {
|
||||
fn check_invariants(&self, quiesced: bool, cx: &App) {
|
||||
use buffer_diff::DiffHunkStatusKind;
|
||||
use collections::HashSet;
|
||||
use multi_buffer::MultiBufferOffset;
|
||||
use multi_buffer::MultiBufferRow;
|
||||
use multi_buffer::MultiBufferSnapshot;
|
||||
|
||||
fn format_diff(snapshot: &MultiBufferSnapshot) -> String {
|
||||
let text = snapshot.text();
|
||||
let row_infos = snapshot.row_infos(MultiBufferRow(0)).collect::<Vec<_>>();
|
||||
let boundary_rows = snapshot
|
||||
.excerpt_boundaries_in_range(MultiBufferOffset(0)..)
|
||||
.map(|b| b.row)
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
text.split('\n')
|
||||
.enumerate()
|
||||
.zip(row_infos)
|
||||
.map(|((ix, line), info)| {
|
||||
let marker = match info.diff_status.map(|status| status.kind) {
|
||||
Some(DiffHunkStatusKind::Added) => "+ ",
|
||||
Some(DiffHunkStatusKind::Deleted) => "- ",
|
||||
Some(DiffHunkStatusKind::Modified) => unreachable!(),
|
||||
None => {
|
||||
if !line.is_empty() {
|
||||
" "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
};
|
||||
let boundary_row = if boundary_rows.contains(&MultiBufferRow(ix as u32)) {
|
||||
" ----------\n"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let expand = info
|
||||
.expand_info
|
||||
.map(|expand_info| match expand_info.direction {
|
||||
ExpandExcerptDirection::Up => " [↑]",
|
||||
ExpandExcerptDirection::Down => " [↓]",
|
||||
ExpandExcerptDirection::UpAndDown => " [↕]",
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
format!("{boundary_row}{marker}{line}{expand}")
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
let Some(secondary) = &self.secondary else {
|
||||
return;
|
||||
};
|
||||
|
||||
log::info!(
|
||||
"primary:\n\n{}",
|
||||
format_diff(&self.primary_multibuffer.read(cx).snapshot(cx))
|
||||
);
|
||||
|
||||
log::info!(
|
||||
"secondary:\n\n{}",
|
||||
format_diff(&secondary.multibuffer.read(cx).snapshot(cx))
|
||||
);
|
||||
|
||||
let primary_excerpts = self.primary_multibuffer.read(cx).excerpt_ids();
|
||||
let secondary_excerpts = secondary.multibuffer.read(cx).excerpt_ids();
|
||||
assert_eq!(primary_excerpts.len(), secondary_excerpts.len());
|
||||
|
||||
assert_eq!(
|
||||
secondary.primary_to_secondary.len(),
|
||||
primary_excerpts.len(),
|
||||
"primary_to_secondary mapping count should match excerpt count"
|
||||
);
|
||||
assert_eq!(
|
||||
secondary.secondary_to_primary.len(),
|
||||
secondary_excerpts.len(),
|
||||
"secondary_to_primary mapping count should match excerpt count"
|
||||
);
|
||||
|
||||
for primary_id in &primary_excerpts {
|
||||
assert!(
|
||||
secondary.primary_to_secondary.contains_key(primary_id),
|
||||
"primary excerpt {:?} should have a mapping to secondary",
|
||||
primary_id
|
||||
);
|
||||
}
|
||||
for secondary_id in &secondary_excerpts {
|
||||
assert!(
|
||||
secondary.secondary_to_primary.contains_key(secondary_id),
|
||||
"secondary excerpt {:?} should have a mapping to primary",
|
||||
secondary_id
|
||||
);
|
||||
}
|
||||
|
||||
for (primary_id, secondary_id) in &secondary.primary_to_secondary {
|
||||
assert_eq!(
|
||||
secondary.secondary_to_primary.get(secondary_id),
|
||||
Some(primary_id),
|
||||
"mappings should be bijective"
|
||||
);
|
||||
}
|
||||
|
||||
if quiesced {
|
||||
let primary_snapshot = self.primary_multibuffer.read(cx).snapshot(cx);
|
||||
let secondary_snapshot = secondary.multibuffer.read(cx).snapshot(cx);
|
||||
let primary_diff_hunks = primary_snapshot
|
||||
.diff_hunks()
|
||||
.map(|hunk| hunk.diff_base_byte_range)
|
||||
.collect::<Vec<_>>();
|
||||
let secondary_diff_hunks = secondary_snapshot
|
||||
.diff_hunks()
|
||||
.map(|hunk| hunk.diff_base_byte_range)
|
||||
.collect::<Vec<_>>();
|
||||
pretty_assertions::assert_eq!(primary_diff_hunks, secondary_diff_hunks);
|
||||
|
||||
// Filtering out empty lines is a bit of a hack, to work around a case where
|
||||
// the base text has a trailing newline but the current text doesn't, or vice versa.
|
||||
// In this case, we get the additional newline on one side, but that line is not
|
||||
// marked as added/deleted by rowinfos.
|
||||
let primary_unmodified_rows = primary_snapshot
|
||||
.text()
|
||||
.split("\n")
|
||||
.zip(primary_snapshot.row_infos(MultiBufferRow(0)))
|
||||
.filter(|(line, row_info)| !line.is_empty() && row_info.diff_status.is_none())
|
||||
.map(|(line, _)| line.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let secondary_unmodified_rows = secondary_snapshot
|
||||
.text()
|
||||
.split("\n")
|
||||
.zip(secondary_snapshot.row_infos(MultiBufferRow(0)))
|
||||
.filter(|(line, row_info)| !line.is_empty() && row_info.diff_status.is_none())
|
||||
.map(|(line, _)| line.to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
pretty_assertions::assert_eq!(primary_unmodified_rows, secondary_unmodified_rows);
|
||||
}
|
||||
}
|
||||
|
||||
fn randomly_edit_excerpts(
|
||||
&mut self,
|
||||
rng: &mut impl rand::Rng,
|
||||
mutation_count: usize,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
use collections::HashSet;
|
||||
use rand::prelude::*;
|
||||
use std::env;
|
||||
use util::RandomCharIter;
|
||||
|
||||
let max_excerpts = env::var("MAX_EXCERPTS")
|
||||
.map(|i| i.parse().expect("invalid `MAX_EXCERPTS` variable"))
|
||||
.unwrap_or(5);
|
||||
|
||||
for _ in 0..mutation_count {
|
||||
let paths = self
|
||||
.primary_multibuffer
|
||||
.read(cx)
|
||||
.paths()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
let excerpt_ids = self.primary_multibuffer.read(cx).excerpt_ids();
|
||||
|
||||
if rng.random_bool(0.1) && !excerpt_ids.is_empty() {
|
||||
let mut excerpts = HashSet::default();
|
||||
for _ in 0..rng.random_range(0..excerpt_ids.len()) {
|
||||
excerpts.extend(excerpt_ids.choose(rng).copied());
|
||||
}
|
||||
|
||||
let line_count = rng.random_range(0..5);
|
||||
|
||||
log::info!("Expanding excerpts {excerpts:?} by {line_count} lines");
|
||||
|
||||
self.expand_excerpts(
|
||||
excerpts.iter().cloned(),
|
||||
line_count,
|
||||
ExpandExcerptDirection::UpAndDown,
|
||||
cx,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if excerpt_ids.is_empty() || (rng.random() && excerpt_ids.len() < max_excerpts) {
|
||||
let len = rng.random_range(100..500);
|
||||
let text = RandomCharIter::new(&mut *rng).take(len).collect::<String>();
|
||||
let buffer = cx.new(|cx| Buffer::local(text, cx));
|
||||
log::info!(
|
||||
"Creating new buffer {} with text: {:?}",
|
||||
buffer.read(cx).remote_id(),
|
||||
buffer.read(cx).text()
|
||||
);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
let diff = cx.new(|cx| BufferDiff::new_unchanged(&buffer_snapshot, cx));
|
||||
// Create some initial diff hunks.
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.randomly_edit(rng, 1, cx);
|
||||
});
|
||||
let buffer_snapshot = buffer.read(cx).text_snapshot();
|
||||
let ranges = diff.update(cx, |diff, cx| {
|
||||
diff.recalculate_diff_sync(&buffer_snapshot, cx);
|
||||
diff.snapshot(cx)
|
||||
.hunks(&buffer_snapshot)
|
||||
.map(|hunk| hunk.buffer_range.to_point(&buffer_snapshot))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
let path = PathKey::for_buffer(&buffer, cx);
|
||||
self.set_excerpts_for_path(path, buffer, ranges, 2, diff, cx);
|
||||
} else {
|
||||
let remove_count = rng.random_range(1..=paths.len());
|
||||
let paths_to_remove = paths
|
||||
.choose_multiple(rng, remove_count)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
for path in paths_to_remove {
|
||||
self.remove_excerpts_for_path(path.clone(), cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for SplittableEditor {}
|
||||
@@ -265,3 +621,223 @@ impl Render for SplittableEditor {
|
||||
.child(inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl SecondaryEditor {
|
||||
fn sync_path_excerpts(
|
||||
&mut self,
|
||||
path_key: PathKey,
|
||||
primary_multibuffer: &mut MultiBuffer,
|
||||
diff: Entity<BufferDiff>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let Some(excerpt_id) = primary_multibuffer.excerpts_for_path(&path_key).next() else {
|
||||
self.remove_mappings_for_path(&path_key, cx);
|
||||
self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
multibuffer.remove_excerpts_for_path(path_key, cx);
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
let primary_excerpt_ids: Vec<ExcerptId> =
|
||||
primary_multibuffer.excerpts_for_path(&path_key).collect();
|
||||
|
||||
let primary_multibuffer_snapshot = primary_multibuffer.snapshot(cx);
|
||||
let main_buffer = primary_multibuffer_snapshot
|
||||
.buffer_for_excerpt(excerpt_id)
|
||||
.unwrap();
|
||||
let base_text_buffer = diff.read(cx).base_text_buffer();
|
||||
let diff_snapshot = diff.read(cx).snapshot(cx);
|
||||
let base_text_buffer_snapshot = base_text_buffer.read(cx).snapshot();
|
||||
let new = primary_multibuffer
|
||||
.excerpts_for_buffer(main_buffer.remote_id(), cx)
|
||||
.into_iter()
|
||||
.map(|(_, excerpt_range)| {
|
||||
let point_range_to_base_text_point_range = |range: Range<Point>| {
|
||||
let start_row = diff_snapshot.row_to_base_text_row(
|
||||
range.start.row,
|
||||
Bias::Left,
|
||||
main_buffer,
|
||||
);
|
||||
let end_row =
|
||||
diff_snapshot.row_to_base_text_row(range.end.row, Bias::Right, main_buffer);
|
||||
let end_column = diff_snapshot.base_text().line_len(end_row);
|
||||
Point::new(start_row, 0)..Point::new(end_row, end_column)
|
||||
};
|
||||
let primary = excerpt_range.primary.to_point(main_buffer);
|
||||
let context = excerpt_range.context.to_point(main_buffer);
|
||||
ExcerptRange {
|
||||
primary: point_range_to_base_text_point_range(primary),
|
||||
context: point_range_to_base_text_point_range(context),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let main_buffer = primary_multibuffer.buffer(main_buffer.remote_id()).unwrap();
|
||||
|
||||
self.remove_mappings_for_path(&path_key, cx);
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.buffer().update(cx, |buffer, cx| {
|
||||
buffer.update_path_excerpts(
|
||||
path_key.clone(),
|
||||
base_text_buffer,
|
||||
&base_text_buffer_snapshot,
|
||||
new,
|
||||
cx,
|
||||
);
|
||||
buffer.add_inverted_diff(diff, main_buffer, cx);
|
||||
})
|
||||
});
|
||||
|
||||
let secondary_excerpt_ids: Vec<ExcerptId> = self
|
||||
.multibuffer
|
||||
.read(cx)
|
||||
.excerpts_for_path(&path_key)
|
||||
.collect();
|
||||
|
||||
for (primary_id, secondary_id) in primary_excerpt_ids.into_iter().zip(secondary_excerpt_ids)
|
||||
{
|
||||
self.primary_to_secondary.insert(primary_id, secondary_id);
|
||||
self.secondary_to_primary.insert(secondary_id, primary_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_mappings_for_path(&mut self, path_key: &PathKey, cx: &App) {
|
||||
let secondary_excerpt_ids: Vec<ExcerptId> = self
|
||||
.multibuffer
|
||||
.read(cx)
|
||||
.excerpts_for_path(path_key)
|
||||
.collect();
|
||||
|
||||
for secondary_id in secondary_excerpt_ids {
|
||||
if let Some(primary_id) = self.secondary_to_primary.remove(&secondary_id) {
|
||||
self.primary_to_secondary.remove(&primary_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use fs::FakeFs;
|
||||
use gpui::AppContext as _;
|
||||
use language::Capability;
|
||||
use multi_buffer::{MultiBuffer, PathKey};
|
||||
use project::Project;
|
||||
use rand::rngs::StdRng;
|
||||
use settings::SettingsStore;
|
||||
use ui::VisualContext as _;
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::SplittableEditor;
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
let store = SettingsStore::test(cx);
|
||||
cx.set_global(store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
crate::init(cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 100)]
|
||||
async fn test_random_split_editor(mut rng: StdRng, cx: &mut gpui::TestAppContext) {
|
||||
use rand::prelude::*;
|
||||
|
||||
init_test(cx);
|
||||
let project = Project::test(FakeFs::new(cx.executor()), [], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let primary_multibuffer = cx.new(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
|
||||
multibuffer.set_all_diff_hunks_expanded(cx);
|
||||
multibuffer
|
||||
});
|
||||
let editor = cx.new_window_entity(|window, cx| {
|
||||
let mut editor =
|
||||
SplittableEditor::new_unsplit(primary_multibuffer, project, workspace, window, cx);
|
||||
editor.split(&Default::default(), window, cx);
|
||||
editor
|
||||
});
|
||||
|
||||
let operations = std::env::var("OPERATIONS")
|
||||
.map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
|
||||
.unwrap_or(20);
|
||||
let rng = &mut rng;
|
||||
for _ in 0..operations {
|
||||
editor.update(cx, |editor, cx| {
|
||||
let buffers = editor
|
||||
.primary_editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.all_buffers();
|
||||
|
||||
if buffers.is_empty() {
|
||||
editor.randomly_edit_excerpts(rng, 2, cx);
|
||||
editor.check_invariants(true, cx);
|
||||
return;
|
||||
}
|
||||
|
||||
let quiesced = match rng.random_range(0..100) {
|
||||
0..=69 if !buffers.is_empty() => {
|
||||
let buffer = buffers.iter().choose(rng).unwrap();
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
if rng.random() {
|
||||
log::info!("randomly editing single buffer");
|
||||
buffer.randomly_edit(rng, 5, cx);
|
||||
} else {
|
||||
log::info!("randomly undoing/redoing in single buffer");
|
||||
buffer.randomly_undo_redo(rng, cx);
|
||||
}
|
||||
});
|
||||
false
|
||||
}
|
||||
70..=79 => {
|
||||
log::info!("mutating excerpts");
|
||||
editor.randomly_edit_excerpts(rng, 2, cx);
|
||||
false
|
||||
}
|
||||
80..=89 if !buffers.is_empty() => {
|
||||
log::info!("recalculating buffer diff");
|
||||
let buffer = buffers.iter().choose(rng).unwrap();
|
||||
let diff = editor
|
||||
.primary_multibuffer
|
||||
.read(cx)
|
||||
.diff_for(buffer.read(cx).remote_id())
|
||||
.unwrap();
|
||||
let buffer_snapshot = buffer.read(cx).text_snapshot();
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.recalculate_diff_sync(&buffer_snapshot, cx);
|
||||
});
|
||||
false
|
||||
}
|
||||
_ => {
|
||||
log::info!("quiescing");
|
||||
for buffer in buffers {
|
||||
let buffer_snapshot = buffer.read(cx).text_snapshot();
|
||||
let diff = editor
|
||||
.primary_multibuffer
|
||||
.read(cx)
|
||||
.diff_for(buffer.read(cx).remote_id())
|
||||
.unwrap();
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.recalculate_diff_sync(&buffer_snapshot, cx);
|
||||
});
|
||||
let diff_snapshot = diff.read(cx).snapshot(cx);
|
||||
let ranges = diff_snapshot
|
||||
.hunks(&buffer_snapshot)
|
||||
.map(|hunk| hunk.range)
|
||||
.collect::<Vec<_>>();
|
||||
let path = PathKey::for_buffer(&buffer, cx);
|
||||
editor.set_excerpts_for_path(path, buffer, ranges, 2, diff, cx);
|
||||
}
|
||||
true
|
||||
}
|
||||
};
|
||||
|
||||
editor.check_invariants(quiesced, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,11 +365,12 @@ impl ExampleContext {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
|
||||
let file = snapshot.file().unwrap();
|
||||
let diff = diff.read(cx);
|
||||
let base_text = diff.base_text().text();
|
||||
let base_text = diff.read(cx).base_text(cx).text();
|
||||
|
||||
let hunks = diff
|
||||
.hunks(&snapshot, cx)
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.hunks(&snapshot)
|
||||
.map(|hunk| FileEditHunk {
|
||||
base_text: base_text[hunk.diff_base_byte_range.clone()].to_string(),
|
||||
text: snapshot
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::{Context as _, Result};
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use buffer_diff::BufferDiff;
|
||||
use editor::display_map::{BlockPlacement, BlockProperties, BlockStyle};
|
||||
use editor::{Editor, EditorEvent, ExcerptRange, MultiBuffer, multibuffer_context_lines};
|
||||
use git::repository::{CommitDetails, CommitDiff, RepoPath};
|
||||
@@ -262,7 +262,8 @@ impl CommitView {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let path = snapshot.file().unwrap().path().clone();
|
||||
let excerpt_ranges = {
|
||||
let mut hunks = buffer_diff.read(cx).hunks(&snapshot, cx).peekable();
|
||||
let diff_snapshot = buffer_diff.read(cx).snapshot(cx);
|
||||
let mut hunks = diff_snapshot.hunks(&snapshot).peekable();
|
||||
if hunks.peek().is_none() {
|
||||
vec![language::Point::zero()..snapshot.max_point()]
|
||||
} else {
|
||||
@@ -785,35 +786,30 @@ async fn build_buffer_diff(
|
||||
LineEnding::normalize(old_text);
|
||||
}
|
||||
|
||||
let language = cx.update(|cx| buffer.read(cx).language().cloned())?;
|
||||
let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
|
||||
|
||||
let base_buffer = cx
|
||||
.update(|cx| {
|
||||
Buffer::build_snapshot(
|
||||
old_text.as_deref().unwrap_or("").into(),
|
||||
buffer.language().cloned(),
|
||||
Some(language_registry.clone()),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
let diff = cx.new(|cx| BufferDiff::new(&buffer.text, cx))?;
|
||||
|
||||
let diff_snapshot = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
let update = diff
|
||||
.update(cx, |diff, cx| {
|
||||
diff.update_diff(
|
||||
buffer.text.clone(),
|
||||
old_text.map(Arc::new),
|
||||
base_buffer,
|
||||
old_text.map(|old_text| Arc::from(old_text.as_str())),
|
||||
true,
|
||||
language.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&buffer.text, cx);
|
||||
diff.set_snapshot(diff_snapshot, &buffer.text, cx);
|
||||
diff
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.language_changed(language, Some(language_registry.clone()), cx);
|
||||
diff.set_snapshot(update, &buffer.text, cx)
|
||||
})
|
||||
.ok();
|
||||
|
||||
Ok(diff)
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for CommitView {}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
//! FileDiffView provides a UI for displaying differences between two buffers.
|
||||
|
||||
use anyhow::Result;
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use buffer_diff::BufferDiff;
|
||||
use editor::{Editor, EditorEvent, MultiBuffer};
|
||||
use futures::{FutureExt, select_biased};
|
||||
use gpui::{
|
||||
AnyElement, App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, FocusHandle,
|
||||
Focusable, IntoElement, Render, Task, Window,
|
||||
};
|
||||
use language::Buffer;
|
||||
use language::{Buffer, LanguageRegistry};
|
||||
use project::Project;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
@@ -52,8 +52,9 @@ impl FileDiffView {
|
||||
let new_buffer = project
|
||||
.update(cx, |project, cx| project.open_local_buffer(&new_path, cx))?
|
||||
.await?;
|
||||
let languages = project.update(cx, |project, _| project.languages().clone())?;
|
||||
|
||||
let buffer_diff = build_buffer_diff(&old_buffer, &new_buffer, cx).await?;
|
||||
let buffer_diff = build_buffer_diff(&old_buffer, &new_buffer, languages, cx).await?;
|
||||
|
||||
workspace.update_in(cx, |workspace, window, cx| {
|
||||
let diff_view = cx.new(|cx| {
|
||||
@@ -143,19 +144,16 @@ impl FileDiffView {
|
||||
this.new_buffer.read(cx).snapshot(),
|
||||
)
|
||||
})?;
|
||||
let diff_snapshot = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
new_snapshot.text.clone(),
|
||||
Some(old_snapshot.text().into()),
|
||||
old_snapshot,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.set_snapshot(diff_snapshot, &new_snapshot, cx)
|
||||
})?;
|
||||
diff.set_base_text(
|
||||
Some(old_snapshot.text().as_str().into()),
|
||||
old_snapshot.language().cloned(),
|
||||
new_snapshot.text.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await
|
||||
.ok();
|
||||
log::trace!("finish recalculating");
|
||||
}
|
||||
Ok(())
|
||||
@@ -167,27 +165,36 @@ impl FileDiffView {
|
||||
async fn build_buffer_diff(
|
||||
old_buffer: &Entity<Buffer>,
|
||||
new_buffer: &Entity<Buffer>,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<Entity<BufferDiff>> {
|
||||
let old_buffer_snapshot = old_buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
let new_buffer_snapshot = new_buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
|
||||
let diff_snapshot = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
let diff = cx.new(|cx| BufferDiff::new(&new_buffer_snapshot.text, cx))?;
|
||||
|
||||
let update = diff
|
||||
.update(cx, |diff, cx| {
|
||||
diff.update_diff(
|
||||
new_buffer_snapshot.text.clone(),
|
||||
Some(old_buffer_snapshot.text().into()),
|
||||
old_buffer_snapshot,
|
||||
true,
|
||||
new_buffer_snapshot.language().cloned(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
cx.new(|cx| {
|
||||
let mut diff = BufferDiff::new(&new_buffer_snapshot.text, cx);
|
||||
diff.set_snapshot(diff_snapshot, &new_buffer_snapshot.text, cx);
|
||||
diff
|
||||
})
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.language_changed(
|
||||
new_buffer_snapshot.language().cloned(),
|
||||
Some(language_registry),
|
||||
cx,
|
||||
);
|
||||
diff.set_snapshot(update, &new_buffer_snapshot.text, cx);
|
||||
})?;
|
||||
|
||||
Ok(diff)
|
||||
}
|
||||
|
||||
impl EventEmitter<EditorEvent> for FileDiffView {}
|
||||
|
||||
@@ -423,7 +423,7 @@ impl ProjectDiff {
|
||||
let mut has_staged_hunks = false;
|
||||
let mut has_unstaged_hunks = false;
|
||||
for hunk in editor.diff_hunks_in_ranges(&ranges, &snapshot) {
|
||||
match hunk.secondary_status {
|
||||
match hunk.status.secondary {
|
||||
DiffHunkSecondaryStatus::HasSecondaryHunk
|
||||
| DiffHunkSecondaryStatus::SecondaryHunkAdditionPending => {
|
||||
has_unstaged_hunks = true;
|
||||
@@ -525,14 +525,13 @@ impl ProjectDiff {
|
||||
.expect("project diff editor should have a conflict addon");
|
||||
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let diff_read = diff.read(cx);
|
||||
let diff_snapshot = diff.read(cx).snapshot(cx);
|
||||
|
||||
let excerpt_ranges = {
|
||||
let diff_hunk_ranges = diff_read
|
||||
let diff_hunk_ranges = diff_snapshot
|
||||
.hunks_intersecting_range(
|
||||
Anchor::min_max_range_for_buffer(diff_read.buffer_id),
|
||||
Anchor::min_max_range_for_buffer(snapshot.remote_id()),
|
||||
&snapshot,
|
||||
cx,
|
||||
)
|
||||
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot));
|
||||
let conflicts = conflict_addon
|
||||
@@ -551,18 +550,21 @@ impl ProjectDiff {
|
||||
}
|
||||
};
|
||||
|
||||
let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| {
|
||||
let was_empty = multibuffer.is_empty();
|
||||
let (_, is_newly_added) = multibuffer.set_excerpts_for_path(
|
||||
let (was_empty, is_excerpt_newly_added) = self.editor.update(cx, |editor, cx| {
|
||||
let was_empty = editor
|
||||
.primary_editor()
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.is_empty();
|
||||
let (_, is_newly_added) = editor.set_excerpts_for_path(
|
||||
path_key.clone(),
|
||||
buffer,
|
||||
excerpt_ranges,
|
||||
multibuffer_context_lines(cx),
|
||||
diff,
|
||||
cx,
|
||||
);
|
||||
if self.branch_diff.read(cx).diff_base().is_merge_base() {
|
||||
multibuffer.add_diff(diff.clone(), cx);
|
||||
}
|
||||
(was_empty, is_newly_added)
|
||||
});
|
||||
|
||||
@@ -639,9 +641,9 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
|
||||
this.multibuffer.update(cx, |multibuffer, cx| {
|
||||
this.editor.update(cx, |editor, cx| {
|
||||
for path in previous_paths {
|
||||
if let Some(buffer) = multibuffer.buffer_for_path(&path, cx) {
|
||||
if let Some(buffer) = this.multibuffer.read(cx).buffer_for_path(&path, cx) {
|
||||
let skip = match reason {
|
||||
RefreshReason::DiffChanged | RefreshReason::EditorSaved => {
|
||||
buffer.read(cx).is_dirty()
|
||||
@@ -654,7 +656,7 @@ impl ProjectDiff {
|
||||
}
|
||||
|
||||
this.buffer_diff_subscriptions.remove(&path.path);
|
||||
multibuffer.remove_excerpts_for_path(path.clone(), cx);
|
||||
editor.remove_excerpts_for_path(path, cx);
|
||||
}
|
||||
});
|
||||
buffers_to_load
|
||||
@@ -1689,12 +1691,6 @@ mod tests {
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
@@ -1705,6 +1701,12 @@ mod tests {
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("foo.txt", "foo\n".into())],
|
||||
);
|
||||
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let diff = cx.new_window_entity(|window, cx| {
|
||||
ProjectDiff::new(project.clone(), workspace, window, cx)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
let editor = diff.read_with(cx, |diff, cx| diff.editor.read(cx).primary_editor().clone());
|
||||
@@ -1712,8 +1714,8 @@ mod tests {
|
||||
&editor,
|
||||
cx,
|
||||
&"
|
||||
- foo
|
||||
+ ˇFOO
|
||||
- ˇfoo
|
||||
+ FOO
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
@@ -1820,6 +1822,12 @@ mod tests {
|
||||
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
|
||||
let (workspace, cx) =
|
||||
cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("foo", "original\n".into())],
|
||||
"deadbeef",
|
||||
);
|
||||
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer(path!("/project/foo"), cx)
|
||||
@@ -1834,13 +1842,6 @@ mod tests {
|
||||
});
|
||||
cx.run_until_parked();
|
||||
|
||||
fs.set_head_for_repo(
|
||||
path!("/project/.git").as_ref(),
|
||||
&[("foo", "original\n".into())],
|
||||
"deadbeef",
|
||||
);
|
||||
cx.run_until_parked();
|
||||
|
||||
let diff_editor =
|
||||
diff.read_with(cx, |diff, cx| diff.editor.read(cx).primary_editor().clone());
|
||||
|
||||
@@ -1848,8 +1849,8 @@ mod tests {
|
||||
&diff_editor,
|
||||
cx,
|
||||
&"
|
||||
- original
|
||||
+ ˇmodified
|
||||
- ˇoriginal
|
||||
+ modified
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
@@ -1912,9 +1913,9 @@ mod tests {
|
||||
&diff_editor,
|
||||
cx,
|
||||
&"
|
||||
- original
|
||||
- ˇoriginal
|
||||
+ different
|
||||
ˇ"
|
||||
"
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! TextDiffView currently provides a UI for displaying differences between the clipboard and selected text.
|
||||
|
||||
use anyhow::Result;
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use buffer_diff::BufferDiff;
|
||||
use editor::{Editor, EditorEvent, MultiBuffer, ToPoint, actions::DiffClipboardWithSelectionData};
|
||||
use futures::{FutureExt, select_biased};
|
||||
use gpui::{
|
||||
@@ -257,23 +257,25 @@ async fn update_diff_buffer(
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
let source_buffer_snapshot = source_buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
let language = source_buffer_snapshot.language().cloned();
|
||||
|
||||
let base_buffer_snapshot = clipboard_buffer.read_with(cx, |buffer, _| buffer.snapshot())?;
|
||||
let base_text = base_buffer_snapshot.text();
|
||||
|
||||
let diff_snapshot = cx
|
||||
.update(|cx| {
|
||||
BufferDiffSnapshot::new_with_base_buffer(
|
||||
let update = diff
|
||||
.update(cx, |diff, cx| {
|
||||
diff.update_diff(
|
||||
source_buffer_snapshot.text.clone(),
|
||||
Some(Arc::new(base_text)),
|
||||
base_buffer_snapshot,
|
||||
Some(Arc::from(base_text.as_str())),
|
||||
true,
|
||||
language,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await;
|
||||
|
||||
diff.update(cx, |diff, cx| {
|
||||
diff.set_snapshot(diff_snapshot, &source_buffer_snapshot.text, cx);
|
||||
diff.set_snapshot(update, &source_buffer_snapshot.text, cx);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ impl Anchor {
|
||||
.get(&excerpt.buffer_id)
|
||||
.map(|diff| diff.base_text())
|
||||
{
|
||||
let self_anchor = self.diff_base_anchor.filter(|a| base_text.can_resolve(a));
|
||||
let other_anchor = other.diff_base_anchor.filter(|a| base_text.can_resolve(a));
|
||||
let self_anchor = self.diff_base_anchor.filter(|a| a.is_valid(base_text));
|
||||
let other_anchor = other.diff_base_anchor.filter(|a| a.is_valid(base_text));
|
||||
return match (self_anchor, other_anchor) {
|
||||
(Some(a), Some(b)) => a.cmp(&b, base_text),
|
||||
(Some(_), None) => match other.text_anchor.bias {
|
||||
@@ -146,7 +146,7 @@ impl Anchor {
|
||||
.diffs
|
||||
.get(&excerpt.buffer_id)
|
||||
.map(|diff| diff.base_text())
|
||||
&& a.buffer_id == Some(base_text.remote_id())
|
||||
&& a.is_valid(&base_text)
|
||||
{
|
||||
return a.bias_left(base_text);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ impl Anchor {
|
||||
.diffs
|
||||
.get(&excerpt.buffer_id)
|
||||
.map(|diff| diff.base_text())
|
||||
&& a.buffer_id == Some(base_text.remote_id())
|
||||
&& a.is_valid(&base_text)
|
||||
{
|
||||
return a.bias_right(base_text);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -47,15 +47,23 @@ impl MultiBuffer {
|
||||
self.excerpts_by_path.keys()
|
||||
}
|
||||
|
||||
pub fn excerpts_for_path(&self, path: &PathKey) -> impl '_ + Iterator<Item = ExcerptId> {
|
||||
self.excerpts_by_path
|
||||
.get(path)
|
||||
.map(|excerpts| excerpts.as_slice())
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.copied()
|
||||
}
|
||||
|
||||
pub fn path_for_excerpt(&self, excerpt: ExcerptId) -> Option<PathKey> {
|
||||
self.paths_by_excerpt.get(&excerpt).cloned()
|
||||
}
|
||||
|
||||
pub fn remove_excerpts_for_path(&mut self, path: PathKey, cx: &mut Context<Self>) {
|
||||
if let Some(to_remove) = self.excerpts_by_path.remove(&path) {
|
||||
self.remove_excerpts(to_remove, cx)
|
||||
}
|
||||
if let Some(follower) = &self.follower {
|
||||
follower.update(cx, |follower, cx| {
|
||||
follower.remove_excerpts_for_path(path, cx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn buffer_for_path(&self, path: &PathKey, cx: &App) -> Option<Entity<Buffer>> {
|
||||
@@ -278,7 +286,7 @@ impl MultiBuffer {
|
||||
(result, added_a_new_excerpt)
|
||||
}
|
||||
|
||||
fn update_path_excerpts(
|
||||
pub fn update_path_excerpts(
|
||||
&mut self,
|
||||
path: PathKey,
|
||||
buffer: Entity<Buffer>,
|
||||
|
||||
@@ -129,8 +129,8 @@ struct BufferGitState {
|
||||
hunk_staging_operation_count: usize,
|
||||
hunk_staging_operation_count_as_of_write: usize,
|
||||
|
||||
head_text: Option<Arc<String>>,
|
||||
index_text: Option<Arc<String>>,
|
||||
head_text: Option<Arc<str>>,
|
||||
index_text: Option<Arc<str>>,
|
||||
head_changed: bool,
|
||||
index_changed: bool,
|
||||
language_changed: bool,
|
||||
@@ -693,7 +693,6 @@ impl GitStore {
|
||||
oid: Option<git::Oid>,
|
||||
buffer: Entity<Buffer>,
|
||||
repo: Entity<Repository>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<BufferDiff>>> {
|
||||
cx.spawn(async move |this, cx| {
|
||||
@@ -710,9 +709,8 @@ impl GitStore {
|
||||
buffer_diff
|
||||
.update(cx, |buffer_diff, cx| {
|
||||
buffer_diff.set_base_text(
|
||||
content.map(Arc::new),
|
||||
content.map(|s| s.as_str().into()),
|
||||
buffer_snapshot.language().cloned(),
|
||||
Some(languages.clone()),
|
||||
buffer_snapshot.text,
|
||||
cx,
|
||||
)
|
||||
@@ -823,6 +821,7 @@ impl GitStore {
|
||||
|
||||
cx.subscribe(&diff, Self::on_buffer_diff_event).detach();
|
||||
diff_state.update(cx, |diff_state, cx| {
|
||||
diff_state.language_changed = true;
|
||||
diff_state.language = language;
|
||||
diff_state.language_registry = language_registry;
|
||||
|
||||
@@ -2652,7 +2651,7 @@ impl GitStore {
|
||||
.or_default();
|
||||
shared_diffs.entry(buffer_id).or_default().unstaged = Some(diff.clone());
|
||||
})?;
|
||||
let staged_text = diff.read_with(&cx, |diff, _| diff.base_text_string())?;
|
||||
let staged_text = diff.read_with(&cx, |diff, cx| diff.base_text_string(cx))?;
|
||||
Ok(proto::OpenUnstagedDiffResponse { staged_text })
|
||||
}
|
||||
|
||||
@@ -2682,14 +2681,14 @@ impl GitStore {
|
||||
let unstaged_diff = diff.secondary_diff();
|
||||
let index_snapshot = unstaged_diff.and_then(|diff| {
|
||||
let diff = diff.read(cx);
|
||||
diff.base_text_exists().then(|| diff.base_text())
|
||||
diff.base_text_exists().then(|| diff.base_text(cx))
|
||||
});
|
||||
|
||||
let mode;
|
||||
let staged_text;
|
||||
let committed_text;
|
||||
if diff.base_text_exists() {
|
||||
let committed_snapshot = diff.base_text();
|
||||
let committed_snapshot = diff.base_text(cx);
|
||||
committed_text = Some(committed_snapshot.text());
|
||||
if let Some(index_text) = index_snapshot {
|
||||
if index_text.remote_id() == committed_snapshot.remote_id() {
|
||||
@@ -3025,21 +3024,21 @@ impl BufferGitState {
|
||||
Some(DiffBasesChange::SetIndex(index)) => {
|
||||
self.index_text = index.map(|mut index| {
|
||||
text::LineEnding::normalize(&mut index);
|
||||
Arc::new(index)
|
||||
Arc::from(index.as_str())
|
||||
});
|
||||
self.index_changed = true;
|
||||
}
|
||||
Some(DiffBasesChange::SetHead(head)) => {
|
||||
self.head_text = head.map(|mut head| {
|
||||
text::LineEnding::normalize(&mut head);
|
||||
Arc::new(head)
|
||||
Arc::from(head.as_str())
|
||||
});
|
||||
self.head_changed = true;
|
||||
}
|
||||
Some(DiffBasesChange::SetBoth(text)) => {
|
||||
let text = text.map(|mut text| {
|
||||
text::LineEnding::normalize(&mut text);
|
||||
Arc::new(text)
|
||||
Arc::from(text.as_str())
|
||||
});
|
||||
self.head_text = text.clone();
|
||||
self.index_text = text;
|
||||
@@ -3049,12 +3048,12 @@ impl BufferGitState {
|
||||
Some(DiffBasesChange::SetEach { index, head }) => {
|
||||
self.index_text = index.map(|mut index| {
|
||||
text::LineEnding::normalize(&mut index);
|
||||
Arc::new(index)
|
||||
Arc::from(index.as_str())
|
||||
});
|
||||
self.index_changed = true;
|
||||
self.head_text = head.map(|mut head| {
|
||||
text::LineEnding::normalize(&mut head);
|
||||
Arc::new(head)
|
||||
Arc::from(head.as_str())
|
||||
});
|
||||
self.head_changed = true;
|
||||
}
|
||||
@@ -3091,17 +3090,16 @@ impl BufferGitState {
|
||||
let mut new_unstaged_diff = None;
|
||||
if let Some(unstaged_diff) = &unstaged_diff {
|
||||
new_unstaged_diff = Some(
|
||||
BufferDiff::update_diff(
|
||||
unstaged_diff.clone(),
|
||||
buffer.clone(),
|
||||
index,
|
||||
index_changed,
|
||||
language_changed,
|
||||
language.clone(),
|
||||
language_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
.await?,
|
||||
cx.update(|cx| {
|
||||
unstaged_diff.read(cx).update_diff(
|
||||
buffer.clone(),
|
||||
index,
|
||||
index_changed,
|
||||
language.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3115,17 +3113,16 @@ impl BufferGitState {
|
||||
new_unstaged_diff.clone()
|
||||
} else {
|
||||
Some(
|
||||
BufferDiff::update_diff(
|
||||
uncommitted_diff.clone(),
|
||||
buffer.clone(),
|
||||
head,
|
||||
head_changed,
|
||||
language_changed,
|
||||
language.clone(),
|
||||
language_registry.clone(),
|
||||
cx,
|
||||
)
|
||||
.await?,
|
||||
cx.update(|cx| {
|
||||
uncommitted_diff.read(cx).update_diff(
|
||||
buffer.clone(),
|
||||
head,
|
||||
head_changed,
|
||||
language.clone(),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3164,7 +3161,7 @@ impl BufferGitState {
|
||||
{
|
||||
unstaged_diff.update(cx, |diff, cx| {
|
||||
if language_changed {
|
||||
diff.language_changed(cx);
|
||||
diff.language_changed(language.clone(), language_registry.clone(), cx);
|
||||
}
|
||||
diff.set_snapshot(new_unstaged_diff, &buffer, cx)
|
||||
})?
|
||||
@@ -3179,7 +3176,7 @@ impl BufferGitState {
|
||||
{
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
if language_changed {
|
||||
diff.language_changed(cx);
|
||||
diff.language_changed(language, language_registry, cx);
|
||||
}
|
||||
diff.set_snapshot_with_secondary(
|
||||
new_uncommitted_diff,
|
||||
@@ -3689,9 +3686,9 @@ impl Repository {
|
||||
match (current_index_text.as_ref(), current_head_text.as_ref()) {
|
||||
(Some(current_index), Some(current_head)) => {
|
||||
let index_changed =
|
||||
index_text.as_ref() != current_index.as_deref();
|
||||
index_text.as_deref() != current_index.as_deref();
|
||||
let head_changed =
|
||||
head_text.as_ref() != current_head.as_deref();
|
||||
head_text.as_deref() != current_head.as_deref();
|
||||
if index_changed && head_changed {
|
||||
if index_text == head_text {
|
||||
Some(DiffBasesChange::SetBoth(head_text))
|
||||
@@ -3711,13 +3708,13 @@ impl Repository {
|
||||
}
|
||||
(Some(current_index), None) => {
|
||||
let index_changed =
|
||||
index_text.as_ref() != current_index.as_deref();
|
||||
index_text.as_deref() != current_index.as_deref();
|
||||
index_changed
|
||||
.then_some(DiffBasesChange::SetIndex(index_text))
|
||||
}
|
||||
(None, Some(current_head)) => {
|
||||
let head_changed =
|
||||
head_text.as_ref() != current_head.as_deref();
|
||||
head_text.as_deref() != current_head.as_deref();
|
||||
head_changed.then_some(DiffBasesChange::SetHead(head_text))
|
||||
}
|
||||
(None, None) => None,
|
||||
|
||||
@@ -332,8 +332,6 @@ impl BranchDiff {
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||
.await?;
|
||||
|
||||
let languages = project.update(cx, |project, _cx| project.languages().clone())?;
|
||||
|
||||
let changes = if let Some(entry) = branch_diff {
|
||||
let oid = match entry {
|
||||
git::status::TreeDiffStatus::Added { .. } => None,
|
||||
@@ -343,7 +341,7 @@ impl BranchDiff {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.git_store().update(cx, |git_store, cx| {
|
||||
git_store.open_diff_since(oid, buffer.clone(), repo, languages, cx)
|
||||
git_store.open_diff_since(oid, buffer.clone(), repo, cx)
|
||||
})
|
||||
})?
|
||||
.await?
|
||||
|
||||
@@ -7224,9 +7224,9 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
unstaged_diff.update(cx, |unstaged_diff, cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
unstaged_diff.hunks(&snapshot, cx),
|
||||
unstaged_diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&unstaged_diff.base_text_string().unwrap(),
|
||||
&unstaged_diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(0..1, "", "// print goodbye\n", DiffHunkStatus::added_none()),
|
||||
(
|
||||
@@ -7252,9 +7252,11 @@ async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
unstaged_diff.update(cx, |unstaged_diff, cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
unstaged_diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
|
||||
unstaged_diff
|
||||
.snapshot(cx)
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
|
||||
&snapshot,
|
||||
&unstaged_diff.base_text().text(),
|
||||
&unstaged_diff.base_text(cx).text(),
|
||||
&[(
|
||||
2..3,
|
||||
"",
|
||||
@@ -7334,16 +7336,17 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
diff_1.read_with(cx, |diff, _| {
|
||||
assert_eq!(diff.base_text().language().cloned(), Some(language))
|
||||
diff_1.read_with(cx, |diff, cx| {
|
||||
assert_eq!(diff.base_text(cx).language().cloned(), Some(language))
|
||||
});
|
||||
cx.run_until_parked();
|
||||
diff_1.update(cx, |diff, cx| {
|
||||
let snapshot = buffer_1.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
|
||||
diff.snapshot(cx)
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..1,
|
||||
@@ -7382,9 +7385,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
diff_1.update(cx, |diff, cx| {
|
||||
let snapshot = buffer_1.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
|
||||
diff.snapshot(cx)
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text().text(),
|
||||
&diff.base_text(cx).text(),
|
||||
&[(
|
||||
2..3,
|
||||
"",
|
||||
@@ -7411,9 +7415,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
diff_2.update(cx, |diff, cx| {
|
||||
let snapshot = buffer_2.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
|
||||
diff.snapshot(cx)
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
0..0,
|
||||
"// the-deleted-contents\n",
|
||||
@@ -7432,9 +7437,10 @@ async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) {
|
||||
diff_2.update(cx, |diff, cx| {
|
||||
let snapshot = buffer_2.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx),
|
||||
diff.snapshot(cx)
|
||||
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
0..0,
|
||||
"// the-deleted-contents\n",
|
||||
@@ -7503,9 +7509,9 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
// The hunks are initially unstaged.
|
||||
uncommitted_diff.read_with(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7534,14 +7540,15 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
let range =
|
||||
snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_before(Point::new(2, 0));
|
||||
let hunks = diff
|
||||
.hunks_intersecting_range(range, &snapshot, cx)
|
||||
.snapshot(cx)
|
||||
.hunks_intersecting_range(range, &snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
diff.stage_or_unstage_hunks(true, &hunks, &snapshot, true, cx);
|
||||
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7573,6 +7580,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
let event = diff_events.next().await.unwrap();
|
||||
if let BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(changed_range),
|
||||
base_text_changed_range: _,
|
||||
} = event
|
||||
{
|
||||
let changed_range = changed_range.to_point(&snapshot);
|
||||
@@ -7585,9 +7593,9 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
cx.run_until_parked();
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7615,6 +7623,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
let event = diff_events.next().await.unwrap();
|
||||
if let BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(changed_range),
|
||||
base_text_changed_range: _,
|
||||
} = event
|
||||
{
|
||||
let changed_range = changed_range.to_point(&snapshot);
|
||||
@@ -7634,14 +7643,15 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
let range =
|
||||
snapshot.anchor_before(Point::new(3, 0))..snapshot.anchor_before(Point::new(4, 0));
|
||||
let hunks = diff
|
||||
.hunks_intersecting_range(range, &snapshot, cx)
|
||||
.snapshot(cx)
|
||||
.hunks_intersecting_range(range, &snapshot)
|
||||
.collect::<Vec<_>>();
|
||||
diff.stage_or_unstage_hunks(true, &hunks, &snapshot, true, cx);
|
||||
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7671,6 +7681,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
let event = diff_events.next().await.unwrap();
|
||||
if let BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(changed_range),
|
||||
base_text_changed_range: _,
|
||||
} = event
|
||||
{
|
||||
let changed_range = changed_range.to_point(&snapshot);
|
||||
@@ -7683,9 +7694,9 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
cx.run_until_parked();
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7712,6 +7723,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
let event = diff_events.next().await.unwrap();
|
||||
if let BufferDiffEvent::DiffChanged {
|
||||
changed_range: Some(changed_range),
|
||||
base_text_changed_range: _,
|
||||
} = event
|
||||
{
|
||||
let changed_range = changed_range.to_point(&snapshot);
|
||||
@@ -7725,7 +7737,7 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
// Stage two hunks with separate operations.
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
let hunks = diff.hunks(&snapshot, cx).collect::<Vec<_>>();
|
||||
let hunks = diff.snapshot(cx).hunks(&snapshot).collect::<Vec<_>>();
|
||||
diff.stage_or_unstage_hunks(true, &hunks[0..1], &snapshot, true, cx);
|
||||
diff.stage_or_unstage_hunks(true, &hunks[2..3], &snapshot, true, cx);
|
||||
});
|
||||
@@ -7733,9 +7745,9 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
// Both staged hunks appear as pending.
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7763,9 +7775,9 @@ async fn test_staging_hunks(cx: &mut gpui::TestAppContext) {
|
||||
cx.run_until_parked();
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(0..0, "zero\n", "", DiffHunkStatus::deleted(NoSecondaryHunk)),
|
||||
(
|
||||
@@ -7847,9 +7859,9 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext)
|
||||
// The hunks are initially unstaged.
|
||||
uncommitted_diff.read_with(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7878,12 +7890,12 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext)
|
||||
|
||||
// Stage the first hunk.
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
let hunk = diff.hunks(&snapshot, cx).next().unwrap();
|
||||
let hunk = diff.snapshot(cx).hunks(&snapshot).next().unwrap();
|
||||
diff.stage_or_unstage_hunks(true, &[hunk], &snapshot, true, cx);
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7910,12 +7922,12 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext)
|
||||
// Stage the second hunk *before* receiving the FS event for the first hunk.
|
||||
cx.run_until_parked();
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
let hunk = diff.hunks(&snapshot, cx).nth(1).unwrap();
|
||||
let hunk = diff.snapshot(cx).hunks(&snapshot).nth(1).unwrap();
|
||||
diff.stage_or_unstage_hunks(true, &[hunk], &snapshot, true, cx);
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(
|
||||
0..0,
|
||||
@@ -7945,7 +7957,7 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext)
|
||||
|
||||
// Stage the third hunk before receiving the second FS event.
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
let hunk = diff.hunks(&snapshot, cx).nth(2).unwrap();
|
||||
let hunk = diff.snapshot(cx).hunks(&snapshot).nth(2).unwrap();
|
||||
diff.stage_or_unstage_hunks(true, &[hunk], &snapshot, true, cx);
|
||||
});
|
||||
|
||||
@@ -7957,9 +7969,9 @@ async fn test_staging_hunks_with_delayed_fs_event(cx: &mut gpui::TestAppContext)
|
||||
cx.run_until_parked();
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[
|
||||
(0..0, "zero\n", "", DiffHunkStatus::deleted(NoSecondaryHunk)),
|
||||
(
|
||||
@@ -8043,8 +8055,9 @@ async fn test_staging_random_hunks(
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut hunks =
|
||||
uncommitted_diff.update(cx, |diff, cx| diff.hunks(&snapshot, cx).collect::<Vec<_>>());
|
||||
let mut hunks = uncommitted_diff.update(cx, |diff, cx| {
|
||||
diff.snapshot(cx).hunks(&snapshot).collect::<Vec<_>>()
|
||||
});
|
||||
assert_eq!(hunks.len(), 6);
|
||||
|
||||
for _i in 0..operations {
|
||||
@@ -8095,7 +8108,8 @@ async fn test_staging_random_hunks(
|
||||
.map(|hunk| (hunk.range.start.row, hunk.secondary_status))
|
||||
.collect::<Vec<_>>();
|
||||
let actual_hunks = diff
|
||||
.hunks(&snapshot, cx)
|
||||
.snapshot(cx)
|
||||
.hunks(&snapshot)
|
||||
.map(|hunk| (hunk.range.start.row, hunk.secondary_status))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(actual_hunks, expected_hunks);
|
||||
@@ -8160,9 +8174,9 @@ async fn test_single_file_diffs(cx: &mut gpui::TestAppContext) {
|
||||
uncommitted_diff.update(cx, |uncommitted_diff, cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
assert_hunks(
|
||||
uncommitted_diff.hunks(&snapshot, cx),
|
||||
uncommitted_diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&uncommitted_diff.base_text_string().unwrap(),
|
||||
&uncommitted_diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
1..2,
|
||||
" println!(\"hello from HEAD\");\n",
|
||||
@@ -8225,7 +8239,7 @@ async fn test_staging_hunk_preserve_executable_permission(cx: &mut gpui::TestApp
|
||||
.unwrap();
|
||||
|
||||
uncommitted_diff.update(cx, |diff, cx| {
|
||||
let hunks = diff.hunks(&snapshot, cx).collect::<Vec<_>>();
|
||||
let hunks = diff.snapshot(cx).hunks(&snapshot).collect::<Vec<_>>();
|
||||
diff.stage_or_unstage_hunks(true, &hunks, &snapshot, true, cx);
|
||||
});
|
||||
|
||||
@@ -10342,8 +10356,8 @@ async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppCo
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
unstaged_diff.update(cx, |unstaged_diff, _cx| {
|
||||
let base_text = unstaged_diff.base_text_string().unwrap();
|
||||
unstaged_diff.update(cx, |unstaged_diff, cx| {
|
||||
let base_text = unstaged_diff.base_text_string(cx).unwrap();
|
||||
assert_eq!(base_text, file_1_staged, "Should start with file_1 staged");
|
||||
});
|
||||
|
||||
@@ -10367,13 +10381,13 @@ async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppCo
|
||||
// the `BufferChangedFilePath` event being handled.
|
||||
unstaged_diff.update(cx, |unstaged_diff, cx| {
|
||||
let snapshot = buffer.read(cx).snapshot();
|
||||
let base_text = unstaged_diff.base_text_string().unwrap();
|
||||
let base_text = unstaged_diff.base_text_string(cx).unwrap();
|
||||
assert_eq!(
|
||||
base_text, file_2_staged,
|
||||
"Diff bases should be automatically updated to file_2 staged content"
|
||||
);
|
||||
|
||||
let hunks: Vec<_> = unstaged_diff.hunks(&snapshot, cx).collect();
|
||||
let hunks: Vec<_> = unstaged_diff.snapshot(cx).hunks(&snapshot).collect();
|
||||
assert!(!hunks.is_empty(), "Should have diff hunks for file_2");
|
||||
});
|
||||
|
||||
@@ -10386,8 +10400,8 @@ async fn test_buffer_changed_file_path_updates_git_diff(cx: &mut gpui::TestAppCo
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
uncommitted_diff.update(cx, |uncommitted_diff, _cx| {
|
||||
let base_text = uncommitted_diff.base_text_string().unwrap();
|
||||
uncommitted_diff.update(cx, |uncommitted_diff, cx| {
|
||||
let base_text = uncommitted_diff.base_text_string(cx).unwrap();
|
||||
assert_eq!(
|
||||
base_text, file_2_committed,
|
||||
"Uncommitted diff should compare against file_2 committed content"
|
||||
@@ -10975,9 +10989,9 @@ async fn test_optimistic_hunks_in_staged_files(cx: &mut gpui::TestAppContext) {
|
||||
// The hunk is initially unstaged.
|
||||
uncommitted_diff.read_with(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
1..2,
|
||||
"two\n",
|
||||
@@ -11002,7 +11016,9 @@ async fn test_optimistic_hunks_in_staged_files(cx: &mut gpui::TestAppContext) {
|
||||
for _ in 0..10 {
|
||||
cx.executor().tick();
|
||||
let [hunk]: [_; 1] = uncommitted_diff
|
||||
.read_with(cx, |diff, cx| diff.hunks(&snapshot, cx).collect::<Vec<_>>())
|
||||
.read_with(cx, |diff, cx| {
|
||||
diff.snapshot(cx).hunks(&snapshot).collect::<Vec<_>>()
|
||||
})
|
||||
.try_into()
|
||||
.unwrap();
|
||||
match hunk.secondary_status {
|
||||
@@ -11014,9 +11030,9 @@ async fn test_optimistic_hunks_in_staged_files(cx: &mut gpui::TestAppContext) {
|
||||
}
|
||||
uncommitted_diff.read_with(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
1..2,
|
||||
"two\n",
|
||||
@@ -11033,9 +11049,9 @@ async fn test_optimistic_hunks_in_staged_files(cx: &mut gpui::TestAppContext) {
|
||||
// The hunk is now fully staged.
|
||||
uncommitted_diff.read_with(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[(
|
||||
1..2,
|
||||
"two\n",
|
||||
@@ -11058,9 +11074,9 @@ async fn test_optimistic_hunks_in_staged_files(cx: &mut gpui::TestAppContext) {
|
||||
// After committing, there are no more hunks.
|
||||
uncommitted_diff.read_with(cx, |diff, cx| {
|
||||
assert_hunks(
|
||||
diff.hunks(&snapshot, cx),
|
||||
diff.snapshot(cx).hunks(&snapshot),
|
||||
&snapshot,
|
||||
&diff.base_text_string().unwrap(),
|
||||
&diff.base_text_string(cx).unwrap(),
|
||||
&[] as &[(Range<u32>, &str, &str, DiffHunkStatus)],
|
||||
);
|
||||
});
|
||||
|
||||
@@ -96,8 +96,11 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
diff.update(cx, |diff, _| {
|
||||
assert_eq!(diff.base_text_string().unwrap(), "fn one() -> usize { 0 }");
|
||||
diff.update(cx, |diff, cx| {
|
||||
assert_eq!(
|
||||
diff.base_text_string(cx).unwrap(),
|
||||
"fn one() -> usize { 0 }"
|
||||
);
|
||||
});
|
||||
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
@@ -157,9 +160,9 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
|
||||
&[("src/lib2.rs", "fn one() -> usize { 100 }".into())],
|
||||
);
|
||||
cx.executor().run_until_parked();
|
||||
diff.update(cx, |diff, _| {
|
||||
diff.update(cx, |diff, cx| {
|
||||
assert_eq!(
|
||||
diff.base_text_string().unwrap(),
|
||||
diff.base_text_string(cx).unwrap(),
|
||||
"fn one() -> usize { 100 }"
|
||||
);
|
||||
});
|
||||
@@ -1443,12 +1446,12 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
|
||||
.unwrap();
|
||||
|
||||
diff.read_with(cx, |diff, cx| {
|
||||
assert_eq!(diff.base_text_string().unwrap(), text_1);
|
||||
assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
|
||||
assert_eq!(
|
||||
diff.secondary_diff()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
.unwrap(),
|
||||
text_1
|
||||
);
|
||||
@@ -1462,12 +1465,12 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
diff.read_with(cx, |diff, cx| {
|
||||
assert_eq!(diff.base_text_string().unwrap(), text_1);
|
||||
assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
|
||||
assert_eq!(
|
||||
diff.secondary_diff()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
.unwrap(),
|
||||
text_2
|
||||
);
|
||||
@@ -1482,12 +1485,12 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
diff.read_with(cx, |diff, cx| {
|
||||
assert_eq!(diff.base_text_string().unwrap(), text_2);
|
||||
assert_eq!(diff.base_text_string(cx).unwrap(), text_2);
|
||||
assert_eq!(
|
||||
diff.secondary_diff()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
.unwrap(),
|
||||
text_2
|
||||
);
|
||||
@@ -1588,12 +1591,12 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
||||
.unwrap();
|
||||
|
||||
diff.read_with(cx, |diff, cx| {
|
||||
assert_eq!(diff.base_text_string().unwrap(), text_1);
|
||||
assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
|
||||
assert_eq!(
|
||||
diff.secondary_diff()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
.unwrap(),
|
||||
text_1
|
||||
);
|
||||
@@ -1607,12 +1610,12 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
diff.read_with(cx, |diff, cx| {
|
||||
assert_eq!(diff.base_text_string().unwrap(), text_1);
|
||||
assert_eq!(diff.base_text_string(cx).unwrap(), text_1);
|
||||
assert_eq!(
|
||||
diff.secondary_diff()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
.unwrap(),
|
||||
text_2
|
||||
);
|
||||
@@ -1627,12 +1630,12 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
diff.read_with(cx, |diff, cx| {
|
||||
assert_eq!(diff.base_text_string().unwrap(), text_2);
|
||||
assert_eq!(diff.base_text_string(cx).unwrap(), text_2);
|
||||
assert_eq!(
|
||||
diff.secondary_diff()
|
||||
.unwrap()
|
||||
.read(cx)
|
||||
.base_text_string()
|
||||
.base_text_string(cx)
|
||||
.unwrap(),
|
||||
text_2
|
||||
);
|
||||
|
||||
@@ -168,7 +168,7 @@ impl Chunk {
|
||||
if self.is_char_boundary(offset) {
|
||||
return true;
|
||||
}
|
||||
if PANIC {
|
||||
if PANIC || cfg!(debug_assertions) {
|
||||
panic_char_boundary(&self.text, offset);
|
||||
} else {
|
||||
log_err_char_boundary(&self.text, offset);
|
||||
|
||||
@@ -3155,6 +3155,7 @@ impl ToOffset for Point {
|
||||
}
|
||||
|
||||
impl ToOffset for usize {
|
||||
#[track_caller]
|
||||
fn to_offset(&self, snapshot: &BufferSnapshot) -> usize {
|
||||
if snapshot
|
||||
.as_rope()
|
||||
|
||||
Reference in New Issue
Block a user