diff --git a/Cargo.lock b/Cargo.lock index 6a8b4cb5dd4e1b0d4a4f32e24855ae22a2a82fed..338e842505b09d891c7416bd54abefcabff627e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7273,7 +7273,6 @@ dependencies = [ "ctor", "db", "editor", - "feature_flags", "futures 0.3.31", "fuzzy", "git", @@ -14842,6 +14841,7 @@ dependencies = [ "client", "collections", "editor", + "feature_flags", "futures 0.3.31", "gpui", "itertools 0.14.0", diff --git a/crates/git_ui/Cargo.toml b/crates/git_ui/Cargo.toml index d877176742210e16a6b22d279259df976a26679a..a7b330dc7b96cdffad5f357ce4efa1e0b889d81e 100644 --- a/crates/git_ui/Cargo.toml +++ b/crates/git_ui/Cargo.toml @@ -27,7 +27,6 @@ command_palette_hooks.workspace = true component.workspace = true db.workspace = true editor.workspace = true -feature_flags.workspace = true futures.workspace = true fuzzy.workspace = true git.workspace = true diff --git a/crates/git_ui/src/project_diff.rs b/crates/git_ui/src/project_diff.rs index cb9310d6ed43dd38a6e341344fda266dce624377..80f6abb4a693aeb35f4dd0e81f1f9e236bd612ff 100644 --- a/crates/git_ui/src/project_diff.rs +++ b/crates/git_ui/src/project_diff.rs @@ -8,13 +8,12 @@ use anyhow::{Context as _, Result, anyhow}; use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus}; use collections::{HashMap, HashSet}; use editor::{ - Addon, Editor, EditorEvent, SelectionEffects, SplitDiffFeatureFlag, SplittableEditor, - ToggleSplitDiff, + Addon, Editor, EditorEvent, SelectionEffects, SplittableEditor, actions::{GoToHunk, GoToPreviousHunk, SendReviewToAgent}, multibuffer_context_lines, scroll::Autoscroll, }; -use feature_flags::FeatureFlagAppExt as _; + use git::{ Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext, repository::{Branch, RepoPath, Upstream, UpstreamTracking, UpstreamTrackingStatus}, @@ -480,7 +479,6 @@ impl ProjectDiff { } fn button_states(&self, cx: &App) -> ButtonStates { - let is_split = self.editor.read(cx).is_split(); let editor = self.editor.read(cx).rhs_editor().read(cx); let snapshot = self.multibuffer.read(cx).snapshot(cx); let prev_next = snapshot.diff_hunks().nth(1).is_some(); @@ -541,7 +539,6 @@ impl ProjectDiff { selection, stage_all, unstage_all, - is_split, } } @@ -987,6 +984,8 @@ impl Item for ProjectDiff { Some(self_handle.clone().into()) } else if type_id == TypeId::of::() { Some(self.editor.read(cx).rhs_editor().clone().into()) + } else if type_id == TypeId::of::() { + Some(self.editor.clone().into()) } else { None } @@ -1293,7 +1292,6 @@ struct ButtonStates { selection: bool, stage_all: bool, unstage_all: bool, - is_split: bool, } impl Render for ProjectDiffToolbar { @@ -1433,31 +1431,6 @@ impl Render for ProjectDiffToolbar { ) }, ) - .map(|this| { - if !cx.has_flag::() { - return this; - } - this.child( - Button::new( - "toggle-split", - if button_states.is_split { - "Stacked View" - } else { - "Split View" - }, - ) - .tooltip(Tooltip::for_action_title_in( - "Toggle Split View", - &ToggleSplitDiff, - &focus_handle, - )) - .on_click(cx.listener( - |this, _, window, cx| { - this.dispatch_action(&ToggleSplitDiff, window, cx); - }, - )), - ) - }) .child( Button::new("commit", "Commit") .tooltip(Tooltip::for_action_title_in( diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index f716fcc1c60f691b32549670acc6cc669617b525..42a8360f4738e3e7440f22ffc77a19c1588fb4a0 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -92,6 +92,8 @@ pub enum IconName { DebugStepOut, DebugStepOver, Diff, + DiffSplit, + DiffStacked, Disconnected, Download, EditorAtom, diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index b08b6396aa638d027a15d6c3926b676a24650791..1b0864e5cc2e25334085b39affe29f61a4147aa0 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -26,6 +26,7 @@ any_vec.workspace = true bitflags.workspace = true collections.workspace = true editor.workspace = true +feature_flags.workspace = true futures.workspace = true gpui.workspace = true language.workspace = true diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index f9a0f4c6e7b4bc4af2ae53e9f1f0ae9c06a0960c..302d99b1b20c2151e2efe5d9358de63628d63cd5 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -13,14 +13,16 @@ use crate::{ use any_vec::AnyVec; use collections::HashMap; use editor::{ - DisplayPoint, Editor, EditorSettings, MultiBufferOffset, + DisplayPoint, Editor, EditorSettings, MultiBufferOffset, SplitDiffFeatureFlag, + SplittableEditor, ToggleSplitDiff, actions::{Backtab, FoldAll, Tab, ToggleFoldAll, UnfoldAll}, }; +use feature_flags::FeatureFlagAppExt as _; use futures::channel::oneshot; use gpui::{ Action, App, ClickEvent, Context, Entity, EventEmitter, Focusable, InteractiveElement as _, IntoElement, KeyContext, ParentElement as _, Render, ScrollHandle, Styled, Subscription, Task, - Window, actions, div, + WeakEntity, Window, actions, div, }; use language::{Language, LanguageRegistry}; use project::{ @@ -132,6 +134,8 @@ pub struct BufferSearchBar { editor_needed_width: Pixels, regex_language: Option>, is_collapsed: bool, + splittable_editor: Option>, + _splittable_editor_subscription: Option, } impl EventEmitter for BufferSearchBar {} @@ -140,32 +144,99 @@ impl Render for BufferSearchBar { fn render(&mut self, window: &mut Window, cx: &mut Context) -> impl IntoElement { let focus_handle = self.focus_handle(cx); + let has_splittable_editor = + self.splittable_editor.is_some() && cx.has_flag::(); + let split_buttons = if has_splittable_editor { + self.splittable_editor + .as_ref() + .and_then(|weak| weak.upgrade()) + .map(|splittable_editor| { + let is_split = splittable_editor.read(cx).is_split(); + let focus_handle = splittable_editor.focus_handle(cx); + h_flex() + .gap_0p5() + .child( + IconButton::new("diff-stacked", IconName::DiffStacked) + .shape(IconButtonShape::Square) + .toggle_state(!is_split) + .tooltip(|_, cx| { + Tooltip::for_action("Stacked", &ToggleSplitDiff, cx) + }) + .when(is_split, |button| { + let focus_handle = focus_handle.clone(); + button.on_click(move |_, window, cx| { + focus_handle.focus(window, cx); + window.dispatch_action(ToggleSplitDiff.boxed_clone(), cx); + }) + }), + ) + .child( + IconButton::new("diff-split", IconName::DiffSplit) + .shape(IconButtonShape::Square) + .toggle_state(is_split) + .tooltip(|_, cx| { + Tooltip::for_action("Side by Side", &ToggleSplitDiff, cx) + }) + .when(!is_split, |button| { + button.on_click({ + let focus_handle = focus_handle.clone(); + move |_, window, cx| { + focus_handle.focus(window, cx); + window + .dispatch_action(ToggleSplitDiff.boxed_clone(), cx); + } + }) + }), + ) + }) + } else { + None + }; + let collapse_expand_button = if self.needs_expand_collapse_option(cx) { let query_editor_focus = self.query_editor.focus_handle(cx); let (icon, label, tooltip_label) = if self.is_collapsed { - ( - IconName::ChevronUpDown, - "Expand All", - "Expand All Search Results", - ) + (IconName::ChevronUpDown, "Expand All", "Expand All Files") } else { ( IconName::ChevronDownUp, "Collapse All", - "Collapse All Search Results", + "Collapse All Files", ) }; if self.dismissed { - let button = Button::new("multibuffer-collapse-expand-empty", label) + if has_splittable_editor { + return h_flex() + .gap_1() + .child( + IconButton::new("multibuffer-collapse-expand-empty", icon) + .shape(IconButtonShape::Square) + .tooltip(move |_, cx| { + Tooltip::for_action_in( + tooltip_label, + &ToggleFoldAll, + &query_editor_focus, + cx, + ) + }) + .on_click(|_event, window, cx| { + window.dispatch_action(ToggleFoldAll.boxed_clone(), cx) + }), + ) + .children(split_buttons) + .into_any_element(); + } + + return Button::new("multibuffer-collapse-expand-empty", label) .icon_position(IconPosition::Start) .icon(icon) .tooltip(move |_, cx| { Tooltip::for_action_in( tooltip_label, &ToggleFoldAll, - &query_editor_focus.clone(), + &query_editor_focus, cx, ) }) @@ -173,24 +244,27 @@ impl Render for BufferSearchBar { window.dispatch_action(ToggleFoldAll.boxed_clone(), cx) }) .into_any_element(); - - return button; } Some( - IconButton::new("multibuffer-collapse-expand", icon) - .shape(IconButtonShape::Square) - .tooltip(move |_, cx| { - Tooltip::for_action_in( - tooltip_label, - &ToggleFoldAll, - &query_editor_focus, - cx, - ) - }) - .on_click(|_event, window, cx| { - window.dispatch_action(ToggleFoldAll.boxed_clone(), cx) - }) + h_flex() + .gap_1() + .child( + IconButton::new("multibuffer-collapse-expand", icon) + .shape(IconButtonShape::Square) + .tooltip(move |_, cx| { + Tooltip::for_action_in( + tooltip_label, + &ToggleFoldAll, + &query_editor_focus, + cx, + ) + }) + .on_click(|_event, window, cx| { + window.dispatch_action(ToggleFoldAll.boxed_clone(), cx) + }), + ) + .children(split_buttons) .into_any_element(), ) } else { @@ -558,9 +632,20 @@ impl ToolbarItemView for BufferSearchBar { cx.notify(); self.active_searchable_item_subscriptions.take(); self.active_searchable_item.take(); + self.splittable_editor = None; + self._splittable_editor_subscription = None; self.pending_search.take(); + if let Some(splittable_editor) = item + .and_then(|item| item.act_as_type(TypeId::of::(), cx)) + .and_then(|entity| entity.downcast::().ok()) + { + self._splittable_editor_subscription = + Some(cx.observe(&splittable_editor, |_, _, cx| cx.notify())); + self.splittable_editor = Some(splittable_editor.downgrade()); + } + if let Some(searchable_item_handle) = item.and_then(|item| item.to_searchable_item_handle(cx)) { @@ -818,6 +903,8 @@ impl BufferSearchBar { editor_needed_width: px(0.), regex_language: None, is_collapsed: false, + splittable_editor: None, + _splittable_editor_subscription: None, } }