@@ -5,16 +5,17 @@ use editor::{
display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
};
use gpui::{
- App, Context, Entity, InteractiveElement as _, ParentElement as _, Subscription, WeakEntity,
+ App, Context, Entity, InteractiveElement as _, ParentElement as _, Subscription, Task,
+ WeakEntity,
};
use language::{Anchor, Buffer, BufferId};
-use project::{ConflictRegion, ConflictSet, ConflictSetUpdate};
+use project::{ConflictRegion, ConflictSet, ConflictSetUpdate, ProjectItem as _};
use std::{ops::Range, sync::Arc};
use ui::{
ActiveTheme, AnyElement, Element as _, StatefulInteractiveElement, Styled,
- StyledTypography as _, div, h_flex, rems,
+ StyledTypography as _, Window, div, h_flex, rems,
};
-use util::{debug_panic, maybe};
+use util::{ResultExt as _, debug_panic, maybe};
pub(crate) struct ConflictAddon {
buffers: HashMap<BufferId, BufferConflicts>,
@@ -404,8 +405,16 @@ fn render_conflict_buttons(
let editor = editor.clone();
let conflict = conflict.clone();
let ours = conflict.ours.clone();
- move |_, _, cx| {
- resolve_conflict(editor.clone(), excerpt_id, &conflict, &[ours.clone()], cx)
+ move |_, window, cx| {
+ resolve_conflict(
+ editor.clone(),
+ excerpt_id,
+ conflict.clone(),
+ vec![ours.clone()],
+ window,
+ cx,
+ )
+ .detach()
}
}),
)
@@ -422,14 +431,16 @@ fn render_conflict_buttons(
let editor = editor.clone();
let conflict = conflict.clone();
let theirs = conflict.theirs.clone();
- move |_, _, cx| {
+ move |_, window, cx| {
resolve_conflict(
editor.clone(),
excerpt_id,
- &conflict,
- &[theirs.clone()],
+ conflict.clone(),
+ vec![theirs.clone()],
+ window,
cx,
)
+ .detach()
}
}),
)
@@ -447,69 +458,101 @@ fn render_conflict_buttons(
let conflict = conflict.clone();
let ours = conflict.ours.clone();
let theirs = conflict.theirs.clone();
- move |_, _, cx| {
+ move |_, window, cx| {
resolve_conflict(
editor.clone(),
excerpt_id,
- &conflict,
- &[ours.clone(), theirs.clone()],
+ conflict.clone(),
+ vec![ours.clone(), theirs.clone()],
+ window,
cx,
)
+ .detach()
}
}),
)
.into_any()
}
-fn resolve_conflict(
+pub(crate) fn resolve_conflict(
editor: WeakEntity<Editor>,
excerpt_id: ExcerptId,
- resolved_conflict: &ConflictRegion,
- ranges: &[Range<Anchor>],
+ resolved_conflict: ConflictRegion,
+ ranges: Vec<Range<Anchor>>,
+ window: &mut Window,
cx: &mut App,
-) {
- let Some(editor) = editor.upgrade() else {
- return;
- };
-
- let multibuffer = editor.read(cx).buffer().read(cx);
- let snapshot = multibuffer.snapshot(cx);
- let Some(buffer) = resolved_conflict
- .ours
- .end
- .buffer_id
- .and_then(|buffer_id| multibuffer.buffer(buffer_id))
- else {
- return;
- };
- let buffer_snapshot = buffer.read(cx).snapshot();
-
- resolved_conflict.resolve(buffer, ranges, cx);
-
- editor.update(cx, |editor, cx| {
- let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
- let Some(state) = conflict_addon.buffers.get_mut(&buffer_snapshot.remote_id()) else {
+) -> Task<()> {
+ window.spawn(cx, async move |cx| {
+ let Some((workspace, project, multibuffer, buffer)) = editor
+ .update(cx, |editor, cx| {
+ let workspace = editor.workspace()?;
+ let project = editor.project.clone()?;
+ let multibuffer = editor.buffer().clone();
+ let buffer_id = resolved_conflict.ours.end.buffer_id?;
+ let buffer = multibuffer.read(cx).buffer(buffer_id)?;
+ resolved_conflict.resolve(buffer.clone(), &ranges, cx);
+ let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
+ let snapshot = multibuffer.read(cx).snapshot(cx);
+ let buffer_snapshot = buffer.read(cx).snapshot();
+ let state = conflict_addon
+ .buffers
+ .get_mut(&buffer_snapshot.remote_id())?;
+ let ix = state
+ .block_ids
+ .binary_search_by(|(range, _)| {
+ range
+ .start
+ .cmp(&resolved_conflict.range.start, &buffer_snapshot)
+ })
+ .ok()?;
+ let &(_, block_id) = &state.block_ids[ix];
+ let start = snapshot
+ .anchor_in_excerpt(excerpt_id, resolved_conflict.range.start)
+ .unwrap();
+ let end = snapshot
+ .anchor_in_excerpt(excerpt_id, resolved_conflict.range.end)
+ .unwrap();
+ editor.remove_highlighted_rows::<ConflictsOuter>(vec![start..end], cx);
+ editor.remove_highlighted_rows::<ConflictsOurs>(vec![start..end], cx);
+ editor.remove_highlighted_rows::<ConflictsTheirs>(vec![start..end], cx);
+ editor.remove_highlighted_rows::<ConflictsOursMarker>(vec![start..end], cx);
+ editor.remove_highlighted_rows::<ConflictsTheirsMarker>(vec![start..end], cx);
+ editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
+ Some((workspace, project, multibuffer, buffer))
+ })
+ .ok()
+ .flatten()
+ else {
return;
};
- let Ok(ix) = state.block_ids.binary_search_by(|(range, _)| {
- range
- .start
- .cmp(&resolved_conflict.range.start, &buffer_snapshot)
- }) else {
+ let Some(save) = project
+ .update(cx, |project, cx| {
+ if multibuffer.read(cx).all_diff_hunks_expanded() {
+ project.save_buffer(buffer.clone(), cx)
+ } else {
+ Task::ready(Ok(()))
+ }
+ })
+ .ok()
+ else {
return;
};
- let &(_, block_id) = &state.block_ids[ix];
- let start = snapshot
- .anchor_in_excerpt(excerpt_id, resolved_conflict.range.start)
- .unwrap();
- let end = snapshot
- .anchor_in_excerpt(excerpt_id, resolved_conflict.range.end)
- .unwrap();
- editor.remove_highlighted_rows::<ConflictsOuter>(vec![start..end], cx);
- editor.remove_highlighted_rows::<ConflictsOurs>(vec![start..end], cx);
- editor.remove_highlighted_rows::<ConflictsTheirs>(vec![start..end], cx);
- editor.remove_highlighted_rows::<ConflictsOursMarker>(vec![start..end], cx);
- editor.remove_highlighted_rows::<ConflictsTheirsMarker>(vec![start..end], cx);
- editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
+ if save.await.log_err().is_none() {
+ let open_path = maybe!({
+ let path = buffer
+ .read_with(cx, |buffer, cx| buffer.project_path(cx))
+ .ok()
+ .flatten()?;
+ workspace
+ .update_in(cx, |workspace, window, cx| {
+ workspace.open_path_preview(path, None, false, false, false, window, cx)
+ })
+ .ok()
+ });
+
+ if let Some(open_path) = open_path {
+ open_path.await.log_err();
+ }
+ }
})
}
@@ -148,6 +148,17 @@ impl ProjectDiff {
});
diff_display_editor
});
+ window.defer(cx, {
+ let workspace = workspace.clone();
+ let editor = editor.clone();
+ move |window, cx| {
+ workspace.update(cx, |workspace, cx| {
+ editor.update(cx, |editor, cx| {
+ editor.added_to_workspace(workspace, window, cx);
+ })
+ });
+ }
+ });
cx.subscribe_in(&editor, window, Self::handle_editor_event)
.detach();
@@ -1323,6 +1334,7 @@ fn merge_anchor_ranges<'a>(
mod tests {
use db::indoc;
use editor::test::editor_test_context::{EditorTestContext, assert_state_with_diff};
+ use git::status::{UnmergedStatus, UnmergedStatusCode};
use gpui::TestAppContext;
use project::FakeFs;
use serde_json::json;
@@ -1583,7 +1595,10 @@ mod tests {
);
}
- use crate::project_diff::{self, ProjectDiff};
+ use crate::{
+ conflict_view::resolve_conflict,
+ project_diff::{self, ProjectDiff},
+ };
#[gpui::test]
async fn test_go_to_prev_hunk_multibuffer(cx: &mut TestAppContext) {
@@ -1754,4 +1769,80 @@ mod tests {
cx.assert_excerpts_with_selections(&format!("[EXCERPT]\nĖ{git_contents}"));
}
+
+ #[gpui::test]
+ async fn test_saving_resolved_conflicts(cx: &mut TestAppContext) {
+ init_test(cx);
+
+ let fs = FakeFs::new(cx.executor());
+ fs.insert_tree(
+ path!("/project"),
+ json!({
+ ".git": {},
+ "foo": "<<<<<<< x\nours\n=======\ntheirs\n>>>>>>> y\n",
+ }),
+ )
+ .await;
+ fs.set_status_for_repo(
+ Path::new(path!("/project/.git")),
+ &[(
+ Path::new("foo"),
+ UnmergedStatus {
+ first_head: UnmergedStatusCode::Updated,
+ second_head: UnmergedStatusCode::Updated,
+ }
+ .into(),
+ )],
+ );
+ let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
+ let (workspace, cx) =
+ cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
+ let diff = cx.new_window_entity(|window, cx| {
+ ProjectDiff::new(project.clone(), workspace, window, cx)
+ });
+ cx.run_until_parked();
+
+ cx.update(|window, cx| {
+ let editor = diff.read(cx).editor.clone();
+ let excerpt_ids = editor.read(cx).buffer().read(cx).excerpt_ids();
+ assert_eq!(excerpt_ids.len(), 1);
+ let excerpt_id = excerpt_ids[0];
+ let buffer = editor
+ .read(cx)
+ .buffer()
+ .read(cx)
+ .all_buffers()
+ .into_iter()
+ .next()
+ .unwrap();
+ let buffer_id = buffer.read(cx).remote_id();
+ let conflict_set = diff
+ .read(cx)
+ .editor
+ .read(cx)
+ .addon::<ConflictAddon>()
+ .unwrap()
+ .conflict_set(buffer_id)
+ .unwrap();
+ assert!(conflict_set.read(cx).has_conflict);
+ let snapshot = conflict_set.read(cx).snapshot();
+ assert_eq!(snapshot.conflicts.len(), 1);
+
+ let ours_range = snapshot.conflicts[0].ours.clone();
+
+ resolve_conflict(
+ editor.downgrade(),
+ excerpt_id,
+ snapshot.conflicts[0].clone(),
+ vec![ours_range],
+ window,
+ cx,
+ )
+ })
+ .await;
+
+ let contents = fs.read_file_sync(path!("/project/foo")).unwrap();
+ let contents = String::from_utf8(contents).unwrap();
+ assert_eq!(contents, "ours\n");
+ }
}