git: Add toolbar buttons for SplittableEditor (#48323)

Release Notes:

- N/A
This commit is contained in:
Cole Miller 2026-02-04 23:15:41 -05:00 committed by GitHub
parent 323680f6fe
commit 804caba60c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 123 additions and 61 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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::<Editor>() {
Some(self.editor.read(cx).rhs_editor().clone().into())
} else if type_id == TypeId::of::<SplittableEditor>() {
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::<SplitDiffFeatureFlag>() {
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(

View file

@ -92,6 +92,8 @@ pub enum IconName {
DebugStepOut,
DebugStepOver,
Diff,
DiffSplit,
DiffStacked,
Disconnected,
Download,
EditorAtom,

View file

@ -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

View file

@ -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<Arc<Language>>,
is_collapsed: bool,
splittable_editor: Option<WeakEntity<SplittableEditor>>,
_splittable_editor_subscription: Option<Subscription>,
}
impl EventEmitter<Event> for BufferSearchBar {}
@ -140,46 +144,94 @@ impl Render for BufferSearchBar {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx);
let has_splittable_editor =
self.splittable_editor.is_some() && cx.has_flag::<SplitDiffFeatureFlag>();
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(),
cx,
)
})
.on_click(|_event, window, cx| {
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,
@ -191,6 +243,28 @@ impl Render for BufferSearchBar {
.on_click(|_event, window, cx| {
window.dispatch_action(ToggleFoldAll.boxed_clone(), cx)
})
.into_any_element();
}
Some(
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::<SplittableEditor>(), cx))
.and_then(|entity| entity.downcast::<SplittableEditor>().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,
}
}