commit_modal.rs

  1#![allow(unused, dead_code)]
  2
  3use crate::git_panel::{commit_message_editor, GitPanel};
  4use crate::repository_selector::RepositorySelector;
  5use anyhow::Result;
  6use git::Commit;
  7use language::Buffer;
  8use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
  9use settings::Settings;
 10use theme::ThemeSettings;
 11use ui::{prelude::*, Tooltip};
 12
 13use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
 14use gpui::*;
 15use project::git::Repository;
 16use project::{Fs, Project};
 17use std::sync::Arc;
 18use workspace::dock::{Dock, DockPosition, PanelHandle};
 19use workspace::{ModalView, Workspace};
 20
 21pub fn init(cx: &mut App) {
 22    cx.observe_new(|workspace: &mut Workspace, window, cx| {
 23        let Some(window) = window else {
 24            return;
 25        };
 26        CommitModal::register(workspace, window, cx)
 27    })
 28    .detach();
 29}
 30
 31pub struct CommitModal {
 32    git_panel: Entity<GitPanel>,
 33    commit_editor: Entity<Editor>,
 34    restore_dock: RestoreDock,
 35}
 36
 37impl Focusable for CommitModal {
 38    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 39        self.commit_editor.focus_handle(cx)
 40    }
 41}
 42
 43impl EventEmitter<DismissEvent> for CommitModal {}
 44impl ModalView for CommitModal {
 45    fn on_before_dismiss(
 46        &mut self,
 47        window: &mut Window,
 48        cx: &mut Context<Self>,
 49    ) -> workspace::DismissDecision {
 50        self.git_panel.update(cx, |git_panel, cx| {
 51            git_panel.set_modal_open(false, cx);
 52        });
 53        self.restore_dock.dock.update(cx, |dock, cx| {
 54            if let Some(active_index) = self.restore_dock.active_index {
 55                dock.activate_panel(active_index, window, cx)
 56            }
 57            dock.set_open(self.restore_dock.is_open, window, cx)
 58        });
 59        workspace::DismissDecision::Dismiss(true)
 60    }
 61}
 62
 63struct RestoreDock {
 64    dock: WeakEntity<Dock>,
 65    is_open: bool,
 66    active_index: Option<usize>,
 67}
 68
 69impl CommitModal {
 70    pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
 71        workspace.register_action(|workspace, _: &Commit, window, cx| {
 72            let Some(git_panel) = workspace.panel::<GitPanel>(cx) else {
 73                return;
 74            };
 75
 76            let (can_commit, conflict) = git_panel.update(cx, |git_panel, cx| {
 77                let can_commit = git_panel.can_commit();
 78                let conflict = git_panel.has_unstaged_conflicts();
 79                (can_commit, conflict)
 80            });
 81            if !can_commit {
 82                let message = if conflict {
 83                    "There are still conflicts. You must stage these before committing."
 84                } else {
 85                    "No changes to commit."
 86                };
 87                let prompt = window.prompt(PromptLevel::Warning, message, None, &["Ok"], cx);
 88                cx.spawn(|_, _| async move {
 89                    prompt.await.ok();
 90                })
 91                .detach();
 92            }
 93
 94            let dock = workspace.dock_at_position(git_panel.position(window, cx));
 95            let is_open = dock.read(cx).is_open();
 96            let active_index = dock.read(cx).active_panel_index();
 97            let dock = dock.downgrade();
 98            let restore_dock_position = RestoreDock {
 99                dock,
100                is_open,
101                active_index,
102            };
103            workspace.open_panel::<GitPanel>(window, cx);
104            workspace.toggle_modal(window, cx, move |window, cx| {
105                CommitModal::new(git_panel, restore_dock_position, window, cx)
106            })
107        });
108    }
109
110    fn new(
111        git_panel: Entity<GitPanel>,
112        restore_dock: RestoreDock,
113        window: &mut Window,
114        cx: &mut Context<Self>,
115    ) -> Self {
116        let panel = git_panel.read(cx);
117
118        let commit_editor = git_panel.update(cx, |git_panel, cx| {
119            git_panel.set_modal_open(true, cx);
120            let buffer = git_panel.commit_message_buffer(cx).clone();
121            let project = git_panel.project.clone();
122            cx.new(|cx| commit_message_editor(buffer, project.clone(), false, window, cx))
123        });
124
125        Self {
126            git_panel,
127            commit_editor,
128            restore_dock,
129        }
130    }
131
132    pub fn render_commit_editor(
133        &self,
134        name_and_email: Option<(SharedString, SharedString)>,
135        window: &mut Window,
136        cx: &mut Context<Self>,
137    ) -> impl IntoElement {
138        let editor = self.commit_editor.clone();
139
140        let panel_editor_style = panel_editor_style(true, window, cx);
141
142        let settings = ThemeSettings::get_global(cx);
143        let line_height = relative(settings.buffer_line_height.value())
144            .to_pixels(settings.buffer_font_size(cx).into(), window.rem_size());
145
146        v_flex()
147            .justify_between()
148            .relative()
149            .w_full()
150            .h_full()
151            .pt_2()
152            .bg(cx.theme().colors().editor_background)
153            .child(EditorElement::new(&self.commit_editor, panel_editor_style))
154            .child(self.render_footer(window, cx))
155    }
156
157    pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
158        let (branch, tooltip, title, co_authors) = self.git_panel.update(cx, |git_panel, cx| {
159            let branch = git_panel
160                .active_repository
161                .as_ref()
162                .and_then(|repo| {
163                    repo.read(cx)
164                        .repository_entry
165                        .branch()
166                        .map(|b| b.name.clone())
167                })
168                .unwrap_or_else(|| "<no branch>".into());
169            let tooltip = if git_panel.has_staged_changes() {
170                "Commit staged changes"
171            } else {
172                "Commit changes to tracked files"
173            };
174            let title = if git_panel.has_staged_changes() {
175                "Commit"
176            } else {
177                "Commit All"
178            };
179            let co_authors = git_panel.render_co_authors(cx);
180            (branch, tooltip, title, co_authors)
181        });
182
183        let branch_selector = Button::new("branch-selector", branch)
184            .color(Color::Muted)
185            .style(ButtonStyle::Subtle)
186            .icon(IconName::GitBranch)
187            .icon_size(IconSize::Small)
188            .icon_color(Color::Muted)
189            .size(ButtonSize::Compact)
190            .icon_position(IconPosition::Start)
191            .tooltip(Tooltip::for_action_title(
192                "Switch Branch",
193                &zed_actions::git::Branch,
194            ))
195            .on_click(cx.listener(|_, _, window, cx| {
196                window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
197            }))
198            .style(ButtonStyle::Transparent);
199
200        h_flex()
201            .w_full()
202            .justify_between()
203            .child(branch_selector)
204            .child(
205                h_flex().children(co_authors).child(
206                    panel_filled_button(title)
207                        .tooltip(Tooltip::for_action_title(tooltip, &git::Commit))
208                        .on_click(cx.listener(|this, _, window, cx| {
209                            this.commit(&Default::default(), window, cx);
210                        })),
211                ),
212            )
213    }
214
215    fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
216        cx.emit(DismissEvent);
217    }
218    fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
219        self.git_panel
220            .update(cx, |git_panel, cx| git_panel.commit_changes(window, cx));
221        cx.emit(DismissEvent);
222    }
223}
224
225impl Render for CommitModal {
226    fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
227        v_flex()
228            .id("commit-modal")
229            .key_context("GitCommit")
230            .elevation_3(cx)
231            .on_action(cx.listener(Self::dismiss))
232            .on_action(cx.listener(Self::commit))
233            .relative()
234            .bg(cx.theme().colors().editor_background)
235            .rounded(px(16.))
236            .border_1()
237            .border_color(cx.theme().colors().border)
238            .py_2()
239            .px_4()
240            .w(px(480.))
241            .min_h(rems(18.))
242            .flex_1()
243            .overflow_hidden()
244            .child(
245                v_flex()
246                    .flex_1()
247                    .child(self.render_commit_editor(None, window, cx)),
248            )
249    }
250}