New commit review flow in project diff view (#25229)

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
Conrad Irwin 2025-02-20 23:52:34 -07:00 committed by GitHub
parent 6b9397c380
commit 4871d3c9e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 982 additions and 480 deletions

View file

@ -0,0 +1,244 @@
#![allow(unused, dead_code)]
use crate::git_panel::{commit_message_editor, GitPanel};
use crate::repository_selector::RepositorySelector;
use anyhow::Result;
use git::Commit;
use language::Buffer;
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
use settings::Settings;
use theme::ThemeSettings;
use ui::{prelude::*, Tooltip};
use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
use gpui::*;
use project::git::Repository;
use project::{Fs, Project};
use std::sync::Arc;
use workspace::dock::{Dock, DockPosition, PanelHandle};
use workspace::{ModalView, Workspace};
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, window, cx| {
let Some(window) = window else {
return;
};
CommitModal::register(workspace, window, cx)
})
.detach();
}
pub struct CommitModal {
git_panel: Entity<GitPanel>,
commit_editor: Entity<Editor>,
restore_dock: RestoreDock,
}
impl Focusable for CommitModal {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.commit_editor.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for CommitModal {}
impl ModalView for CommitModal {
fn on_before_dismiss(
&mut self,
window: &mut Window,
cx: &mut Context<Self>,
) -> workspace::DismissDecision {
self.git_panel.update(cx, |git_panel, cx| {
git_panel.set_modal_open(false, cx);
});
self.restore_dock.dock.update(cx, |dock, cx| {
if let Some(active_index) = self.restore_dock.active_index {
dock.activate_panel(active_index, window, cx)
}
dock.set_open(self.restore_dock.is_open, window, cx)
});
workspace::DismissDecision::Dismiss(true)
}
}
struct RestoreDock {
dock: WeakEntity<Dock>,
is_open: bool,
active_index: Option<usize>,
}
impl CommitModal {
pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
workspace.register_action(|workspace, _: &Commit, window, cx| {
let Some(git_panel) = workspace.panel::<GitPanel>(cx) else {
return;
};
let (can_commit, conflict) = git_panel.update(cx, |git_panel, cx| {
let can_commit = git_panel.can_commit();
let conflict = git_panel.has_unstaged_conflicts();
(can_commit, conflict)
});
if !can_commit {
let message = if conflict {
"There are still conflicts. You must stage these before committing."
} else {
"No changes to commit."
};
let prompt = window.prompt(PromptLevel::Warning, message, None, &["Ok"], cx);
cx.spawn(|_, _| async move {
prompt.await.ok();
})
.detach();
}
let dock = workspace.dock_at_position(git_panel.position(window, cx));
let is_open = dock.read(cx).is_open();
let active_index = dock.read(cx).active_panel_index();
let dock = dock.downgrade();
let restore_dock_position = RestoreDock {
dock,
is_open,
active_index,
};
workspace.open_panel::<GitPanel>(window, cx);
workspace.toggle_modal(window, cx, move |window, cx| {
CommitModal::new(git_panel, restore_dock_position, window, cx)
})
});
}
fn new(
git_panel: Entity<GitPanel>,
restore_dock: RestoreDock,
window: &mut Window,
cx: &mut Context<Self>,
) -> Self {
let panel = git_panel.read(cx);
let commit_editor = git_panel.update(cx, |git_panel, cx| {
git_panel.set_modal_open(true, cx);
let buffer = git_panel.commit_message_buffer(cx).clone();
let project = git_panel.project.clone();
cx.new(|cx| commit_message_editor(buffer, project.clone(), false, window, cx))
});
Self {
git_panel,
commit_editor,
restore_dock,
}
}
pub fn render_commit_editor(
&self,
name_and_email: Option<(SharedString, SharedString)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let editor = self.commit_editor.clone();
let panel_editor_style = panel_editor_style(true, window, cx);
let settings = ThemeSettings::get_global(cx);
let line_height = relative(settings.buffer_line_height.value())
.to_pixels(settings.buffer_font_size.into(), window.rem_size());
v_flex()
.justify_between()
.relative()
.w_full()
.h_full()
.pt_2()
.bg(cx.theme().colors().editor_background)
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
.child(self.render_footer(window, cx))
}
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let (branch, tooltip, title, co_authors) = self.git_panel.update(cx, |git_panel, cx| {
let branch = git_panel
.active_repository
.as_ref()
.and_then(|repo| repo.read(cx).branch().map(|b| b.name.clone()))
.unwrap_or_else(|| "<no branch>".into());
let tooltip = if git_panel.has_staged_changes() {
"Commit staged changes"
} else {
"Commit changes to tracked files"
};
let title = if git_panel.has_staged_changes() {
"Commit"
} else {
"Commit All"
};
let co_authors = git_panel.render_co_authors(cx);
(branch, tooltip, title, co_authors)
});
let branch_selector = Button::new("branch-selector", branch)
.color(Color::Muted)
.style(ButtonStyle::Subtle)
.icon(IconName::GitBranch)
.icon_size(IconSize::Small)
.icon_color(Color::Muted)
.size(ButtonSize::Compact)
.icon_position(IconPosition::Start)
.tooltip(Tooltip::for_action_title(
"Switch Branch",
&zed_actions::git::Branch,
))
.on_click(cx.listener(|_, _, window, cx| {
window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
}))
.style(ButtonStyle::Transparent);
h_flex()
.w_full()
.justify_between()
.child(branch_selector)
.child(
h_flex().children(co_authors).child(
panel_filled_button(title)
.tooltip(Tooltip::for_action_title(tooltip, &git::Commit))
.on_click(cx.listener(|this, _, window, cx| {
this.commit(&Default::default(), window, cx);
})),
),
)
}
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
self.git_panel
.update(cx, |git_panel, cx| git_panel.commit_changes(window, cx));
cx.emit(DismissEvent);
}
}
impl Render for CommitModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
v_flex()
.id("commit-modal")
.key_context("GitCommit")
.elevation_3(cx)
.on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::commit))
.relative()
.bg(cx.theme().colors().editor_background)
.rounded(px(16.))
.border_1()
.border_color(cx.theme().colors().border)
.py_2()
.px_4()
.w(px(480.))
.min_h(rems(18.))
.flex_1()
.overflow_hidden()
.child(
v_flex()
.flex_1()
.child(self.render_commit_editor(None, window, cx)),
)
}
}