New commit review flow in project diff view (#25229)

Closes #ISSUE

Release Notes:

- N/A

---------

Co-authored-by: Nate Butler <iamnbutler@gmail.com>
This commit is contained in:
Conrad Irwin 2025-02-20 23:52:34 -07:00 committed by GitHub
parent 6b9397c380
commit 4871d3c9e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 982 additions and 480 deletions

View file

@ -1,4 +1,5 @@
use crate::git_panel_settings::StatusStyle;
use crate::project_diff::Diff;
use crate::repository_selector::RepositorySelectorPopoverMenu;
use crate::{
git_panel_settings::GitPanelSettings, git_status_icon, repository_selector::RepositorySelector,
@ -78,16 +79,16 @@ pub fn init(cx: &mut App) {
workspace.toggle_panel_focus::<GitPanel>(window, cx);
});
workspace.register_action(|workspace, _: &Commit, window, cx| {
workspace.open_panel::<GitPanel>(window, cx);
if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
git_panel
.read(cx)
.commit_editor
.focus_handle(cx)
.focus(window);
}
});
// workspace.register_action(|workspace, _: &Commit, window, cx| {
// workspace.open_panel::<GitPanel>(window, cx);
// if let Some(git_panel) = workspace.panel::<GitPanel>(cx) {
// git_panel
// .read(cx)
// .commit_editor
// .focus_handle(cx)
// .focus(window);
// }
// });
},
)
.detach();
@ -174,7 +175,7 @@ struct PendingOperation {
}
pub struct GitPanel {
active_repository: Option<Entity<Repository>>,
pub(crate) active_repository: Option<Entity<Repository>>,
commit_editor: Entity<Editor>,
conflicted_count: usize,
conflicted_staged_count: usize,
@ -190,7 +191,7 @@ pub struct GitPanel {
pending: Vec<PendingOperation>,
pending_commit: Option<Task<()>>,
pending_serialization: Task<Option<()>>,
project: Entity<Project>,
pub(crate) project: Entity<Project>,
repository_selector: Entity<RepositorySelector>,
scroll_handle: UniformListScrollHandle,
scrollbar_state: ScrollbarState,
@ -202,17 +203,20 @@ pub struct GitPanel {
width: Option<Pixels>,
workspace: WeakEntity<Workspace>,
context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
modal_open: bool,
}
fn commit_message_editor(
pub(crate) fn commit_message_editor(
commit_message_buffer: Entity<Buffer>,
project: Entity<Project>,
in_panel: bool,
window: &mut Window,
cx: &mut Context<'_, Editor>,
) -> Editor {
let buffer = cx.new(|cx| MultiBuffer::singleton(commit_message_buffer, cx));
let max_lines = if in_panel { 6 } else { 18 };
let mut commit_editor = Editor::new(
EditorMode::AutoHeight { max_lines: 6 },
EditorMode::AutoHeight { max_lines },
buffer,
None,
false,
@ -251,8 +255,9 @@ impl GitPanel {
// just to let us render a placeholder editor.
// Once the active git repo is set, this buffer will be replaced.
let temporary_buffer = cx.new(|cx| Buffer::local("", cx));
let commit_editor =
cx.new(|cx| commit_message_editor(temporary_buffer, project.clone(), window, cx));
let commit_editor = cx.new(|cx| {
commit_message_editor(temporary_buffer, project.clone(), true, window, cx)
});
commit_editor.update(cx, |editor, cx| {
editor.clear(window, cx);
});
@ -309,6 +314,7 @@ impl GitPanel {
width: Some(px(360.)),
context_menu: None,
workspace,
modal_open: false,
};
git_panel.schedule_update(false, window, cx);
git_panel.show_scrollbar = git_panel.should_show_scrollbar(cx);
@ -351,6 +357,11 @@ impl GitPanel {
);
}
pub(crate) fn set_modal_open(&mut self, open: bool, cx: &mut Context<Self>) {
self.modal_open = open;
cx.notify();
}
fn dispatch_context(&self, window: &mut Window, cx: &Context<Self>) -> KeyContext {
let mut dispatch_context = KeyContext::new_with_defaults();
dispatch_context.add("GitPanel");
@ -592,7 +603,6 @@ impl GitPanel {
})
.ok()
});
self.focus_handle.focus(window);
}
fn open_file(
@ -998,6 +1008,16 @@ impl GitPanel {
.detach();
}
pub fn commit_message_buffer(&self, cx: &App) -> Entity<Buffer> {
self.commit_editor
.read(cx)
.buffer()
.read(cx)
.as_singleton()
.unwrap()
.clone()
}
fn toggle_staged_for_selected(
&mut self,
_: &git::ToggleStaged,
@ -1022,7 +1042,7 @@ impl GitPanel {
self.commit_changes(window, cx)
}
fn commit_changes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
pub(crate) fn commit_changes(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let Some(active_repository) = self.active_repository.clone() else {
return;
};
@ -1288,7 +1308,7 @@ impl GitPanel {
!= Some(&buffer)
{
git_panel.commit_editor = cx.new(|cx| {
commit_message_editor(buffer, git_panel.project.clone(), window, cx)
commit_message_editor(buffer, git_panel.project.clone(), true, window, cx)
});
}
})
@ -1476,12 +1496,18 @@ impl GitPanel {
entry.is_staged
}
fn has_staged_changes(&self) -> bool {
pub(crate) fn has_staged_changes(&self) -> bool {
self.tracked_staged_count > 0
|| self.new_staged_count > 0
|| self.conflicted_staged_count > 0
}
pub(crate) fn has_unstaged_changes(&self) -> bool {
self.tracked_count > self.tracked_staged_count
|| self.new_count > self.new_staged_count
|| self.conflicted_count > self.conflicted_staged_count
}
fn has_conflicts(&self) -> bool {
self.conflicted_count > 0
}
@ -1490,7 +1516,7 @@ impl GitPanel {
self.tracked_count > 0
}
fn has_unstaged_conflicts(&self) -> bool {
pub fn has_unstaged_conflicts(&self) -> bool {
self.conflicted_count > 0 && self.conflicted_count != self.conflicted_staged_count
}
@ -1564,7 +1590,17 @@ impl GitPanel {
.size(LabelSize::Small)
.color(Color::Muted),
)
.child(self.render_repository_selector(cx)),
.child(self.render_repository_selector(cx))
.child(div().flex_grow())
.child(
Button::new("diff", "+/-")
.tooltip(Tooltip::for_action_title("Open diff", &Diff))
.on_click(|_, _, cx| {
cx.defer(|cx| {
cx.dispatch_action(&Diff);
})
}),
),
)
} else {
None
@ -1587,48 +1623,21 @@ impl GitPanel {
)
}
pub fn render_commit_editor(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let editor = self.commit_editor.clone();
let can_commit = (self.has_staged_changes() || self.has_tracked_changes())
&& self.pending_commit.is_none()
&& !editor.read(cx).is_empty(cx)
&& !self.has_unstaged_conflicts()
&& self.has_write_access(cx);
pub fn can_commit(&self) -> bool {
(self.has_staged_changes() || self.has_tracked_changes()) && !self.has_unstaged_conflicts()
}
// let can_commit_all =
// !self.commit_pending && self.can_commit_all && !editor.read(cx).is_empty(cx);
let panel_editor_style = panel_editor_style(true, window, cx);
pub fn can_stage_all(&self) -> bool {
self.has_unstaged_changes()
}
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let focus_handle_1 = self.focus_handle(cx).clone();
let tooltip = if self.has_staged_changes() {
"Commit staged changes"
} else {
"Commit changes to tracked files"
};
let title = if self.has_staged_changes() {
"Commit"
} else {
"Commit All"
};
let commit_button = panel_filled_button(title)
.tooltip(move |window, cx| {
let focus_handle = focus_handle_1.clone();
Tooltip::for_action_in(tooltip, &Commit, &focus_handle, window, cx)
})
.disabled(!can_commit)
.on_click({
cx.listener(move |this, _: &ClickEvent, window, cx| this.commit_changes(window, cx))
});
pub fn can_unstage_all(&self) -> bool {
self.has_staged_changes()
}
pub(crate) fn render_co_authors(&self, cx: &Context<Self>) -> Option<AnyElement> {
let potential_co_authors = self.potential_co_authors(cx);
let enable_coauthors = if potential_co_authors.is_empty() {
if potential_co_authors.is_empty() {
None
} else {
Some(
@ -1654,9 +1663,48 @@ impl GitPanel {
.on_click(cx.listener(|this, _, _, cx| {
this.add_coauthors = !this.add_coauthors;
cx.notify();
})),
}))
.into_any_element(),
)
}
}
pub fn render_commit_editor(
&self,
window: &mut Window,
cx: &mut Context<Self>,
) -> impl IntoElement {
let editor = self.commit_editor.clone();
let can_commit = self.can_commit()
&& self.pending_commit.is_none()
&& !editor.read(cx).is_empty(cx)
&& self.has_write_access(cx);
let panel_editor_style = panel_editor_style(true, window, cx);
let enable_coauthors = self.render_co_authors(cx);
let editor_focus_handle = editor.read(cx).focus_handle(cx).clone();
let focus_handle_1 = self.focus_handle(cx).clone();
let tooltip = if self.has_staged_changes() {
"Commit staged changes"
} else {
"Commit changes to tracked files"
};
let title = if self.has_staged_changes() {
"Commit"
} else {
"Commit All"
};
let commit_button = panel_filled_button(title)
.tooltip(move |window, cx| {
let focus_handle = focus_handle_1.clone();
Tooltip::for_action_in(tooltip, &Commit, &focus_handle, window, cx)
})
.disabled(!can_commit)
.on_click({
cx.listener(move |this, _: &ClickEvent, window, cx| this.commit_changes(window, cx))
});
let branch = self
.active_repository
@ -1698,26 +1746,28 @@ impl GitPanel {
.on_click(cx.listener(move |_, _: &ClickEvent, window, _cx| {
window.focus(&editor_focus_handle);
}))
.child(EditorElement::new(&self.commit_editor, panel_editor_style))
.child(
h_flex()
.absolute()
.bottom_0()
.left_2()
.h(footer_size)
.flex_none()
.child(branch_selector),
)
.child(
h_flex()
.absolute()
.bottom_0()
.right_2()
.h(footer_size)
.flex_none()
.children(enable_coauthors)
.child(commit_button),
)
.when(!self.modal_open, |el| {
el.child(EditorElement::new(&self.commit_editor, panel_editor_style))
.child(
h_flex()
.absolute()
.bottom_0()
.left_2()
.h(footer_size)
.flex_none()
.child(branch_selector),
)
.child(
h_flex()
.absolute()
.bottom_0()
.right_2()
.h(footer_size)
.flex_none()
.children(enable_coauthors)
.child(commit_button),
)
})
}
fn render_previous_commit(&self, cx: &mut Context<Self>) -> Option<impl IntoElement> {
@ -1892,8 +1942,8 @@ impl GitPanel {
Some(
h_flex()
.id("start-slot")
.text_lg()
.child(checkbox)
.child(git_status_icon(entry.status_entry()?.status, cx))
.on_mouse_down(MouseButton::Left, |_, _, cx| {
// prevent the list item active state triggering when toggling checkbox
cx.stop_propagation();