diff --git a/Cargo.lock b/Cargo.lock index 5ad5d048bc88a23e547ff8eb62f38951d135f3fb..96a8df761bb8cab563cf66f1acfc36e2b2280f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2753,6 +2753,7 @@ dependencies = [ "ctor", "dashmap 6.1.0", "derive_more", + "diff 0.1.0", "editor", "env_logger 0.11.6", "envy", @@ -3837,6 +3838,24 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.0" +dependencies = [ + "futures 0.3.31", + "git2", + "gpui", + "language", + "log", + "pretty_assertions", + "rope", + "serde_json", + "sum_tree", + "text", + "unindent", + "util", +] + [[package]] name = "diff" version = "0.1.13" @@ -4007,6 +4026,7 @@ dependencies = [ "convert_case 0.7.1", "ctor", "db", + "diff 0.1.0", "emojis", "env_logger 0.11.6", "file_icons", @@ -5306,6 +5326,7 @@ dependencies = [ "anyhow", "collections", "db", + "diff 0.1.0", "editor", "feature_flags", "futures 0.3.31", @@ -7910,9 +7931,9 @@ dependencies = [ "clock", "collections", "ctor", + "diff 0.1.0", "env_logger 0.11.6", "futures 0.3.31", - "git", "gpui", "indoc", "itertools 0.14.0", @@ -9919,7 +9940,7 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" dependencies = [ - "diff", + "diff 0.1.13", "yansi", ] @@ -10015,6 +10036,7 @@ dependencies = [ "client", "clock", "collections", + "diff 0.1.0", "env_logger 0.11.6", "fancy-regex 0.14.0", "fs", diff --git a/Cargo.toml b/Cargo.toml index ff50372ed342d2c4d6a60b6e8c1df06ffc89f5c9..e108955b785c63a52007ef07a09c249f658aa2ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,7 @@ members = [ "crates/db", "crates/deepseek", "crates/diagnostics", + "crates/diff", "crates/docs_preprocessor", "crates/editor", "crates/evals", @@ -231,6 +232,7 @@ copilot = { path = "crates/copilot" } db = { path = "crates/db" } deepseek = { path = "crates/deepseek" } diagnostics = { path = "crates/diagnostics" } +diff = { path = "crates/diff" } editor = { path = "crates/editor" } extension = { path = "crates/extension" } extension_host = { path = "crates/extension_host" } diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index db293c5173806c804c52bb7d2ddc336801c93853..7d61621c955a9c06d7f68b2e45988c7db5b5faa5 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -33,6 +33,7 @@ clock.workspace = true collections.workspace = true dashmap.workspace = true derive_more.workspace = true +diff.workspace = true envy = "0.4.2" futures.workspace = true google_ai.workspace = true diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index eea17d45fb3ba9c4b39644c95d1971cf23964105..2fa325b6a77576b515809ae3772ae2d3ee46ae22 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -309,8 +309,8 @@ impl Server { .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) .add_request_handler(forward_read_only_project_request::) - .add_request_handler(forward_read_only_project_request::) - .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) + .add_request_handler(forward_read_only_project_request::) .add_request_handler( forward_mutating_project_request::, ) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index a512a9f10cb6a68556f14f1de4f119f18a98b6a0..e9c8ab39f1217c373ddc6a2e5184cd9f55caa6c7 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -2598,25 +2598,25 @@ async fn test_git_diff_base_change( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let local_unstaged_changes_a = project_local + let local_unstaged_diff_a = project_local .update(cx_a, |p, cx| { - p.open_unstaged_changes(buffer_local_a.clone(), cx) + p.open_unstaged_diff(buffer_local_a.clone(), cx) }) .await .unwrap(); // Wait for it to catch up to the new diff executor.run_until_parked(); - local_unstaged_changes_a.read_with(cx_a, |change_set, cx| { + local_unstaged_diff_a.read_with(cx_a, |diff, cx| { let buffer = buffer_local_a.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, - &change_set.base_text_string().unwrap(), + &diff.base_text_string().unwrap(), &[(1..2, "", "two\n")], ); }); @@ -2626,47 +2626,47 @@ async fn test_git_diff_base_change( .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let remote_unstaged_changes_a = project_remote + let remote_unstaged_diff_a = project_remote .update(cx_b, |p, cx| { - p.open_unstaged_changes(buffer_remote_a.clone(), cx) + p.open_unstaged_diff(buffer_remote_a.clone(), cx) }) .await .unwrap(); // Wait remote buffer to catch up to the new diff executor.run_until_parked(); - remote_unstaged_changes_a.read_with(cx_b, |change_set, cx| { + remote_unstaged_diff_a.read_with(cx_b, |diff, cx| { let buffer = buffer_remote_a.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, - &change_set.base_text_string().unwrap(), + &diff.base_text_string().unwrap(), &[(1..2, "", "two\n")], ); }); // Open uncommitted changes on the guest, without opening them on the host first - let remote_uncommitted_changes_a = project_remote + let remote_uncommitted_diff_a = project_remote .update(cx_b, |p, cx| { - p.open_uncommitted_changes(buffer_remote_a.clone(), cx) + p.open_uncommitted_diff(buffer_remote_a.clone(), cx) }) .await .unwrap(); executor.run_until_parked(); - remote_uncommitted_changes_a.read_with(cx_b, |change_set, cx| { + remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| { let buffer = buffer_remote_a.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(committed_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, - &change_set.base_text_string().unwrap(), + &diff.base_text_string().unwrap(), &[(1..2, "TWO\n", "two\n")], ); }); @@ -2683,44 +2683,44 @@ async fn test_git_diff_base_change( // Wait for buffer_local_a to receive it executor.run_until_parked(); - local_unstaged_changes_a.read_with(cx_a, |change_set, cx| { + local_unstaged_diff_a.read_with(cx_a, |diff, cx| { let buffer = buffer_local_a.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(new_staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, - &change_set.base_text_string().unwrap(), + &diff.base_text_string().unwrap(), &[(2..3, "", "three\n")], ); }); - remote_unstaged_changes_a.read_with(cx_b, |change_set, cx| { + remote_unstaged_diff_a.read_with(cx_b, |diff, cx| { let buffer = buffer_remote_a.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(new_staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, - &change_set.base_text_string().unwrap(), + &diff.base_text_string().unwrap(), &[(2..3, "", "three\n")], ); }); - remote_uncommitted_changes_a.read_with(cx_b, |change_set, cx| { + remote_uncommitted_diff_a.read_with(cx_b, |diff, cx| { let buffer = buffer_remote_a.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(new_committed_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, - &change_set.base_text_string().unwrap(), + &diff.base_text_string().unwrap(), &[(1..2, "TWO_HUNDRED\n", "two\n")], ); }); @@ -2748,25 +2748,25 @@ async fn test_git_diff_base_change( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx)) .await .unwrap(); - let local_unstaged_changes_b = project_local + let local_unstaged_diff_b = project_local .update(cx_a, |p, cx| { - p.open_unstaged_changes(buffer_local_b.clone(), cx) + p.open_unstaged_diff(buffer_local_b.clone(), cx) }) .await .unwrap(); // Wait for it to catch up to the new diff executor.run_until_parked(); - local_unstaged_changes_b.read_with(cx_a, |change_set, cx| { + local_unstaged_diff_b.read_with(cx_a, |diff, cx| { let buffer = buffer_local_b.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, - &change_set.base_text_string().unwrap(), + &diff.base_text_string().unwrap(), &[(1..2, "", "two\n")], ); }); @@ -2776,22 +2776,22 @@ async fn test_git_diff_base_change( .update(cx_b, |p, cx| p.open_buffer((worktree_id, "sub/b.txt"), cx)) .await .unwrap(); - let remote_unstaged_changes_b = project_remote + let remote_unstaged_diff_b = project_remote .update(cx_b, |p, cx| { - p.open_unstaged_changes(buffer_remote_b.clone(), cx) + p.open_unstaged_diff(buffer_remote_b.clone(), cx) }) .await .unwrap(); executor.run_until_parked(); - remote_unstaged_changes_b.read_with(cx_b, |change_set, cx| { + remote_unstaged_diff_b.read_with(cx_b, |diff, cx| { let buffer = buffer_remote_b.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, &staged_text, &[(1..2, "", "two\n")], @@ -2806,28 +2806,28 @@ async fn test_git_diff_base_change( // Wait for buffer_local_b to receive it executor.run_until_parked(); - local_unstaged_changes_b.read_with(cx_a, |change_set, cx| { + local_unstaged_diff_b.read_with(cx_a, |diff, cx| { let buffer = buffer_local_b.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(new_staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, &new_staged_text, &[(2..3, "", "three\n")], ); }); - remote_unstaged_changes_b.read_with(cx_b, |change_set, cx| { + remote_unstaged_diff_b.read_with(cx_b, |diff, cx| { let buffer = buffer_remote_b.read(cx); assert_eq!( - change_set.base_text_string().as_deref(), + diff.base_text_string().as_deref(), Some(new_staged_text.as_str()) ); - git::diff::assert_hunks( - change_set.diff_to_buffer.hunks_in_row_range(0..4, buffer), + diff::assert_hunks( + diff.snapshot.hunks_in_row_range(0..4, buffer), buffer, &new_staged_text, &[(2..3, "", "three\n")], diff --git a/crates/collab/src/tests/random_project_collaboration_tests.rs b/crates/collab/src/tests/random_project_collaboration_tests.rs index e4d1ae79a5c36d833484592429a947c23a8ad96a..655def73b90c2fdc05fa3b258050d3bda9ae0b74 100644 --- a/crates/collab/src/tests/random_project_collaboration_tests.rs +++ b/crates/collab/src/tests/random_project_collaboration_tests.rs @@ -1339,7 +1339,7 @@ impl RandomizedTest for ProjectCollaborationTest { project .buffer_store() .read(cx) - .get_unstaged_changes(host_buffer.read(cx).remote_id(), cx) + .get_unstaged_diff(host_buffer.read(cx).remote_id(), cx) .unwrap() .read(cx) .base_text_string() @@ -1348,7 +1348,7 @@ impl RandomizedTest for ProjectCollaborationTest { project .buffer_store() .read(cx) - .get_unstaged_changes(guest_buffer.read(cx).remote_id(), cx) + .get_unstaged_diff(guest_buffer.read(cx).remote_id(), cx) .unwrap() .read(cx) .base_text_string() diff --git a/crates/diff/Cargo.toml b/crates/diff/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6641fdf1cb67ff579eec4cf62ee3b2e0b902119a --- /dev/null +++ b/crates/diff/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "diff" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/diff.rs" + +[dependencies] +futures.workspace = true +git2.workspace = true +gpui.workspace = true +language.workspace = true +log.workspace = true +rope.workspace = true +sum_tree.workspace = true +text.workspace = true +util.workspace = true + +[dev-dependencies] +unindent.workspace = true +serde_json.workspace = true +pretty_assertions.workspace = true +text = {workspace = true, features = ["test-support"]} + +[features] +test-support = [] diff --git a/crates/diff/LICENSE-GPL b/crates/diff/LICENSE-GPL new file mode 120000 index 0000000000000000000000000000000000000000..89e542f750cd3860a0598eff0dc34b56d7336dc4 --- /dev/null +++ b/crates/diff/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/git/src/diff.rs b/crates/diff/src/diff.rs similarity index 62% rename from crates/git/src/diff.rs rename to crates/diff/src/diff.rs index 764c254119321847260275372d1d9c4c7e16f836..adb25417a713048715df7bfc85ac4f09ab651458 100644 --- a/crates/git/src/diff.rs +++ b/crates/diff/src/diff.rs @@ -1,10 +1,12 @@ +use futures::{channel::oneshot, future::OptionFuture}; +use git2::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; +use gpui::{App, Context, Entity, EventEmitter}; +use language::{Language, LanguageRegistry}; use rope::Rope; -use std::{cmp, iter, ops::Range}; +use std::{cmp, future::Future, iter, ops::Range, sync::Arc}; use sum_tree::SumTree; -use text::{Anchor, BufferSnapshot, OffsetRangeExt, Point}; - -pub use git2 as libgit; -use libgit::{DiffLineType as GitDiffLineType, DiffOptions as GitOptions, Patch as GitPatch}; +use text::{Anchor, BufferId, OffsetRangeExt, Point}; +use util::ResultExt; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum DiffHunkStatus { @@ -62,36 +64,110 @@ impl sum_tree::Summary for DiffHunkSummary { } } -#[derive(Debug, Clone)] -pub struct BufferDiff { - tree: SumTree, +#[derive(Clone)] +pub struct BufferDiffSnapshot { + hunks: SumTree, + pub base_text: Option, } -impl BufferDiff { - pub fn new(buffer: &BufferSnapshot) -> BufferDiff { - BufferDiff { - tree: SumTree::new(buffer), +impl std::fmt::Debug for BufferDiffSnapshot { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BufferDiffSnapshot") + .field("hunks", &self.hunks) + .finish() + } +} + +impl BufferDiffSnapshot { + pub fn new(buffer: &text::BufferSnapshot) -> BufferDiffSnapshot { + BufferDiffSnapshot { + hunks: SumTree::new(buffer), + base_text: None, } } - pub fn new_with_single_insertion(buffer: &BufferSnapshot) -> Self { + pub fn new_with_single_insertion(cx: &mut App) -> Self { + let base_text = language::Buffer::build_empty_snapshot(cx); Self { - tree: SumTree::from_item( + hunks: SumTree::from_item( InternalDiffHunk { buffer_range: Anchor::MIN..Anchor::MAX, diff_base_byte_range: 0..0, }, - buffer, + &base_text, ), + base_text: Some(base_text), + } + } + + #[cfg(any(test, feature = "test-support"))] + pub fn build_sync( + buffer: text::BufferSnapshot, + diff_base: String, + cx: &mut gpui::TestAppContext, + ) -> Self { + let snapshot = + cx.update(|cx| Self::build(buffer, Some(Arc::new(diff_base)), None, None, cx)); + cx.executor().block(snapshot) + } + + pub fn build( + buffer: text::BufferSnapshot, + diff_base: Option>, + language: Option>, + language_registry: Option>, + cx: &mut App, + ) -> impl Future { + let base_text_snapshot = diff_base.as_ref().map(|base_text| { + language::Buffer::build_snapshot( + Rope::from(base_text.as_str()), + language.clone(), + language_registry.clone(), + cx, + ) + }); + let base_text_snapshot = cx + .background_executor() + .spawn(OptionFuture::from(base_text_snapshot)); + + let hunks = cx.background_executor().spawn({ + let buffer = buffer.clone(); + async move { Self::recalculate_hunks(diff_base, buffer) } + }); + + async move { + let (base_text, hunks) = futures::join!(base_text_snapshot, hunks); + Self { base_text, hunks } } } - pub fn build(diff_base: Option<&str>, buffer: &text::BufferSnapshot) -> Self { - let mut tree = SumTree::new(buffer); + pub fn build_with_base_buffer( + buffer: text::BufferSnapshot, + diff_base: Option>, + diff_base_buffer: Option, + cx: &App, + ) -> impl Future { + cx.background_executor().spawn({ + let buffer = buffer.clone(); + async move { + let hunks = Self::recalculate_hunks(diff_base, buffer); + Self { + hunks, + base_text: diff_base_buffer, + } + } + }) + } + + fn recalculate_hunks( + diff_base: Option>, + buffer: text::BufferSnapshot, + ) -> SumTree { + let mut tree = SumTree::new(&buffer); if let Some(diff_base) = diff_base { let buffer_text = buffer.as_rope().to_string(); - let patch = Self::diff(diff_base, &buffer_text); + let patch = Self::diff(&diff_base, &buffer_text); // A common case in Zed is that the empty buffer is represented as just a newline, // but if we just compute a naive diff you get a "preserved" line in the middle, @@ -102,32 +178,32 @@ impl BufferDiff { buffer_range: buffer.anchor_before(0)..buffer.anchor_before(0), diff_base_byte_range: 0..diff_base.len() - 1, }, - buffer, + &buffer, ); - return Self { tree }; + return tree; } if let Some(patch) = patch { let mut divergence = 0; for hunk_index in 0..patch.num_hunks() { let hunk = - Self::process_patch_hunk(&patch, hunk_index, buffer, &mut divergence); - tree.push(hunk, buffer); + Self::process_patch_hunk(&patch, hunk_index, &buffer, &mut divergence); + tree.push(hunk, &buffer); } } } - Self { tree } + tree } pub fn is_empty(&self) -> bool { - self.tree.is_empty() + self.hunks.is_empty() } pub fn hunks_in_row_range<'a>( &'a self, range: Range, - buffer: &'a BufferSnapshot, + buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator { let start = buffer.anchor_before(Point::new(range.start, 0)); let end = buffer.anchor_after(Point::new(range.end, 0)); @@ -138,12 +214,12 @@ impl BufferDiff { pub fn hunks_intersecting_range<'a>( &'a self, range: Range, - buffer: &'a BufferSnapshot, + buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator { let range = range.to_offset(buffer); let mut cursor = self - .tree + .hunks .filter::<_, DiffHunkSummary>(buffer, move |summary| { let summary_range = summary.buffer_range.to_offset(buffer); let before_start = summary_range.end < range.start; @@ -194,10 +270,10 @@ impl BufferDiff { pub fn hunks_intersecting_range_rev<'a>( &'a self, range: Range, - buffer: &'a BufferSnapshot, + buffer: &'a text::BufferSnapshot, ) -> impl 'a + Iterator { let mut cursor = self - .tree + .hunks .filter::<_, DiffHunkSummary>(buffer, move |summary| { let before_start = summary.buffer_range.end.cmp(&range.start, buffer).is_lt(); let after_end = summary.buffer_range.start.cmp(&range.end, buffer).is_gt(); @@ -223,9 +299,13 @@ impl BufferDiff { }) } - pub fn compare(&self, old: &Self, new_snapshot: &BufferSnapshot) -> Option> { - let mut new_cursor = self.tree.cursor::<()>(new_snapshot); - let mut old_cursor = old.tree.cursor::<()>(new_snapshot); + pub fn compare( + &self, + old: &Self, + new_snapshot: &text::BufferSnapshot, + ) -> Option> { + let mut new_cursor = self.hunks.cursor::<()>(new_snapshot); + let mut old_cursor = old.hunks.cursor::<()>(new_snapshot); old_cursor.next(new_snapshot); new_cursor.next(new_snapshot); let mut start = None; @@ -288,15 +368,11 @@ impl BufferDiff { #[cfg(test)] fn clear(&mut self, buffer: &text::BufferSnapshot) { - self.tree = SumTree::new(buffer); - } - - pub fn update(&mut self, diff_base: &Rope, buffer: &text::BufferSnapshot) { - *self = Self::build(Some(&diff_base.to_string()), buffer); + self.hunks = SumTree::new(buffer); } #[cfg(test)] - fn hunks<'a>(&'a self, text: &'a BufferSnapshot) -> impl 'a + Iterator { + fn hunks<'a>(&'a self, text: &'a text::BufferSnapshot) -> impl 'a + Iterator { let start = text.anchor_before(Point::new(0, 0)); let end = text.anchor_after(Point::new(u32::MAX, u32::MAX)); self.hunks_intersecting_range(start..end, text) @@ -391,12 +467,171 @@ impl BufferDiff { } } +pub struct BufferDiff { + pub buffer_id: BufferId, + pub snapshot: BufferDiffSnapshot, + pub unstaged_diff: Option>, +} + +impl std::fmt::Debug for BufferDiff { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BufferChangeSet") + .field("buffer_id", &self.buffer_id) + .field("snapshot", &self.snapshot) + .finish() + } +} + +pub enum BufferDiffEvent { + DiffChanged { changed_range: Range }, + LanguageChanged, +} + +impl EventEmitter for BufferDiff {} + +impl BufferDiff { + pub fn set_state( + &mut self, + snapshot: BufferDiffSnapshot, + buffer: &text::BufferSnapshot, + cx: &mut Context, + ) { + if let Some(base_text) = snapshot.base_text.as_ref() { + let changed_range = if Some(base_text.remote_id()) + != self + .snapshot + .base_text + .as_ref() + .map(|buffer| buffer.remote_id()) + { + Some(text::Anchor::MIN..text::Anchor::MAX) + } else { + snapshot.compare(&self.snapshot, buffer) + }; + if let Some(changed_range) = changed_range { + cx.emit(BufferDiffEvent::DiffChanged { changed_range }); + } + } + self.snapshot = snapshot; + } + + pub fn diff_hunks_intersecting_range<'a>( + &'a self, + range: Range, + buffer_snapshot: &'a text::BufferSnapshot, + ) -> impl 'a + Iterator { + self.snapshot + .hunks_intersecting_range(range, buffer_snapshot) + } + + pub fn diff_hunks_intersecting_range_rev<'a>( + &'a self, + range: Range, + buffer_snapshot: &'a text::BufferSnapshot, + ) -> impl 'a + Iterator { + self.snapshot + .hunks_intersecting_range_rev(range, buffer_snapshot) + } + + /// Used in cases where the change set isn't derived from git. + pub fn set_base_text( + &mut self, + base_buffer: Entity, + buffer: text::BufferSnapshot, + cx: &mut Context, + ) -> oneshot::Receiver<()> { + let (tx, rx) = oneshot::channel(); + let this = cx.weak_entity(); + let base_buffer = base_buffer.read(cx); + let language_registry = base_buffer.language_registry(); + let base_buffer = base_buffer.snapshot(); + let base_text = Arc::new(base_buffer.text()); + + let snapshot = BufferDiffSnapshot::build( + buffer.clone(), + Some(base_text), + base_buffer.language().cloned(), + language_registry, + cx, + ); + let complete_on_drop = util::defer(|| { + tx.send(()).ok(); + }); + cx.spawn(|_, mut cx| async move { + let snapshot = snapshot.await; + let Some(this) = this.upgrade() else { + return; + }; + this.update(&mut cx, |this, cx| { + this.set_state(snapshot, &buffer, cx); + }) + .log_err(); + drop(complete_on_drop) + }) + .detach(); + rx + } + + #[cfg(any(test, feature = "test-support"))] + pub fn base_text_string(&self) -> Option { + self.snapshot.base_text.as_ref().map(|buffer| buffer.text()) + } + + pub fn new(buffer: &Entity, cx: &mut App) -> Self { + BufferDiff { + buffer_id: buffer.read(cx).remote_id(), + snapshot: BufferDiffSnapshot::new(&buffer.read(cx)), + unstaged_diff: None, + } + } + + #[cfg(any(test, feature = "test-support"))] + pub fn new_with_base_text( + base_text: &str, + buffer: &Entity, + cx: &mut App, + ) -> Self { + let mut base_text = base_text.to_owned(); + text::LineEnding::normalize(&mut base_text); + let snapshot = BufferDiffSnapshot::build( + buffer.read(cx).text_snapshot(), + Some(base_text.into()), + None, + None, + cx, + ); + let snapshot = cx.background_executor().block(snapshot); + BufferDiff { + buffer_id: buffer.read(cx).remote_id(), + snapshot, + unstaged_diff: None, + } + } + + #[cfg(any(test, feature = "test-support"))] + pub fn recalculate_diff_sync(&mut self, buffer: text::BufferSnapshot, cx: &mut Context) { + let base_text = self + .snapshot + .base_text + .as_ref() + .map(|base_text| base_text.text()); + let snapshot = BufferDiffSnapshot::build_with_base_buffer( + buffer.clone(), + base_text.clone().map(Arc::new), + self.snapshot.base_text.clone(), + cx, + ); + let snapshot = cx.background_executor().block(snapshot); + self.set_state(snapshot, &buffer, cx); + } +} + /// Range (crossing new lines), old, new #[cfg(any(test, feature = "test-support"))] #[track_caller] pub fn assert_hunks( diff_hunks: Iter, - buffer: &BufferSnapshot, + buffer: &text::BufferSnapshot, diff_base: &str, expected_hunks: &[(Range, &str, &str)], ) where @@ -429,18 +664,18 @@ mod tests { use std::assert_eq; use super::*; + use gpui::TestAppContext; use text::{Buffer, BufferId}; use unindent::Unindent as _; - #[test] - fn test_buffer_diff_simple() { + #[gpui::test] + async fn test_buffer_diff_simple(cx: &mut gpui::TestAppContext) { let diff_base = " one two three " .unindent(); - let diff_base_rope = Rope::from(diff_base.clone()); let buffer_text = " one @@ -450,8 +685,7 @@ mod tests { .unindent(); let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text); - let mut diff = BufferDiff::new(&buffer); - diff.update(&diff_base_rope, &buffer); + let mut diff = BufferDiffSnapshot::build_sync(buffer.clone(), diff_base.clone(), cx); assert_hunks( diff.hunks(&buffer), &buffer, @@ -460,7 +694,7 @@ mod tests { ); buffer.edit([(0..0, "point five\n")]); - diff.update(&diff_base_rope, &buffer); + diff = BufferDiffSnapshot::build_sync(buffer.clone(), diff_base.clone(), cx); assert_hunks( diff.hunks(&buffer), &buffer, @@ -472,9 +706,10 @@ mod tests { assert_hunks(diff.hunks(&buffer), &buffer, &diff_base, &[]); } - #[test] - fn test_buffer_diff_range() { - let diff_base = " + #[gpui::test] + async fn test_buffer_diff_range(cx: &mut TestAppContext) { + let diff_base = Arc::new( + " one two three @@ -486,8 +721,8 @@ mod tests { nine ten " - .unindent(); - let diff_base_rope = Rope::from(diff_base.clone()); + .unindent(), + ); let buffer_text = " A @@ -511,8 +746,17 @@ mod tests { .unindent(); let buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text); - let mut diff = BufferDiff::new(&buffer); - diff.update(&diff_base_rope, &buffer); + let diff = cx + .update(|cx| { + BufferDiffSnapshot::build( + buffer.snapshot(), + Some(diff_base.clone()), + None, + None, + cx, + ) + }) + .await; assert_eq!(diff.hunks(&buffer).count(), 8); assert_hunks( @@ -527,8 +771,8 @@ mod tests { ); } - #[test] - fn test_buffer_diff_compare() { + #[gpui::test] + async fn test_buffer_diff_compare(cx: &mut TestAppContext) { let base_text = " zero one @@ -557,8 +801,8 @@ mod tests { let mut buffer = Buffer::new(0, BufferId::new(1).unwrap(), buffer_text_1); - let empty_diff = BufferDiff::new(&buffer); - let diff_1 = BufferDiff::build(Some(&base_text), &buffer); + let empty_diff = BufferDiffSnapshot::new(&buffer); + let diff_1 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx); let range = diff_1.compare(&empty_diff, &buffer).unwrap(); assert_eq!(range.to_point(&buffer), Point::new(0, 0)..Point::new(8, 0)); @@ -576,7 +820,7 @@ mod tests { " .unindent(), ); - let diff_2 = BufferDiff::build(Some(&base_text), &buffer); + let diff_2 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx); assert_eq!(None, diff_2.compare(&diff_1, &buffer)); // Edit turns a deletion hunk into a modification. @@ -593,7 +837,7 @@ mod tests { " .unindent(), ); - let diff_3 = BufferDiff::build(Some(&base_text), &buffer); + let diff_3 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx); let range = diff_3.compare(&diff_2, &buffer).unwrap(); assert_eq!(range.to_point(&buffer), Point::new(1, 0)..Point::new(2, 0)); @@ -610,7 +854,7 @@ mod tests { " .unindent(), ); - let diff_4 = BufferDiff::build(Some(&base_text), &buffer); + let diff_4 = BufferDiffSnapshot::build_sync(buffer.clone(), base_text.clone(), cx); let range = diff_4.compare(&diff_3, &buffer).unwrap(); assert_eq!(range.to_point(&buffer), Point::new(3, 4)..Point::new(4, 0)); @@ -628,7 +872,7 @@ mod tests { " .unindent(), ); - let diff_5 = BufferDiff::build(Some(&base_text), &buffer); + let diff_5 = BufferDiffSnapshot::build_sync(buffer.snapshot(), base_text.clone(), cx); let range = diff_5.compare(&diff_4, &buffer).unwrap(); assert_eq!(range.to_point(&buffer), Point::new(3, 0)..Point::new(4, 0)); @@ -646,7 +890,7 @@ mod tests { " .unindent(), ); - let diff_6 = BufferDiff::build(Some(&base_text), &buffer); + let diff_6 = BufferDiffSnapshot::build_sync(buffer.snapshot(), base_text, cx); let range = diff_6.compare(&diff_5, &buffer).unwrap(); assert_eq!(range.to_point(&buffer), Point::new(7, 0)..Point::new(8, 0)); } diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 756fb9da7f962a98405c644e823a2e92720ec0af..d78dff8d2a1a0585b180d40346469bbee167d831 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -38,6 +38,7 @@ clock.workspace = true collections.workspace = true convert_case.workspace = true db.workspace = true +diff.workspace = true emojis.workspace = true file_icons.workspace = true futures.workspace = true diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 8ac797b72cbbd5c496c68bda204c065c1b320a68..d6ed7d27d9366ab0e1e37304ab8a1b0116462a2d 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -47,7 +47,6 @@ mod signature_help; #[cfg(any(test, feature = "test-support"))] pub mod test; -use ::git::diff::DiffHunkStatus; pub(crate) use actions::*; pub use actions::{OpenExcerpts, OpenExcerptsSplit}; use aho_corasick::AhoCorasick; @@ -74,6 +73,7 @@ use code_context_menus::{ AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu, CompletionsMenu, ContextMenuOrigin, }; +use diff::DiffHunkStatus; use git::blame::GitBlame; use gpui::{ div, impl_actions, linear_color_stop, linear_gradient, point, prelude::*, pulsating_between, @@ -1287,7 +1287,7 @@ impl Editor { let mut code_action_providers = Vec::new(); if let Some(project) = project.clone() { - get_uncommitted_changes_for_buffer( + get_uncommitted_diff_for_buffer( &project, buffer.read(cx).all_buffers(), buffer.clone(), @@ -6773,11 +6773,12 @@ impl Editor { cx: &mut App, ) -> Option<()> { let buffer = self.buffer.read(cx); - let change_set = buffer.change_set_for(hunk.buffer_id)?; + let diff = buffer.diff_for(hunk.buffer_id)?; let buffer = buffer.buffer(hunk.buffer_id)?; let buffer = buffer.read(cx); - let original_text = change_set + let original_text = diff .read(cx) + .snapshot .base_text .as_ref()? .as_rope() @@ -13731,9 +13732,9 @@ impl Editor { } => { self.tasks_update_task = Some(self.refresh_runnables(window, cx)); let buffer_id = buffer.read(cx).remote_id(); - if self.buffer.read(cx).change_set_for(buffer_id).is_none() { + if self.buffer.read(cx).diff_for(buffer_id).is_none() { if let Some(project) = &self.project { - get_uncommitted_changes_for_buffer( + get_uncommitted_diff_for_buffer( project, [buffer.clone()], self.buffer.clone(), @@ -14492,7 +14493,7 @@ impl Editor { } } -fn get_uncommitted_changes_for_buffer( +fn get_uncommitted_diff_for_buffer( project: &Entity, buffers: impl IntoIterator>, buffer: Entity, @@ -14501,15 +14502,15 @@ fn get_uncommitted_changes_for_buffer( let mut tasks = Vec::new(); project.update(cx, |project, cx| { for buffer in buffers { - tasks.push(project.open_uncommitted_changes(buffer.clone(), cx)) + tasks.push(project.open_uncommitted_diff(buffer.clone(), cx)) } }); cx.spawn(|mut cx| async move { - let change_sets = futures::future::join_all(tasks).await; + let diffs = futures::future::join_all(tasks).await; buffer .update(&mut cx, |buffer, cx| { - for change_set in change_sets.into_iter().flatten() { - buffer.add_change_set(change_set, cx); + for diff in diffs.into_iter().flatten() { + buffer.add_diff(diff, cx); } }) .ok(); diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 491510ed32b2b8efdef05ea0908a8c654e37e172..1c4839f4f9db57265d01958ed3d216b8bcb88fb2 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -7,6 +7,7 @@ use crate::{ }, JoinLines, }; +use diff::{BufferDiff, DiffHunkStatus}; use futures::StreamExt; use gpui::{ div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext, @@ -26,7 +27,7 @@ use language_settings::{Formatter, FormatterList, IndentGuideSettings}; use multi_buffer::IndentGuide; use parking_lot::Mutex; use pretty_assertions::{assert_eq, assert_ne}; -use project::{buffer_store::BufferChangeSet, FakeFs}; +use project::FakeFs; use project::{ lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT, project_settings::{LspSettings, ProjectSettings}, @@ -12440,11 +12441,10 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) { (buffer_2.clone(), base_text_2), (buffer_3.clone(), base_text_3), ] { - let change_set = - cx.new(|cx| BufferChangeSet::new_with_base_text(&diff_base, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx)); editor .buffer - .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx)); + .update(cx, |buffer, cx| buffer.add_diff(diff, cx)); } }); cx.executor().run_until_parked(); @@ -13134,11 +13134,10 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext) (buffer_2.clone(), file_2_old), (buffer_3.clone(), file_3_old), ] { - let change_set = - cx.new(|cx| BufferChangeSet::new_with_base_text(&diff_base, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx)); editor .buffer - .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx)); + .update(cx, |buffer, cx| buffer.add_diff(diff, cx)); } }) .unwrap(); @@ -13251,10 +13250,10 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext }); editor .update(cx, |editor, _window, cx| { - let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx)); editor .buffer - .update(cx, |buffer, cx| buffer.add_change_set(change_set, cx)) + .update(cx, |buffer, cx| buffer.add_diff(diff, cx)) }) .unwrap(); @@ -14420,11 +14419,10 @@ async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut gpui::TestAppContex editor.buffer().update(cx, |multibuffer, cx| { let buffer = multibuffer.as_singleton().unwrap(); - let change_set = - cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); multibuffer.set_all_diff_hunks_expanded(cx); - multibuffer.add_change_set(change_set, cx); + multibuffer.add_diff(diff, cx); buffer.read(cx).remote_id() }) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 96d736888e82c69ab014ffdef4035161d60a8c45..c95b70b26338643671c63307549155304977c1ac 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -26,8 +26,9 @@ use crate::{ }; use client::ParticipantIndex; use collections::{BTreeMap, HashMap, HashSet}; +use diff::DiffHunkStatus; use file_icons::FileIcons; -use git::{blame::BlameEntry, diff::DiffHunkStatus, Oid}; +use git::{blame::BlameEntry, Oid}; use gpui::{ anchored, deferred, div, fill, linear_color_stop, linear_gradient, outline, point, px, quad, relative, size, svg, transparent_black, Action, AnyElement, App, AvailableSpace, Axis, Bounds, diff --git a/crates/editor/src/hunk_diff.rs b/crates/editor/src/hunk_diff.rs index d6e6be1c863ead9f50e44e73b237a72b219a3552..8bed3e2ccb8054427e35db0ba264c23152cafcef 100644 --- a/crates/editor/src/hunk_diff.rs +++ b/crates/editor/src/hunk_diff.rs @@ -56,7 +56,7 @@ pub(super) struct ExpandedHunk { pub(crate) struct DiffMapSnapshot(TreeMap); pub(crate) struct DiffBaseState { - pub(crate) change_set: Model, + pub(crate) diff: Model, pub(crate) last_version: Option, _subscription: Subscription, } @@ -80,38 +80,29 @@ impl DiffMap { self.snapshot.clone() } - pub fn add_change_set( + pub fn add_diff( &mut self, - change_set: Model, + diff: Model, window: &mut Window, cx: &mut Context, ) { - let buffer_id = change_set.read(cx).buffer_id; + let buffer_id = diff.read(cx).buffer_id; self.snapshot .0 - .insert(buffer_id, change_set.read(cx).diff_to_buffer.clone()); + .insert(buffer_id, diff.read(cx).diff_to_buffer.clone()); self.diff_bases.insert( buffer_id, DiffBaseState { last_version: None, - _subscription: cx.observe_in( - &change_set, - window, - move |editor, change_set, window, cx| { - editor - .diff_map - .snapshot - .0 - .insert(buffer_id, change_set.read(cx).diff_to_buffer.clone()); - Editor::sync_expanded_diff_hunks( - &mut editor.diff_map, - buffer_id, - window, - cx, - ); - }, - ), - change_set, + _subscription: cx.observe_in(&diff, window, move |editor, diff, window, cx| { + editor + .diff_map + .snapshot + .0 + .insert(buffer_id, diff.read(cx).diff_to_buffer.clone()); + Editor::sync_expanded_diff_hunks(&mut editor.diff_map, buffer_id, window, cx); + }), + diff, }, ); Editor::sync_expanded_diff_hunks(self, buffer_id, window, cx); @@ -399,7 +390,7 @@ impl Editor { self.diff_map .diff_bases .get(&buffer_id)? - .change_set + .diff .read(cx) .base_text .clone() @@ -953,12 +944,12 @@ impl Editor { let mut diff_base_buffer = None; let mut diff_base_buffer_unchanged = true; if let Some(diff_base_state) = diff_base_state { - diff_base_state.change_set.update(cx, |change_set, _| { - if diff_base_state.last_version != Some(change_set.base_text_version) { - diff_base_state.last_version = Some(change_set.base_text_version); + diff_base_state.diff.update(cx, |diff, _| { + if diff_base_state.last_version != Some(diff.base_text_version) { + diff_base_state.last_version = Some(diff.base_text_version); diff_base_buffer_unchanged = false; } - diff_base_buffer = change_set.base_text.clone(); + diff_base_buffer = diff.base_text.clone(); }) } @@ -1498,14 +1489,14 @@ mod tests { (buffer_1.clone(), diff_base_1), (buffer_2.clone(), diff_base_2), ] { - let change_set = cx.new(|cx| { + let diff = cx.new(|cx| { BufferChangeSet::new_with_base_text( diff_base.to_string(), buffer.read(cx).text_snapshot(), cx, ) }); - editor.diff_map.add_change_set(change_set, window, cx) + editor.diff_map.add_diff(diff, window, cx) } }) .unwrap(); diff --git a/crates/editor/src/proposed_changes_editor.rs b/crates/editor/src/proposed_changes_editor.rs index 9a61656a58c34f1e0777b047adc3e6165b665a0a..2c7903296b13cc4372f48cb624512323b30bb228 100644 --- a/crates/editor/src/proposed_changes_editor.rs +++ b/crates/editor/src/proposed_changes_editor.rs @@ -1,10 +1,11 @@ use crate::{ApplyAllDiffHunks, Editor, EditorEvent, SemanticsProvider}; use collections::HashSet; +use diff::BufferDiff; use futures::{channel::mpsc, future::join_all}; use gpui::{App, Entity, EventEmitter, Focusable, Render, Subscription, Task}; use language::{Buffer, BufferEvent, Capability}; use multi_buffer::{ExcerptRange, MultiBuffer}; -use project::{buffer_store::BufferChangeSet, Project}; +use project::Project; use smol::stream::StreamExt; use std::{any::TypeId, ops::Range, rc::Rc, time::Duration}; use text::ToOffset; @@ -106,12 +107,10 @@ impl ProposedChangesEditor { let buffer = buffer.read(cx); let base_buffer = buffer.base_buffer()?; let buffer = buffer.text_snapshot(); - let change_set = this - .multibuffer - .read(cx) - .change_set_for(buffer.remote_id())?; - Some(change_set.update(cx, |change_set, cx| { - change_set.set_base_text(base_buffer.clone(), buffer, cx) + let diff = + this.multibuffer.read(cx).diff_for(buffer.remote_id())?; + Some(diff.update(cx, |diff, cx| { + diff.set_base_text(base_buffer.clone(), buffer, cx) })) }) .collect::>() @@ -172,7 +171,7 @@ impl ProposedChangesEditor { }); let mut buffer_entries = Vec::new(); - let mut new_change_sets = Vec::new(); + let mut new_diffs = Vec::new(); for location in locations { let branch_buffer; if let Some(ix) = self @@ -185,14 +184,14 @@ impl ProposedChangesEditor { buffer_entries.push(entry); } else { branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx)); - new_change_sets.push(cx.new(|cx| { - let mut change_set = BufferChangeSet::new(&branch_buffer, cx); - let _ = change_set.set_base_text( + new_diffs.push(cx.new(|cx| { + let mut diff = BufferDiff::new(&branch_buffer, cx); + let _ = diff.set_base_text( location.buffer.clone(), branch_buffer.read(cx).text_snapshot(), cx, ); - change_set + diff })); buffer_entries.push(BufferEntry { branch: branch_buffer.clone(), @@ -217,8 +216,8 @@ impl ProposedChangesEditor { self.editor.update(cx, |editor, cx| { editor.change_selections(None, window, cx, |selections| selections.refresh()); editor.buffer.update(cx, |buffer, cx| { - for change_set in new_change_sets { - buffer.add_change_set(change_set, cx) + for diff in new_diffs { + buffer.add_diff(diff, cx) } }) }); diff --git a/crates/editor/src/test/editor_test_context.rs b/crates/editor/src/test/editor_test_context.rs index c51dc0d6a62e5da2f4fc46a7e9b5ecdec1e303d1..7cfaf5622404000da500b90ea73a02a61e804f08 100644 --- a/crates/editor/src/test/editor_test_context.rs +++ b/crates/editor/src/test/editor_test_context.rs @@ -3,8 +3,9 @@ use crate::{ RowExt, }; use collections::BTreeMap; +use diff::DiffHunkStatus; use futures::Future; -use git::diff::DiffHunkStatus; + use gpui::{ prelude::*, AnyWindowHandle, App, Context, Entity, Focusable as _, Keystroke, Pixels, Point, VisualTestContext, Window, WindowHandle, diff --git a/crates/git/src/git.rs b/crates/git/src/git.rs index 3ee0d23be7b15078b5c8a4fbc9bed1f49773e522..1d0c11e813a5fe49f58c3f708a34ef016c71a8cc 100644 --- a/crates/git/src/git.rs +++ b/crates/git/src/git.rs @@ -1,6 +1,5 @@ pub mod blame; pub mod commit; -pub mod diff; mod hosting_provider; mod remote; pub mod repository; diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index 8a2519b8c007f41b9111fbbb1fe6f85c3b3d9ff7..ad4dbdf9905e40e7667738c591a3ae6b47bc1664 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -16,6 +16,7 @@ path = "src/git_ui.rs" anyhow.workspace = true collections.workspace = true db.workspace = true +diff.workspace = true editor.workspace = true feature_flags.workspace = true futures.workspace = true diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 5d2689ed4cfa59c15023f394a3c7380f2ab34aa4..74d7c26c485c273e36bef6c67fa9447a4e870a62 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -2,6 +2,7 @@ use std::any::{Any, TypeId}; use anyhow::Result; use collections::HashSet; +use diff::BufferDiff; use editor::{scroll::Autoscroll, Editor, EditorEvent}; use feature_flags::FeatureFlagViewExt; use futures::StreamExt; @@ -11,7 +12,7 @@ use gpui::{ }; use language::{Anchor, Buffer, Capability, OffsetRangeExt, Point}; use multi_buffer::{MultiBuffer, PathKey}; -use project::{buffer_store::BufferChangeSet, git::GitState, Project, ProjectPath}; +use project::{git::GitState, Project, ProjectPath}; use theme::ActiveTheme; use ui::prelude::*; use util::ResultExt as _; @@ -43,7 +44,7 @@ pub(crate) struct ProjectDiff { struct DiffBuffer { path_key: PathKey, buffer: Entity, - change_set: Entity, + diff: Entity, } const CONFLICT_NAMESPACE: &'static str = "0"; @@ -285,13 +286,13 @@ impl ProjectDiff { let buffer = load_buffer.await?; let changes = project .update(&mut cx, |project, cx| { - project.open_uncommitted_changes(buffer.clone(), cx) + project.open_uncommitted_diff(buffer.clone(), cx) })? .await?; Ok(DiffBuffer { path_key, buffer, - change_set: changes, + diff: changes, }) })); } @@ -312,15 +313,14 @@ impl ProjectDiff { ) { let path_key = diff_buffer.path_key; let buffer = diff_buffer.buffer; - let change_set = diff_buffer.change_set; + let diff = diff_buffer.diff; let snapshot = buffer.read(cx).snapshot(); - let change_set = change_set.read(cx); - let diff_hunk_ranges = if change_set.base_text.is_none() { + let diff = diff.read(cx); + let diff_hunk_ranges = if diff.snapshot.base_text.is_none() { vec![Point::zero()..snapshot.max_point()] } else { - change_set - .diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot) + diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot) .map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot)) .collect::>() }; diff --git a/crates/multi_buffer/Cargo.toml b/crates/multi_buffer/Cargo.toml index b8b625378d78592d4f6c4619344883f2fbd250f9..c9e1be241ea619b37e0c7fc9fa912df34b658e49 100644 --- a/crates/multi_buffer/Cargo.toml +++ b/crates/multi_buffer/Cargo.toml @@ -14,9 +14,10 @@ doctest = false [features] test-support = [ - "text/test-support", - "language/test-support", + "diff/test-support", "gpui/test-support", + "language/test-support", + "text/test-support", "util/test-support", ] @@ -25,15 +26,14 @@ anyhow.workspace = true clock.workspace = true collections.workspace = true ctor.workspace = true +diff.workspace = true env_logger.workspace = true futures.workspace = true -git.workspace = true gpui.workspace = true itertools.workspace = true language.workspace = true log.workspace = true parking_lot.workspace = true -project.workspace = true rand.workspace = true rope.workspace = true smol.workspace = true @@ -47,12 +47,13 @@ tree-sitter.workspace = true util.workspace = true [dev-dependencies] +diff = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } +indoc.workspace = true language = { workspace = true, features = ["test-support"] } +pretty_assertions.workspace = true project = { workspace = true, features = ["test-support"] } rand.workspace = true settings = { workspace = true, features = ["test-support"] } text = { workspace = true, features = ["test-support"] } util = { workspace = true, features = ["test-support"] } -pretty_assertions.workspace = true -indoc.workspace = true diff --git a/crates/multi_buffer/src/anchor.rs b/crates/multi_buffer/src/anchor.rs index 423f4af31f48a3ba97578c569429e1c1b355641d..ca6bc8cbf65c46cf5dba9f836fe6d74dd4a6fd97 100644 --- a/crates/multi_buffer/src/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -70,15 +70,15 @@ impl Anchor { return text_cmp; } if self.diff_base_anchor.is_some() || other.diff_base_anchor.is_some() { - if let Some(diff_base) = snapshot.diffs.get(&excerpt.buffer_id) { - let self_anchor = self - .diff_base_anchor - .filter(|a| diff_base.base_text.can_resolve(a)); - let other_anchor = other - .diff_base_anchor - .filter(|a| diff_base.base_text.can_resolve(a)); + if let Some(base_text) = snapshot + .diffs + .get(&excerpt.buffer_id) + .and_then(|diff| diff.base_text.as_ref()) + { + 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)); return match (self_anchor, other_anchor) { - (Some(a), Some(b)) => a.cmp(&b, &diff_base.base_text), + (Some(a), Some(b)) => a.cmp(&b, base_text), (Some(_), None) => match other.text_anchor.bias { Bias::Left => Ordering::Greater, Bias::Right => Ordering::Less, @@ -107,9 +107,13 @@ impl Anchor { excerpt_id: self.excerpt_id, text_anchor: self.text_anchor.bias_left(&excerpt.buffer), diff_base_anchor: self.diff_base_anchor.map(|a| { - if let Some(base) = snapshot.diffs.get(&excerpt.buffer_id) { - if a.buffer_id == Some(base.base_text.remote_id()) { - return a.bias_left(&base.base_text); + if let Some(base_text) = snapshot + .diffs + .get(&excerpt.buffer_id) + .and_then(|diff| diff.base_text.as_ref()) + { + if a.buffer_id == Some(base_text.remote_id()) { + return a.bias_left(base_text); } } a @@ -128,9 +132,13 @@ impl Anchor { excerpt_id: self.excerpt_id, text_anchor: self.text_anchor.bias_right(&excerpt.buffer), diff_base_anchor: self.diff_base_anchor.map(|a| { - if let Some(base) = snapshot.diffs.get(&excerpt.buffer_id) { - if a.buffer_id == Some(base.base_text.remote_id()) { - return a.bias_right(&base.base_text); + if let Some(base_text) = snapshot + .diffs + .get(&excerpt.buffer_id) + .and_then(|diff| diff.base_text.as_ref()) + { + if a.buffer_id == Some(base_text.remote_id()) { + return a.bias_right(&base_text); } } a diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index 1986944a1d6e95f2b7dfd6a28d0c58ac54ca2e53..4f0f3a18bddb6c725c9ccbbdf3c6441d7594dffa 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -9,8 +9,8 @@ pub use position::{TypedOffset, TypedPoint, TypedRow}; use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{BTreeMap, Bound, HashMap, HashSet}; +use diff::{BufferDiff, BufferDiffEvent, BufferDiffSnapshot, DiffHunkStatus}; use futures::{channel::mpsc, SinkExt}; -use git::diff::DiffHunkStatus; use gpui::{App, Context, Entity, EntityId, EventEmitter, Task}; use itertools::Itertools; use language::{ @@ -21,7 +21,7 @@ use language::{ TextDimension, TextObject, ToOffset as _, ToPoint as _, TransactionId, TreeSitterOptions, Unclipped, }; -use project::buffer_store::{BufferChangeSet, BufferChangeSetEvent}; + use rope::DimensionPair; use smallvec::SmallVec; use smol::future::yield_now; @@ -68,7 +68,7 @@ pub struct MultiBuffer { buffers: RefCell>, // only used by consumers using `set_excerpts_for_buffer` buffers_by_path: BTreeMap>, - diff_bases: HashMap, + diffs: HashMap, all_diff_hunks_expanded: bool, subscriptions: Topic, /// If true, the multi-buffer only contains a single [`Buffer`] and a single [`Excerpt`] @@ -215,23 +215,21 @@ struct BufferState { _subscriptions: [gpui::Subscription; 2], } -struct ChangeSetState { - change_set: Entity, +struct DiffState { + diff: Entity, _subscription: gpui::Subscription, } -impl ChangeSetState { - fn new(change_set: Entity, cx: &mut Context) -> Self { - ChangeSetState { - _subscription: cx.subscribe(&change_set, |this, change_set, event, cx| match event { - BufferChangeSetEvent::DiffChanged { changed_range } => { - this.buffer_diff_changed(change_set, changed_range.clone(), cx) - } - BufferChangeSetEvent::LanguageChanged => { - this.buffer_diff_language_changed(change_set, cx) +impl DiffState { + fn new(diff: Entity, cx: &mut Context) -> Self { + DiffState { + _subscription: cx.subscribe(&diff, |this, diff, event, cx| match event { + BufferDiffEvent::DiffChanged { changed_range } => { + this.buffer_diff_changed(diff, changed_range.clone(), cx) } + BufferDiffEvent::LanguageChanged => this.buffer_diff_language_changed(diff, cx), }), - change_set, + diff, } } } @@ -242,7 +240,7 @@ pub struct MultiBufferSnapshot { singleton: bool, excerpts: SumTree, excerpt_ids: SumTree, - diffs: TreeMap, + diffs: TreeMap, pub diff_transforms: SumTree, trailing_excerpt_update_count: usize, non_text_state_update_count: usize, @@ -268,12 +266,6 @@ pub enum DiffTransform { }, } -#[derive(Clone)] -struct DiffSnapshot { - diff: git::diff::BufferDiff, - base_text: language::BufferSnapshot, -} - #[derive(Clone)] pub struct ExcerptInfo { pub id: ExcerptId, @@ -318,7 +310,7 @@ pub struct RowInfo { pub buffer_id: Option, pub buffer_row: Option, pub multibuffer_row: Option, - pub diff_status: Option, + pub diff_status: Option, } /// A slice into a [`Buffer`] that is being edited in a [`MultiBuffer`]. @@ -397,7 +389,7 @@ pub struct MultiBufferRows<'a> { pub struct MultiBufferChunks<'a> { excerpts: Cursor<'a, Excerpt, ExcerptOffset>, diff_transforms: Cursor<'a, DiffTransform, (usize, ExcerptOffset)>, - diffs: &'a TreeMap, + diffs: &'a TreeMap, diff_base_chunks: Option<(BufferId, BufferChunks<'a>)>, buffer_chunk: Option>, range: Range, @@ -431,7 +423,7 @@ pub struct ReversedMultiBufferBytes<'a> { struct MultiBufferCursor<'a, D: TextDimension> { excerpts: Cursor<'a, Excerpt, ExcerptDimension>, diff_transforms: Cursor<'a, DiffTransform, (OutputDimension, ExcerptDimension)>, - diffs: &'a TreeMap, + diffs: &'a TreeMap, cached_region: Option>, } @@ -517,7 +509,7 @@ impl MultiBuffer { ..MultiBufferSnapshot::default() }), buffers: RefCell::default(), - diff_bases: HashMap::default(), + diffs: HashMap::default(), all_diff_hunks_expanded: false, subscriptions: Topic::default(), singleton: false, @@ -539,7 +531,7 @@ impl MultiBuffer { snapshot: Default::default(), buffers: Default::default(), buffers_by_path: Default::default(), - diff_bases: HashMap::default(), + diffs: HashMap::default(), all_diff_hunks_expanded: false, subscriptions: Default::default(), singleton: false, @@ -573,17 +565,14 @@ impl MultiBuffer { ); } let mut diff_bases = HashMap::default(); - for (buffer_id, change_set_state) in self.diff_bases.iter() { - diff_bases.insert( - *buffer_id, - ChangeSetState::new(change_set_state.change_set.clone(), new_cx), - ); + for (buffer_id, diff) in self.diffs.iter() { + diff_bases.insert(*buffer_id, DiffState::new(diff.diff.clone(), new_cx)); } Self { snapshot: RefCell::new(self.snapshot.borrow().clone()), buffers: RefCell::new(buffers), buffers_by_path: Default::default(), - diff_bases, + diffs: diff_bases, all_diff_hunks_expanded: self.all_diff_hunks_expanded, subscriptions: Default::default(), singleton: self.singleton, @@ -2152,71 +2141,49 @@ impl MultiBuffer { }); } - fn buffer_diff_language_changed( - &mut self, - change_set: Entity, - cx: &mut Context, - ) { + fn buffer_diff_language_changed(&mut self, diff: Entity, cx: &mut Context) { self.sync(cx); let mut snapshot = self.snapshot.borrow_mut(); - let change_set = change_set.read(cx); - let buffer_id = change_set.buffer_id; - let base_text = change_set.base_text.clone(); - let diff = change_set.diff_to_buffer.clone(); - if let Some(base_text) = base_text { - snapshot.diffs.insert( - buffer_id, - DiffSnapshot { - diff: diff.clone(), - base_text, - }, - ); - } else { - snapshot.diffs.remove(&buffer_id); - } + let diff = diff.read(cx); + let buffer_id = diff.buffer_id; + let diff = diff.snapshot.clone(); + snapshot.diffs.insert(buffer_id, diff); } fn buffer_diff_changed( &mut self, - change_set: Entity, + diff: Entity, range: Range, cx: &mut Context, ) { - let change_set = change_set.read(cx); - let buffer_id = change_set.buffer_id; - let diff = change_set.diff_to_buffer.clone(); - let base_text = change_set.base_text.clone(); self.sync(cx); - let mut snapshot = self.snapshot.borrow_mut(); - let base_text_changed = snapshot - .diffs - .get(&buffer_id) - .map_or(true, |diff_snapshot| { - change_set.base_text.as_ref().map_or(true, |base_text| { - base_text.remote_id() != diff_snapshot.base_text.remote_id() - }) - }); - if let Some(base_text) = base_text { - snapshot.diffs.insert( - buffer_id, - DiffSnapshot { - diff: diff.clone(), - base_text, - }, - ); - } else if self.all_diff_hunks_expanded { - let base_text = Buffer::build_empty_snapshot(cx); - snapshot.diffs.insert( - buffer_id, - DiffSnapshot { - diff: git::diff::BufferDiff::new_with_single_insertion(&base_text), - base_text, - }, - ); - } else { - snapshot.diffs.remove(&buffer_id); + let diff = diff.read(cx); + let buffer_id = diff.buffer_id; + let mut diff = diff.snapshot.clone(); + if diff.base_text.is_none() && self.all_diff_hunks_expanded { + diff = BufferDiffSnapshot::new_with_single_insertion(cx); } + + let mut snapshot = self.snapshot.borrow_mut(); + let base_text_changed = + snapshot + .diffs + .get(&buffer_id) + .map_or(true, |diff_snapshot| { + match (&diff_snapshot.base_text, &diff.base_text) { + (None, None) => false, + (None, Some(_)) => true, + (Some(_), None) => true, + (Some(old), Some(new)) => { + let (old_id, old_empty) = (old.remote_id(), old.is_empty()); + let (new_id, new_empty) = (new.remote_id(), new.is_empty()); + new_id != old_id && (!new_empty || !old_empty) + } + } + }); + snapshot.diffs.insert(buffer_id, diff); + let buffers = self.buffers.borrow(); let Some(buffer_state) = buffers.get(&buffer_id) else { return; @@ -2352,17 +2319,14 @@ impl MultiBuffer { self.as_singleton().unwrap().read(cx).is_parsing() } - pub fn add_change_set(&mut self, change_set: Entity, cx: &mut Context) { - let buffer_id = change_set.read(cx).buffer_id; - self.buffer_diff_changed(change_set.clone(), text::Anchor::MIN..text::Anchor::MAX, cx); - self.diff_bases - .insert(buffer_id, ChangeSetState::new(change_set, cx)); + pub fn add_diff(&mut self, diff: Entity, cx: &mut Context) { + let buffer_id = diff.read(cx).buffer_id; + self.buffer_diff_changed(diff.clone(), text::Anchor::MIN..text::Anchor::MAX, cx); + self.diffs.insert(buffer_id, DiffState::new(diff, cx)); } - pub fn change_set_for(&self, buffer_id: BufferId) -> Option> { - self.diff_bases - .get(&buffer_id) - .map(|state| state.change_set.clone()) + pub fn diff_for(&self, buffer_id: BufferId) -> Option> { + self.diffs.get(&buffer_id).map(|state| state.diff.clone()) } pub fn expand_diff_hunks(&mut self, ranges: Vec>, cx: &mut Context) { @@ -2920,9 +2884,11 @@ impl MultiBuffer { while let Some(excerpt) = excerpts.item() { // Recompute the expanded hunks in the portion of the excerpt that // intersects the edit. - if let Some(diff_state) = snapshot.diffs.get(&excerpt.buffer_id) { - let diff = &diff_state.diff; - let base_text = &diff_state.base_text; + if let Some((diff, base_text)) = snapshot + .diffs + .get(&excerpt.buffer_id) + .and_then(|diff| Some((diff, diff.base_text.as_ref()?))) + { let buffer = &excerpt.buffer; let excerpt_start = *excerpts.start(); let excerpt_end = excerpt_start + ExcerptOffset::new(excerpt.text_summary.len); @@ -3445,8 +3411,7 @@ impl MultiBufferSnapshot { let buffer_start = buffer.anchor_before(buffer_range.start); let buffer_end = buffer.anchor_after(buffer_range.end); Some( - diff.diff - .hunks_intersecting_range(buffer_start..buffer_end, buffer) + diff.hunks_intersecting_range(buffer_start..buffer_end, buffer) .map(|hunk| { ( Point::new(hunk.row_range.start, 0)..Point::new(hunk.row_range.end, 0), @@ -3782,8 +3747,8 @@ impl MultiBufferSnapshot { let buffer_end = excerpt.buffer.anchor_before(buffer_offset); let buffer_end_row = buffer_end.to_point(&excerpt.buffer).row; - if let Some(diff_state) = self.diffs.get(&excerpt.buffer_id) { - for hunk in diff_state.diff.hunks_intersecting_range_rev( + if let Some(diff) = self.diffs.get(&excerpt.buffer_id) { + for hunk in diff.hunks_intersecting_range_rev( excerpt.range.context.start..buffer_end, &excerpt.buffer, ) { @@ -3851,7 +3816,7 @@ impl MultiBufferSnapshot { } pub fn has_diff_hunks(&self) -> bool { - self.diffs.values().any(|diff| !diff.diff.is_empty()) + self.diffs.values().any(|diff| !diff.is_empty()) } pub fn surrounding_word( @@ -4313,7 +4278,11 @@ impl MultiBufferSnapshot { } => { let buffer_start = base_text_byte_range.start + start_overshoot; let mut buffer_end = base_text_byte_range.start + end_overshoot; - let Some(buffer_diff) = self.diffs.get(buffer_id) else { + let Some(base_text) = self + .diffs + .get(buffer_id) + .and_then(|diff| diff.base_text.as_ref()) + else { panic!("{:?} is in non-existent deleted hunk", range.start) }; @@ -4323,9 +4292,8 @@ impl MultiBufferSnapshot { buffer_end -= 1; } - let mut summary = buffer_diff - .base_text - .text_summary_for_range::(buffer_start..buffer_end); + let mut summary = + base_text.text_summary_for_range::(buffer_start..buffer_end); if include_trailing_newline { summary.add_assign(&D::from_text_summary(&TextSummary::newline())) @@ -4362,12 +4330,15 @@ impl MultiBufferSnapshot { .. } => { let buffer_end = base_text_byte_range.start + overshoot; - let Some(buffer_diff) = self.diffs.get(buffer_id) else { - panic!("{:?} is in non-extant deleted hunk", range.end) + let Some(base_text) = self + .diffs + .get(buffer_id) + .and_then(|diff| diff.base_text.as_ref()) + else { + panic!("{:?} is in non-existent deleted hunk", range.end) }; - let mut suffix = buffer_diff - .base_text + let mut suffix = base_text .text_summary_for_range::(base_text_byte_range.start..buffer_end); if *has_trailing_newline && buffer_end == base_text_byte_range.end + 1 { suffix.add_assign(&D::from_text_summary(&TextSummary::newline())) @@ -4467,14 +4438,18 @@ impl MultiBufferSnapshot { }) => { let mut in_deleted_hunk = false; if let Some(diff_base_anchor) = &anchor.diff_base_anchor { - if let Some(diff) = self.diffs.get(buffer_id) { - if diff.base_text.can_resolve(&diff_base_anchor) { - let base_text_offset = diff_base_anchor.to_offset(&diff.base_text); + if let Some(base_text) = self + .diffs + .get(buffer_id) + .and_then(|diff| diff.base_text.as_ref()) + { + if base_text.can_resolve(&diff_base_anchor) { + let base_text_offset = diff_base_anchor.to_offset(&base_text); if base_text_offset >= base_text_byte_range.start && base_text_offset <= base_text_byte_range.end { - let position_in_hunk = - diff.base_text.text_summary_for_range::( + let position_in_hunk = base_text + .text_summary_for_range::( base_text_byte_range.start..base_text_offset, ); position.add_assign(&position_in_hunk); @@ -4800,15 +4775,17 @@ impl MultiBufferSnapshot { .. }) = diff_transforms.item() { - let diff_base = self.diffs.get(buffer_id).expect("missing diff base"); + let base_text = self + .diffs + .get(buffer_id) + .and_then(|diff| diff.base_text.as_ref()) + .expect("missing diff base"); if offset_in_transform > base_text_byte_range.len() { debug_assert!(*has_trailing_newline); bias = Bias::Right; } else { diff_base_anchor = Some( - diff_base - .base_text - .anchor_at(base_text_byte_range.start + offset_in_transform, bias), + base_text.anchor_at(base_text_byte_range.start + offset_in_transform, bias), ); bias = Bias::Left; } @@ -6144,7 +6121,7 @@ where .. } => { let diff = self.diffs.get(&buffer_id)?; - let buffer = &diff.base_text; + let buffer = diff.base_text.as_ref()?; let mut rope_cursor = buffer.as_rope().cursor(0); let buffer_start = rope_cursor.summary::(base_text_byte_range.start); let buffer_range_len = rope_cursor.summary::(base_text_byte_range.end); @@ -7186,7 +7163,7 @@ impl<'a> Iterator for MultiBufferChunks<'a> { } chunks } else { - let base_buffer = &self.diffs.get(&buffer_id)?.base_text; + let base_buffer = &self.diffs.get(&buffer_id)?.base_text.as_ref()?; base_buffer.chunks(base_text_start..base_text_end, self.language_aware) }; diff --git a/crates/multi_buffer/src/multi_buffer_tests.rs b/crates/multi_buffer/src/multi_buffer_tests.rs index 25c3a4cf9124674b9409dc9789c96297a9978dfa..d98a9db30a5272b3e9498b86fb6345692fe96928 100644 --- a/crates/multi_buffer/src/multi_buffer_tests.rs +++ b/crates/multi_buffer/src/multi_buffer_tests.rs @@ -1,5 +1,5 @@ use super::*; -use git::diff::DiffHunkStatus; +use diff::DiffHunkStatus; use gpui::{App, TestAppContext}; use indoc::indoc; use language::{Buffer, Rope}; @@ -361,11 +361,9 @@ fn test_diff_boundary_anchors(cx: &mut TestAppContext) { let base_text = "one\ntwo\nthree\n"; let text = "one\nthree\n"; let buffer = cx.new(|cx| Buffer::local(text, cx)); - let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); - multibuffer.update(cx, |multibuffer, cx| { - multibuffer.add_change_set(change_set, cx) - }); + multibuffer.update(cx, |multibuffer, cx| multibuffer.add_diff(diff, cx)); let (before, after) = multibuffer.update(cx, |multibuffer, cx| { let before = multibuffer.snapshot(cx).anchor_before(Point::new(1, 0)); @@ -405,14 +403,14 @@ fn test_diff_hunks_in_range(cx: &mut TestAppContext) { let base_text = "one\ntwo\nthree\nfour\nfive\nsix\nseven\neight\n"; let text = "one\nfour\nseven\n"; let buffer = cx.new(|cx| Buffer::local(text, cx)); - let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx)); let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| { (multibuffer.snapshot(cx), multibuffer.subscribe()) }); multibuffer.update(cx, |multibuffer, cx| { - multibuffer.add_change_set(change_set, cx); + multibuffer.add_diff(diff, cx); multibuffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx); }); @@ -498,11 +496,11 @@ fn test_editing_text_in_diff_hunks(cx: &mut TestAppContext) { let base_text = "one\ntwo\nfour\nfive\nsix\nseven\n"; let text = "one\ntwo\nTHREE\nfour\nfive\nseven\n"; let buffer = cx.new(|cx| Buffer::local(text, cx)); - let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(&base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(&base_text, &buffer, cx)); let multibuffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx)); let (mut snapshot, mut subscription) = multibuffer.update(cx, |multibuffer, cx| { - multibuffer.add_change_set(change_set.clone(), cx); + multibuffer.add_diff(diff.clone(), cx); (multibuffer.snapshot(cx), multibuffer.subscribe()) }); @@ -979,10 +977,10 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) { let buffer = cx.new(|cx| Buffer::local("", cx)); let base_text = "a\nb\nc"; - let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); multibuffer.update(cx, |multibuffer, cx| { multibuffer.set_all_diff_hunks_expanded(cx); - multibuffer.add_change_set(change_set.clone(), cx); + multibuffer.add_diff(diff.clone(), cx); multibuffer.push_excerpts( buffer.clone(), [ExcerptRange { @@ -1018,8 +1016,8 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) { buffer.update(cx, |buffer, cx| { buffer.edit([(0..0, "a\nb\nc")], None, cx); - change_set.update(cx, |change_set, cx| { - change_set.recalculate_diff_sync(buffer.snapshot().text, cx); + diff.update(cx, |diff, cx| { + diff.recalculate_diff_sync(buffer.snapshot().text, cx); }); assert_eq!(buffer.text(), "a\nb\nc") }); @@ -1030,8 +1028,8 @@ fn test_empty_diff_excerpt(cx: &mut TestAppContext) { buffer.update(cx, |buffer, cx| { buffer.undo(cx); - change_set.update(cx, |change_set, cx| { - change_set.recalculate_diff_sync(buffer.snapshot().text, cx); + diff.update(cx, |diff, cx| { + diff.recalculate_diff_sync(buffer.snapshot().text, cx); }); assert_eq!(buffer.text(), "") }); @@ -1273,12 +1271,12 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) { ); let buffer = cx.new(|cx| Buffer::local(text, cx)); - let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); cx.run_until_parked(); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx); - multibuffer.add_change_set(change_set.clone(), cx); + multibuffer.add_diff(diff.clone(), cx); multibuffer }); @@ -1463,8 +1461,8 @@ fn test_basic_diff_hunks(cx: &mut TestAppContext) { assert_line_indents(&snapshot); // Recalculate the diff, changing the first diff hunk. - change_set.update(cx, |change_set, cx| { - change_set.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx); + diff.update(cx, |diff, cx| { + diff.recalculate_diff_sync(buffer.read(cx).text_snapshot(), cx); }); cx.run_until_parked(); assert_new_snapshot( @@ -1516,12 +1514,12 @@ fn test_repeatedly_expand_a_diff_hunk(cx: &mut TestAppContext) { ); let buffer = cx.new(|cx| Buffer::local(text, cx)); - let change_set = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx)); cx.run_until_parked(); let multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::singleton(buffer.clone(), cx); - multibuffer.add_change_set(change_set.clone(), cx); + multibuffer.add_diff(diff.clone(), cx); multibuffer }); @@ -1918,8 +1916,8 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) { let buffer_1 = cx.new(|cx| Buffer::local(text_1, cx)); let buffer_2 = cx.new(|cx| Buffer::local(text_2, cx)); - let change_set_1 = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_1, &buffer_1, cx)); - let change_set_2 = cx.new(|cx| BufferChangeSet::new_with_base_text(base_text_2, &buffer_2, cx)); + let diff_1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_1, &buffer_1, cx)); + let diff_2 = cx.new(|cx| BufferDiff::new_with_base_text(base_text_2, &buffer_2, cx)); cx.run_until_parked(); let multibuffer = cx.new(|cx| { @@ -1940,8 +1938,8 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) { }], cx, ); - multibuffer.add_change_set(change_set_1.clone(), cx); - multibuffer.add_change_set(change_set_2.clone(), cx); + multibuffer.add_diff(diff_1.clone(), cx); + multibuffer.add_diff(diff_2.clone(), cx); multibuffer }); @@ -2001,11 +1999,11 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) { let id_1 = buffer_1.read_with(cx, |buffer, _| buffer.remote_id()); let id_2 = buffer_2.read_with(cx, |buffer, _| buffer.remote_id()); - let base_id_1 = change_set_1.read_with(cx, |change_set, _| { - change_set.base_text.as_ref().unwrap().remote_id() + let base_id_1 = diff_1.read_with(cx, |diff, _| { + diff.snapshot.base_text.as_ref().unwrap().remote_id() }); - let base_id_2 = change_set_2.read_with(cx, |change_set, _| { - change_set.base_text.as_ref().unwrap().remote_id() + let base_id_2 = diff_2.read_with(cx, |diff, _| { + diff.snapshot.base_text.as_ref().unwrap().remote_id() }); let buffer_lines = (0..=snapshot.max_row().0) @@ -2101,7 +2099,7 @@ fn test_diff_hunks_with_multiple_excerpts(cx: &mut TestAppContext) { #[derive(Default)] struct ReferenceMultibuffer { excerpts: Vec, - change_sets: HashMap>, + diffs: HashMap>, } #[derive(Debug)] @@ -2190,10 +2188,10 @@ impl ReferenceMultibuffer { .unwrap(); let buffer = excerpt.buffer.read(cx).snapshot(); let buffer_id = buffer.remote_id(); - let Some(change_set) = self.change_sets.get(&buffer_id) else { + let Some(diff) = self.diffs.get(&buffer_id) else { return; }; - let diff = change_set.read(cx).diff_to_buffer.clone(); + let diff = diff.read(cx).snapshot.clone(); let excerpt_range = excerpt.range.to_offset(&buffer); for hunk in diff.hunks_intersecting_range(range, &buffer) { let hunk_range = hunk.buffer_range.to_offset(&buffer); @@ -2227,9 +2225,9 @@ impl ReferenceMultibuffer { excerpt_boundary_rows.insert(MultiBufferRow(text.matches('\n').count() as u32)); let buffer = excerpt.buffer.read(cx); let buffer_range = excerpt.range.to_offset(buffer); - let change_set = self.change_sets.get(&buffer.remote_id()).unwrap().read(cx); - let diff = change_set.diff_to_buffer.clone(); - let base_buffer = change_set.base_text.as_ref().unwrap(); + let diff = self.diffs.get(&buffer.remote_id()).unwrap().read(cx); + let diff = diff.snapshot.clone(); + let base_buffer = diff.base_text.as_ref().unwrap(); let mut offset = buffer_range.start; let mut hunks = diff @@ -2367,12 +2365,7 @@ impl ReferenceMultibuffer { let buffer = excerpt.buffer.read(cx).snapshot(); let excerpt_range = excerpt.range.to_offset(&buffer); let buffer_id = buffer.remote_id(); - let diff = &self - .change_sets - .get(&buffer_id) - .unwrap() - .read(cx) - .diff_to_buffer; + let diff = &self.diffs.get(&buffer_id).unwrap().read(cx).snapshot; let mut hunks = diff.hunks_in_row_range(0..u32::MAX, &buffer).peekable(); excerpt.expanded_diff_hunks.retain(|hunk_anchor| { if !hunk_anchor.is_valid(&buffer) { @@ -2396,9 +2389,9 @@ impl ReferenceMultibuffer { } } - fn add_change_set(&mut self, change_set: Entity, cx: &mut App) { - let buffer_id = change_set.read(cx).buffer_id; - self.change_sets.insert(buffer_id, change_set); + fn add_diff(&mut self, diff: Entity, cx: &mut App) { + let buffer_id = diff.read(cx).buffer_id; + self.diffs.insert(buffer_id, diff); } } @@ -2528,16 +2521,16 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { multibuffer.update(cx, |multibuffer, cx| { for buffer in multibuffer.all_buffers() { let snapshot = buffer.read(cx).snapshot(); - let _ = multibuffer - .change_set_for(snapshot.remote_id()) - .unwrap() - .update(cx, |change_set, cx| { + let _ = multibuffer.diff_for(snapshot.remote_id()).unwrap().update( + cx, + |diff, cx| { log::info!( "recalculating diff for buffer {:?}", snapshot.remote_id(), ); - change_set.recalculate_diff_sync(snapshot.text, cx); - }); + diff.recalculate_diff_sync(snapshot.text, cx); + }, + ); } reference.diffs_updated(cx); needs_diff_calculation = false; @@ -2550,12 +2543,11 @@ async fn test_random_multibuffer(cx: &mut TestAppContext, mut rng: StdRng) { .collect::(); let buffer = cx.new(|cx| Buffer::local(base_text.clone(), cx)); - let change_set = - cx.new(|cx| BufferChangeSet::new_with_base_text(&base_text, &buffer, cx)); + let diff = cx.new(|cx| BufferDiff::new_with_base_text(&base_text, &buffer, cx)); multibuffer.update(cx, |multibuffer, cx| { - reference.add_change_set(change_set.clone(), cx); - multibuffer.add_change_set(change_set, cx) + reference.add_diff(diff.clone(), cx); + multibuffer.add_diff(diff, cx) }); buffers.push(buffer); buffers.last().unwrap() diff --git a/crates/project/Cargo.toml b/crates/project/Cargo.toml index 5149a818cf55787e20f2f26af00f49bd0980187c..bb96d1b518cc6821068533a2f45022207770fd3e 100644 --- a/crates/project/Cargo.toml +++ b/crates/project/Cargo.toml @@ -30,6 +30,7 @@ async-trait.workspace = true client.workspace = true clock.workspace = true collections.workspace = true +diff.workspace = true fs.workspace = true futures.workspace = true fuzzy.workspace = true @@ -77,6 +78,7 @@ fancy-regex.workspace = true [dev-dependencies] client = { workspace = true, features = ["test-support"] } collections = { workspace = true, features = ["test-support"] } +diff = { workspace = true, features = ["test-support"] } env_logger.workspace = true fs = { workspace = true, features = ["test-support"] } git2.workspace = true diff --git a/crates/project/src/buffer_store.rs b/crates/project/src/buffer_store.rs index 82bfca95b7216f1c81d3e9f6f7ac93e8dc90b99f..57702120d9f9ac12f09b8bbbba4a2e63b0b8e9e4 100644 --- a/crates/project/src/buffer_store.rs +++ b/crates/project/src/buffer_store.rs @@ -8,13 +8,10 @@ use ::git::{parse_git_remote_url, BuildPermalinkParams, GitHostingProviderRegist use anyhow::{anyhow, bail, Context as _, Result}; use client::Client; use collections::{hash_map, HashMap, HashSet}; +use diff::{BufferDiff, BufferDiffEvent, BufferDiffSnapshot}; use fs::Fs; -use futures::{ - channel::oneshot, - future::{OptionFuture, Shared}, - Future, FutureExt as _, StreamExt, -}; -use git::{blame::Blame, diff::BufferDiff, repository::RepoPath}; +use futures::{channel::oneshot, future::Shared, Future, FutureExt as _, StreamExt}; +use git::{blame::Blame, repository::RepoPath}; use gpui::{ App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Subscription, Task, WeakEntity, }; @@ -38,12 +35,12 @@ use std::{ sync::Arc, time::Instant, }; -use text::{BufferId, Rope}; +use text::BufferId; use util::{debug_panic, maybe, ResultExt as _, TryFutureExt}; use worktree::{File, PathChange, ProjectEntryId, UpdatedGitRepositoriesSet, Worktree, WorktreeId}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -enum ChangeSetKind { +enum DiffKind { Unstaged, Uncommitted, } @@ -54,10 +51,8 @@ pub struct BufferStore { #[allow(clippy::type_complexity)] loading_buffers: HashMap, Arc>>>>, #[allow(clippy::type_complexity)] - loading_change_sets: HashMap< - (BufferId, ChangeSetKind), - Shared, Arc>>>, - >, + loading_diffs: + HashMap<(BufferId, DiffKind), Shared, Arc>>>>, worktree_store: Entity, opened_buffers: HashMap, downstream_client: Option<(AnyProtoClient, u64)>, @@ -67,14 +62,14 @@ pub struct BufferStore { #[derive(Hash, Eq, PartialEq, Clone)] struct SharedBuffer { buffer: Entity, - change_set: Option>, + diff: Option>, lsp_handle: Option, } #[derive(Default)] -struct BufferChangeSetState { - unstaged_changes: Option>, - uncommitted_changes: Option>, +struct BufferDiffState { + unstaged_diff: Option>, + uncommitted_diff: Option>, recalculate_diff_task: Option>>, language: Option>, language_registry: Option>, @@ -99,21 +94,19 @@ enum DiffBasesChange { SetBoth(Option), } -impl BufferChangeSetState { +impl BufferDiffState { fn buffer_language_changed(&mut self, buffer: Entity, cx: &mut Context) { self.language = buffer.read(cx).language().cloned(); self.language_changed = true; let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx); } - fn unstaged_changes(&self) -> Option> { - self.unstaged_changes.as_ref().and_then(|set| set.upgrade()) + fn unstaged_diff(&self) -> Option> { + self.unstaged_diff.as_ref().and_then(|set| set.upgrade()) } - fn uncommitted_changes(&self) -> Option> { - self.uncommitted_changes - .as_ref() - .and_then(|set| set.upgrade()) + fn uncommitted_diff(&self) -> Option> { + self.uncommitted_diff.as_ref().and_then(|set| set.upgrade()) } fn handle_base_texts_updated( @@ -199,8 +192,8 @@ impl BufferChangeSetState { let language = self.language.clone(); let language_registry = self.language_registry.clone(); - let unstaged_changes = self.unstaged_changes(); - let uncommitted_changes = self.uncommitted_changes(); + let unstaged_diff = self.unstaged_diff(); + let uncommitted_diff = self.uncommitted_diff(); let head = self.head_text.clone(); let index = self.index_text.clone(); let index_changed = self.index_changed; @@ -212,90 +205,71 @@ impl BufferChangeSetState { _ => false, }; self.recalculate_diff_task = Some(cx.spawn(|this, mut cx| async move { - if let Some(unstaged_changes) = &unstaged_changes { - let staged_snapshot = if index_changed || language_changed { - let staged_snapshot = cx.update(|cx| { - index.as_ref().map(|head| { - language::Buffer::build_snapshot( - Rope::from(head.as_str()), - language.clone(), - language_registry.clone(), + if let Some(unstaged_diff) = &unstaged_diff { + let snapshot = if index_changed || language_changed { + cx.update(|cx| { + BufferDiffSnapshot::build( + buffer.clone(), + index, + language.clone(), + language_registry.clone(), + cx, + ) + })? + .await + } else { + unstaged_diff + .read_with(&cx, |changes, cx| { + BufferDiffSnapshot::build_with_base_buffer( + buffer.clone(), + index, + changes.snapshot.base_text.clone(), cx, ) - }) - })?; - cx.background_executor() - .spawn(OptionFuture::from(staged_snapshot)) - } else { - Task::ready( - unstaged_changes - .read_with(&cx, |change_set, _| change_set.base_text.clone())?, - ) + })? + .await }; - let diff = - cx.background_executor().spawn({ - let buffer = buffer.clone(); - async move { - BufferDiff::build(index.as_ref().map(|index| index.as_str()), &buffer) - } - }); - - let (staged_snapshot, diff) = futures::join!(staged_snapshot, diff); - - unstaged_changes.update(&mut cx, |unstaged_changes, cx| { - unstaged_changes.set_state(staged_snapshot.clone(), diff, &buffer, cx); + unstaged_diff.update(&mut cx, |unstaged_diff, cx| { + unstaged_diff.set_state(snapshot, &buffer, cx); if language_changed { - cx.emit(BufferChangeSetEvent::LanguageChanged); + cx.emit(BufferDiffEvent::LanguageChanged); } })?; } - if let Some(uncommitted_changes) = &uncommitted_changes { - let (snapshot, diff) = if let (Some(unstaged_changes), true) = - (&unstaged_changes, index_matches_head) - { - unstaged_changes.read_with(&cx, |change_set, _| { - ( - change_set.base_text.clone(), - change_set.diff_to_buffer.clone(), - ) - })? - } else { - let committed_snapshot = if head_changed || language_changed { - let committed_snapshot = cx.update(|cx| { - head.as_ref().map(|head| { - language::Buffer::build_snapshot( - Rope::from(head.as_str()), - language.clone(), - language_registry.clone(), + if let Some(uncommitted_diff) = &uncommitted_diff { + let snapshot = + if let (Some(unstaged_diff), true) = (&unstaged_diff, index_matches_head) { + unstaged_diff.read_with(&cx, |diff, _| diff.snapshot.clone())? + } else if head_changed || language_changed { + cx.update(|cx| { + BufferDiffSnapshot::build( + buffer.clone(), + head, + language.clone(), + language_registry.clone(), + cx, + ) + })? + .await + } else { + uncommitted_diff + .read_with(&cx, |changes, cx| { + BufferDiffSnapshot::build_with_base_buffer( + buffer.clone(), + head, + changes.snapshot.base_text.clone(), cx, ) - }) - })?; - cx.background_executor() - .spawn(OptionFuture::from(committed_snapshot)) - } else { - Task::ready( - uncommitted_changes - .read_with(&cx, |change_set, _| change_set.base_text.clone())?, - ) + })? + .await }; - let diff = cx.background_executor().spawn({ - let buffer = buffer.clone(); - let head = head.clone(); - async move { - BufferDiff::build(head.as_ref().map(|head| head.as_str()), &buffer) - } - }); - futures::join!(committed_snapshot, diff) - }; - - uncommitted_changes.update(&mut cx, |change_set, cx| { - change_set.set_state(snapshot, diff, &buffer, cx); + uncommitted_diff.update(&mut cx, |diff, cx| { + diff.set_state(snapshot, &buffer, cx); if language_changed { - cx.emit(BufferChangeSetEvent::LanguageChanged); + cx.emit(BufferDiffEvent::LanguageChanged); } })?; } @@ -317,28 +291,6 @@ impl BufferChangeSetState { } } -pub struct BufferChangeSet { - pub buffer_id: BufferId, - pub base_text: Option, - pub diff_to_buffer: BufferDiff, - pub unstaged_change_set: Option>, -} - -impl std::fmt::Debug for BufferChangeSet { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("BufferChangeSet") - .field("buffer_id", &self.buffer_id) - .field("base_text", &self.base_text.as_ref().map(|s| s.text())) - .field("diff_to_buffer", &self.diff_to_buffer) - .finish() - } -} - -pub enum BufferChangeSetEvent { - DiffChanged { changed_range: Range }, - LanguageChanged, -} - enum BufferStoreState { Local(LocalBufferStore), Remote(RemoteBufferStore), @@ -364,7 +316,7 @@ struct LocalBufferStore { enum OpenBuffer { Complete { buffer: WeakEntity, - change_set_state: Entity, + diff_state: Entity, }, Operations(Vec), } @@ -384,12 +336,12 @@ pub struct ProjectTransaction(pub HashMap, language::Transaction> impl EventEmitter for BufferStore {} impl RemoteBufferStore { - fn open_unstaged_changes(&self, buffer_id: BufferId, cx: &App) -> Task>> { + fn open_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Task>> { let project_id = self.project_id; let client = self.upstream_client.clone(); cx.background_executor().spawn(async move { let response = client - .request(proto::OpenUnstagedChanges { + .request(proto::OpenUnstagedDiff { project_id, buffer_id: buffer_id.to_proto(), }) @@ -398,18 +350,18 @@ impl RemoteBufferStore { }) } - fn open_uncommitted_changes( + fn open_uncommitted_diff( &self, buffer_id: BufferId, cx: &App, ) -> Task> { - use proto::open_uncommitted_changes_response::Mode; + use proto::open_uncommitted_diff_response::Mode; let project_id = self.project_id; let client = self.upstream_client.clone(); cx.background_executor().spawn(async move { let response = client - .request(proto::OpenUncommittedChanges { + .request(proto::OpenUncommittedDiff { project_id, buffer_id: buffer_id.to_proto(), }) @@ -839,13 +791,9 @@ impl LocalBufferStore { ) { debug_assert!(worktree_handle.read(cx).is_local()); - let mut change_set_state_updates = Vec::new(); + let mut diff_state_updates = Vec::new(); for buffer in this.opened_buffers.values() { - let OpenBuffer::Complete { - buffer, - change_set_state, - } = buffer - else { + let OpenBuffer::Complete { buffer, diff_state } = buffer else { continue; }; let Some(buffer) = buffer.upgrade() else { @@ -858,22 +806,22 @@ impl LocalBufferStore { if file.worktree != worktree_handle { continue; } - let change_set_state = change_set_state.read(cx); + let diff_state = diff_state.read(cx); if changed_repos .iter() .any(|(work_dir, _)| file.path.starts_with(work_dir)) { let snapshot = buffer.text_snapshot(); - change_set_state_updates.push(( + diff_state_updates.push(( snapshot.clone(), file.path.clone(), - change_set_state - .unstaged_changes + diff_state + .unstaged_diff .as_ref() .and_then(|set| set.upgrade()) .is_some(), - change_set_state - .uncommitted_changes + diff_state + .uncommitted_diff .as_ref() .and_then(|set| set.upgrade()) .is_some(), @@ -881,7 +829,7 @@ impl LocalBufferStore { } } - if change_set_state_updates.is_empty() { + if diff_state_updates.is_empty() { return; } @@ -891,7 +839,7 @@ impl LocalBufferStore { let diff_bases_changes_by_buffer = cx .background_executor() .spawn(async move { - change_set_state_updates + diff_state_updates .into_iter() .filter_map( |(buffer_snapshot, path, needs_staged_text, needs_committed_text)| { @@ -934,9 +882,8 @@ impl LocalBufferStore { this.update(&mut cx, |this, cx| { for (buffer_snapshot, diff_bases_change) in diff_bases_changes_by_buffer { - let Some(OpenBuffer::Complete { - change_set_state, .. - }) = this.opened_buffers.get_mut(&buffer_snapshot.remote_id()) + let Some(OpenBuffer::Complete { diff_state, .. }) = + this.opened_buffers.get_mut(&buffer_snapshot.remote_id()) else { continue; }; @@ -944,7 +891,7 @@ impl LocalBufferStore { continue; }; - change_set_state.update(cx, |change_set_state, cx| { + diff_state.update(cx, |diff_state, cx| { use proto::update_diff_bases::Mode; if let Some((client, project_id)) = this.downstream_client.as_ref() { @@ -972,11 +919,8 @@ impl LocalBufferStore { client.send(message).log_err(); } - let _ = change_set_state.diff_bases_changed( - buffer_snapshot, - diff_bases_change, - cx, - ); + let _ = + diff_state.diff_bases_changed(buffer_snapshot, diff_bases_change, cx); }); } }) @@ -1282,8 +1226,8 @@ impl BufferStore { client.add_entity_request_handler(Self::handle_blame_buffer); client.add_entity_request_handler(Self::handle_reload_buffers); client.add_entity_request_handler(Self::handle_get_permalink_to_line); - client.add_entity_request_handler(Self::handle_open_unstaged_changes); - client.add_entity_request_handler(Self::handle_open_uncommitted_changes); + client.add_entity_request_handler(Self::handle_open_unstaged_diff); + client.add_entity_request_handler(Self::handle_open_uncommitted_diff); client.add_entity_message_handler(Self::handle_update_diff_bases); } @@ -1305,7 +1249,7 @@ impl BufferStore { opened_buffers: Default::default(), shared_buffers: Default::default(), loading_buffers: Default::default(), - loading_change_sets: Default::default(), + loading_diffs: Default::default(), worktree_store, } } @@ -1328,7 +1272,7 @@ impl BufferStore { downstream_client: None, opened_buffers: Default::default(), loading_buffers: Default::default(), - loading_change_sets: Default::default(), + loading_diffs: Default::default(), shared_buffers: Default::default(), worktree_store, } @@ -1401,33 +1345,30 @@ impl BufferStore { .spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) } - pub fn open_unstaged_changes( + pub fn open_unstaged_diff( &mut self, buffer: Entity, cx: &mut Context, - ) -> Task>> { + ) -> Task>> { let buffer_id = buffer.read(cx).remote_id(); - if let Some(change_set) = self.get_unstaged_changes(buffer_id, cx) { - return Task::ready(Ok(change_set)); + if let Some(diff) = self.get_unstaged_diff(buffer_id, cx) { + return Task::ready(Ok(diff)); } - let task = match self - .loading_change_sets - .entry((buffer_id, ChangeSetKind::Unstaged)) - { + let task = match self.loading_diffs.entry((buffer_id, DiffKind::Unstaged)) { hash_map::Entry::Occupied(e) => e.get().clone(), hash_map::Entry::Vacant(entry) => { let staged_text = match &self.state { BufferStoreState::Local(this) => this.load_staged_text(&buffer, cx), - BufferStoreState::Remote(this) => this.open_unstaged_changes(buffer_id, cx), + BufferStoreState::Remote(this) => this.open_unstaged_diff(buffer_id, cx), }; entry .insert( cx.spawn(move |this, cx| async move { - Self::open_change_set_internal( + Self::open_diff_internal( this, - ChangeSetKind::Unstaged, + DiffKind::Unstaged, staged_text.await.map(DiffBasesChange::SetIndex), buffer, cx, @@ -1445,20 +1386,17 @@ impl BufferStore { .spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) } - pub fn open_uncommitted_changes( + pub fn open_uncommitted_diff( &mut self, buffer: Entity, cx: &mut Context, - ) -> Task>> { + ) -> Task>> { let buffer_id = buffer.read(cx).remote_id(); - if let Some(change_set) = self.get_uncommitted_changes(buffer_id, cx) { - return Task::ready(Ok(change_set)); + if let Some(diff) = self.get_uncommitted_diff(buffer_id, cx) { + return Task::ready(Ok(diff)); } - let task = match self - .loading_change_sets - .entry((buffer_id, ChangeSetKind::Uncommitted)) - { + let task = match self.loading_diffs.entry((buffer_id, DiffKind::Uncommitted)) { hash_map::Entry::Occupied(e) => e.get().clone(), hash_map::Entry::Vacant(entry) => { let changes = match &self.state { @@ -1479,15 +1417,15 @@ impl BufferStore { Ok(diff_bases_change) }) } - BufferStoreState::Remote(this) => this.open_uncommitted_changes(buffer_id, cx), + BufferStoreState::Remote(this) => this.open_uncommitted_diff(buffer_id, cx), }; entry .insert( cx.spawn(move |this, cx| async move { - Self::open_change_set_internal( + Self::open_diff_internal( this, - ChangeSetKind::Uncommitted, + DiffKind::Uncommitted, changes.await, buffer, cx, @@ -1505,30 +1443,18 @@ impl BufferStore { .spawn(async move { task.await.map_err(|e| anyhow!("{e}")) }) } - #[cfg(any(test, feature = "test-support"))] - pub fn set_unstaged_change_set( - &mut self, - buffer_id: BufferId, - change_set: Entity, - ) { - self.loading_change_sets.insert( - (buffer_id, ChangeSetKind::Unstaged), - Task::ready(Ok(change_set)).shared(), - ); - } - - async fn open_change_set_internal( + async fn open_diff_internal( this: WeakEntity, - kind: ChangeSetKind, + kind: DiffKind, texts: Result, buffer: Entity, mut cx: AsyncApp, - ) -> Result> { + ) -> Result> { let diff_bases_change = match texts { Err(e) => { this.update(&mut cx, |this, cx| { let buffer_id = buffer.read(cx).remote_id(); - this.loading_change_sets.remove(&(buffer_id, kind)); + this.loading_diffs.remove(&(buffer_id, kind)); })?; return Err(e); } @@ -1537,15 +1463,14 @@ impl BufferStore { this.update(&mut cx, |this, cx| { let buffer_id = buffer.read(cx).remote_id(); - this.loading_change_sets.remove(&(buffer_id, kind)); + this.loading_diffs.remove(&(buffer_id, kind)); - if let Some(OpenBuffer::Complete { - change_set_state, .. - }) = this.opened_buffers.get_mut(&buffer.read(cx).remote_id()) + if let Some(OpenBuffer::Complete { diff_state, .. }) = + this.opened_buffers.get_mut(&buffer.read(cx).remote_id()) { - change_set_state.update(cx, |change_set_state, cx| { + diff_state.update(cx, |diff_state, cx| { let buffer_id = buffer.read(cx).remote_id(); - change_set_state.buffer_subscription.get_or_insert_with(|| { + diff_state.buffer_subscription.get_or_insert_with(|| { cx.subscribe(&buffer, |this, buffer, event, cx| match event { BufferEvent::LanguageChanged => { this.buffer_language_changed(buffer, cx) @@ -1554,47 +1479,41 @@ impl BufferStore { }) }); - let change_set = cx.new(|cx| BufferChangeSet { + let diff = cx.new(|cx| BufferDiff { buffer_id, - base_text: None, - diff_to_buffer: BufferDiff::new(&buffer.read(cx).text_snapshot()), - unstaged_change_set: None, + snapshot: BufferDiffSnapshot::new(&buffer.read(cx).text_snapshot()), + unstaged_diff: None, }); match kind { - ChangeSetKind::Unstaged => { - change_set_state.unstaged_changes = Some(change_set.downgrade()) - } - ChangeSetKind::Uncommitted => { - let unstaged_change_set = - if let Some(change_set) = change_set_state.unstaged_changes() { - change_set - } else { - let unstaged_change_set = cx.new(|cx| BufferChangeSet { - buffer_id, - base_text: None, - diff_to_buffer: BufferDiff::new( - &buffer.read(cx).text_snapshot(), - ), - unstaged_change_set: None, - }); - change_set_state.unstaged_changes = - Some(unstaged_change_set.downgrade()); - unstaged_change_set - }; + DiffKind::Unstaged => diff_state.unstaged_diff = Some(diff.downgrade()), + DiffKind::Uncommitted => { + let unstaged_diff = if let Some(diff) = diff_state.unstaged_diff() { + diff + } else { + let unstaged_diff = cx.new(|cx| BufferDiff { + buffer_id, + snapshot: BufferDiffSnapshot::new( + &buffer.read(cx).text_snapshot(), + ), + unstaged_diff: None, + }); + diff_state.unstaged_diff = Some(unstaged_diff.downgrade()); + unstaged_diff + }; - change_set.update(cx, |change_set, _| { - change_set.unstaged_change_set = Some(unstaged_change_set); + diff.update(cx, |diff, _| { + diff.unstaged_diff = Some(unstaged_diff); }); - change_set_state.uncommitted_changes = Some(change_set.downgrade()) + diff_state.uncommitted_diff = Some(diff.downgrade()) } }; let buffer = buffer.read(cx).text_snapshot(); - let rx = change_set_state.diff_bases_changed(buffer, diff_bases_change, cx); + let rx = diff_state.diff_bases_changed(buffer, diff_bases_change, cx); Ok(async move { rx.await.ok(); - Ok(change_set) + Ok(diff) }) }) } else { @@ -1807,7 +1726,7 @@ impl BufferStore { let is_remote = buffer.read(cx).replica_id() != 0; let open_buffer = OpenBuffer::Complete { buffer: buffer.downgrade(), - change_set_state: cx.new(|_| BufferChangeSetState::default()), + diff_state: cx.new(|_| BufferDiffState::default()), }; let handle = cx.entity().downgrade(); @@ -1888,39 +1807,21 @@ impl BufferStore { }) } - pub fn get_unstaged_changes( - &self, - buffer_id: BufferId, - cx: &App, - ) -> Option> { - if let OpenBuffer::Complete { - change_set_state, .. - } = self.opened_buffers.get(&buffer_id)? - { - change_set_state - .read(cx) - .unstaged_changes - .as_ref()? - .upgrade() + pub fn get_unstaged_diff(&self, buffer_id: BufferId, cx: &App) -> Option> { + if let OpenBuffer::Complete { diff_state, .. } = self.opened_buffers.get(&buffer_id)? { + diff_state.read(cx).unstaged_diff.as_ref()?.upgrade() } else { None } } - pub fn get_uncommitted_changes( + pub fn get_uncommitted_diff( &self, buffer_id: BufferId, cx: &App, - ) -> Option> { - if let OpenBuffer::Complete { - change_set_state, .. - } = self.opened_buffers.get(&buffer_id)? - { - change_set_state - .read(cx) - .uncommitted_changes - .as_ref()? - .upgrade() + ) -> Option> { + if let OpenBuffer::Complete { diff_state, .. } = self.opened_buffers.get(&buffer_id)? { + diff_state.read(cx).uncommitted_diff.as_ref()?.upgrade() } else { None } @@ -2040,13 +1941,12 @@ impl BufferStore { ) -> impl Future { let mut futures = Vec::new(); for buffer in buffers { - if let Some(OpenBuffer::Complete { - change_set_state, .. - }) = self.opened_buffers.get_mut(&buffer.read(cx).remote_id()) + if let Some(OpenBuffer::Complete { diff_state, .. }) = + self.opened_buffers.get_mut(&buffer.read(cx).remote_id()) { let buffer = buffer.read(cx).text_snapshot(); - futures.push(change_set_state.update(cx, |change_set_state, cx| { - change_set_state.recalculate_diffs(buffer, cx) + futures.push(diff_state.update(cx, |diff_state, cx| { + diff_state.recalculate_diffs(buffer, cx) })); } } @@ -2156,7 +2056,7 @@ impl BufferStore { .entry(buffer_id) .or_insert_with(|| SharedBuffer { buffer: buffer.clone(), - change_set: None, + diff: None, lsp_handle: None, }); @@ -2461,16 +2361,16 @@ impl BufferStore { }) } - pub async fn handle_open_unstaged_changes( + pub async fn handle_open_unstaged_diff( this: Entity, - request: TypedEnvelope, + request: TypedEnvelope, mut cx: AsyncApp, - ) -> Result { + ) -> Result { let buffer_id = BufferId::new(request.payload.buffer_id)?; - let change_set = this + let diff = this .update(&mut cx, |this, cx| { let buffer = this.get(buffer_id)?; - Some(this.open_unstaged_changes(buffer, cx)) + Some(this.open_unstaged_diff(buffer, cx)) })? .ok_or_else(|| anyhow!("no such buffer"))? .await?; @@ -2481,25 +2381,25 @@ impl BufferStore { .or_default(); debug_assert!(shared_buffers.contains_key(&buffer_id)); if let Some(shared) = shared_buffers.get_mut(&buffer_id) { - shared.change_set = Some(change_set.clone()); + shared.diff = Some(diff.clone()); } })?; - let staged_text = change_set.read_with(&cx, |change_set, _| { - change_set.base_text.as_ref().map(|buffer| buffer.text()) + let staged_text = diff.read_with(&cx, |diff, _| { + diff.snapshot.base_text.as_ref().map(|buffer| buffer.text()) })?; - Ok(proto::OpenUnstagedChangesResponse { staged_text }) + Ok(proto::OpenUnstagedDiffResponse { staged_text }) } - pub async fn handle_open_uncommitted_changes( + pub async fn handle_open_uncommitted_diff( this: Entity, - request: TypedEnvelope, + request: TypedEnvelope, mut cx: AsyncApp, - ) -> Result { + ) -> Result { let buffer_id = BufferId::new(request.payload.buffer_id)?; - let change_set = this + let diff = this .update(&mut cx, |this, cx| { let buffer = this.get(buffer_id)?; - Some(this.open_uncommitted_changes(buffer, cx)) + Some(this.open_uncommitted_diff(buffer, cx)) })? .ok_or_else(|| anyhow!("no such buffer"))? .await?; @@ -2510,21 +2410,21 @@ impl BufferStore { .or_default(); debug_assert!(shared_buffers.contains_key(&buffer_id)); if let Some(shared) = shared_buffers.get_mut(&buffer_id) { - shared.change_set = Some(change_set.clone()); + shared.diff = Some(diff.clone()); } })?; - change_set.read_with(&cx, |change_set, cx| { - use proto::open_uncommitted_changes_response::Mode; + diff.read_with(&cx, |diff, cx| { + use proto::open_uncommitted_diff_response::Mode; - let staged_buffer = change_set - .unstaged_change_set + let staged_buffer = diff + .unstaged_diff .as_ref() - .and_then(|change_set| change_set.read(cx).base_text.as_ref()); + .and_then(|diff| diff.read(cx).snapshot.base_text.as_ref()); let mode; let staged_text; let committed_text; - if let Some(committed_buffer) = &change_set.base_text { + if let Some(committed_buffer) = &diff.snapshot.base_text { committed_text = Some(committed_buffer.text()); if let Some(staged_buffer) = staged_buffer { if staged_buffer.remote_id() == committed_buffer.remote_id() { @@ -2544,7 +2444,7 @@ impl BufferStore { staged_text = staged_buffer.as_ref().map(|buffer| buffer.text()); } - proto::OpenUncommittedChangesResponse { + proto::OpenUncommittedDiffResponse { committed_text, staged_text, mode: mode.into(), @@ -2559,15 +2459,13 @@ impl BufferStore { ) -> Result<()> { let buffer_id = BufferId::new(request.payload.buffer_id)?; this.update(&mut cx, |this, cx| { - if let Some(OpenBuffer::Complete { - change_set_state, - buffer, - }) = this.opened_buffers.get_mut(&buffer_id) + if let Some(OpenBuffer::Complete { diff_state, buffer }) = + this.opened_buffers.get_mut(&buffer_id) { if let Some(buffer) = buffer.upgrade() { let buffer = buffer.read(cx).text_snapshot(); - change_set_state.update(cx, |change_set_state, cx| { - change_set_state.handle_base_texts_updated(buffer, request.payload, cx); + diff_state.update(cx, |diff_state, cx| { + diff_state.handle_base_texts_updated(buffer, request.payload, cx); }) } } @@ -2628,7 +2526,7 @@ impl BufferStore { buffer_id, SharedBuffer { buffer: buffer.clone(), - change_set: None, + diff: None, lsp_handle: None, }, ); @@ -2783,126 +2681,6 @@ impl BufferStore { } } -impl EventEmitter for BufferChangeSet {} - -impl BufferChangeSet { - fn set_state( - &mut self, - base_text: Option, - diff: BufferDiff, - buffer: &text::BufferSnapshot, - cx: &mut Context, - ) { - if let Some(base_text) = base_text.as_ref() { - let changed_range = if Some(base_text.remote_id()) - != self.base_text.as_ref().map(|buffer| buffer.remote_id()) - { - Some(text::Anchor::MIN..text::Anchor::MAX) - } else { - diff.compare(&self.diff_to_buffer, buffer) - }; - if let Some(changed_range) = changed_range { - cx.emit(BufferChangeSetEvent::DiffChanged { changed_range }); - } - } - self.base_text = base_text; - self.diff_to_buffer = diff; - } - - pub fn diff_hunks_intersecting_range<'a>( - &'a self, - range: Range, - buffer_snapshot: &'a text::BufferSnapshot, - ) -> impl 'a + Iterator { - self.diff_to_buffer - .hunks_intersecting_range(range, buffer_snapshot) - } - - pub fn diff_hunks_intersecting_range_rev<'a>( - &'a self, - range: Range, - buffer_snapshot: &'a text::BufferSnapshot, - ) -> impl 'a + Iterator { - self.diff_to_buffer - .hunks_intersecting_range_rev(range, buffer_snapshot) - } - - /// Used in cases where the change set isn't derived from git. - pub fn set_base_text( - &mut self, - base_buffer: Entity, - buffer: text::BufferSnapshot, - cx: &mut Context, - ) -> oneshot::Receiver<()> { - let (tx, rx) = oneshot::channel(); - let this = cx.weak_entity(); - let base_buffer = base_buffer.read(cx).snapshot(); - cx.spawn(|_, mut cx| async move { - let diff = cx - .background_executor() - .spawn({ - let base_buffer = base_buffer.clone(); - let buffer = buffer.clone(); - async move { BufferDiff::build(Some(&base_buffer.text()), &buffer) } - }) - .await; - let Some(this) = this.upgrade() else { - tx.send(()).ok(); - return; - }; - this.update(&mut cx, |this, cx| { - this.set_state(Some(base_buffer), diff, &buffer, cx); - }) - .log_err(); - tx.send(()).ok(); - }) - .detach(); - rx - } - - #[cfg(any(test, feature = "test-support"))] - pub fn base_text_string(&self) -> Option { - self.base_text.as_ref().map(|buffer| buffer.text()) - } - - pub fn new(buffer: &Entity, cx: &mut App) -> Self { - BufferChangeSet { - buffer_id: buffer.read(cx).remote_id(), - base_text: None, - diff_to_buffer: BufferDiff::new(&buffer.read(cx).text_snapshot()), - unstaged_change_set: None, - } - } - - #[cfg(any(test, feature = "test-support"))] - pub fn new_with_base_text(base_text: &str, buffer: &Entity, cx: &mut App) -> Self { - let mut base_text = base_text.to_owned(); - text::LineEnding::normalize(&mut base_text); - let diff_to_buffer = BufferDiff::build(Some(&base_text), &buffer.read(cx).text_snapshot()); - let base_text = language::Buffer::build_snapshot_sync(base_text.into(), None, None, cx); - BufferChangeSet { - buffer_id: buffer.read(cx).remote_id(), - base_text: Some(base_text), - diff_to_buffer, - unstaged_change_set: None, - } - } - - #[cfg(any(test, feature = "test-support"))] - pub fn recalculate_diff_sync( - &mut self, - snapshot: text::BufferSnapshot, - cx: &mut Context, - ) { - let mut base_text = self.base_text.as_ref().map(|buffer| buffer.text()); - if let Some(base_text) = base_text.as_mut() { - text::LineEnding::normalize(base_text); - } - let diff_to_buffer = BufferDiff::build(base_text.as_deref(), &snapshot); - self.set_state(self.base_text.clone(), diff_to_buffer, &snapshot, cx); - } -} - impl OpenBuffer { fn upgrade(&self) -> Option> { match self { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 2a7759daa4e556d7681d4c98332422323b0ea109..7f2f52aee63a2cc701150230693b60a91e9d2cdb 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -21,6 +21,7 @@ mod project_tests; mod direnv; mod environment; +use diff::BufferDiff; pub use environment::EnvironmentErrorMessage; use git::Repository; pub mod search_history; @@ -28,7 +29,7 @@ mod yarn; use crate::git::GitState; use anyhow::{anyhow, Context as _, Result}; -use buffer_store::{BufferChangeSet, BufferStore, BufferStoreEvent}; +use buffer_store::{BufferStore, BufferStoreEvent}; use client::{ proto, Client, Collaborator, PendingEntitySubscription, ProjectId, TypedEnvelope, UserStore, }; @@ -1955,31 +1956,31 @@ impl Project { }) } - pub fn open_unstaged_changes( + pub fn open_unstaged_diff( &mut self, buffer: Entity, cx: &mut Context, - ) -> Task>> { + ) -> Task>> { if self.is_disconnected(cx) { return Task::ready(Err(anyhow!(ErrorCode::Disconnected))); } self.buffer_store.update(cx, |buffer_store, cx| { - buffer_store.open_unstaged_changes(buffer, cx) + buffer_store.open_unstaged_diff(buffer, cx) }) } - pub fn open_uncommitted_changes( + pub fn open_uncommitted_diff( &mut self, buffer: Entity, cx: &mut Context, - ) -> Task>> { + ) -> Task>> { if self.is_disconnected(cx) { return Task::ready(Err(anyhow!(ErrorCode::Disconnected))); } self.buffer_store.update(cx, |buffer_store, cx| { - buffer_store.open_uncommitted_changes(buffer, cx) + buffer_store.open_uncommitted_diff(buffer, cx) }) } diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index dfd5a5dc56fa2205cee56bd1e140bfe2539adf4c..9d93a68783b5d869f1f82c460e22e3bc19ae982f 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -1,5 +1,5 @@ use crate::{Event, *}; -use ::git::diff::assert_hunks; +use diff::assert_hunks; use fs::FakeFs; use futures::{future, StreamExt}; use gpui::{App, SemanticVersion, UpdateGlobal}; @@ -5639,7 +5639,7 @@ async fn test_reordering_worktrees(cx: &mut gpui::TestAppContext) { } #[gpui::test] -async fn test_unstaged_changes_for_buffer(cx: &mut gpui::TestAppContext) { +async fn test_unstaged_diff_for_buffer(cx: &mut gpui::TestAppContext) { init_test(cx); let staged_contents = r#" @@ -5681,20 +5681,20 @@ async fn test_unstaged_changes_for_buffer(cx: &mut gpui::TestAppContext) { }) .await .unwrap(); - let unstaged_changes = project + let unstaged_diff = project .update(cx, |project, cx| { - project.open_unstaged_changes(buffer.clone(), cx) + project.open_unstaged_diff(buffer.clone(), cx) }) .await .unwrap(); cx.run_until_parked(); - unstaged_changes.update(cx, |unstaged_changes, cx| { + unstaged_diff.update(cx, |unstaged_diff, cx| { let snapshot = buffer.read(cx).snapshot(); assert_hunks( - unstaged_changes.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + unstaged_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), &snapshot, - &unstaged_changes.base_text.as_ref().unwrap().text(), + &unstaged_diff.base_text_string().unwrap(), &[ (0..1, "", "// print goodbye\n"), ( @@ -5719,19 +5719,19 @@ async fn test_unstaged_changes_for_buffer(cx: &mut gpui::TestAppContext) { ); cx.run_until_parked(); - unstaged_changes.update(cx, |unstaged_changes, cx| { + unstaged_diff.update(cx, |unstaged_diff, cx| { let snapshot = buffer.read(cx).snapshot(); assert_hunks( - unstaged_changes.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + unstaged_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), &snapshot, - &unstaged_changes.base_text.as_ref().unwrap().text(), + &unstaged_diff.snapshot.base_text.as_ref().unwrap().text(), &[(2..3, "", " println!(\"goodbye world\");\n")], ); }); } #[gpui::test] -async fn test_uncommitted_changes_for_buffer(cx: &mut gpui::TestAppContext) { +async fn test_uncommitted_diff_for_buffer(cx: &mut gpui::TestAppContext) { init_test(cx); let committed_contents = r#" @@ -5783,20 +5783,20 @@ async fn test_uncommitted_changes_for_buffer(cx: &mut gpui::TestAppContext) { }) .await .unwrap(); - let uncommitted_changes = project + let uncommitted_diff = project .update(cx, |project, cx| { - project.open_uncommitted_changes(buffer.clone(), cx) + project.open_uncommitted_diff(buffer.clone(), cx) }) .await .unwrap(); cx.run_until_parked(); - uncommitted_changes.update(cx, |uncommitted_changes, cx| { + uncommitted_diff.update(cx, |uncommitted_diff, cx| { let snapshot = buffer.read(cx).snapshot(); assert_hunks( - uncommitted_changes.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + uncommitted_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), &snapshot, - &uncommitted_changes.base_text.as_ref().unwrap().text(), + &uncommitted_diff.snapshot.base_text.as_ref().unwrap().text(), &[ (0..1, "", "// print goodbye\n"), ( @@ -5821,12 +5821,12 @@ async fn test_uncommitted_changes_for_buffer(cx: &mut gpui::TestAppContext) { ); cx.run_until_parked(); - uncommitted_changes.update(cx, |uncommitted_changes, cx| { + uncommitted_diff.update(cx, |uncommitted_diff, cx| { let snapshot = buffer.read(cx).snapshot(); assert_hunks( - uncommitted_changes.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + uncommitted_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), &snapshot, - &uncommitted_changes.base_text.as_ref().unwrap().text(), + &uncommitted_diff.snapshot.base_text.as_ref().unwrap().text(), &[(2..3, "", " println!(\"goodbye world\");\n")], ); }); @@ -5874,20 +5874,20 @@ async fn test_single_file_diffs(cx: &mut gpui::TestAppContext) { }) .await .unwrap(); - let uncommitted_changes = project + let uncommitted_diff = project .update(cx, |project, cx| { - project.open_uncommitted_changes(buffer.clone(), cx) + project.open_uncommitted_diff(buffer.clone(), cx) }) .await .unwrap(); cx.run_until_parked(); - uncommitted_changes.update(cx, |uncommitted_changes, cx| { + uncommitted_diff.update(cx, |uncommitted_diff, cx| { let snapshot = buffer.read(cx).snapshot(); assert_hunks( - uncommitted_changes.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), + uncommitted_diff.diff_hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot), &snapshot, - &uncommitted_changes.base_text.as_ref().unwrap().text(), + &uncommitted_diff.snapshot.base_text.as_ref().unwrap().text(), &[( 1..2, " println!(\"hello from HEAD\");\n", diff --git a/crates/proto/proto/zed.proto b/crates/proto/proto/zed.proto index fd10f0d113412619bb2e0ebdccc51753913fdcdc..1799f507292ec2b0a2332afed6254a654c9e5742 100644 --- a/crates/proto/proto/zed.proto +++ b/crates/proto/proto/zed.proto @@ -304,8 +304,8 @@ message Envelope { SyncExtensionsResponse sync_extensions_response = 286; InstallExtension install_extension = 287; - OpenUnstagedChanges open_unstaged_changes = 288; - OpenUnstagedChangesResponse open_unstaged_changes_response = 289; + OpenUnstagedDiff open_unstaged_diff = 288; + OpenUnstagedDiffResponse open_unstaged_diff_response = 289; RegisterBufferWithLanguageServers register_buffer_with_language_servers = 290; @@ -314,8 +314,8 @@ message Envelope { Commit commit = 295; OpenCommitMessageBuffer open_commit_message_buffer = 296; - OpenUncommittedChanges open_uncommitted_changes = 297; - OpenUncommittedChangesResponse open_uncommitted_changes_response = 298; // current max + OpenUncommittedDiff open_uncommitted_diff = 297; + OpenUncommittedDiffResponse open_uncommitted_diff_response = 298; // current max } reserved 87 to 88; @@ -2062,21 +2062,21 @@ message UpdateDiffBases { Mode mode = 5; } -message OpenUnstagedChanges { +message OpenUnstagedDiff { uint64 project_id = 1; uint64 buffer_id = 2; } -message OpenUnstagedChangesResponse { +message OpenUnstagedDiffResponse { optional string staged_text = 1; } -message OpenUncommittedChanges { +message OpenUncommittedDiff { uint64 project_id = 1; uint64 buffer_id = 2; } -message OpenUncommittedChangesResponse { +message OpenUncommittedDiffResponse { enum Mode { INDEX_MATCHES_HEAD = 0; INDEX_AND_HEAD = 1; diff --git a/crates/proto/src/proto.rs b/crates/proto/src/proto.rs index ec35aef44ed7670dc7efb2091df8c5f277af4452..0ba9b6ef19a8e71315e596bfc263bde8073d6ec9 100644 --- a/crates/proto/src/proto.rs +++ b/crates/proto/src/proto.rs @@ -219,10 +219,10 @@ messages!( (GetImplementationResponse, Background), (GetLlmToken, Background), (GetLlmTokenResponse, Background), - (OpenUnstagedChanges, Foreground), - (OpenUnstagedChangesResponse, Foreground), - (OpenUncommittedChanges, Foreground), - (OpenUncommittedChangesResponse, Foreground), + (OpenUnstagedDiff, Foreground), + (OpenUnstagedDiffResponse, Foreground), + (OpenUncommittedDiff, Foreground), + (OpenUncommittedDiffResponse, Foreground), (GetUsers, Foreground), (Hello, Foreground), (IncomingCall, Foreground), @@ -424,8 +424,8 @@ request_messages!( (GetProjectSymbols, GetProjectSymbolsResponse), (GetReferences, GetReferencesResponse), (GetSignatureHelp, GetSignatureHelpResponse), - (OpenUnstagedChanges, OpenUnstagedChangesResponse), - (OpenUncommittedChanges, OpenUncommittedChangesResponse), + (OpenUnstagedDiff, OpenUnstagedDiffResponse), + (OpenUncommittedDiff, OpenUncommittedDiffResponse), (GetSupermavenApiKey, GetSupermavenApiKeyResponse), (GetTypeDefinition, GetTypeDefinitionResponse), (LinkedEditingRange, LinkedEditingRangeResponse), @@ -546,8 +546,8 @@ entity_messages!( GetProjectSymbols, GetReferences, GetSignatureHelp, - OpenUnstagedChanges, - OpenUncommittedChanges, + OpenUnstagedDiff, + OpenUncommittedDiff, GetTypeDefinition, InlayHints, JoinProject, diff --git a/crates/remote_server/src/remote_editing_tests.rs b/crates/remote_server/src/remote_editing_tests.rs index 4e34953ea26417866cf603a31bd7ae013d327ca5..7552e950aaca64a7a5a6ba78f70a5725671543aa 100644 --- a/crates/remote_server/src/remote_editing_tests.rs +++ b/crates/remote_server/src/remote_editing_tests.rs @@ -84,18 +84,15 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test }) .await .unwrap(); - let change_set = project + let diff = project .update(cx, |project, cx| { - project.open_unstaged_changes(buffer.clone(), cx) + project.open_unstaged_diff(buffer.clone(), cx) }) .await .unwrap(); - change_set.update(cx, |change_set, _| { - assert_eq!( - change_set.base_text_string().unwrap(), - "fn one() -> usize { 0 }" - ); + diff.update(cx, |diff, _| { + assert_eq!(diff.base_text_string().unwrap(), "fn one() -> usize { 0 }"); }); buffer.update(cx, |buffer, cx| { @@ -155,9 +152,9 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test &[("src/lib2.rs".into(), "fn one() -> usize { 100 }".into())], ); cx.executor().run_until_parked(); - change_set.update(cx, |change_set, _| { + diff.update(cx, |diff, _| { assert_eq!( - change_set.base_text_string().unwrap(), + diff.base_text_string().unwrap(), "fn one() -> usize { 100 }" ); }); @@ -1239,18 +1236,17 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC }) .await .unwrap(); - let change_set = project + let diff = project .update(cx, |project, cx| { - project.open_uncommitted_changes(buffer.clone(), cx) + project.open_uncommitted_diff(buffer.clone(), cx) }) .await .unwrap(); - change_set.read_with(cx, |change_set, cx| { - assert_eq!(change_set.base_text_string().unwrap(), text_1); + diff.read_with(cx, |diff, cx| { + assert_eq!(diff.base_text_string().unwrap(), text_1); assert_eq!( - change_set - .unstaged_change_set + diff.unstaged_diff .as_ref() .unwrap() .read(cx) @@ -1267,11 +1263,10 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC ); cx.executor().run_until_parked(); - change_set.read_with(cx, |change_set, cx| { - assert_eq!(change_set.base_text_string().unwrap(), text_1); + diff.read_with(cx, |diff, cx| { + assert_eq!(diff.base_text_string().unwrap(), text_1); assert_eq!( - change_set - .unstaged_change_set + diff.unstaged_diff .as_ref() .unwrap() .read(cx) @@ -1288,11 +1283,10 @@ async fn test_remote_git_diffs(cx: &mut TestAppContext, server_cx: &mut TestAppC ); cx.executor().run_until_parked(); - change_set.read_with(cx, |change_set, cx| { - assert_eq!(change_set.base_text_string().unwrap(), text_2); + diff.read_with(cx, |diff, cx| { + assert_eq!(diff.base_text_string().unwrap(), text_2); assert_eq!( - change_set - .unstaged_change_set + diff.unstaged_diff .as_ref() .unwrap() .read(cx) diff --git a/crates/worktree/src/worktree.rs b/crates/worktree/src/worktree.rs index da48baf0958245d390b25f47076d83c4afd9d53c..fc2603bd442f40261799f915b15b94c3e3c94cd6 100644 --- a/crates/worktree/src/worktree.rs +++ b/crates/worktree/src/worktree.rs @@ -5386,7 +5386,7 @@ fn send_status_update_inner( let new_snapshot = state.snapshot.clone(); let old_snapshot = mem::replace(&mut state.prev_snapshot, new_snapshot.snapshot.clone()); - let changes = build_change_set(phase, &old_snapshot, &new_snapshot, &state.changed_paths); + let changes = build_diff(phase, &old_snapshot, &new_snapshot, &state.changed_paths); state.changed_paths.clear(); status_updates_tx @@ -5399,7 +5399,7 @@ fn send_status_update_inner( .is_ok() } -fn build_change_set( +fn build_diff( phase: BackgroundScannerPhase, old_snapshot: &Snapshot, new_snapshot: &Snapshot,