#![allow(unused, dead_code)] use crate::repository_selector::RepositorySelector;use anyhow::Result;use git::{CommitAllChanges, CommitChanges};use language::Buffer;use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};use ui::{prelude::*, Tooltip}; use editor::{Editor, EditorElement, EditorMode, MultiBuffer};use gpui::*;use project::git::Repository;use project::{Fs, Project};use std::sync::Arc;use workspace::{ModalView, Workspace}; actions!( git, [QuickCommitWithMessage, QuickCommitStaged, QuickCommitAll]); pub fn init(cx: &mut App) { cx.observe_new(|workspace: &mut Workspace, window, cx| { let Some(window) = window else { return; }; QuickCommitModal::register(workspace, window, cx) }) .detach();} fn commit_message_editor( commit_message_buffer: Option<Entity<Buffer>>, window: &mut Window, cx: &mut Context<'_, Editor>,) -> Editor { let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer { let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx)); Editor::new( EditorMode::AutoHeight { max_lines: 10 }, buffer, None, false, window, cx, ) } else { Editor::auto_height(10, window, cx) }; commit_editor.set_use_autoclose(false); commit_editor.set_show_gutter(false, cx); commit_editor.set_show_wrap_guides(false, cx); commit_editor.set_show_indent_guides(false, cx); commit_editor.set_placeholder_text("Enter commit message", cx); commit_editor} pub struct QuickCommitModal { focus_handle: FocusHandle, fs: Arc<dyn Fs>, project: Entity<Project>, active_repository: Option<Entity<Repository>>, repository_selector: Entity<RepositorySelector>, commit_editor: Entity<Editor>, width: Option<Pixels>, commit_task: Task<Result<()>>, commit_pending: bool, can_commit: bool, can_commit_all: bool, enable_auto_coauthors: bool,} impl Focusable for QuickCommitModal { fn focus_handle(&self, cx: &App) -> gpui::FocusHandle { self.focus_handle.clone() }} impl EventEmitter<DismissEvent> for QuickCommitModal {}impl ModalView for QuickCommitModal {} impl QuickCommitModal { pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) { workspace.register_action(|workspace, _: &QuickCommitWithMessage, window, cx| { let project = workspace.project().clone(); let fs = workspace.app_state().fs.clone(); workspace.toggle_modal(window, cx, move |window, cx| { QuickCommitModal::new(project, fs, window, None, cx) }); }); } pub fn new( project: Entity<Project>, fs: Arc<dyn Fs>, window: &mut Window, commit_message_buffer: Option<Entity<Buffer>>, cx: &mut Context<Self>, ) -> Self { let git_store = project.read(cx).git_store().clone(); let active_repository = project.read(cx).active_repository(cx); let focus_handle = cx.focus_handle(); let commit_editor = cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx)); commit_editor.update(cx, |editor, cx| { editor.clear(window, cx); }); let repository_selector = cx.new(|cx| RepositorySelector::new(project.clone(), window, cx)); Self { focus_handle, fs, project, active_repository, repository_selector, commit_editor, width: None, commit_task: Task::ready(Ok(())), commit_pending: false, can_commit: false, can_commit_all: false, enable_auto_coauthors: true, } } pub fn render_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { let all_repositories = self .project .read(cx) .git_store() .read(cx) .all_repositories(); let entry_count = self .active_repository .as_ref() .map_or(0, |repo| repo.read(cx).entry_count()); let changes_string = match entry_count { 0 => "No changes".to_string(), 1 => "1 change".to_string(), n => format!("{} changes", n), }; div().absolute().top_0().right_0().child( panel_icon_button("open_change_list", IconName::PanelRight) .disabled(true) .tooltip(Tooltip::text("Changes list coming soon!")), ) } pub fn render_commit_editor( &self, name_and_email: Option<(SharedString, SharedString)>, window: &mut Window, cx: &mut Context<Self>, ) -> impl IntoElement { let editor = self.commit_editor.clone(); let can_commit = !self.commit_pending && self.can_commit && !editor.read(cx).is_empty(cx); let editor_focus_handle = editor.read(cx).focus_handle(cx).clone(); let focus_handle_1 = self.focus_handle(cx).clone(); let focus_handle_2 = self.focus_handle(cx).clone(); let panel_editor_style = panel_editor_style(true, window, cx); let commit_staged_button = panel_filled_button("Commit") .tooltip(move |window, cx| { let focus_handle = focus_handle_1.clone(); Tooltip::for_action_in( "Commit all staged changes", &CommitChanges, &focus_handle, window, cx, ) }) .when(!can_commit, |this| { this.disabled(true).style(ButtonStyle::Transparent) }); // .on_click({ // let name_and_email = name_and_email.clone(); // cx.listener(move |this, _: &ClickEvent, window, cx| { // this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx) // }) // }); let commit_all_button = panel_filled_button("Commit All") .tooltip(move |window, cx| { let focus_handle = focus_handle_2.clone(); Tooltip::for_action_in( "Commit all changes, including unstaged changes", &CommitAllChanges, &focus_handle, window, cx, ) }) .when(!can_commit, |this| { this.disabled(true).style(ButtonStyle::Transparent) }); // .on_click({ // let name_and_email = name_and_email.clone(); // cx.listener(move |this, _: &ClickEvent, window, cx| { // this.commit_tracked_changes( // &CommitAllChanges, // name_and_email.clone(), // window, // cx, // ) // }) // }); let co_author_button = panel_icon_button("add-co-author", IconName::UserGroup) .icon_color(if self.enable_auto_coauthors { Color::Muted } else { Color::Accent }) .icon_size(IconSize::Small) .toggle_state(self.enable_auto_coauthors) // .on_click({ // cx.listener(move |this, _: &ClickEvent, _, cx| { // this.toggle_auto_coauthors(cx); // }) // }) .tooltip(move |window, cx| { Tooltip::with_meta( "Toggle automatic co-authors", None, "Automatically adds current collaborators", window, cx, ) }); panel_editor_container(window, cx) .id("commit-editor-container") .relative() .w_full() .border_t_1() .border_color(cx.theme().colors().border) .h(px(140.)) .bg(cx.theme().colors().editor_background) .on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| { window.focus(&editor_focus_handle); })) .child(EditorElement::new(&self.commit_editor, panel_editor_style)) .child(div().flex_1()) .child( h_flex() .items_center() .h_8() .justify_between() .gap_1() .child(co_author_button) .child(commit_all_button) .child(commit_staged_button), ) } pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { h_flex() .w_full() .justify_between() .child(h_flex().child("cmd+esc clear message")) .child( h_flex() .child(panel_filled_button("Commit")) .child(panel_filled_button("Commit All")), ) } fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) { cx.emit(DismissEvent); }} impl Render for QuickCommitModal { fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement { v_flex() .id("quick-commit-modal") .key_context("QuickCommit") .on_action(cx.listener(Self::dismiss)) .relative() .bg(cx.theme().colors().elevated_surface_background) .rounded(px(16.)) .border_1() .border_color(cx.theme().colors().border) .py_2() .px_4() .w(self.width.unwrap_or(px(640.))) .h(px(450.)) .flex_1() .overflow_hidden() .child(self.render_header(window, cx)) .child( v_flex() .flex_1() // TODO: pass name_and_email .child(self.render_commit_editor(None, window, cx)), ) .child(self.render_footer(window, cx)) }}
#![allow(unused, dead_code)]
use crate::repository_selector::RepositorySelector;
use anyhow::Result;
use git::{CommitAllChanges, CommitChanges};
use language::Buffer;
use panel::{panel_editor_container, panel_editor_style, panel_filled_button, panel_icon_button};
use ui::{prelude::*, Tooltip};
use editor::{Editor, EditorElement, EditorMode, MultiBuffer};
use gpui::*;
use project::git::Repository;
use project::{Fs, Project};
use std::sync::Arc;
use workspace::{ModalView, Workspace};
actions!(
git,
[QuickCommitWithMessage, QuickCommitStaged, QuickCommitAll]
);
pub fn init(cx: &mut App) {
cx.observe_new(|workspace: &mut Workspace, window, cx| {
let Some(window) = window else {
return;
};
QuickCommitModal::register(workspace, window, cx)
})
.detach();
}
fn commit_message_editor(
commit_message_buffer: Option<Entity<Buffer>>,
window: &mut Window,
cx: &mut Context<'_, Editor>,
) -> Editor {
let mut commit_editor = if let Some(commit_message_buffer) = commit_message_buffer {
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
Editor::new(
EditorMode::AutoHeight { max_lines: 10 },
buffer,
None,
false,
window,
cx,
)
} else {
Editor::auto_height(10, window, cx)
};
commit_editor.set_use_autoclose(false);
commit_editor.set_show_gutter(false, cx);
commit_editor.set_show_wrap_guides(false, cx);
commit_editor.set_show_indent_guides(false, cx);
commit_editor.set_placeholder_text("Enter commit message", cx);
commit_editor
}
pub struct QuickCommitModal {
focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
project: Entity<Project>,
active_repository: Option<Entity<Repository>>,
repository_selector: Entity<RepositorySelector>,
commit_editor: Entity<Editor>,
width: Option<Pixels>,
commit_task: Task<Result<()>>,
commit_pending: bool,
can_commit: bool,
can_commit_all: bool,
enable_auto_coauthors: bool,
}
impl Focusable for QuickCommitModal {
fn focus_handle(&self, cx: &App) -> gpui::FocusHandle {
self.focus_handle.clone()
}
}
impl EventEmitter<DismissEvent> for QuickCommitModal {}
impl ModalView for QuickCommitModal {}
impl QuickCommitModal {
pub fn register(workspace: &mut Workspace, _: &mut Window, cx: &mut Context<Workspace>) {
workspace.register_action(|workspace, _: &QuickCommitWithMessage, window, cx| {
let project = workspace.project().clone();
let fs = workspace.app_state().fs.clone();
workspace.toggle_modal(window, cx, move |window, cx| {
QuickCommitModal::new(project, fs, window, None, cx)
});
});
}
pub fn new(
project: Entity<Project>,
fs: Arc<dyn Fs>,
window: &mut Window,
commit_message_buffer: Option<Entity<Buffer>>,
cx: &mut Context<Self>,
) -> Self {
let git_store = project.read(cx).git_store().clone();
let active_repository = project.read(cx).active_repository(cx);
let focus_handle = cx.focus_handle();
let commit_editor = cx.new(|cx| commit_message_editor(commit_message_buffer, window, cx));
commit_editor.update(cx, |editor, cx| {
editor.clear(window, cx);
});
let repository_selector = cx.new(|cx| RepositorySelector::new(project.clone(), window, cx));
Self {
focus_handle,
fs,
project,
active_repository,
repository_selector,
commit_editor,
width: None,
commit_task: Task::ready(Ok(())),
commit_pending: false,
can_commit: false,
can_commit_all: false,
enable_auto_coauthors: true,
}
}
pub fn render_header(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let all_repositories = self
.project
.read(cx)
.git_store()
.read(cx)
.all_repositories();
let entry_count = self
.active_repository
.as_ref()
.map_or(0, |repo| repo.read(cx).entry_count());
let changes_string = match entry_count {
0 => "No changes".to_string(),
1 => "1 change".to_string(),
n => format!("{} changes", n),
};
div().absolute().top_0().right_0().child(
panel_icon_button("open_change_list", IconName::PanelRight)
.disabled(true)
.tooltip(Tooltip::text("Changes list coming soon!")),
)
}
pub fn render_commit_editor(
&self,
name_and_email: Option<(SharedString, SharedString)>,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let editor = self.commit_editor.clone();
let can_commit = !self.commit_pending && self.can_commit && !editor.read(cx).is_empty(cx);
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let focus_handle_1 = self.focus_handle(cx).clone();
let focus_handle_2 = self.focus_handle(cx).clone();
let panel_editor_style = panel_editor_style(true, window, cx);
let commit_staged_button = panel_filled_button("Commit")
.tooltip(move |window, cx| {
let focus_handle = focus_handle_1.clone();
Tooltip::for_action_in(
"Commit all staged changes",
&CommitChanges,
&focus_handle,
window,
cx,
)
})
.when(!can_commit, |this| {
this.disabled(true).style(ButtonStyle::Transparent)
});
// .on_click({
// let name_and_email = name_and_email.clone();
// cx.listener(move |this, _: &ClickEvent, window, cx| {
// this.commit_changes(&CommitChanges, name_and_email.clone(), window, cx)
// })
// });
let commit_all_button = panel_filled_button("Commit All")
.tooltip(move |window, cx| {
let focus_handle = focus_handle_2.clone();
Tooltip::for_action_in(
"Commit all changes, including unstaged changes",
&CommitAllChanges,
&focus_handle,
window,
cx,
)
})
.when(!can_commit, |this| {
this.disabled(true).style(ButtonStyle::Transparent)
});
// .on_click({
// let name_and_email = name_and_email.clone();
// cx.listener(move |this, _: &ClickEvent, window, cx| {
// this.commit_tracked_changes(
// &CommitAllChanges,
// name_and_email.clone(),
// window,
// cx,
// )
// })
// });
let co_author_button = panel_icon_button("add-co-author", IconName::UserGroup)
.icon_color(if self.enable_auto_coauthors {
Color::Muted
} else {
Color::Accent
})
.icon_size(IconSize::Small)
.toggle_state(self.enable_auto_coauthors)
// .on_click({
// cx.listener(move |this, _: &ClickEvent, _, cx| {
// this.toggle_auto_coauthors(cx);
// })
// })
.tooltip(move |window, cx| {
Tooltip::with_meta(
"Toggle automatic co-authors",
None,
"Automatically adds current collaborators",
window,
cx,
)
});
panel_editor_container(window, cx)
.id("commit-editor-container")
.relative()
.w_full()
.border_t_1()
.border_color(cx.theme().colors().border)
.h(px(140.))
.bg(cx.theme().colors().editor_background)
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
window.focus(&editor_focus_handle);
}))
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
.child(div().flex_1())
.child(
h_flex()
.items_center()
.h_8()
.justify_between()
.gap_1()
.child(co_author_button)
.child(commit_all_button)
.child(commit_staged_button),
)
}
pub fn render_footer(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
h_flex()
.w_full()
.justify_between()
.child(h_flex().child("cmd+esc clear message"))
.child(
h_flex()
.child(panel_filled_button("Commit"))
.child(panel_filled_button("Commit All")),
)
}
fn dismiss(&mut self, _: &menu::Cancel, _: &mut Window, cx: &mut Context<Self>) {
cx.emit(DismissEvent);
}
}
impl Render for QuickCommitModal {
fn render(&mut self, window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
v_flex()
.id("quick-commit-modal")
.key_context("QuickCommit")
.on_action(cx.listener(Self::dismiss))
.relative()
.bg(cx.theme().colors().elevated_surface_background)
.rounded(px(16.))
.border_1()
.border_color(cx.theme().colors().border)
.py_2()
.px_4()
.w(self.width.unwrap_or(px(640.)))
.h(px(450.))
.flex_1()
.overflow_hidden()
.child(self.render_header(window, cx))
.child(
v_flex()
.flex_1()
// TODO: pass name_and_email
.child(self.render_commit_editor(None, window, cx)),
)
.child(self.render_footer(window, cx))
}
}