diff --git a/assets/settings/default.json b/assets/settings/default.json index 8541c1fb749946..84ebcd65e2a451 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1450,6 +1450,11 @@ // Should the name or path be displayed first in the git view. // "path_style": "file_name_first" or "file_path_first" "path_style": "file_name_first", + // Whether the project diff view opens in stacked or side-by-side mode. + // + // Choices: stacked, side_by_side + // Default: stacked + "default_diff_view": "stacked", }, // The list of custom Git hosting providers. "git_hosting_providers": [ diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index b9f601760e86fc..f673e52f6b97c0 100644 --- a/crates/editor/src/split.rs +++ b/crates/editor/src/split.rs @@ -467,16 +467,25 @@ impl SplittableEditor { } fn split(&mut self, _: &SplitDiff, window: &mut Window, cx: &mut Context) { + let Some(workspace) = self.workspace.upgrade() else { + return; + }; + let project = workspace.read(cx).project().clone(); + self.do_split(project, window, cx); + } + + pub fn do_split( + &mut self, + project: Entity, + window: &mut Window, + cx: &mut Context, + ) { if !cx.has_flag::() { return; } if self.lhs.is_some() { return; } - let Some(workspace) = self.workspace.upgrade() else { - return; - }; - let project = workspace.read(cx).project().clone(); let lhs_multibuffer = cx.new(|cx| { let mut multibuffer = MultiBuffer::new(Capability::ReadOnly); diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index 90eeb853f2e561..6d50467e7a335b 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -26,6 +26,7 @@ use gpui::{ }; use language::{Anchor, Buffer, Capability, OffsetRangeExt}; use multi_buffer::{MultiBuffer, PathKey}; +use project::project_settings::ProjectSettings; use project::{ Project, ProjectPath, git_store::{ @@ -33,7 +34,7 @@ use project::{ branch_diff::{self, BranchDiffEvent, DiffBase}, }, }; -use settings::{Settings, SettingsStore}; +use settings::{DefaultDiffView, Settings, SettingsStore}; use smol::future::yield_now; use std::any::{Any, TypeId}; use std::sync::Arc; @@ -300,7 +301,7 @@ impl ProjectDiff { }); let editor = cx.new(|cx| { - let diff_display_editor = SplittableEditor::new_unsplit( + let mut diff_display_editor = SplittableEditor::new_unsplit( multibuffer.clone(), project.clone(), workspace.clone(), @@ -332,6 +333,12 @@ impl ProjectDiff { } } }); + + if ProjectSettings::get_global(cx).git.default_diff_view == DefaultDiffView::SideBySide + { + diff_display_editor.do_split(project.clone(), window, cx); + } + diff_display_editor }); let editor_subscription = cx.subscribe_in(&editor, window, Self::handle_editor_event); @@ -2727,4 +2734,73 @@ mod tests { assert_eq!(paths_b.len(), 1); assert_eq!(*paths_b[0], *"b.txt"); } + + #[gpui::test] + async fn test_default_diff_view_is_stacked_when_no_setting_specified(cx: &mut TestAppContext) { + init_test(cx); + assert_default_diff_view(None, false, cx).await; + } + + #[gpui::test] + async fn test_default_diff_view_is_stacked_when_set_to_stacked(cx: &mut TestAppContext) { + init_test(cx); + assert_default_diff_view(Some(settings::DefaultDiffView::Stacked), false, cx).await; + } + + #[gpui::test] + async fn test_default_diff_view_is_split_when_set_to_side_by_side(cx: &mut TestAppContext) { + init_test(cx); + assert_default_diff_view(Some(settings::DefaultDiffView::SideBySide), true, cx).await; + } + + async fn assert_default_diff_view( + default_diff_view: Option, + expect_split: bool, + cx: &mut TestAppContext, + ) { + if let Some(default_diff_view) = default_diff_view { + cx.update(|cx| { + let mut settings = ProjectSettings::get_global(cx).clone(); + settings.git.default_diff_view = default_diff_view; + ProjectSettings::override_global(settings, cx); + }); + } + + let fs = FakeFs::new(cx.executor()); + fs.insert_tree( + path!("/project"), + json!({ + ".git": {}, + "foo.txt": "dominate\n", + }), + ) + .await; + let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await; + + fs.set_head_and_index_for_repo( + Path::new(path!("/project/.git")), + &[("foo.txt", "intimidate\n".to_string())], + ); + + let (workspace, cx) = + cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); + cx.run_until_parked(); + + cx.focus(&workspace); + cx.update(|window, cx| { + window.dispatch_action(project_diff::Diff.boxed_clone(), cx); + }); + cx.run_until_parked(); + + let diff_item = workspace.update(cx, |workspace, cx| { + workspace.active_item_as::(cx).unwrap() + }); + diff_item.read_with(cx, |diff, cx| { + assert_eq!( + diff.editor.read(cx).is_split(), + expect_split, + "Expected is_split() to be {expect_split}" + ); + }); + } } diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index c295938ef56f69..e580285ad5a656 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -454,6 +454,10 @@ pub struct GitSettings { /// /// Default: file_name_first pub path_style: GitPathStyle, + /// Whether the project diff view opens in stacked or side-by-side mode. + /// + /// Default: stacked + pub default_diff_view: settings::DefaultDiffView, } #[derive(Clone, Copy, Debug)] @@ -643,6 +647,7 @@ impl Settings for ProjectSettings { }, hunk_style: git.hunk_style.unwrap(), path_style: git.path_style.unwrap().into(), + default_diff_view: git.default_diff_view.unwrap(), }; Self { context_servers: project diff --git a/crates/settings_content/src/project.rs b/crates/settings_content/src/project.rs index 1bcacbd3254604..017cb51608b1e6 100644 --- a/crates/settings_content/src/project.rs +++ b/crates/settings_content/src/project.rs @@ -9,8 +9,8 @@ use settings_macros::{MergeFrom, with_fallible_options}; use util::serde::default_true; use crate::{ - AllLanguageSettingsContent, DelayMs, ExtendingVec, ParseStatus, ProjectTerminalSettingsContent, - RootUserSettings, SlashCommandSettings, fallible_options, + AllLanguageSettingsContent, DefaultDiffView, DelayMs, ExtendingVec, ParseStatus, + ProjectTerminalSettingsContent, RootUserSettings, SlashCommandSettings, fallible_options, }; #[with_fallible_options] @@ -468,6 +468,10 @@ pub struct GitSettings { /// /// Default: file_name_first pub path_style: Option, + /// Whether the project diff view opens in stacked or side-by-side mode. + /// + /// Default: stacked + pub default_diff_view: Option, } #[with_fallible_options] diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 8644e44f84c8f1..b393d8403043c5 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -584,6 +584,29 @@ pub struct GitPanelSettingsContent { pub tree_view: Option, } +#[derive( + Default, + Copy, + Clone, + Debug, + Serialize, + Deserialize, + JsonSchema, + MergeFrom, + PartialEq, + Eq, + strum::VariantArray, + strum::VariantNames, +)] +#[serde(rename_all = "snake_case")] +pub enum DefaultDiffView { + /// Show the old and new content in a single editor, interleaved. + #[default] + Stacked, + /// Show the old and new content in side-by-side editors. + SideBySide, +} + #[derive( Default, Copy, diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index d7327650fc636c..f1abe1a84ebf2a 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -516,6 +516,7 @@ fn init_renderers(cx: &mut App) { .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) + .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown)