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}