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| repo.read(cx).branch().map(|b| b.name.clone()))
163 .unwrap_or_else(|| "<no branch>".into());
164 let tooltip = if git_panel.has_staged_changes() {
165 "Commit staged changes"
166 } else {
167 "Commit changes to tracked files"
168 };
169 let title = if git_panel.has_staged_changes() {
170 "Commit"
171 } else {
172 "Commit All"
173 };
174 let co_authors = git_panel.render_co_authors(cx);
175 (branch, tooltip, title, co_authors)
176 });
177
178 let branch_selector = Button::new("branch-selector", branch)
179 .color(Color::Muted)
180 .style(ButtonStyle::Subtle)
181 .icon(IconName::GitBranch)
182 .icon_size(IconSize::Small)
183 .icon_color(Color::Muted)
184 .size(ButtonSize::Compact)
185 .icon_position(IconPosition::Start)
186 .tooltip(Tooltip::for_action_title(
187 "Switch Branch",
188 &zed_actions::git::Branch,
189 ))
190 .on_click(cx.listener(|_, _, window, cx| {
191 window.dispatch_action(zed_actions::git::Branch.boxed_clone(), cx);
192 }))
193 .style(ButtonStyle::Transparent);
194
195 h_flex()
196 .w_full()
197 .justify_between()
198 .child(branch_selector)
199 .child(
200 h_flex().children(co_authors).child(
201 panel_filled_button(title)
202 .tooltip(Tooltip::for_action_title(tooltip, &git::Commit))
203 .on_click(cx.listener(|this, _, window, cx| {
204 this.commit(&Default::default(), window, cx);
205 })),
206 ),
207 )
208 }
209
210 fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
211 cx.emit(DismissEvent);
212 }
213 fn commit(&mut self, _: &git::Commit, window: &mut Window, cx: &mut Context<Self>) {
214 self.git_panel
215 .update(cx, |git_panel, cx| git_panel.commit_changes(window, cx));
216 cx.emit(DismissEvent);
217 }
218}
219
220impl Render for CommitModal {
221 fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
222 v_flex()
223 .id("commit-modal")
224 .key_context("GitCommit")
225 .elevation_3(cx)
226 .on_action(cx.listener(Self::dismiss))
227 .on_action(cx.listener(Self::commit))
228 .relative()
229 .bg(cx.theme().colors().editor_background)
230 .rounded(px(16.))
231 .border_1()
232 .border_color(cx.theme().colors().border)
233 .py_2()
234 .px_4()
235 .w(px(480.))
236 .min_h(rems(18.))
237 .flex_1()
238 .overflow_hidden()
239 .child(
240 v_flex()
241 .flex_1()
242 .child(self.render_commit_editor(None, window, cx)),
243 )
244 }
245}