git_panel: Add support for vertically expanding the commit editor (#55043)

Using the existing commit editor in the Git Panel to type out longer
commit messages has been somewhat hard. I believe this happens because
it takes a very small portion of the UI which, unfortunately, when `git:
expand commit editor` is used, a modal ends up taking the center of the
editor, making it possible to have the commit editor open on the side,
while the `git: diff` view is open.

As such, this Pull Request introduces a new
`git::ToggleFillCommitEditor` action that allows users to update the
commit editor's height so as to take as much vertical space as possible,
hiding the entries status and simply rendering the Git Panel's footer.

This makes it easier to be able to write longer commit messages while
still having the `git: branch diff` on the side, something that's very
complicated with the default number of lines in the commit editor and
impossible using the `CommitModal`.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Added a `git::ToggleFillCommitEditor` action that expands the commit
editor to fill the git panel's available vertical space.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-04-28 16:09:23 +00:00
1 parent 2b0f1fa68c
commit 463047c5c7
5 files changed
+157 -28

No files matched your search

+2
View File
@@ -1071,6 +1071,8 @@
"ctrl-shift-enter": "git::Amend",
"alt-up": "git_panel::FocusChanges",
"alt-l": "git::GenerateCommitMessage",
"shift-escape": "git::ExpandCommitEditor",
"alt-shift-escape": "git::ToggleFillCommitEditor",
},
},
{
+1
View File
@@ -1102,6 +1102,7 @@
"shift-tab": "git_panel::FocusChanges",
"alt-up": "git_panel::FocusChanges",
"shift-escape": "git::ExpandCommitEditor",
"alt-shift-escape": "git::ToggleFillCommitEditor",
"alt-tab": "git::GenerateCommitMessage",
},
},
+2
View File
@@ -1069,6 +1069,8 @@
"ctrl-shift-enter": "git::Amend",
"alt-up": "git_panel::FocusChanges",
"alt-l": "git::GenerateCommitMessage",
"shift-escape": "git::ExpandCommitEditor",
"alt-shift-escape": "git::ToggleFillCommitEditor",
},
},
{
+3
View File
@@ -94,6 +94,9 @@ actions!(
Cancel,
/// Expands the commit message editor.
ExpandCommitEditor,
/// Toggles whether the commit message editor fills all the available
/// vertical space within the git panel.
ToggleFillCommitEditor,
/// Generates a commit message using AI.
GenerateCommitMessage,
/// Initializes a new git repository.
+149 -28
View File
@@ -16,7 +16,7 @@ use askpass::AskPassDelegate;
use collections::{BTreeMap, HashMap, HashSet};
use db::kvp::KeyValueStore;
use editor::{
Direction, Editor, EditorElement, EditorMode, MultiBuffer, MultiBufferOffset,
Direction, Editor, EditorElement, EditorMode, MultiBuffer, MultiBufferOffset, SizingBehavior,
actions::ExpandAllDiffHunks,
};
use editor::{EditorStyle, RewrapOptions};
@@ -34,7 +34,7 @@ use git::status::{DiffStat, StageStatus};
use git::{Amend, Signoff, ToggleStaged, repository::RepoPath, status::FileStatus};
use git::{
ExpandCommitEditor, GitHostingProviderRegistry, RestoreTrackedFiles, StageAll, StashAll,
StashApply, StashPop, TrashUntrackedFiles, UnstageAll,
StashApply, StashPop, ToggleFillCommitEditor, TrashUntrackedFiles, UnstageAll,
};
use gpui::{
Action, Anchor, AsyncApp, AsyncWindowContext, Bounds, ClickEvent, DismissEvent, Empty, Entity,
@@ -247,6 +247,13 @@ pub fn register(workspace: &mut Workspace) {
workspace.register_action(|workspace, _: &ExpandCommitEditor, window, cx| {
CommitModal::toggle(workspace, None, window, cx)
});
workspace.register_action(|workspace, _: &ToggleFillCommitEditor, window, cx| {
if let Some(panel) = workspace.panel::<GitPanel>(cx) {
panel.update(cx, |panel, cx| {
panel.toggle_fill_commit_editor(&Default::default(), window, cx)
});
}
});
workspace.register_action(|workspace, _: &git::Init, window, cx| {
if let Some(panel) = workspace.panel::<GitPanel>(cx) {
panel.update(cx, |panel, cx| panel.git_init(window, cx));
@@ -619,6 +626,8 @@ impl TruncatedPatch {
pub struct GitPanel {
pub(crate) active_repository: Option<Entity<Repository>>,
pub(crate) commit_editor: Entity<Editor>,
/// Whether the commit editor should fill the vertical height of the panel.
commit_editor_expanded: bool,
conflicted_count: usize,
conflicted_staged_count: usize,
add_coauthors: bool,
@@ -809,6 +818,7 @@ impl GitPanel {
let mut this = Self {
active_repository,
commit_editor,
commit_editor_expanded: false,
conflicted_count: 0,
conflicted_staged_count: 0,
add_coauthors: true,
@@ -4259,9 +4269,34 @@ impl GitPanel {
}
}
fn toggle_fill_commit_editor(
&mut self,
_: &ToggleFillCommitEditor,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.commit_editor_expanded = !self.commit_editor_expanded;
self.commit_editor.update(cx, |editor, _cx| {
if self.commit_editor_expanded {
editor.set_mode(EditorMode::Full {
scale_ui_elements_with_buffer_font_size: false,
show_active_line_background: false,
sizing_behavior: SizingBehavior::ExcludeOverscrollMargin,
})
} else {
editor.set_mode(EditorMode::AutoHeight {
min_lines: MAX_PANEL_EDITOR_LINES,
max_lines: Some(MAX_PANEL_EDITOR_LINES),
})
}
});
cx.notify();
}
fn expand_commit_editor(
&mut self,
_: &git::ExpandCommitEditor,
_: &ExpandCommitEditor,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -4375,10 +4410,7 @@ impl GitPanel {
let active_repository = self.active_repository.clone()?;
let panel_editor_style = panel_editor_style(true, window, cx);
let enable_coauthors = self.render_co_authors(cx);
let editor_focus_handle = self.commit_editor.focus_handle(cx);
let expand_tooltip_focus_handle = editor_focus_handle;
let branch = active_repository.read(cx).branch.clone();
let head_commit = active_repository.read(cx).head_commit.clone();
@@ -4414,6 +4446,7 @@ impl GitPanel {
};
let footer = v_flex()
.when(self.commit_editor_expanded, |this| this.flex_1().min_h_0())
.child(PanelRepoFooter::new(
display_name,
branch,
@@ -4448,7 +4481,10 @@ impl GitPanel {
.cursor_text()
.relative()
.w_full()
.h(max_height + footer_size)
.when(self.commit_editor_expanded, |this| this.flex_1().min_h_0())
.when(!self.commit_editor_expanded, |this| {
this.h(max_height + footer_size)
})
.border_t_1()
.border_color(if title_exceeds_limit {
cx.theme().status().warning_border
@@ -4486,6 +4522,9 @@ impl GitPanel {
)
.child(
div()
.when(self.commit_editor_expanded, |this| {
this.flex_1().min_h_0().pb(footer_size)
})
.pr_2p5()
.on_action(|&zed_actions::editor::MoveUp, _, cx| {
cx.stop_propagation();
@@ -4500,19 +4539,21 @@ impl GitPanel {
.absolute()
.top_2()
.right_2()
.opacity(0.5)
.hover(|this| this.opacity(1.0))
.gap_px()
.opacity(0.6)
.hover(|s| s.opacity(1.0))
.child(
panel_icon_button("expand-commit-editor", IconName::Maximize)
panel_icon_button("expand-commit-editor", IconName::MaximizeAlt)
.icon_size(IconSize::Small)
.size(ui::ButtonSize::Default)
.tooltip(move |_window, cx| {
Tooltip::for_action_in(
"Open Commit Modal",
&git::ExpandCommitEditor,
&expand_tooltip_focus_handle,
cx,
)
.tooltip({
move |_window, cx| {
Tooltip::for_action_in(
"Open Commit Modal",
&git::ExpandCommitEditor,
&editor_focus_handle,
cx,
)
}
})
.on_click(cx.listener({
move |_, _, window, cx| {
@@ -4522,7 +4563,36 @@ impl GitPanel {
)
}
})),
),
)
.child({
let (icon, label) = if self.commit_editor_expanded {
(IconName::Minimize, "Collapse Commit Editor")
} else {
(IconName::Maximize, "Expand Commit Editor")
};
let focus_handle = self.focus_handle.clone();
panel_icon_button("fill-commit-editor", icon)
.icon_size(IconSize::Small)
.tooltip({
move |_window, cx| {
Tooltip::for_action_in(
label,
&git::ToggleFillCommitEditor,
&focus_handle,
cx,
)
}
})
.on_click(cx.listener({
move |_, _, window, cx| {
window.dispatch_action(
git::ToggleFillCommitEditor.boxed_clone(),
cx,
)
}
}))
}),
),
);
@@ -5932,15 +6002,22 @@ impl Render for GitPanel {
.child(
v_flex()
.size_full()
.children(self.render_panel_header(window, cx))
.map(|this| {
if let Some(repo) = self.active_repository.clone()
&& has_entries
{
this.child(self.render_entries(has_write_access, repo, window, cx))
} else {
this.child(self.render_empty_state(cx).into_any_element())
}
.when(!self.commit_editor_expanded, |this| {
this.children(self.render_panel_header(window, cx))
.map(|this| {
if let Some(repo) = self.active_repository.clone()
&& has_entries
{
this.child(self.render_entries(
has_write_access,
repo,
window,
cx,
))
} else {
this.child(self.render_empty_state(cx).into_any_element())
}
})
})
.children(self.render_footer(window, cx))
.when(self.amend_pending, |this| {
@@ -8275,4 +8352,48 @@ mod tests {
);
});
}
#[gpui::test]
async fn test_fill_commit_editor_toggle(cx: &mut TestAppContext) {
init_test(cx);
let fs = FakeFs::new(cx.background_executor.clone());
fs.insert_tree(
"/root",
json!({ "project": { ".git": {}, "src": { "main.rs": "fn main() {}" } } }),
)
.await;
let project = Project::test(fs.clone(), [Path::new(path!("/root/project"))], cx).await;
let window_handle =
cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
let workspace = window_handle
.read_with(cx, |mw, _| mw.workspace().clone())
.unwrap();
let cx = &mut VisualTestContext::from_window(window_handle.into(), cx);
cx.executor().run_until_parked();
let panel = workspace.update_in(cx, GitPanel::new);
panel.update_in(cx, |panel, window, cx| {
assert!(!panel.commit_editor_expanded);
assert!(matches!(
panel.commit_editor.read(cx).mode().clone(),
EditorMode::AutoHeight { .. }
));
panel.toggle_fill_commit_editor(&ToggleFillCommitEditor, window, cx);
assert!(panel.commit_editor_expanded);
assert!(matches!(
panel.commit_editor.read(cx).mode().clone(),
EditorMode::Full { .. }
));
panel.toggle_fill_commit_editor(&ToggleFillCommitEditor, window, cx);
assert!(!panel.commit_editor_expanded);
assert!(matches!(
panel.commit_editor.read(cx).mode().clone(),
EditorMode::AutoHeight { .. }
));
});
}
}