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}