Restructure git diff state management to allow viewing buffers with different diff bases (#21258)
This is a pure refactor of our Git diff state management. Buffers are no longer are associated with one single diff (the unstaged changes). Instead, there is an explicit project API for retrieving a buffer's unstaged changes, and the `Editor` view layer is responsible for choosing what diff to associate with a buffer. The reason for this change is that we'll soon want to add multiple "git diff views" to Zed, one of which will show the *uncommitted* changes for a buffer. But that view will need to co-exist with other views of the same buffer, which may want to show the unstaged changes. ### Todo * [x] Get git gutter and git hunks working with new structure * [x] Update editor tests to use new APIs * [x] Update buffer tests * [x] Restructure remoting/collab protocol * [x] Update assertions about staged text in `random_project_collaboration_tests` * [x] Move buffer tests for git diff management to a new spot, using the new APIs Release Notes: - N/A --------- Co-authored-by: Richard <richard@zed.dev> Co-authored-by: Cole <cole@zed.dev> Co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
co-authored by
Richard
Cole
Conrad
parent
31796171de
commit
a2115e7242
+127
-115
@@ -83,7 +83,7 @@ use gpui::{
|
||||
use highlight_matching_bracket::refresh_matching_bracket_highlights;
|
||||
use hover_popover::{hide_hover, HoverState};
|
||||
pub(crate) use hunk_diff::HoveredHunk;
|
||||
use hunk_diff::{diff_hunk_to_display, ExpandedHunks};
|
||||
use hunk_diff::{diff_hunk_to_display, DiffMap, DiffMapSnapshot};
|
||||
use indent_guides::ActiveIndentGuidesState;
|
||||
use inlay_hint_cache::{InlayHintCache, InlaySplice, InvalidationStrategy};
|
||||
pub use inline_completion::Direction;
|
||||
@@ -625,7 +625,7 @@ pub struct Editor {
|
||||
enable_inline_completions: bool,
|
||||
show_inline_completions_override: Option<bool>,
|
||||
inlay_hint_cache: InlayHintCache,
|
||||
expanded_hunks: ExpandedHunks,
|
||||
diff_map: DiffMap,
|
||||
next_inlay_id: usize,
|
||||
_subscriptions: Vec<Subscription>,
|
||||
pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
|
||||
@@ -692,6 +692,7 @@ pub struct EditorSnapshot {
|
||||
git_blame_gutter_max_author_length: Option<usize>,
|
||||
pub display_snapshot: DisplaySnapshot,
|
||||
pub placeholder_text: Option<Arc<str>>,
|
||||
diff_map: DiffMapSnapshot,
|
||||
is_focused: bool,
|
||||
scroll_anchor: ScrollAnchor,
|
||||
ongoing_scroll: OngoingScroll,
|
||||
@@ -2002,11 +2003,10 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
let inlay_hint_settings = inlay_hint_settings(
|
||||
selections.newest_anchor().head(),
|
||||
&buffer.read(cx).snapshot(cx),
|
||||
cx,
|
||||
);
|
||||
let buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
|
||||
let inlay_hint_settings =
|
||||
inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
|
||||
let focus_handle = cx.focus_handle();
|
||||
cx.on_focus(&focus_handle, Self::handle_focus).detach();
|
||||
cx.on_focus_in(&focus_handle, Self::handle_focus_in)
|
||||
@@ -2023,6 +2023,28 @@ impl Editor {
|
||||
|
||||
let mut code_action_providers = Vec::new();
|
||||
if let Some(project) = project.clone() {
|
||||
let mut tasks = Vec::new();
|
||||
buffer.update(cx, |multibuffer, cx| {
|
||||
project.update(cx, |project, cx| {
|
||||
multibuffer.for_each_buffer(|buffer| {
|
||||
tasks.push(project.open_unstaged_changes(buffer.clone(), cx))
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let change_sets = futures::future::join_all(tasks).await;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for change_set in change_sets {
|
||||
if let Some(change_set) = change_set.log_err() {
|
||||
this.diff_map.add_change_set(change_set, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
|
||||
code_action_providers.push(Arc::new(project) as Arc<_>);
|
||||
}
|
||||
|
||||
@@ -2105,7 +2127,7 @@ impl Editor {
|
||||
inline_completion_provider: None,
|
||||
active_inline_completion: None,
|
||||
inlay_hint_cache: InlayHintCache::new(inlay_hint_settings),
|
||||
expanded_hunks: ExpandedHunks::default(),
|
||||
diff_map: DiffMap::default(),
|
||||
gutter_hovered: false,
|
||||
pixel_position_of_newest_cursor: None,
|
||||
last_bounds: None,
|
||||
@@ -2365,6 +2387,7 @@ impl Editor {
|
||||
scroll_anchor: self.scroll_manager.anchor(),
|
||||
ongoing_scroll: self.scroll_manager.ongoing_scroll(),
|
||||
placeholder_text: self.placeholder_text.clone(),
|
||||
diff_map: self.diff_map.snapshot(),
|
||||
is_focused: self.focus_handle.is_focused(cx),
|
||||
current_line_highlight: self
|
||||
.current_line_highlight
|
||||
@@ -6503,12 +6526,12 @@ impl Editor {
|
||||
|
||||
pub fn revert_file(&mut self, _: &RevertFile, cx: &mut ViewContext<Self>) {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
for hunk in hunks_for_rows(
|
||||
Some(MultiBufferRow(0)..multi_buffer_snapshot.max_row()).into_iter(),
|
||||
&multi_buffer_snapshot,
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in hunks_for_ranges(
|
||||
Some(Point::zero()..snapshot.buffer_snapshot.max_point()).into_iter(),
|
||||
&snapshot,
|
||||
) {
|
||||
Self::prepare_revert_change(&mut revert_changes, self.buffer(), &hunk, cx);
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
if !revert_changes.is_empty() {
|
||||
self.transact(cx, |editor, cx| {
|
||||
@@ -6525,7 +6548,7 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn revert_selected_hunks(&mut self, _: &RevertSelectedHunks, cx: &mut ViewContext<Self>) {
|
||||
let revert_changes = self.gather_revert_changes(&self.selections.disjoint_anchors(), cx);
|
||||
let revert_changes = self.gather_revert_changes(&self.selections.all(cx), cx);
|
||||
if !revert_changes.is_empty() {
|
||||
self.transact(cx, |editor, cx| {
|
||||
editor.revert(revert_changes, cx);
|
||||
@@ -6533,6 +6556,18 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn revert_hunk(&mut self, hunk: HoveredHunk, cx: &mut ViewContext<Editor>) {
|
||||
let snapshot = self.buffer.read(cx).read(cx);
|
||||
if let Some(hunk) = crate::hunk_diff::to_diff_hunk(&hunk, &snapshot) {
|
||||
drop(snapshot);
|
||||
let mut revert_changes = HashMap::default();
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
if !revert_changes.is_empty() {
|
||||
self.revert(revert_changes, cx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_active_item_in_terminal(&mut self, _: &OpenInTerminal, cx: &mut ViewContext<Self>) {
|
||||
if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
|
||||
let project_path = buffer.read(cx).project_path(cx)?;
|
||||
@@ -6552,26 +6587,33 @@ impl Editor {
|
||||
|
||||
fn gather_revert_changes(
|
||||
&mut self,
|
||||
selections: &[Selection<Anchor>],
|
||||
selections: &[Selection<Point>],
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>> {
|
||||
let mut revert_changes = HashMap::default();
|
||||
let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
for hunk in hunks_for_selections(&multi_buffer_snapshot, selections) {
|
||||
Self::prepare_revert_change(&mut revert_changes, self.buffer(), &hunk, cx);
|
||||
let snapshot = self.snapshot(cx);
|
||||
for hunk in hunks_for_selections(&snapshot, selections) {
|
||||
self.prepare_revert_change(&mut revert_changes, &hunk, cx);
|
||||
}
|
||||
revert_changes
|
||||
}
|
||||
|
||||
pub fn prepare_revert_change(
|
||||
&mut self,
|
||||
revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
|
||||
multi_buffer: &Model<MultiBuffer>,
|
||||
hunk: &MultiBufferDiffHunk,
|
||||
cx: &AppContext,
|
||||
) -> Option<()> {
|
||||
let buffer = multi_buffer.read(cx).buffer(hunk.buffer_id)?;
|
||||
let buffer = self.buffer.read(cx).buffer(hunk.buffer_id)?;
|
||||
let buffer = buffer.read(cx);
|
||||
let original_text = buffer.diff_base()?.slice(hunk.diff_base_byte_range.clone());
|
||||
let change_set = &self.diff_map.diff_bases.get(&hunk.buffer_id)?.change_set;
|
||||
let original_text = change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
.as_ref()?
|
||||
.read(cx)
|
||||
.as_rope()
|
||||
.slice(hunk.diff_base_byte_range.clone());
|
||||
let buffer_snapshot = buffer.snapshot();
|
||||
let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
|
||||
if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
|
||||
@@ -9752,80 +9794,63 @@ impl Editor {
|
||||
}
|
||||
|
||||
fn go_to_next_hunk(&mut self, _: &GoToHunk, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self
|
||||
.display_map
|
||||
.update(cx, |display_map, cx| display_map.snapshot(cx));
|
||||
let snapshot = self.snapshot(cx);
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
self.go_to_hunk_after_position(&snapshot, selection.head(), cx);
|
||||
}
|
||||
|
||||
fn go_to_hunk_after_position(
|
||||
&mut self,
|
||||
snapshot: &DisplaySnapshot,
|
||||
snapshot: &EditorSnapshot,
|
||||
position: Point,
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> Option<MultiBufferDiffHunk> {
|
||||
if let Some(hunk) = self.go_to_next_hunk_in_direction(
|
||||
snapshot,
|
||||
position,
|
||||
false,
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(MultiBufferRow(position.row + 1)..MultiBufferRow::MAX),
|
||||
cx,
|
||||
) {
|
||||
return Some(hunk);
|
||||
for (ix, position) in [position, Point::zero()].into_iter().enumerate() {
|
||||
if let Some(hunk) = self.go_to_next_hunk_in_direction(
|
||||
snapshot,
|
||||
position,
|
||||
ix > 0,
|
||||
snapshot.diff_map.diff_hunks_in_range(
|
||||
position + Point::new(1, 0)..snapshot.buffer_snapshot.max_point(),
|
||||
&snapshot.buffer_snapshot,
|
||||
),
|
||||
cx,
|
||||
) {
|
||||
return Some(hunk);
|
||||
}
|
||||
}
|
||||
|
||||
let wrapped_point = Point::zero();
|
||||
self.go_to_next_hunk_in_direction(
|
||||
snapshot,
|
||||
wrapped_point,
|
||||
true,
|
||||
snapshot.buffer_snapshot.git_diff_hunks_in_range(
|
||||
MultiBufferRow(wrapped_point.row + 1)..MultiBufferRow::MAX,
|
||||
),
|
||||
cx,
|
||||
)
|
||||
None
|
||||
}
|
||||
|
||||
fn go_to_prev_hunk(&mut self, _: &GoToPrevHunk, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self
|
||||
.display_map
|
||||
.update(cx, |display_map, cx| display_map.snapshot(cx));
|
||||
let snapshot = self.snapshot(cx);
|
||||
let selection = self.selections.newest::<Point>(cx);
|
||||
|
||||
self.go_to_hunk_before_position(&snapshot, selection.head(), cx);
|
||||
}
|
||||
|
||||
fn go_to_hunk_before_position(
|
||||
&mut self,
|
||||
snapshot: &DisplaySnapshot,
|
||||
snapshot: &EditorSnapshot,
|
||||
position: Point,
|
||||
cx: &mut ViewContext<'_, Editor>,
|
||||
) -> Option<MultiBufferDiffHunk> {
|
||||
if let Some(hunk) = self.go_to_next_hunk_in_direction(
|
||||
snapshot,
|
||||
position,
|
||||
false,
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(position.row)),
|
||||
cx,
|
||||
) {
|
||||
return Some(hunk);
|
||||
for (ix, position) in [position, snapshot.buffer_snapshot.max_point()]
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
if let Some(hunk) = self.go_to_next_hunk_in_direction(
|
||||
snapshot,
|
||||
position,
|
||||
ix > 0,
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range_rev(Point::zero()..position, &snapshot.buffer_snapshot),
|
||||
cx,
|
||||
) {
|
||||
return Some(hunk);
|
||||
}
|
||||
}
|
||||
|
||||
let wrapped_point = snapshot.buffer_snapshot.max_point();
|
||||
self.go_to_next_hunk_in_direction(
|
||||
snapshot,
|
||||
wrapped_point,
|
||||
true,
|
||||
snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(wrapped_point.row)),
|
||||
cx,
|
||||
)
|
||||
None
|
||||
}
|
||||
|
||||
fn go_to_next_hunk_in_direction(
|
||||
@@ -11270,13 +11295,13 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut buffers_affected = HashMap::default();
|
||||
let mut buffers_affected = HashSet::default();
|
||||
let multi_buffer = self.buffer().read(cx);
|
||||
for crease in &creases {
|
||||
if let Some((_, buffer, _)) =
|
||||
multi_buffer.excerpt_containing(crease.range().start.clone(), cx)
|
||||
{
|
||||
buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
|
||||
buffers_affected.insert(buffer.read(cx).remote_id());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11286,8 +11311,8 @@ impl Editor {
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
}
|
||||
|
||||
for buffer in buffers_affected.into_values() {
|
||||
self.sync_expanded_diff_hunks(buffer, cx);
|
||||
for buffer_id in buffers_affected {
|
||||
Self::sync_expanded_diff_hunks(&mut self.diff_map, buffer_id, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -11344,11 +11369,11 @@ impl Editor {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut buffers_affected = HashMap::default();
|
||||
let mut buffers_affected = HashSet::default();
|
||||
let multi_buffer = self.buffer().read(cx);
|
||||
for range in ranges {
|
||||
if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
|
||||
buffers_affected.insert(buffer.read(cx).remote_id(), buffer);
|
||||
buffers_affected.insert(buffer.read(cx).remote_id());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11358,8 +11383,8 @@ impl Editor {
|
||||
self.request_autoscroll(Autoscroll::fit(), cx);
|
||||
}
|
||||
|
||||
for buffer in buffers_affected.into_values() {
|
||||
self.sync_expanded_diff_hunks(buffer, cx);
|
||||
for buffer_id in buffers_affected {
|
||||
Self::sync_expanded_diff_hunks(&mut self.diff_map, buffer_id, cx);
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
@@ -12653,15 +12678,11 @@ impl Editor {
|
||||
multi_buffer::Event::FileHandleChanged | multi_buffer::Event::Reloaded => {
|
||||
cx.emit(EditorEvent::TitleChanged)
|
||||
}
|
||||
multi_buffer::Event::DiffBaseChanged => {
|
||||
self.scrollbar_marker_state.dirty = true;
|
||||
cx.emit(EditorEvent::DiffBaseChanged);
|
||||
cx.notify();
|
||||
}
|
||||
multi_buffer::Event::DiffUpdated { buffer } => {
|
||||
self.sync_expanded_diff_hunks(buffer.clone(), cx);
|
||||
cx.notify();
|
||||
}
|
||||
// multi_buffer::Event::DiffBaseChanged => {
|
||||
// self.scrollbar_marker_state.dirty = true;
|
||||
// cx.emit(EditorEvent::DiffBaseChanged);
|
||||
// cx.notify();
|
||||
// }
|
||||
multi_buffer::Event::Closed => cx.emit(EditorEvent::Closed),
|
||||
multi_buffer::Event::DiagnosticsUpdated => {
|
||||
self.refresh_active_diagnostics(cx);
|
||||
@@ -12829,7 +12850,7 @@ impl Editor {
|
||||
// When editing branch buffers, jump to the corresponding location
|
||||
// in their base buffer.
|
||||
let buffer = buffer_handle.read(cx);
|
||||
if let Some(base_buffer) = buffer.diff_base_buffer() {
|
||||
if let Some(base_buffer) = buffer.base_buffer() {
|
||||
range = buffer.range_to_version(range, &base_buffer.read(cx).version());
|
||||
buffer_handle = base_buffer;
|
||||
}
|
||||
@@ -13606,35 +13627,29 @@ fn test_wrap_with_prefix() {
|
||||
}
|
||||
|
||||
fn hunks_for_selections(
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
selections: &[Selection<Anchor>],
|
||||
snapshot: &EditorSnapshot,
|
||||
selections: &[Selection<Point>],
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
let buffer_rows_for_selections = selections.iter().map(|selection| {
|
||||
let head = selection.head();
|
||||
let tail = selection.tail();
|
||||
let start = MultiBufferRow(tail.to_point(multi_buffer_snapshot).row);
|
||||
let end = MultiBufferRow(head.to_point(multi_buffer_snapshot).row);
|
||||
if start > end {
|
||||
end..start
|
||||
} else {
|
||||
start..end
|
||||
}
|
||||
});
|
||||
|
||||
hunks_for_rows(buffer_rows_for_selections, multi_buffer_snapshot)
|
||||
hunks_for_ranges(
|
||||
selections.iter().map(|selection| selection.range()),
|
||||
snapshot,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn hunks_for_rows(
|
||||
rows: impl Iterator<Item = Range<MultiBufferRow>>,
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
pub fn hunks_for_ranges(
|
||||
ranges: impl Iterator<Item = Range<Point>>,
|
||||
snapshot: &EditorSnapshot,
|
||||
) -> Vec<MultiBufferDiffHunk> {
|
||||
let mut hunks = Vec::new();
|
||||
let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
|
||||
HashMap::default();
|
||||
for selected_multi_buffer_rows in rows {
|
||||
for query_range in ranges {
|
||||
let query_rows =
|
||||
selected_multi_buffer_rows.start..selected_multi_buffer_rows.end.next_row();
|
||||
for hunk in multi_buffer_snapshot.git_diff_hunks_in_range(query_rows.clone()) {
|
||||
MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
|
||||
for hunk in snapshot.diff_map.diff_hunks_in_range(
|
||||
Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
|
||||
&snapshot.buffer_snapshot,
|
||||
) {
|
||||
// Deleted hunk is an empty row range, no caret can be placed there and Zed allows to revert it
|
||||
// when the caret is just above or just below the deleted hunk.
|
||||
let allow_adjacent = hunk_status(&hunk) == DiffHunkStatus::Removed;
|
||||
@@ -13643,10 +13658,7 @@ pub fn hunks_for_rows(
|
||||
|| hunk.row_range.start == query_rows.end
|
||||
|| hunk.row_range.end == query_rows.start
|
||||
} else {
|
||||
// `selected_multi_buffer_rows` are inclusive (e.g. [2..2] means 2nd row is selected)
|
||||
// `hunk.row_range` is exclusive (e.g. [2..3] means 2nd row is selected)
|
||||
hunk.row_range.overlaps(&selected_multi_buffer_rows)
|
||||
|| selected_multi_buffer_rows.end == hunk.row_range.start
|
||||
hunk.row_range.overlaps(&query_rows)
|
||||
};
|
||||
if related_to_selection {
|
||||
if !processed_buffer_rows
|
||||
|
||||
+181
-263
@@ -25,7 +25,7 @@ use language::{
|
||||
use language_settings::{Formatter, FormatterList, IndentGuideSettings};
|
||||
use multi_buffer::MultiBufferIndentGuide;
|
||||
use parking_lot::Mutex;
|
||||
use project::FakeFs;
|
||||
use project::{buffer_store::BufferChangeSet, FakeFs};
|
||||
use project::{
|
||||
lsp_command::SIGNATURE_HELP_HIGHLIGHT_CURRENT,
|
||||
project_settings::{LspSettings, ProjectSettings},
|
||||
@@ -3313,7 +3313,7 @@ async fn test_join_lines_with_git_diff_base(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
// Join lines
|
||||
@@ -3353,16 +3353,15 @@ async fn test_custom_newlines_cause_no_false_positive_diffs(
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorTestContext::new(cx).await;
|
||||
cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
|
||||
cx.set_diff_base(Some("Line 0\r\nLine 1\r\nLine 2\r\nLine 3"));
|
||||
cx.set_diff_base("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
assert_eq!(
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.snapshot(cx)
|
||||
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
|
||||
snapshot
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.collect::<Vec<_>>(),
|
||||
Vec::new(),
|
||||
"Should not have any diffs for files with custom newlines"
|
||||
@@ -10088,7 +10087,7 @@ async fn go_to_hunk(executor: BackgroundExecutor, cx: &mut gpui::TestAppContext)
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
@@ -11125,17 +11124,18 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
|
||||
async fn test_addition_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
let base_text = indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
let base_text = indoc! {r#"
|
||||
struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
|
||||
// When addition hunks are not adjacent to carets, no hunk revert is performed
|
||||
assert_hunk_revert(
|
||||
@@ -11266,17 +11266,18 @@ struct Row10;"#};
|
||||
async fn test_modification_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
|
||||
let base_text = indoc! {r#"struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
let base_text = indoc! {r#"
|
||||
struct Row;
|
||||
struct Row1;
|
||||
struct Row2;
|
||||
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
struct Row4;
|
||||
struct Row5;
|
||||
struct Row6;
|
||||
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
struct Row8;
|
||||
struct Row9;
|
||||
struct Row10;"#};
|
||||
|
||||
// Modification hunks behave the same as the addition ones.
|
||||
assert_hunk_revert(
|
||||
@@ -11494,54 +11495,18 @@ struct Row10;"#};
|
||||
async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx, |_| {});
|
||||
|
||||
let cols = 4;
|
||||
let rows = 10;
|
||||
let sample_text_1 = sample_text(rows, cols, 'a');
|
||||
assert_eq!(
|
||||
sample_text_1,
|
||||
"aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
|
||||
);
|
||||
let sample_text_2 = sample_text(rows, cols, 'l');
|
||||
assert_eq!(
|
||||
sample_text_2,
|
||||
"llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
|
||||
);
|
||||
let sample_text_3 = sample_text(rows, cols, 'v');
|
||||
assert_eq!(
|
||||
sample_text_3,
|
||||
"vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
|
||||
);
|
||||
let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
|
||||
let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
|
||||
let base_text_3 =
|
||||
"vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
|
||||
|
||||
fn diff_every_buffer_row(
|
||||
buffer: &Model<Buffer>,
|
||||
sample_text: String,
|
||||
cols: usize,
|
||||
cx: &mut gpui::TestAppContext,
|
||||
) {
|
||||
// revert first character in each row, creating one large diff hunk per buffer
|
||||
let is_first_char = |offset: usize| offset % cols == 0;
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.set_text(
|
||||
sample_text
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(offset, c)| if is_first_char(offset) { 'X' } else { c })
|
||||
.collect::<String>(),
|
||||
cx,
|
||||
);
|
||||
buffer.set_diff_base(Some(sample_text), cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
}
|
||||
let text_1 = edit_first_char_of_every_line(base_text_1);
|
||||
let text_2 = edit_first_char_of_every_line(base_text_2);
|
||||
let text_3 = edit_first_char_of_every_line(base_text_3);
|
||||
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(sample_text_1.clone(), cx));
|
||||
diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
|
||||
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(sample_text_2.clone(), cx));
|
||||
diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
|
||||
|
||||
let buffer_3 = cx.new_model(|cx| Buffer::local(sample_text_3.clone(), cx));
|
||||
diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(text_1.clone(), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(text_2.clone(), cx));
|
||||
let buffer_3 = cx.new_model(|cx| Buffer::local(text_3.clone(), cx));
|
||||
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
@@ -11604,57 +11569,85 @@ async fn test_multibuffer_reverts(cx: &mut gpui::TestAppContext) {
|
||||
|
||||
let (editor, cx) = cx.add_window_view(|cx| build_editor(multibuffer, cx));
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "XaaaXbbbX\nccXc\ndXdd\n\nhXhh\nXiiiXjjjX\n\nXlllXmmmX\nnnXn\noXoo\n\nsXss\nXtttXuuuX\n\nXvvvXwwwX\nxxXx\nyXyy\n\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n");
|
||||
for (buffer, diff_base) in [
|
||||
(buffer_1.clone(), base_text_1),
|
||||
(buffer_2.clone(), base_text_2),
|
||||
(buffer_3.clone(), base_text_3),
|
||||
] {
|
||||
let change_set = cx.new_model(|cx| {
|
||||
BufferChangeSet::new_with_base_text(
|
||||
diff_base.to_string(),
|
||||
buffer.read(cx).text_snapshot(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
|
||||
editor.select_all(&SelectAll, cx);
|
||||
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// When all ranges are selected, all buffer hunks are reverted.
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
|
||||
});
|
||||
buffer_1.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_1);
|
||||
assert_eq!(buffer.text(), base_text_1);
|
||||
});
|
||||
buffer_2.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_2);
|
||||
assert_eq!(buffer.text(), base_text_2);
|
||||
});
|
||||
buffer_3.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_3);
|
||||
assert_eq!(buffer.text(), base_text_3);
|
||||
});
|
||||
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.undo(&Default::default(), cx);
|
||||
});
|
||||
|
||||
diff_every_buffer_row(&buffer_1, sample_text_1.clone(), cols, cx);
|
||||
diff_every_buffer_row(&buffer_2, sample_text_2.clone(), cols, cx);
|
||||
diff_every_buffer_row(&buffer_3, sample_text_3.clone(), cols, cx);
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |s| {
|
||||
s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
|
||||
});
|
||||
editor.revert_selected_hunks(&RevertSelectedHunks, cx);
|
||||
});
|
||||
|
||||
// Now, when all ranges selected belong to buffer_1, the revert should succeed,
|
||||
// but not affect buffer_2 and its related excerpts.
|
||||
editor.update(cx, |editor, cx| {
|
||||
assert_eq!(
|
||||
editor.text(cx),
|
||||
"aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX\n\n\nXvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X\n\n"
|
||||
"aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
|
||||
);
|
||||
});
|
||||
buffer_1.update(cx, |buffer, _| {
|
||||
assert_eq!(buffer.text(), sample_text_1);
|
||||
assert_eq!(buffer.text(), base_text_1);
|
||||
});
|
||||
buffer_2.update(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"XlllXmmmX\nnnXn\noXoo\nXpppXqqqX\nrrXr\nsXss\nXtttXuuuX"
|
||||
"Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
|
||||
);
|
||||
});
|
||||
buffer_3.update(cx, |buffer, _| {
|
||||
assert_eq!(
|
||||
buffer.text(),
|
||||
"XvvvXwwwX\nxxXx\nyXyy\nXzzzX{{{X\n||X|\n}X}}\nX~~~X\u{7f}\u{7f}\u{7f}X"
|
||||
"Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
|
||||
);
|
||||
});
|
||||
|
||||
fn edit_first_char_of_every_line(text: &str) -> String {
|
||||
text.split('\n')
|
||||
.map(|line| format!("X{}", &line[1..]))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
@@ -12049,7 +12042,7 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
@@ -12057,14 +12050,14 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
||||
editor.toggle_hunk_diff(&ToggleHunkDiff, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::modified;
|
||||
|
||||
|
||||
fn main() {
|
||||
- println!("hello");
|
||||
+ println!("hello there");
|
||||
+ ˇ println!("hello there");
|
||||
|
||||
println!("around the");
|
||||
println!("world");
|
||||
@@ -12080,28 +12073,13 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
||||
}
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_editor_state(
|
||||
&r#"
|
||||
use some::modified;
|
||||
|
||||
ˇ
|
||||
fn main() {
|
||||
println!("hello there");
|
||||
|
||||
println!("around the");
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod;
|
||||
+ use some::modified;
|
||||
|
||||
- const A: u32 = 42;
|
||||
|
||||
ˇ
|
||||
fn main() {
|
||||
- println!("hello");
|
||||
+ println!("hello there");
|
||||
@@ -12117,11 +12095,11 @@ async fn test_toggle_hunk_diff(executor: BackgroundExecutor, cx: &mut gpui::Test
|
||||
editor.cancel(&Cancel, cx);
|
||||
});
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::modified;
|
||||
|
||||
|
||||
ˇ
|
||||
fn main() {
|
||||
println!("hello there");
|
||||
|
||||
@@ -12176,14 +12154,14 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -12192,7 +12170,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
- const B: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
fn main(ˇ) {
|
||||
- println!("hello");
|
||||
+ //println!("hello");
|
||||
|
||||
@@ -12204,16 +12182,16 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some("new diff base!"));
|
||||
cx.set_diff_base("new diff base!");
|
||||
executor.run_until_parked();
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
fn main(ˇ) {
|
||||
//println!("hello");
|
||||
|
||||
println!("world");
|
||||
@@ -12228,7 +12206,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- new diff base!
|
||||
+ use some::mod2;
|
||||
@@ -12236,7 +12214,7 @@ async fn test_diff_base_change_with_expanded_diff_hunks(
|
||||
+ const A: u32 = 42;
|
||||
+ const C: u32 = 42;
|
||||
+
|
||||
+ fn main() {
|
||||
+ fn main(ˇ) {
|
||||
+ //println!("hello");
|
||||
+
|
||||
+ println!("world");
|
||||
@@ -12304,7 +12282,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
@@ -12312,10 +12290,10 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod1;
|
||||
use some::mod2;
|
||||
«use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
- const B: u32 = 42;
|
||||
@@ -12327,7 +12305,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
|
||||
println!("world");
|
||||
+ //
|
||||
+ //
|
||||
+ //ˇ»
|
||||
}
|
||||
|
||||
fn another() {
|
||||
@@ -12347,9 +12325,9 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// Hunks are not shown if their position is within a fold
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod2;
|
||||
«use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
@@ -12359,7 +12337,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
|
||||
println!("world");
|
||||
//
|
||||
//
|
||||
//ˇ»
|
||||
}
|
||||
|
||||
fn another() {
|
||||
@@ -12381,10 +12359,10 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
// The deletions reappear when unfolding.
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
- use some::mod1;
|
||||
use some::mod2;
|
||||
«use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
- const B: u32 = 42;
|
||||
@@ -12407,7 +12385,7 @@ async fn test_fold_unfold_diff_hunk(executor: BackgroundExecutor, cx: &mut gpui:
|
||||
- fn another2() {
|
||||
println!("another2");
|
||||
}
|
||||
"#
|
||||
ˇ»"#
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
@@ -12423,21 +12401,9 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
||||
let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
|
||||
let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
|
||||
|
||||
let buffer_1 = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::local(file_1_new.to_string(), cx);
|
||||
buffer.set_diff_base(Some(file_1_old.into()), cx);
|
||||
buffer
|
||||
});
|
||||
let buffer_2 = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::local(file_2_new.to_string(), cx);
|
||||
buffer.set_diff_base(Some(file_2_old.into()), cx);
|
||||
buffer
|
||||
});
|
||||
let buffer_3 = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::local(file_3_new.to_string(), cx);
|
||||
buffer.set_diff_base(Some(file_3_old.into()), cx);
|
||||
buffer
|
||||
});
|
||||
let buffer_1 = cx.new_model(|cx| Buffer::local(file_1_new.to_string(), cx));
|
||||
let buffer_2 = cx.new_model(|cx| Buffer::local(file_2_new.to_string(), cx));
|
||||
let buffer_3 = cx.new_model(|cx| Buffer::local(file_3_new.to_string(), cx));
|
||||
|
||||
let multi_buffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
@@ -12499,6 +12465,25 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
||||
});
|
||||
|
||||
let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
for (buffer, diff_base) in [
|
||||
(buffer_1.clone(), file_1_old),
|
||||
(buffer_2.clone(), file_2_old),
|
||||
(buffer_3.clone(), file_3_old),
|
||||
] {
|
||||
let change_set = cx.new_model(|cx| {
|
||||
BufferChangeSet::new_with_base_text(
|
||||
diff_base.to_string(),
|
||||
buffer.read(cx).text_snapshot(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut cx = EditorTestContext::for_editor(editor, cx).await;
|
||||
cx.run_until_parked();
|
||||
|
||||
@@ -12538,9 +12523,9 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
"
|
||||
aaa
|
||||
«aaa
|
||||
- bbb
|
||||
ccc
|
||||
ddd
|
||||
@@ -12566,8 +12551,8 @@ async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut gpui::TestAppContext)
|
||||
777
|
||||
|
||||
000
|
||||
!!!"
|
||||
.unindent(),
|
||||
!!!ˇ»"
|
||||
.unindent(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12578,12 +12563,7 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
|
||||
let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
|
||||
let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\n";
|
||||
|
||||
let buffer = cx.new_model(|cx| {
|
||||
let mut buffer = Buffer::local(text.to_string(), cx);
|
||||
buffer.set_diff_base(Some(base.into()), cx);
|
||||
buffer
|
||||
});
|
||||
|
||||
let buffer = cx.new_model(|cx| Buffer::local(text.to_string(), cx));
|
||||
let multi_buffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
multibuffer.push_excerpts(
|
||||
@@ -12604,15 +12584,24 @@ async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut gpui::TestAppContext
|
||||
});
|
||||
|
||||
let editor = cx.add_window(|cx| Editor::new(EditorMode::Full, multi_buffer, None, true, cx));
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
let buffer = buffer.read(cx).text_snapshot();
|
||||
let change_set = cx
|
||||
.new_model(|cx| BufferChangeSet::new_with_base_text(base.to_string(), buffer, cx));
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut cx = EditorTestContext::for_editor(editor, cx).await;
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| editor.expand_all_hunk_diffs(&Default::default(), cx));
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
"
|
||||
aaa
|
||||
ˇaaa
|
||||
- bbb
|
||||
+ BBB
|
||||
|
||||
@@ -12667,7 +12656,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
@@ -12675,7 +12664,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -12683,7 +12672,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
const A: u32 = 42;
|
||||
+ const B: u32 = 42;
|
||||
+ const C: u32 = 42;
|
||||
+
|
||||
+ ˇ
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
@@ -12697,7 +12686,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
cx.update_editor(|editor, cx| editor.handle_input("const D: u32 = 42;\n", cx));
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -12706,7 +12695,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
+ const B: u32 = 42;
|
||||
+ const C: u32 = 42;
|
||||
+ const D: u32 = 42;
|
||||
+
|
||||
+ ˇ
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
@@ -12720,7 +12709,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
cx.update_editor(|editor, cx| editor.handle_input("const E: u32 = 42;\n", cx));
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -12730,7 +12719,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
+ const C: u32 = 42;
|
||||
+ const D: u32 = 42;
|
||||
+ const E: u32 = 42;
|
||||
+
|
||||
+ ˇ
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
@@ -12746,7 +12735,7 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -12756,32 +12745,6 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
+ const C: u32 = 42;
|
||||
+ const D: u32 = 42;
|
||||
+ const E: u32 = 42;
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.move_up(&MoveUp, cx);
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
editor.move_up(&MoveUp, cx);
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
editor.move_up(&MoveUp, cx);
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_editor_state(
|
||||
&r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
const B: u32 = 42;
|
||||
ˇ
|
||||
fn main() {
|
||||
println!("hello");
|
||||
@@ -12792,14 +12755,23 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.move_up(&MoveUp, cx);
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
editor.move_up(&MoveUp, cx);
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
editor.move_up(&MoveUp, cx);
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
const A: u32 = 42;
|
||||
+ const B: u32 = 42;
|
||||
|
||||
ˇ
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
@@ -12814,13 +12786,13 @@ async fn test_edits_around_expanded_insertion_hunks(
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
- use some::mod2;
|
||||
-
|
||||
- const A: u32 = 42;
|
||||
|
||||
ˇ
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
@@ -12875,7 +12847,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.update_editor(|editor, cx| {
|
||||
@@ -12883,13 +12855,13 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
- const A: u32 = 42;
|
||||
const B: u32 = 42;
|
||||
ˇconst B: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
|
||||
@@ -12906,32 +12878,16 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_editor_state(
|
||||
&r#"
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
- const A: u32 = 42;
|
||||
- const B: u32 = 42;
|
||||
ˇconst C: u32 = 42;
|
||||
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.assert_diff_hunks(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
- const A: u32 = 42;
|
||||
- const B: u32 = 42;
|
||||
const C: u32 = 42;
|
||||
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
@@ -12945,22 +12901,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
editor.delete_line(&DeleteLine, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_editor_state(
|
||||
&r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
ˇ
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -12968,7 +12909,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
- const A: u32 = 42;
|
||||
- const B: u32 = 42;
|
||||
- const C: u32 = 42;
|
||||
|
||||
ˇ
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
@@ -12983,22 +12924,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
editor.handle_input("replacement", cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
cx.assert_editor_state(
|
||||
&r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
|
||||
replacementˇ
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
|
||||
println!("world");
|
||||
}
|
||||
"#
|
||||
.unindent(),
|
||||
);
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -13007,7 +12933,7 @@ async fn test_edits_around_expanded_deletion_hunks(
|
||||
- const B: u32 = 42;
|
||||
- const C: u32 = 42;
|
||||
-
|
||||
+ replacement
|
||||
+ replacementˇ
|
||||
|
||||
fn main() {
|
||||
println!("hello");
|
||||
@@ -13064,14 +12990,14 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
.unindent(),
|
||||
);
|
||||
|
||||
cx.set_diff_base(Some(&diff_base));
|
||||
cx.set_diff_base(&diff_base);
|
||||
executor.run_until_parked();
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor.expand_all_hunk_diffs(&ExpandAllHunkDiffs, cx);
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -13079,7 +13005,7 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
const A: u32 = 42;
|
||||
const B: u32 = 42;
|
||||
- const C: u32 = 42;
|
||||
+ const C: u32 = 43
|
||||
+ const C: u32 = 43ˇ
|
||||
const D: u32 = 42;
|
||||
|
||||
|
||||
@@ -13096,7 +13022,7 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
});
|
||||
executor.run_until_parked();
|
||||
|
||||
cx.assert_diff_hunks(
|
||||
cx.assert_state_with_diff(
|
||||
r#"
|
||||
use some::mod1;
|
||||
use some::mod2;
|
||||
@@ -13106,7 +13032,7 @@ async fn test_edit_after_expanded_modification_hunk(
|
||||
- const C: u32 = 42;
|
||||
+ const C: u32 = 43
|
||||
+ new_line
|
||||
+
|
||||
+ ˇ
|
||||
const D: u32 = 42;
|
||||
|
||||
|
||||
@@ -14185,22 +14111,14 @@ fn assert_hunk_revert(
|
||||
cx: &mut EditorLspTestContext,
|
||||
) {
|
||||
cx.set_state(not_reverted_text_with_selections);
|
||||
cx.update_editor(|editor, cx| {
|
||||
editor
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.as_singleton()
|
||||
.unwrap()
|
||||
.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(Some(base_text.into()), cx);
|
||||
});
|
||||
});
|
||||
cx.set_diff_base(base_text);
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let reverted_hunk_statuses = cx.update_editor(|editor, cx| {
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let reverted_hunk_statuses = snapshot
|
||||
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
|
||||
.diff_map
|
||||
.diff_hunks_in_range(0..snapshot.buffer_snapshot.len(), &snapshot.buffer_snapshot)
|
||||
.map(|hunk| hunk_status(&hunk))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
||||
@@ -1169,7 +1169,7 @@ impl EditorElement {
|
||||
let editor = self.editor.read(cx);
|
||||
let is_singleton = editor.is_singleton(cx);
|
||||
// Git
|
||||
(is_singleton && scrollbar_settings.git_diff && snapshot.buffer_snapshot.has_git_diffs())
|
||||
(is_singleton && scrollbar_settings.git_diff && !snapshot.diff_map.is_empty())
|
||||
||
|
||||
// Buffer Search Results
|
||||
(is_singleton && scrollbar_settings.search_results && editor.has_background_highlights::<BufferSearchHighlights>())
|
||||
@@ -1320,17 +1320,8 @@ impl EditorElement {
|
||||
cx: &mut WindowContext,
|
||||
) -> Vec<(DisplayDiffHunk, Option<Hitbox>)> {
|
||||
let buffer_snapshot = &snapshot.buffer_snapshot;
|
||||
|
||||
let buffer_start_row = MultiBufferRow(
|
||||
DisplayPoint::new(display_rows.start, 0)
|
||||
.to_point(snapshot)
|
||||
.row,
|
||||
);
|
||||
let buffer_end_row = MultiBufferRow(
|
||||
DisplayPoint::new(display_rows.end, 0)
|
||||
.to_point(snapshot)
|
||||
.row,
|
||||
);
|
||||
let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(snapshot);
|
||||
let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(snapshot);
|
||||
|
||||
let git_gutter_setting = ProjectSettings::get_global(cx)
|
||||
.git
|
||||
@@ -1338,7 +1329,7 @@ impl EditorElement {
|
||||
.unwrap_or_default();
|
||||
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
let expanded_hunks = &editor.expanded_hunks.hunks;
|
||||
let expanded_hunks = &editor.diff_map.hunks;
|
||||
let expanded_hunks_start_ix = expanded_hunks
|
||||
.binary_search_by(|hunk| {
|
||||
hunk.hunk_range
|
||||
@@ -1349,8 +1340,10 @@ impl EditorElement {
|
||||
.unwrap_err();
|
||||
let mut expanded_hunks = expanded_hunks[expanded_hunks_start_ix..].iter().peekable();
|
||||
|
||||
let display_hunks = buffer_snapshot
|
||||
.git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
|
||||
let mut display_hunks: Vec<(DisplayDiffHunk, Option<Hitbox>)> = editor
|
||||
.diff_map
|
||||
.snapshot
|
||||
.diff_hunks_in_range(buffer_start..buffer_end, &buffer_snapshot)
|
||||
.filter_map(|hunk| {
|
||||
let display_hunk = diff_hunk_to_display(&hunk, snapshot);
|
||||
|
||||
@@ -1393,25 +1386,23 @@ impl EditorElement {
|
||||
Some(display_hunk)
|
||||
})
|
||||
.dedup()
|
||||
.map(|hunk| match git_gutter_setting {
|
||||
GitGutterSetting::TrackedFiles => {
|
||||
let hitbox = match hunk {
|
||||
DisplayDiffHunk::Unfolded { .. } => {
|
||||
let hunk_bounds = Self::diff_hunk_bounds(
|
||||
snapshot,
|
||||
line_height,
|
||||
gutter_hitbox.bounds,
|
||||
&hunk,
|
||||
);
|
||||
Some(cx.insert_hitbox(hunk_bounds, true))
|
||||
}
|
||||
DisplayDiffHunk::Folded { .. } => None,
|
||||
};
|
||||
(hunk, hitbox)
|
||||
}
|
||||
GitGutterSetting::Hide => (hunk, None),
|
||||
})
|
||||
.map(|hunk| (hunk, None))
|
||||
.collect();
|
||||
|
||||
if let GitGutterSetting::TrackedFiles = git_gutter_setting {
|
||||
for (hunk, hitbox) in &mut display_hunks {
|
||||
if let DisplayDiffHunk::Unfolded { .. } = hunk {
|
||||
let hunk_bounds = Self::diff_hunk_bounds(
|
||||
snapshot,
|
||||
line_height,
|
||||
gutter_hitbox.bounds,
|
||||
&hunk,
|
||||
);
|
||||
*hitbox = Some(cx.insert_hitbox(hunk_bounds, true));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
display_hunks
|
||||
})
|
||||
}
|
||||
@@ -3755,10 +3746,8 @@ impl EditorElement {
|
||||
let mut marker_quads = Vec::new();
|
||||
if scrollbar_settings.git_diff {
|
||||
let marker_row_ranges = snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(
|
||||
MultiBufferRow::MIN..MultiBufferRow::MAX,
|
||||
)
|
||||
.diff_map
|
||||
.diff_hunks(&snapshot.buffer_snapshot)
|
||||
.map(|hunk| {
|
||||
let start_display_row =
|
||||
MultiBufferPoint::new(hunk.row_range.start.0, 0)
|
||||
@@ -5440,7 +5429,7 @@ impl Element for EditorElement {
|
||||
|
||||
let expanded_add_hunks_by_rows = self.editor.update(cx, |editor, _| {
|
||||
editor
|
||||
.expanded_hunks
|
||||
.diff_map
|
||||
.hunks(false)
|
||||
.filter(|hunk| hunk.status == DiffHunkStatus::Added)
|
||||
.map(|expanded_hunk| {
|
||||
|
||||
@@ -9,13 +9,15 @@ use std::{
|
||||
use anyhow::Context as _;
|
||||
use collections::{BTreeMap, HashMap};
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use git::{diff::DiffHunk, repository::GitFileStatus};
|
||||
use git::{
|
||||
diff::{BufferDiff, DiffHunk},
|
||||
repository::GitFileStatus,
|
||||
};
|
||||
use gpui::{
|
||||
actions, AnyElement, AnyView, AppContext, EventEmitter, FocusHandle, FocusableView,
|
||||
InteractiveElement, Model, Render, Subscription, Task, View, WeakView,
|
||||
};
|
||||
use language::{Buffer, BufferRow, BufferSnapshot};
|
||||
use language::{Buffer, BufferRow};
|
||||
use multi_buffer::{ExcerptId, ExcerptRange, ExpandExcerptDirection, MultiBuffer};
|
||||
use project::{Project, ProjectEntryId, ProjectPath, WorktreeId};
|
||||
use text::{OffsetRangeExt, ToPoint};
|
||||
@@ -215,54 +217,56 @@ impl ProjectDiffEditor {
|
||||
.ok()
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
let buffers_with_git_diff = cx
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
let mut open_tasks = open_tasks
|
||||
.into_iter()
|
||||
.map(|(status, entry_id, entry_path, open_task)| async move {
|
||||
let (_, opened_model) = open_task.await.with_context(|| {
|
||||
format!(
|
||||
"loading buffer {} for git diff",
|
||||
entry_path.path.display()
|
||||
)
|
||||
})?;
|
||||
let buffer = match opened_model.downcast::<Buffer>() {
|
||||
Ok(buffer) => buffer,
|
||||
Err(_model) => anyhow::bail!(
|
||||
"Could not load {} as a buffer for git diff",
|
||||
entry_path.path.display()
|
||||
),
|
||||
};
|
||||
anyhow::Ok((status, entry_id, entry_path, buffer))
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>();
|
||||
|
||||
let mut buffers_with_git_diff = Vec::new();
|
||||
while let Some(opened_buffer) = open_tasks.next().await {
|
||||
if let Some(opened_buffer) = opened_buffer.log_err() {
|
||||
buffers_with_git_diff.push(opened_buffer);
|
||||
}
|
||||
}
|
||||
buffers_with_git_diff
|
||||
})
|
||||
.await;
|
||||
|
||||
let Some((buffers, mut new_entries)) = cx
|
||||
.update(|cx| {
|
||||
let Some((buffers, mut new_entries, change_sets)) = cx
|
||||
.spawn(|mut cx| async move {
|
||||
let mut new_entries = Vec::new();
|
||||
let mut buffers = HashMap::<
|
||||
ProjectEntryId,
|
||||
(GitFileStatus, Model<Buffer>, BufferSnapshot),
|
||||
(
|
||||
GitFileStatus,
|
||||
text::BufferSnapshot,
|
||||
Model<Buffer>,
|
||||
BufferDiff,
|
||||
),
|
||||
>::default();
|
||||
let mut new_entries = Vec::new();
|
||||
for (status, entry_id, entry_path, buffer) in buffers_with_git_diff {
|
||||
let buffer_snapshot = buffer.read(cx).snapshot();
|
||||
buffers.insert(entry_id, (status, buffer, buffer_snapshot));
|
||||
let mut change_sets = Vec::new();
|
||||
for (status, entry_id, entry_path, open_task) in open_tasks {
|
||||
let (_, opened_model) = open_task.await.with_context(|| {
|
||||
format!("loading buffer {} for git diff", entry_path.path.display())
|
||||
})?;
|
||||
let buffer = match opened_model.downcast::<Buffer>() {
|
||||
Ok(buffer) => buffer,
|
||||
Err(_model) => anyhow::bail!(
|
||||
"Could not load {} as a buffer for git diff",
|
||||
entry_path.path.display()
|
||||
),
|
||||
};
|
||||
let change_set = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.open_unstaged_changes(buffer.clone(), cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
cx.update(|cx| {
|
||||
buffers.insert(
|
||||
entry_id,
|
||||
(
|
||||
status,
|
||||
buffer.read(cx).text_snapshot(),
|
||||
buffer,
|
||||
change_set.read(cx).diff_to_buffer.clone(),
|
||||
),
|
||||
);
|
||||
})?;
|
||||
change_sets.push(change_set);
|
||||
new_entries.push((entry_path, entry_id));
|
||||
}
|
||||
(buffers, new_entries)
|
||||
|
||||
Ok((buffers, new_entries, change_sets))
|
||||
})
|
||||
.ok()
|
||||
.await
|
||||
.log_err()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
@@ -271,14 +275,14 @@ impl ProjectDiffEditor {
|
||||
.background_executor()
|
||||
.spawn(async move {
|
||||
let mut new_changes = HashMap::<ProjectEntryId, Changes>::default();
|
||||
for (entry_id, (status, buffer, buffer_snapshot)) in buffers {
|
||||
for (entry_id, (status, buffer_snapshot, buffer, buffer_diff)) in buffers {
|
||||
new_changes.insert(
|
||||
entry_id,
|
||||
Changes {
|
||||
_status: status,
|
||||
buffer,
|
||||
hunks: buffer_snapshot
|
||||
.git_diff_hunks_in_row_range(0..BufferRow::MAX)
|
||||
hunks: buffer_diff
|
||||
.hunks_in_row_range(0..BufferRow::MAX, &buffer_snapshot)
|
||||
.collect::<Vec<_>>(),
|
||||
},
|
||||
);
|
||||
@@ -294,33 +298,16 @@ impl ProjectDiffEditor {
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut diff_recalculations = FuturesUnordered::new();
|
||||
project_diff_editor
|
||||
.update(&mut cx, |project_diff_editor, cx| {
|
||||
project_diff_editor.update_excerpts(id, new_changes, new_entry_order, cx);
|
||||
for buffer in project_diff_editor
|
||||
.editor
|
||||
.read(cx)
|
||||
.buffer()
|
||||
.read(cx)
|
||||
.all_buffers()
|
||||
{
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
if let Some(diff_recalculation) = buffer.recalculate_diff(cx) {
|
||||
diff_recalculations.push(diff_recalculation);
|
||||
}
|
||||
for change_set in change_sets {
|
||||
project_diff_editor.editor.update(cx, |editor, cx| {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
});
|
||||
}
|
||||
})
|
||||
.ok();
|
||||
|
||||
cx.background_executor()
|
||||
.spawn(async move {
|
||||
while let Some(()) = diff_recalculations.next().await {
|
||||
// another diff is calculated
|
||||
}
|
||||
})
|
||||
.await;
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -1100,13 +1087,13 @@ impl Render for ProjectDiffEditor {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{ops::Deref as _, path::Path, sync::Arc};
|
||||
// use std::{ops::Deref as _, path::Path, sync::Arc};
|
||||
|
||||
use fs::RealFs;
|
||||
use gpui::{SemanticVersion, TestAppContext, VisualTestContext};
|
||||
use settings::SettingsStore;
|
||||
// use fs::RealFs;
|
||||
// use gpui::{SemanticVersion, TestAppContext, VisualTestContext};
|
||||
// use settings::SettingsStore;
|
||||
|
||||
use super::*;
|
||||
// use super::*;
|
||||
|
||||
// TODO finish
|
||||
// #[gpui::test]
|
||||
@@ -1122,114 +1109,114 @@ mod tests {
|
||||
// // Apply randomized changes to the project: select a random file, random change and apply to buffers
|
||||
// }
|
||||
|
||||
#[gpui::test]
|
||||
async fn simple_edit_test(cx: &mut TestAppContext) {
|
||||
cx.executor().allow_parking();
|
||||
init_test(cx);
|
||||
// #[gpui::test]
|
||||
// async fn simple_edit_test(cx: &mut TestAppContext) {
|
||||
// cx.executor().allow_parking();
|
||||
// init_test(cx);
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let dst = dir.path();
|
||||
// let dir = tempfile::tempdir().unwrap();
|
||||
// let dst = dir.path();
|
||||
|
||||
std::fs::write(dst.join("file_a"), "This is file_a").unwrap();
|
||||
std::fs::write(dst.join("file_b"), "This is file_b").unwrap();
|
||||
// std::fs::write(dst.join("file_a"), "This is file_a").unwrap();
|
||||
// std::fs::write(dst.join("file_b"), "This is file_b").unwrap();
|
||||
|
||||
run_git(dst, &["init"]);
|
||||
run_git(dst, &["add", "*"]);
|
||||
run_git(dst, &["commit", "-m", "Initial commit"]);
|
||||
// run_git(dst, &["init"]);
|
||||
// run_git(dst, &["add", "*"]);
|
||||
// run_git(dst, &["commit", "-m", "Initial commit"]);
|
||||
|
||||
let project = Project::test(Arc::new(RealFs::default()), [dst], cx).await;
|
||||
let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
// let project = Project::test(Arc::new(RealFs::default()), [dst], cx).await;
|
||||
// let workspace = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
// let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
|
||||
|
||||
let file_a_editor = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let file_a_editor = workspace.open_abs_path(dst.join("file_a"), true, cx);
|
||||
ProjectDiffEditor::deploy(workspace, &Deploy, cx);
|
||||
file_a_editor
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
.expect("did not open an item at all")
|
||||
.downcast::<Editor>()
|
||||
.expect("did not open an editor for file_a");
|
||||
// let file_a_editor = workspace
|
||||
// .update(cx, |workspace, cx| {
|
||||
// let file_a_editor = workspace.open_abs_path(dst.join("file_a"), true, cx);
|
||||
// ProjectDiffEditor::deploy(workspace, &Deploy, cx);
|
||||
// file_a_editor
|
||||
// })
|
||||
// .unwrap()
|
||||
// .await
|
||||
// .expect("did not open an item at all")
|
||||
// .downcast::<Editor>()
|
||||
// .expect("did not open an editor for file_a");
|
||||
|
||||
let project_diff_editor = workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
workspace
|
||||
.active_pane()
|
||||
.read(cx)
|
||||
.items()
|
||||
.find_map(|item| item.downcast::<ProjectDiffEditor>())
|
||||
})
|
||||
.unwrap()
|
||||
.expect("did not find a ProjectDiffEditor");
|
||||
project_diff_editor.update(cx, |project_diff_editor, cx| {
|
||||
assert!(
|
||||
project_diff_editor.editor.read(cx).text(cx).is_empty(),
|
||||
"Should have no changes after opening the diff on no git changes"
|
||||
);
|
||||
});
|
||||
// let project_diff_editor = workspace
|
||||
// .update(cx, |workspace, cx| {
|
||||
// workspace
|
||||
// .active_pane()
|
||||
// .read(cx)
|
||||
// .items()
|
||||
// .find_map(|item| item.downcast::<ProjectDiffEditor>())
|
||||
// })
|
||||
// .unwrap()
|
||||
// .expect("did not find a ProjectDiffEditor");
|
||||
// project_diff_editor.update(cx, |project_diff_editor, cx| {
|
||||
// assert!(
|
||||
// project_diff_editor.editor.read(cx).text(cx).is_empty(),
|
||||
// "Should have no changes after opening the diff on no git changes"
|
||||
// );
|
||||
// });
|
||||
|
||||
let old_text = file_a_editor.update(cx, |editor, cx| editor.text(cx));
|
||||
let change = "an edit after git add";
|
||||
file_a_editor
|
||||
.update(cx, |file_a_editor, cx| {
|
||||
file_a_editor.insert(change, cx);
|
||||
file_a_editor.save(false, project.clone(), cx)
|
||||
})
|
||||
.await
|
||||
.expect("failed to save a file");
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
cx.run_until_parked();
|
||||
// let old_text = file_a_editor.update(cx, |editor, cx| editor.text(cx));
|
||||
// let change = "an edit after git add";
|
||||
// file_a_editor
|
||||
// .update(cx, |file_a_editor, cx| {
|
||||
// file_a_editor.insert(change, cx);
|
||||
// file_a_editor.save(false, project.clone(), cx)
|
||||
// })
|
||||
// .await
|
||||
// .expect("failed to save a file");
|
||||
// cx.executor().advance_clock(Duration::from_secs(1));
|
||||
// cx.run_until_parked();
|
||||
|
||||
// TODO does not work on Linux for some reason, returning a blank line
|
||||
// hence disable the last check for now, and do some fiddling to avoid the warnings.
|
||||
#[cfg(target_os = "linux")]
|
||||
{
|
||||
if true {
|
||||
return;
|
||||
}
|
||||
}
|
||||
project_diff_editor.update(cx, |project_diff_editor, cx| {
|
||||
// TODO assert it better: extract added text (based on the background changes) and deleted text (based on the deleted blocks added)
|
||||
assert_eq!(
|
||||
project_diff_editor.editor.read(cx).text(cx),
|
||||
format!("{change}{old_text}"),
|
||||
"Should have a new change shown in the beginning, and the old text shown as deleted text afterwards"
|
||||
);
|
||||
});
|
||||
}
|
||||
// // TODO does not work on Linux for some reason, returning a blank line
|
||||
// // hence disable the last check for now, and do some fiddling to avoid the warnings.
|
||||
// #[cfg(target_os = "linux")]
|
||||
// {
|
||||
// if true {
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// project_diff_editor.update(cx, |project_diff_editor, cx| {
|
||||
// // TODO assert it better: extract added text (based on the background changes) and deleted text (based on the deleted blocks added)
|
||||
// assert_eq!(
|
||||
// project_diff_editor.editor.read(cx).text(cx),
|
||||
// format!("{change}{old_text}"),
|
||||
// "Should have a new change shown in the beginning, and the old text shown as deleted text afterwards"
|
||||
// );
|
||||
// });
|
||||
// }
|
||||
|
||||
fn run_git(path: &Path, args: &[&str]) -> String {
|
||||
let output = std::process::Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(path)
|
||||
.output()
|
||||
.expect("git commit failed");
|
||||
// fn run_git(path: &Path, args: &[&str]) -> String {
|
||||
// let output = std::process::Command::new("git")
|
||||
// .args(args)
|
||||
// .current_dir(path)
|
||||
// .output()
|
||||
// .expect("git commit failed");
|
||||
|
||||
format!(
|
||||
"Stdout: {}; stderr: {}",
|
||||
String::from_utf8(output.stdout).unwrap(),
|
||||
String::from_utf8(output.stderr).unwrap()
|
||||
)
|
||||
}
|
||||
// format!(
|
||||
// "Stdout: {}; stderr: {}",
|
||||
// String::from_utf8(output.stdout).unwrap(),
|
||||
// String::from_utf8(output.stderr).unwrap()
|
||||
// )
|
||||
// }
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
if std::env::var("RUST_LOG").is_ok() {
|
||||
env_logger::try_init().ok();
|
||||
}
|
||||
// fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
// if std::env::var("RUST_LOG").is_ok() {
|
||||
// env_logger::try_init().ok();
|
||||
// }
|
||||
|
||||
cx.update(|cx| {
|
||||
assets::Assets.load_test_fonts(cx);
|
||||
let settings_store = SettingsStore::test(cx);
|
||||
cx.set_global(settings_store);
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
client::init_settings(cx);
|
||||
language::init(cx);
|
||||
Project::init_settings(cx);
|
||||
workspace::init_settings(cx);
|
||||
crate::init(cx);
|
||||
});
|
||||
}
|
||||
// cx.update(|cx| {
|
||||
// assets::Assets.load_test_fonts(cx);
|
||||
// let settings_store = SettingsStore::test(cx);
|
||||
// cx.set_global(settings_store);
|
||||
// theme::init(theme::LoadThemes::JustBase, cx);
|
||||
// release_channel::init(SemanticVersion::default(), cx);
|
||||
// client::init_settings(cx);
|
||||
// language::init(cx);
|
||||
// Project::init_settings(cx);
|
||||
// workspace::init_settings(cx);
|
||||
// crate::init(cx);
|
||||
// });
|
||||
// }
|
||||
}
|
||||
|
||||
+301
-263
@@ -1,12 +1,17 @@
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use collections::{HashMap, HashSet};
|
||||
use git::diff::DiffHunkStatus;
|
||||
use gpui::{Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Task, View};
|
||||
use gpui::{
|
||||
Action, AnchorCorner, AppContext, CursorStyle, Hsla, Model, MouseButton, Subscription, Task,
|
||||
View,
|
||||
};
|
||||
use language::{Buffer, BufferId, Point};
|
||||
use multi_buffer::{
|
||||
Anchor, AnchorRangeExt, ExcerptRange, MultiBuffer, MultiBufferDiffHunk, MultiBufferRow,
|
||||
MultiBufferSnapshot, ToPoint,
|
||||
MultiBufferSnapshot, ToOffset, ToPoint,
|
||||
};
|
||||
use project::buffer_store::BufferChangeSet;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use sum_tree::TreeMap;
|
||||
use text::OffsetRangeExt;
|
||||
use ui::{
|
||||
prelude::*, ActiveTheme, ContextMenu, IconButtonShape, InteractiveElement, IntoElement,
|
||||
@@ -29,10 +34,11 @@ pub(super) struct HoveredHunk {
|
||||
pub diff_base_byte_range: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct ExpandedHunks {
|
||||
#[derive(Default)]
|
||||
pub(super) struct DiffMap {
|
||||
pub(crate) hunks: Vec<ExpandedHunk>,
|
||||
diff_base: HashMap<BufferId, DiffBaseBuffer>,
|
||||
pub(crate) diff_bases: HashMap<BufferId, DiffBaseState>,
|
||||
pub(crate) snapshot: DiffMapSnapshot,
|
||||
hunk_update_tasks: HashMap<Option<BufferId>, Task<()>>,
|
||||
expand_all: bool,
|
||||
}
|
||||
@@ -46,10 +52,13 @@ pub(super) struct ExpandedHunk {
|
||||
pub folded: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DiffBaseBuffer {
|
||||
buffer: Model<Buffer>,
|
||||
diff_base_version: usize,
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct DiffMapSnapshot(TreeMap<BufferId, git::diff::BufferDiff>);
|
||||
|
||||
pub(crate) struct DiffBaseState {
|
||||
pub(crate) change_set: Model<BufferChangeSet>,
|
||||
pub(crate) last_version: Option<usize>,
|
||||
_subscription: Subscription,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -66,7 +75,38 @@ pub enum DisplayDiffHunk {
|
||||
},
|
||||
}
|
||||
|
||||
impl ExpandedHunks {
|
||||
impl DiffMap {
|
||||
pub fn snapshot(&self) -> DiffMapSnapshot {
|
||||
self.snapshot.clone()
|
||||
}
|
||||
|
||||
pub fn add_change_set(
|
||||
&mut self,
|
||||
change_set: Model<BufferChangeSet>,
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let buffer_id = change_set.read(cx).buffer_id;
|
||||
self.snapshot
|
||||
.0
|
||||
.insert(buffer_id, change_set.read(cx).diff_to_buffer.clone());
|
||||
Editor::sync_expanded_diff_hunks(self, buffer_id, cx);
|
||||
self.diff_bases.insert(
|
||||
buffer_id,
|
||||
DiffBaseState {
|
||||
last_version: None,
|
||||
_subscription: cx.observe(&change_set, move |editor, change_set, 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, cx);
|
||||
}),
|
||||
change_set,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn hunks(&self, include_folded: bool) -> impl Iterator<Item = &ExpandedHunk> {
|
||||
self.hunks
|
||||
.iter()
|
||||
@@ -74,9 +114,92 @@ impl ExpandedHunks {
|
||||
}
|
||||
}
|
||||
|
||||
impl DiffMapSnapshot {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.values().all(|diff| diff.is_empty())
|
||||
}
|
||||
|
||||
pub fn diff_hunks<'a>(
|
||||
&'a self,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
self.diff_hunks_in_range(0..buffer_snapshot.len(), buffer_snapshot)
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
|
||||
buffer_snapshot
|
||||
.excerpts_for_range(range.clone())
|
||||
.filter_map(move |excerpt| {
|
||||
let buffer = excerpt.buffer();
|
||||
let buffer_id = buffer.remote_id();
|
||||
let diff = self.0.get(&buffer_id)?;
|
||||
let buffer_range = excerpt.map_range_to_buffer(range.clone());
|
||||
let buffer_range =
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
|
||||
Some(
|
||||
diff.hunks_intersecting_range(buffer_range, excerpt.buffer())
|
||||
.map(move |hunk| {
|
||||
let start =
|
||||
excerpt.map_point_from_buffer(Point::new(hunk.row_range.start, 0));
|
||||
let end =
|
||||
excerpt.map_point_from_buffer(Point::new(hunk.row_range.end, 0));
|
||||
MultiBufferDiffHunk {
|
||||
row_range: MultiBufferRow(start.row)..MultiBufferRow(end.row),
|
||||
buffer_id,
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn diff_hunks_in_range_rev<'a, T: ToOffset>(
|
||||
&'a self,
|
||||
range: Range<T>,
|
||||
buffer_snapshot: &'a MultiBufferSnapshot,
|
||||
) -> impl Iterator<Item = MultiBufferDiffHunk> + 'a {
|
||||
let range = range.start.to_offset(buffer_snapshot)..range.end.to_offset(buffer_snapshot);
|
||||
buffer_snapshot
|
||||
.excerpts_for_range_rev(range.clone())
|
||||
.filter_map(move |excerpt| {
|
||||
let buffer = excerpt.buffer();
|
||||
let buffer_id = buffer.remote_id();
|
||||
let diff = self.0.get(&buffer_id)?;
|
||||
let buffer_range = excerpt.map_range_to_buffer(range.clone());
|
||||
let buffer_range =
|
||||
buffer.anchor_before(buffer_range.start)..buffer.anchor_after(buffer_range.end);
|
||||
Some(
|
||||
diff.hunks_intersecting_range_rev(buffer_range, excerpt.buffer())
|
||||
.map(move |hunk| {
|
||||
let start_row = excerpt
|
||||
.map_point_from_buffer(Point::new(hunk.row_range.start, 0))
|
||||
.row;
|
||||
let end_row = excerpt
|
||||
.map_point_from_buffer(Point::new(hunk.row_range.end, 0))
|
||||
.row;
|
||||
MultiBufferDiffHunk {
|
||||
row_range: MultiBufferRow(start_row)..MultiBufferRow(end_row),
|
||||
buffer_id,
|
||||
buffer_range: hunk.buffer_range.clone(),
|
||||
diff_base_byte_range: hunk.diff_base_byte_range.clone(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
})
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
impl Editor {
|
||||
pub fn set_expand_all_diff_hunks(&mut self) {
|
||||
self.expanded_hunks.expand_all = true;
|
||||
self.diff_map.expand_all = true;
|
||||
}
|
||||
|
||||
pub(super) fn toggle_hovered_hunk(
|
||||
@@ -92,18 +215,15 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub fn toggle_hunk_diff(&mut self, _: &ToggleHunkDiff, cx: &mut ViewContext<Self>) {
|
||||
let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
|
||||
let selections = self.selections.disjoint_anchors();
|
||||
self.toggle_hunks_expanded(
|
||||
hunks_for_selections(&multi_buffer_snapshot, &selections),
|
||||
cx,
|
||||
);
|
||||
let snapshot = self.snapshot(cx);
|
||||
let selections = self.selections.all(cx);
|
||||
self.toggle_hunks_expanded(hunks_for_selections(&snapshot, &selections), cx);
|
||||
}
|
||||
|
||||
pub fn expand_all_hunk_diffs(&mut self, _: &ExpandAllHunkDiffs, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let display_rows_with_expanded_hunks = self
|
||||
.expanded_hunks
|
||||
.diff_map
|
||||
.hunks(false)
|
||||
.map(|hunk| &hunk.hunk_range)
|
||||
.map(|anchor_range| {
|
||||
@@ -119,10 +239,10 @@ impl Editor {
|
||||
)
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let hunks = snapshot
|
||||
.display_snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
|
||||
let hunks = self
|
||||
.diff_map
|
||||
.snapshot
|
||||
.diff_hunks(&snapshot.display_snapshot.buffer_snapshot)
|
||||
.filter(|hunk| {
|
||||
let hunk_display_row_range = Point::new(hunk.row_range.start.0, 0)
|
||||
.to_display_point(&snapshot.display_snapshot)
|
||||
@@ -140,11 +260,11 @@ impl Editor {
|
||||
hunks_to_toggle: Vec<MultiBufferDiffHunk>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
if self.expanded_hunks.expand_all {
|
||||
if self.diff_map.expand_all {
|
||||
return;
|
||||
}
|
||||
|
||||
let previous_toggle_task = self.expanded_hunks.hunk_update_tasks.remove(&None);
|
||||
let previous_toggle_task = self.diff_map.hunk_update_tasks.remove(&None);
|
||||
let new_toggle_task = cx.spawn(move |editor, mut cx| async move {
|
||||
if let Some(task) = previous_toggle_task {
|
||||
task.await;
|
||||
@@ -154,11 +274,10 @@ impl Editor {
|
||||
.update(&mut cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let mut hunks_to_toggle = hunks_to_toggle.into_iter().fuse().peekable();
|
||||
let mut highlights_to_remove =
|
||||
Vec::with_capacity(editor.expanded_hunks.hunks.len());
|
||||
let mut highlights_to_remove = Vec::with_capacity(editor.diff_map.hunks.len());
|
||||
let mut blocks_to_remove = HashSet::default();
|
||||
let mut hunks_to_expand = Vec::new();
|
||||
editor.expanded_hunks.hunks.retain(|expanded_hunk| {
|
||||
editor.diff_map.hunks.retain(|expanded_hunk| {
|
||||
if expanded_hunk.folded {
|
||||
return true;
|
||||
}
|
||||
@@ -238,7 +357,7 @@ impl Editor {
|
||||
.ok();
|
||||
});
|
||||
|
||||
self.expanded_hunks
|
||||
self.diff_map
|
||||
.hunk_update_tasks
|
||||
.insert(None, cx.background_executor().spawn(new_toggle_task));
|
||||
}
|
||||
@@ -252,30 +371,34 @@ impl Editor {
|
||||
let buffer = self.buffer.clone();
|
||||
let multi_buffer_snapshot = buffer.read(cx).snapshot(cx);
|
||||
let hunk_range = hunk.multi_buffer_range.clone();
|
||||
let (diff_base_buffer, deleted_text_lines) = buffer.update(cx, |buffer, cx| {
|
||||
let buffer = buffer.buffer(hunk_range.start.buffer_id?)?;
|
||||
let diff_base_buffer = diff_base_buffer
|
||||
.or_else(|| self.current_diff_base_buffer(&buffer, cx))
|
||||
.or_else(|| create_diff_base_buffer(&buffer, cx))?;
|
||||
let deleted_text_lines = buffer.read(cx).diff_base().map(|diff_base| {
|
||||
let diff_start_row = diff_base
|
||||
.offset_to_point(hunk.diff_base_byte_range.start)
|
||||
.row;
|
||||
let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
|
||||
diff_end_row - diff_start_row
|
||||
})?;
|
||||
Some((diff_base_buffer, deleted_text_lines))
|
||||
let buffer_id = hunk_range.start.buffer_id?;
|
||||
let diff_base_buffer = diff_base_buffer.or_else(|| {
|
||||
self.diff_map
|
||||
.diff_bases
|
||||
.get(&buffer_id)?
|
||||
.change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
.clone()
|
||||
})?;
|
||||
|
||||
let block_insert_index = match self.expanded_hunks.hunks.binary_search_by(|probe| {
|
||||
probe
|
||||
.hunk_range
|
||||
.start
|
||||
.cmp(&hunk_range.start, &multi_buffer_snapshot)
|
||||
}) {
|
||||
Ok(_already_present) => return None,
|
||||
Err(ix) => ix,
|
||||
};
|
||||
let diff_base = diff_base_buffer.read(cx);
|
||||
let diff_start_row = diff_base
|
||||
.offset_to_point(hunk.diff_base_byte_range.start)
|
||||
.row;
|
||||
let diff_end_row = diff_base.offset_to_point(hunk.diff_base_byte_range.end).row;
|
||||
let deleted_text_lines = diff_end_row - diff_start_row;
|
||||
|
||||
let block_insert_index = self
|
||||
.diff_map
|
||||
.hunks
|
||||
.binary_search_by(|probe| {
|
||||
probe
|
||||
.hunk_range
|
||||
.start
|
||||
.cmp(&hunk_range.start, &multi_buffer_snapshot)
|
||||
})
|
||||
.err()?;
|
||||
|
||||
let blocks;
|
||||
match hunk.status {
|
||||
@@ -315,7 +438,7 @@ impl Editor {
|
||||
);
|
||||
}
|
||||
};
|
||||
self.expanded_hunks.hunks.insert(
|
||||
self.diff_map.hunks.insert(
|
||||
block_insert_index,
|
||||
ExpandedHunk {
|
||||
blocks,
|
||||
@@ -374,8 +497,8 @@ impl Editor {
|
||||
_: &ApplyDiffHunk,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let hunks = hunks_for_selections(&snapshot, &self.selections.disjoint_anchors());
|
||||
let snapshot = self.snapshot(cx);
|
||||
let hunks = hunks_for_selections(&snapshot, &self.selections.all(cx));
|
||||
let mut ranges_by_buffer = HashMap::default();
|
||||
self.transact(cx, |editor, cx| {
|
||||
for hunk in hunks {
|
||||
@@ -401,7 +524,7 @@ impl Editor {
|
||||
|
||||
fn has_multiple_hunks(&self, cx: &AppContext) -> bool {
|
||||
let snapshot = self.buffer.read(cx).snapshot(cx);
|
||||
let mut hunks = snapshot.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX);
|
||||
let mut hunks = self.diff_map.snapshot.diff_hunks(&snapshot);
|
||||
hunks.nth(1).is_some()
|
||||
}
|
||||
|
||||
@@ -415,7 +538,7 @@ impl Editor {
|
||||
.read(cx)
|
||||
.point_to_buffer_offset(hunk.multi_buffer_range.start, cx)
|
||||
.map_or(false, |(buffer, _, _)| {
|
||||
buffer.read(cx).diff_base_buffer().is_some()
|
||||
buffer.read(cx).base_buffer().is_some()
|
||||
});
|
||||
|
||||
let border_color = cx.theme().colors().border_variant;
|
||||
@@ -552,29 +675,9 @@ impl Editor {
|
||||
let editor = editor.clone();
|
||||
let hunk = hunk.clone();
|
||||
move |_event, cx| {
|
||||
let multi_buffer =
|
||||
editor.read(cx).buffer().clone();
|
||||
let multi_buffer_snapshot =
|
||||
multi_buffer.read(cx).snapshot(cx);
|
||||
let mut revert_changes = HashMap::default();
|
||||
if let Some(hunk) =
|
||||
crate::hunk_diff::to_diff_hunk(
|
||||
&hunk,
|
||||
&multi_buffer_snapshot,
|
||||
)
|
||||
{
|
||||
Editor::prepare_revert_change(
|
||||
&mut revert_changes,
|
||||
&multi_buffer,
|
||||
&hunk,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
if !revert_changes.is_empty() {
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.revert(revert_changes, cx)
|
||||
});
|
||||
}
|
||||
editor.update(cx, |editor, cx| {
|
||||
editor.revert_hunk(hunk.clone(), cx);
|
||||
});
|
||||
}
|
||||
}),
|
||||
)
|
||||
@@ -763,13 +866,13 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub(super) fn clear_expanded_diff_hunks(&mut self, cx: &mut ViewContext<'_, Editor>) -> bool {
|
||||
if self.expanded_hunks.expand_all {
|
||||
if self.diff_map.expand_all {
|
||||
return false;
|
||||
}
|
||||
self.expanded_hunks.hunk_update_tasks.clear();
|
||||
self.diff_map.hunk_update_tasks.clear();
|
||||
self.clear_row_highlights::<DiffRowHighlight>();
|
||||
let to_remove = self
|
||||
.expanded_hunks
|
||||
.diff_map
|
||||
.hunks
|
||||
.drain(..)
|
||||
.flat_map(|expanded_hunk| expanded_hunk.blocks.into_iter())
|
||||
@@ -783,48 +886,39 @@ impl Editor {
|
||||
}
|
||||
|
||||
pub(super) fn sync_expanded_diff_hunks(
|
||||
&mut self,
|
||||
buffer: Model<Buffer>,
|
||||
diff_map: &mut DiffMap,
|
||||
buffer_id: BufferId,
|
||||
cx: &mut ViewContext<'_, Self>,
|
||||
) {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let buffer_diff_base_version = buffer.read(cx).diff_base_version();
|
||||
self.expanded_hunks
|
||||
.hunk_update_tasks
|
||||
.remove(&Some(buffer_id));
|
||||
let diff_base_buffer = self.current_diff_base_buffer(&buffer, cx);
|
||||
let diff_base_state = diff_map.diff_bases.get_mut(&buffer_id);
|
||||
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_buffer_unchanged = false;
|
||||
}
|
||||
diff_base_buffer = change_set.base_text.clone();
|
||||
})
|
||||
}
|
||||
|
||||
diff_map.hunk_update_tasks.remove(&Some(buffer_id));
|
||||
|
||||
let new_sync_task = cx.spawn(move |editor, mut cx| async move {
|
||||
let diff_base_buffer_unchanged = diff_base_buffer.is_some();
|
||||
let Ok(diff_base_buffer) =
|
||||
cx.update(|cx| diff_base_buffer.or_else(|| create_diff_base_buffer(&buffer, cx)))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
editor
|
||||
.update(&mut cx, |editor, cx| {
|
||||
if let Some(diff_base_buffer) = &diff_base_buffer {
|
||||
editor.expanded_hunks.diff_base.insert(
|
||||
buffer_id,
|
||||
DiffBaseBuffer {
|
||||
buffer: diff_base_buffer.clone(),
|
||||
diff_base_version: buffer_diff_base_version,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let mut recalculated_hunks = snapshot
|
||||
.buffer_snapshot
|
||||
.git_diff_hunks_in_range(MultiBufferRow::MIN..MultiBufferRow::MAX)
|
||||
.diff_map
|
||||
.diff_hunks(&snapshot.buffer_snapshot)
|
||||
.filter(|hunk| hunk.buffer_id == buffer_id)
|
||||
.fuse()
|
||||
.peekable();
|
||||
let mut highlights_to_remove =
|
||||
Vec::with_capacity(editor.expanded_hunks.hunks.len());
|
||||
let mut highlights_to_remove = Vec::with_capacity(editor.diff_map.hunks.len());
|
||||
let mut blocks_to_remove = HashSet::default();
|
||||
let mut hunks_to_reexpand =
|
||||
Vec::with_capacity(editor.expanded_hunks.hunks.len());
|
||||
editor.expanded_hunks.hunks.retain_mut(|expanded_hunk| {
|
||||
let mut hunks_to_reexpand = Vec::with_capacity(editor.diff_map.hunks.len());
|
||||
editor.diff_map.hunks.retain_mut(|expanded_hunk| {
|
||||
if expanded_hunk.hunk_range.start.buffer_id != Some(buffer_id) {
|
||||
return true;
|
||||
};
|
||||
@@ -874,7 +968,7 @@ impl Editor {
|
||||
> hunk_display_range.end
|
||||
{
|
||||
recalculated_hunks.next();
|
||||
if editor.expanded_hunks.expand_all {
|
||||
if editor.diff_map.expand_all {
|
||||
hunks_to_reexpand.push(HoveredHunk {
|
||||
status,
|
||||
multi_buffer_range,
|
||||
@@ -917,7 +1011,7 @@ impl Editor {
|
||||
retain
|
||||
});
|
||||
|
||||
if editor.expanded_hunks.expand_all {
|
||||
if editor.diff_map.expand_all {
|
||||
for hunk in recalculated_hunks {
|
||||
match diff_hunk_to_display(&hunk, &snapshot) {
|
||||
DisplayDiffHunk::Folded { .. } => {}
|
||||
@@ -935,6 +1029,8 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
drop(recalculated_hunks);
|
||||
}
|
||||
|
||||
editor.remove_highlighted_rows::<DiffRowHighlight>(highlights_to_remove, cx);
|
||||
@@ -949,32 +1045,12 @@ impl Editor {
|
||||
.ok();
|
||||
});
|
||||
|
||||
self.expanded_hunks.hunk_update_tasks.insert(
|
||||
diff_map.hunk_update_tasks.insert(
|
||||
Some(buffer_id),
|
||||
cx.background_executor().spawn(new_sync_task),
|
||||
);
|
||||
}
|
||||
|
||||
fn current_diff_base_buffer(
|
||||
&mut self,
|
||||
buffer: &Model<Buffer>,
|
||||
cx: &mut AppContext,
|
||||
) -> Option<Model<Buffer>> {
|
||||
buffer.update(cx, |buffer, _| {
|
||||
match self.expanded_hunks.diff_base.entry(buffer.remote_id()) {
|
||||
hash_map::Entry::Occupied(o) => {
|
||||
if o.get().diff_base_version != buffer.diff_base_version() {
|
||||
o.remove();
|
||||
None
|
||||
} else {
|
||||
Some(o.get().buffer.clone())
|
||||
}
|
||||
}
|
||||
hash_map::Entry::Vacant(_) => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn go_to_subsequent_hunk(&mut self, position: Anchor, cx: &mut ViewContext<Self>) {
|
||||
let snapshot = self.snapshot(cx);
|
||||
let position = position.to_point(&snapshot.buffer_snapshot);
|
||||
@@ -1021,7 +1097,7 @@ impl Editor {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_diff_hunk(
|
||||
pub(crate) fn to_diff_hunk(
|
||||
hovered_hunk: &HoveredHunk,
|
||||
multi_buffer_snapshot: &MultiBufferSnapshot,
|
||||
) -> Option<MultiBufferDiffHunk> {
|
||||
@@ -1043,24 +1119,6 @@ fn to_diff_hunk(
|
||||
})
|
||||
}
|
||||
|
||||
fn create_diff_base_buffer(buffer: &Model<Buffer>, cx: &mut AppContext) -> Option<Model<Buffer>> {
|
||||
buffer
|
||||
.update(cx, |buffer, _| {
|
||||
let language = buffer.language().cloned();
|
||||
let diff_base = buffer.diff_base()?.clone();
|
||||
Some((buffer.line_ending(), diff_base, language))
|
||||
})
|
||||
.map(|(line_ending, diff_base, language)| {
|
||||
cx.new_model(|cx| {
|
||||
let buffer = Buffer::local_normalized(diff_base, line_ending, cx);
|
||||
match language {
|
||||
Some(language) => buffer.with_language(language, cx),
|
||||
None => buffer,
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn added_hunk_color(cx: &AppContext) -> Hsla {
|
||||
let mut created_color = cx.theme().status().git().created;
|
||||
created_color.fade_out(0.7);
|
||||
@@ -1118,51 +1176,27 @@ fn editor_with_deleted_text(
|
||||
});
|
||||
})]);
|
||||
|
||||
let original_multi_buffer_range = hunk.multi_buffer_range.clone();
|
||||
let diff_base_range = hunk.diff_base_byte_range.clone();
|
||||
editor
|
||||
.register_action::<RevertSelectedHunks>({
|
||||
let hunk = hunk.clone();
|
||||
let parent_editor = parent_editor.clone();
|
||||
move |_, cx| {
|
||||
parent_editor
|
||||
.update(cx, |editor, cx| {
|
||||
let Some((buffer, original_text)) =
|
||||
editor.buffer().update(cx, |buffer, cx| {
|
||||
let (_, buffer, _) = buffer.excerpt_containing(
|
||||
original_multi_buffer_range.start,
|
||||
cx,
|
||||
)?;
|
||||
let original_text =
|
||||
buffer.read(cx).diff_base()?.slice(diff_base_range.clone());
|
||||
Some((buffer, Arc::from(original_text.to_string())))
|
||||
})
|
||||
else {
|
||||
return;
|
||||
};
|
||||
buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(
|
||||
Some((
|
||||
original_multi_buffer_range.start.text_anchor
|
||||
..original_multi_buffer_range.end.text_anchor,
|
||||
original_text,
|
||||
)),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
})
|
||||
.update(cx, |editor, cx| editor.revert_hunk(hunk.clone(), cx))
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
let hunk = hunk.clone();
|
||||
editor
|
||||
.register_action::<ToggleHunkDiff>(move |_, cx| {
|
||||
parent_editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.toggle_hovered_hunk(&hunk, cx);
|
||||
})
|
||||
.ok();
|
||||
.register_action::<ToggleHunkDiff>({
|
||||
let hunk = hunk.clone();
|
||||
move |_, cx| {
|
||||
parent_editor
|
||||
.update(cx, |editor, cx| {
|
||||
editor.toggle_hovered_hunk(&hunk, cx);
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
editor
|
||||
@@ -1272,78 +1306,57 @@ mod tests {
|
||||
let project = Project::test(fs, [], cx).await;
|
||||
|
||||
// buffer has two modified hunks with two rows each
|
||||
let buffer_1 = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer(
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
1.TWO
|
||||
1.three
|
||||
1.FOUR
|
||||
1.FIVE
|
||||
1.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
buffer_1.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
1.zero
|
||||
1.one
|
||||
1.two
|
||||
1.three
|
||||
1.four
|
||||
1.five
|
||||
1.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let diff_base_1 = "
|
||||
1.zero
|
||||
1.one
|
||||
1.two
|
||||
1.three
|
||||
1.four
|
||||
1.five
|
||||
1.six
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let text_1 = "
|
||||
1.zero
|
||||
1.ONE
|
||||
1.TWO
|
||||
1.three
|
||||
1.FOUR
|
||||
1.FIVE
|
||||
1.six
|
||||
"
|
||||
.unindent();
|
||||
|
||||
// buffer has a deletion hunk and an insertion hunk
|
||||
let buffer_2 = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.five
|
||||
2.six
|
||||
"
|
||||
.unindent()
|
||||
.as_str(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
buffer_2.update(cx, |buffer, cx| {
|
||||
buffer.set_diff_base(
|
||||
Some(
|
||||
"
|
||||
2.zero
|
||||
2.one
|
||||
2.one-and-a-half
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.six
|
||||
"
|
||||
.unindent(),
|
||||
),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let diff_base_2 = "
|
||||
2.zero
|
||||
2.one
|
||||
2.one-and-a-half
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.six
|
||||
"
|
||||
.unindent();
|
||||
|
||||
cx.background_executor.run_until_parked();
|
||||
let text_2 = "
|
||||
2.zero
|
||||
2.one
|
||||
2.two
|
||||
2.three
|
||||
2.four
|
||||
2.five
|
||||
2.six
|
||||
"
|
||||
.unindent();
|
||||
|
||||
let buffer_1 = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer(text_1.as_str(), None, cx)
|
||||
});
|
||||
let buffer_2 = project.update(cx, |project, cx| {
|
||||
project.create_local_buffer(text_2.as_str(), None, cx)
|
||||
});
|
||||
|
||||
let multibuffer = cx.new_model(|cx| {
|
||||
let mut multibuffer = MultiBuffer::new(ReadWrite);
|
||||
@@ -1392,10 +1405,30 @@ mod tests {
|
||||
multibuffer
|
||||
});
|
||||
|
||||
let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx));
|
||||
let editor = cx.add_window(|cx| Editor::for_multibuffer(multibuffer, None, false, cx));
|
||||
editor
|
||||
.update(cx, |editor, cx| {
|
||||
for (buffer, diff_base) in [
|
||||
(buffer_1.clone(), diff_base_1),
|
||||
(buffer_2.clone(), diff_base_2),
|
||||
] {
|
||||
let change_set = cx.new_model(|cx| {
|
||||
BufferChangeSet::new_with_base_text(
|
||||
diff_base.to_string(),
|
||||
buffer.read(cx).text_snapshot(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
cx.background_executor.run_until_parked();
|
||||
|
||||
let snapshot = editor.update(cx, |editor, cx| editor.snapshot(cx)).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
snapshot.text(),
|
||||
snapshot.buffer_snapshot.text(),
|
||||
"
|
||||
1.zero
|
||||
1.ONE
|
||||
@@ -1438,7 +1471,8 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range(MultiBufferRow(0)..MultiBufferRow(12))
|
||||
.diff_map
|
||||
.diff_hunks_in_range(Point::zero()..Point::new(12, 0), &snapshot.buffer_snapshot)
|
||||
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
|
||||
.collect::<Vec<_>>(),
|
||||
&expected,
|
||||
@@ -1446,7 +1480,11 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
snapshot
|
||||
.git_diff_hunks_in_range_rev(MultiBufferRow(0)..MultiBufferRow(12))
|
||||
.diff_map
|
||||
.diff_hunks_in_range_rev(
|
||||
Point::zero()..Point::new(12, 0),
|
||||
&snapshot.buffer_snapshot
|
||||
)
|
||||
.map(|hunk| (hunk_status(&hunk), hunk.row_range))
|
||||
.collect::<Vec<_>>(),
|
||||
expected
|
||||
|
||||
@@ -737,7 +737,7 @@ impl Item for Editor {
|
||||
let buffers = self.buffer().clone().read(cx).all_buffers();
|
||||
let buffers = buffers
|
||||
.into_iter()
|
||||
.map(|handle| handle.read(cx).diff_base_buffer().unwrap_or(handle.clone()))
|
||||
.map(|handle| handle.read(cx).base_buffer().unwrap_or(handle.clone()))
|
||||
.collect::<HashSet<_>>();
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
if format {
|
||||
|
||||
@@ -4,7 +4,7 @@ use futures::{channel::mpsc, future::join_all};
|
||||
use gpui::{AppContext, EventEmitter, FocusableView, Model, Render, Subscription, Task, View};
|
||||
use language::{Buffer, BufferEvent, Capability};
|
||||
use multi_buffer::{ExcerptRange, MultiBuffer};
|
||||
use project::Project;
|
||||
use project::{buffer_store::BufferChangeSet, Project};
|
||||
use smol::stream::StreamExt;
|
||||
use std::{any::TypeId, ops::Range, rc::Rc, time::Duration};
|
||||
use text::ToOffset;
|
||||
@@ -75,7 +75,7 @@ impl ProposedChangesEditor {
|
||||
title: title.into(),
|
||||
buffer_entries: Vec::new(),
|
||||
recalculate_diffs_tx,
|
||||
_recalculate_diffs_task: cx.spawn(|_, mut cx| async move {
|
||||
_recalculate_diffs_task: cx.spawn(|this, mut cx| async move {
|
||||
let mut buffers_to_diff = HashSet::default();
|
||||
while let Some(mut recalculate_diff) = recalculate_diffs_rx.next().await {
|
||||
buffers_to_diff.insert(recalculate_diff.buffer);
|
||||
@@ -96,12 +96,37 @@ impl ProposedChangesEditor {
|
||||
}
|
||||
}
|
||||
|
||||
join_all(buffers_to_diff.drain().filter_map(|buffer| {
|
||||
buffer
|
||||
.update(&mut cx, |buffer, cx| buffer.recalculate_diff(cx))
|
||||
.ok()?
|
||||
}))
|
||||
.await;
|
||||
let recalculate_diff_futures = this
|
||||
.update(&mut cx, |this, cx| {
|
||||
buffers_to_diff
|
||||
.drain()
|
||||
.filter_map(|buffer| {
|
||||
let buffer = buffer.read(cx);
|
||||
let base_buffer = buffer.base_buffer()?;
|
||||
let buffer = buffer.text_snapshot();
|
||||
let change_set = this.editor.update(cx, |editor, _| {
|
||||
Some(
|
||||
editor
|
||||
.diff_map
|
||||
.diff_bases
|
||||
.get(&buffer.remote_id())?
|
||||
.change_set
|
||||
.clone(),
|
||||
)
|
||||
})?;
|
||||
Some(change_set.update(cx, |change_set, cx| {
|
||||
change_set.set_base_text(
|
||||
base_buffer.read(cx).text(),
|
||||
buffer,
|
||||
cx,
|
||||
)
|
||||
}))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
join_all(recalculate_diff_futures).await;
|
||||
}
|
||||
None
|
||||
}),
|
||||
@@ -154,6 +179,7 @@ impl ProposedChangesEditor {
|
||||
});
|
||||
|
||||
let mut buffer_entries = Vec::new();
|
||||
let mut new_change_sets = Vec::new();
|
||||
for location in locations {
|
||||
let branch_buffer;
|
||||
if let Some(ix) = self
|
||||
@@ -166,6 +192,15 @@ impl ProposedChangesEditor {
|
||||
buffer_entries.push(entry);
|
||||
} else {
|
||||
branch_buffer = location.buffer.update(cx, |buffer, cx| buffer.branch(cx));
|
||||
new_change_sets.push(cx.new_model(|cx| {
|
||||
let mut change_set = BufferChangeSet::new(branch_buffer.read(cx));
|
||||
let _ = change_set.set_base_text(
|
||||
location.buffer.read(cx).text(),
|
||||
branch_buffer.read(cx).text_snapshot(),
|
||||
cx,
|
||||
);
|
||||
change_set
|
||||
}));
|
||||
buffer_entries.push(BufferEntry {
|
||||
branch: branch_buffer.clone(),
|
||||
base: location.buffer.clone(),
|
||||
@@ -187,7 +222,10 @@ impl ProposedChangesEditor {
|
||||
|
||||
self.buffer_entries = buffer_entries;
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| selections.refresh())
|
||||
editor.change_selections(None, cx, |selections| selections.refresh());
|
||||
for change_set in new_change_sets {
|
||||
editor.diff_map.add_change_set(change_set, cx)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -217,14 +255,14 @@ impl ProposedChangesEditor {
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
BufferEvent::DiffBaseChanged => {
|
||||
self.recalculate_diffs_tx
|
||||
.unbounded_send(RecalculateDiff {
|
||||
buffer,
|
||||
debounce: false,
|
||||
})
|
||||
.ok();
|
||||
}
|
||||
// BufferEvent::DiffBaseChanged => {
|
||||
// self.recalculate_diffs_tx
|
||||
// .unbounded_send(RecalculateDiff {
|
||||
// buffer,
|
||||
// debounce: false,
|
||||
// })
|
||||
// .ok();
|
||||
// }
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -373,7 +411,7 @@ impl BranchBufferSemanticsProvider {
|
||||
positions: &[text::Anchor],
|
||||
cx: &AppContext,
|
||||
) -> Option<Model<Buffer>> {
|
||||
let base_buffer = buffer.read(cx).diff_base_buffer()?;
|
||||
let base_buffer = buffer.read(cx).base_buffer()?;
|
||||
let version = base_buffer.read(cx).version();
|
||||
if positions
|
||||
.iter()
|
||||
|
||||
@@ -113,7 +113,15 @@ impl EditorLspTestContext {
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(root, json!({ "dir": { file_name.clone(): "" }}))
|
||||
.insert_tree(
|
||||
root,
|
||||
json!({
|
||||
".git": {},
|
||||
"dir": {
|
||||
file_name.clone(): ""
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
|
||||
|
||||
@@ -42,16 +42,16 @@ pub struct EditorTestContext {
|
||||
impl EditorTestContext {
|
||||
pub async fn new(cx: &mut gpui::TestAppContext) -> EditorTestContext {
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
// fs.insert_file("/file", "".to_owned()).await;
|
||||
let root = Self::root_path();
|
||||
fs.insert_tree(
|
||||
root,
|
||||
serde_json::json!({
|
||||
".git": {},
|
||||
"file": "",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
let project = Project::test(fs, [root], cx).await;
|
||||
let project = Project::test(fs.clone(), [root], cx).await;
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_local_buffer(root.join("file"), cx)
|
||||
@@ -65,6 +65,8 @@ impl EditorTestContext {
|
||||
editor
|
||||
});
|
||||
let editor_view = editor.root_view(cx).unwrap();
|
||||
|
||||
cx.run_until_parked();
|
||||
Self {
|
||||
cx: VisualTestContext::from_window(*editor.deref(), cx),
|
||||
window: editor.into(),
|
||||
@@ -276,8 +278,16 @@ impl EditorTestContext {
|
||||
snapshot.anchor_before(ranges[0].start)..snapshot.anchor_after(ranges[0].end)
|
||||
}
|
||||
|
||||
pub fn set_diff_base(&mut self, diff_base: Option<&str>) {
|
||||
self.update_buffer(|buffer, cx| buffer.set_diff_base(diff_base.map(ToOwned::to_owned), cx));
|
||||
pub fn set_diff_base(&mut self, diff_base: &str) {
|
||||
self.cx.run_until_parked();
|
||||
let fs = self
|
||||
.update_editor(|editor, cx| editor.project.as_ref().unwrap().read(cx).fs().as_fake());
|
||||
let path = self.update_buffer(|buffer, _| buffer.file().unwrap().path().clone());
|
||||
fs.set_index_for_repo(
|
||||
&Self::root_path().join(".git"),
|
||||
&[(path.as_ref(), diff_base.to_string())],
|
||||
);
|
||||
self.cx.run_until_parked();
|
||||
}
|
||||
|
||||
/// Change the editor's text and selections using a string containing
|
||||
@@ -319,10 +329,12 @@ impl EditorTestContext {
|
||||
state_context
|
||||
}
|
||||
|
||||
/// Assert about the text of the editor, the selections, and the expanded
|
||||
/// diff hunks.
|
||||
///
|
||||
/// Diff hunks are indicated by lines starting with `+` and `-`.
|
||||
#[track_caller]
|
||||
pub fn assert_diff_hunks(&mut self, expected_diff: String) {
|
||||
// Normalize the expected diff. If it has no diff markers, then insert blank markers
|
||||
// before each line. Strip any whitespace-only lines.
|
||||
pub fn assert_state_with_diff(&mut self, expected_diff: String) {
|
||||
let has_diff_markers = expected_diff
|
||||
.lines()
|
||||
.any(|line| line.starts_with("+") || line.starts_with("-"));
|
||||
@@ -340,11 +352,14 @@ impl EditorTestContext {
|
||||
})
|
||||
.join("\n");
|
||||
|
||||
let actual_selections = self.editor_selections();
|
||||
let actual_marked_text =
|
||||
generate_marked_text(&self.buffer_text(), &actual_selections, true);
|
||||
|
||||
// Read the actual diff from the editor's row highlights and block
|
||||
// decorations.
|
||||
let actual_diff = self.editor.update(&mut self.cx, |editor, cx| {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let text = editor.text(cx);
|
||||
let insertions = editor
|
||||
.highlighted_rows::<DiffRowHighlight>()
|
||||
.map(|(range, _)| {
|
||||
@@ -354,7 +369,7 @@ impl EditorTestContext {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let deletions = editor
|
||||
.expanded_hunks
|
||||
.diff_map
|
||||
.hunks
|
||||
.iter()
|
||||
.filter_map(|hunk| {
|
||||
@@ -371,10 +386,20 @@ impl EditorTestContext {
|
||||
.read(cx)
|
||||
.excerpt_containing(hunk.hunk_range.start, cx)
|
||||
.expect("no excerpt for expanded buffer's hunk start");
|
||||
let deleted_text = buffer
|
||||
.read(cx)
|
||||
.diff_base()
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
let change_set = &editor
|
||||
.diff_map
|
||||
.diff_bases
|
||||
.get(&buffer_id)
|
||||
.expect("should have a diff base for expanded hunk")
|
||||
.change_set;
|
||||
let deleted_text = change_set
|
||||
.read(cx)
|
||||
.base_text
|
||||
.as_ref()
|
||||
.expect("no base text for expanded hunk")
|
||||
.read(cx)
|
||||
.as_rope()
|
||||
.slice(hunk.diff_base_byte_range.clone())
|
||||
.to_string();
|
||||
if let DiffHunkStatus::Modified | DiffHunkStatus::Removed = hunk.status {
|
||||
@@ -384,7 +409,7 @@ impl EditorTestContext {
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
format_diff(text, deletions, insertions)
|
||||
format_diff(actual_marked_text, deletions, insertions)
|
||||
});
|
||||
|
||||
pretty_assertions::assert_eq!(actual_diff, expected_diff_text, "unexpected diff state");
|
||||
|
||||
Reference in New Issue
Block a user