quick_commit.rs

  1#![allow(unused, dead_code)]
  2
  3use crate::repository_selector::RepositorySelector;
  4use anyhow::Result;
  5use git::{CommitAllChanges, CommitChanges};
  6use language::Buffer;
  7use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
  8use ui::{prelude::*, Tooltip};
  9
 10use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
 11use gpui::*;
 12use project::git::Repository;
 13use project::{Fs, Project};
 14use std::sync::Arc;
 15use workspace::{ModalView, Workspace};
 16
 17actions!(
 18    git,
 19    [QuickCommitWithMessage, QuickCommitStaged, QuickCommitAll]
 20);
 21
 22pub fn init(cx: &mut App) {
 23    cx.observe_new(|workspace: &mut Workspace, window, cx| {
 24        let Some(window) = window else {
 25            return;
 26        };
 27        QuickCommitModal::register(workspace, window, cx)
 28    })
 29    .detach();
 30}
 31
 32fn commit_message_editor(
 33    commit_message_buffer: Option<Entity<Buffer>>,
 34    window: &mut Window,
 35    cx: &mut Context<'_, Editor>,
 36) -> Editor {
 37    let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
 38        let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
 39        Editor::new(
 40            EditorMode::AutoHeight { max_lines: 10 },
 41            buffer,
 42            None,
 43            false,
 44            window,
 45            cx,
 46        )
 47    } else {
 48        Editor::auto_height(10, window, cx)
 49    };
 50    commit_editor.set_use_autoclose(false);
 51    commit_editor.set_show_gutter(false, cx);
 52    commit_editor.set_show_wrap_guides(false, cx);
 53    commit_editor.set_show_indent_guides(false, cx);
 54    commit_editor.set_placeholder_text("Enter commit message", cx);
 55    commit_editor
 56}
 57
 58pub struct QuickCommitModal {
 59    focus_handle: FocusHandle,
 60    fs: Arc<dyn Fs>,
 61    project: Entity<Project>,
 62    active_repository: Option<Entity<Repository>>,
 63    repository_selector: Entity<RepositorySelector>,
 64    commit_editor: Entity<Editor>,
 65    width: Option<Pixels>,
 66    commit_task: Task<Result<()>>,
 67    commit_pending: bool,
 68    can_commit: bool,
 69    can_commit_all: bool,
 70    enable_auto_coauthors: bool,
 71}
 72
 73impl Focusable for QuickCommitModal {
 74    fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
 75        self.focus_handle.clone()
 76    }
 77}
 78
 79impl EventEmitter<DismissEvent> for QuickCommitModal {}
 80impl ModalView for QuickCommitModal {}
 81
 82impl QuickCommitModal {
 83    pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
 84        workspace.register_action(|workspace, _: &QuickCommitWithMessage, window, cx| {
 85            let project = workspace.project().clone();
 86            let fs = workspace.app_state().fs.clone();
 87
 88            workspace.toggle_modal(window, cx, move |window, cx| {
 89                QuickCommitModal::new(project, fs, window, None, cx)
 90            });
 91        });
 92    }
 93
 94    pub fn new(
 95        project: Entity<Project>,
 96        fs: Arc<dyn Fs>,
 97        window: &mut Window,
 98        commit_message_buffer: Option<Entity<Buffer>>,
 99        cx: &mut Context<Self>,
100    ) -> Self {
101        let git_store = project.read(cx).git_store().clone();
102        let active_repository = project.read(cx).active_repository(cx);
103
104        let focus_handle = cx.focus_handle();
105
106        let commit_editor = cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
107        commit_editor.update(cx, |editor, cx| {
108            editor.clear(window, cx);
109        });
110
111        let repository_selector = cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
112
113        Self {
114            focus_handle,
115            fs,
116            project,
117            active_repository,
118            repository_selector,
119            commit_editor,
120            width: None,
121            commit_task: Task::ready(Ok(())),
122            commit_pending: false,
123            can_commit: false,
124            can_commit_all: false,
125            enable_auto_coauthors: true,
126        }
127    }
128
129    pub fn render_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
130        let all_repositories = self
131            .project
132            .read(cx)
133            .git_store()
134            .read(cx)
135            .all_repositories();
136        let entry_count = self
137            .active_repository
138            .as_ref()
139            .map_or(0, |repo| repo.read(cx).entry_count());
140
141        let changes_string = match entry_count {
142            0 => "No changes".to_string(),
143            1 => "1 change".to_string(),
144            n => format!("{} changes", n),
145        };
146
147        div().absolute().top_0().right_0().child(
148            panel_icon_button("open_change_list", IconName::PanelRight)
149                .disabled(true)
150                .tooltip(Tooltip::text("Changes list coming soon!")),
151        )
152    }
153
154    pub fn render_commit_editor(
155        &self,
156        name_and_email: Option<(SharedString, SharedString)>,
157        window: &mut Window,
158        cx: &mut Context<Self>,
159    ) -> impl IntoElement {
160        let editor = self.commit_editor.clone();
161        let can_commit = !self.commit_pending && self.can_commit && !editor.read(cx).is_empty(cx);
162        let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
163
164        let focus_handle_1 = self.focus_handle(cx).clone();
165        let focus_handle_2 = self.focus_handle(cx).clone();
166
167        let panel_editor_style = panel_editor_style(true, window, cx);
168
169        let commit_staged_button = panel_filled_button("Commit")
170            .tooltip(move |window, cx| {
171                let focus_handle = focus_handle_1.clone();
172                Tooltip::for_action_in(
173                    "Commit all staged changes",
174                    &CommitChanges,
175                    &focus_handle,
176                    window,
177                    cx,
178                )
179            })
180            .when(!can_commit, |this| {
181                this.disabled(true).style(ButtonStyle::Transparent)
182            });
183        // .on_click({
184        //     let name_and_email = name_and_email.clone();
185        //     cx.listener(move |this, _: &ClickEvent, window, cx| {
186        //         this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx)
187        //     })
188        // });
189
190        let commit_all_button = panel_filled_button("Commit All")
191            .tooltip(move |window, cx| {
192                let focus_handle = focus_handle_2.clone();
193                Tooltip::for_action_in(
194                    "Commit all changes, including unstaged changes",
195                    &CommitAllChanges,
196                    &focus_handle,
197                    window,
198                    cx,
199                )
200            })
201            .when(!can_commit, |this| {
202                this.disabled(true).style(ButtonStyle::Transparent)
203            });
204        // .on_click({
205        //     let name_and_email = name_and_email.clone();
206        //     cx.listener(move |this, _: &ClickEvent, window, cx| {
207        //         this.commit_tracked_changes(
208        //             &CommitAllChanges,
209        //             name_and_email.clone(),
210        //             window,
211        //             cx,
212        //         )
213        //     })
214        // });
215
216        let co_author_button = panel_icon_button("add-co-author", IconName::UserGroup)
217            .icon_color(if self.enable_auto_coauthors {
218                Color::Muted
219            } else {
220                Color::Accent
221            })
222            .icon_size(IconSize::Small)
223            .toggle_state(self.enable_auto_coauthors)
224            // .on_click({
225            //     cx.listener(move |this, _: &ClickEvent, _, cx| {
226            //         this.toggle_auto_coauthors(cx);
227            //     })
228            // })
229            .tooltip(move |window, cx| {
230                Tooltip::with_meta(
231                    "Toggle automatic co-authors",
232                    None,
233                    "Automatically adds current collaborators",
234                    window,
235                    cx,
236                )
237            });
238
239        panel_editor_container(window, cx)
240            .id("commit-editor-container")
241            .relative()
242            .w_full()
243            .border_t_1()
244            .border_color(cx.theme().colors().border)
245            .h(px(140.))
246            .bg(cx.theme().colors().editor_background)
247            .on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
248                window.focus(&editor_focus_handle);
249            }))
250            .child(EditorElement::new(&self.commit_editor, panel_editor_style))
251            .child(div().flex_1())
252            .child(
253                h_flex()
254                    .items_center()
255                    .h_8()
256                    .justify_between()
257                    .gap_1()
258                    .child(co_author_button)
259                    .child(commit_all_button)
260                    .child(commit_staged_button),
261            )
262    }
263
264    pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
265        h_flex()
266            .w_full()
267            .justify_between()
268            .child(h_flex().child("cmd+esc clear message"))
269            .child(
270                h_flex()
271                    .child(panel_filled_button("Commit"))
272                    .child(panel_filled_button("Commit All")),
273            )
274    }
275
276    fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
277        cx.emit(DismissEvent);
278    }
279}
280
281impl Render for QuickCommitModal {
282    fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
283        v_flex()
284            .id("quick-commit-modal")
285            .key_context("QuickCommit")
286            .on_action(cx.listener(Self::dismiss))
287            .relative()
288            .bg(cx.theme().colors().elevated_surface_background)
289            .rounded(px(16.))
290            .border_1()
291            .border_color(cx.theme().colors().border)
292            .py_2()
293            .px_4()
294            .w(self.width.unwrap_or(px(640.)))
295            .h(px(450.))
296            .flex_1()
297            .overflow_hidden()
298            .child(self.render_header(window, cx))
299            .child(
300                v_flex()
301                    .flex_1()
302                    // TODO: pass name_and_email
303                    .child(self.render_commit_editor(None, window, cx)),
304            )
305            .child(self.render_footer(window, cx))
306    }
307}