#![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, commit_editor: Entity, restore_dock: RestoreDock, } impl Focusable for CommitModal { fn focus_handle(&self, cx: &App) -> gpui::FocusHandle { self.commit_editor.focus_handle(cx) } } impl EventEmitter for CommitModal {} impl ModalView for CommitModal { fn on_before_dismiss( &mut self, window: &mut Window, cx: &mut Context, ) -> 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, is_open: bool, active_index: Option, } impl CommitModal { pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context) { workspace.register_action(|workspace, _: &Commit, window, cx| { let Some(git_panel) = workspace.panel::(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::(window, cx); workspace.toggle_modal(window, cx, move |window, cx| { CommitModal::new(git_panel, restore_dock_position, window, cx) }) }); } fn new( git_panel: Entity, restore_dock: RestoreDock, window: &mut Window, cx: &mut Context, ) -> 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, ) -> 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(cx).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) -> 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(|| "".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) { cx.emit(DismissEvent); } fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context) { 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)), ) } }