From 573e096fc5d94df3e8febbac7bbc9c41eb7b6158 Mon Sep 17 00:00:00 2001 From: Nate Butler Date: Thu, 12 Dec 2024 12:21:08 -0500 Subject: [PATCH] More Git panel refinements (#21928) - Add and wire through git method stubs - Organize render methods - Track modifier changes - Swap commit buttons when `option`/`alt` is held - More TODOs Release Notes: - N/A --- crates/git_ui/TODO.md | 42 +++++++ crates/git_ui/src/git_panel.rs | 217 ++++++++++++++++++++++++++------- crates/git_ui/src/git_ui.rs | 13 +- 3 files changed, 230 insertions(+), 42 deletions(-) create mode 100644 crates/git_ui/TODO.md diff --git a/crates/git_ui/TODO.md b/crates/git_ui/TODO.md new file mode 100644 index 0000000000000000000000000000000000000000..b72e5daff67879683b0382b43c690c408393b68c --- /dev/null +++ b/crates/git_ui/TODO.md @@ -0,0 +1,42 @@ +### General + +- [x] Disable staging and committing actions for read-only projects + +### List + +- [ ] Git status item +- [ ] Directory item +- [ ] Scrollbar +- [ ] Add indent size setting +- [ ] Add tree settings + +### List Items + +- [ ] Context menu + - [ ] Discard Changes + - --- + - [ ] Ignore + - [ ] Ignore directory + - --- + - [ ] Copy path + - [ ] Copy relative path + - --- + - [ ] Reveal in Finder + +### Commit Editor + +- [ ] Add commit editor +- [ ] Add commit message placeholder & add commit message to store +- [ ] Add a way to get the current collaborators & automatically add them to the commit message as co-authors +- [ ] Add action to clear commit message +- [x] Swap commit button between "Commit" and "Commit All" based on modifier key + +### Component Updates + +- [ ] ChangedLineCount (new) + - takes `lines_added: usize, lines_removed: usize`, returns a added/removed badge +- [ ] GitStatusIcon (new) +- [ ] Checkbox + - update checkbox design +- [ ] ScrollIndicator + - shows a gradient overlay when more content is available to be scrolled diff --git a/crates/git_ui/src/git_panel.rs b/crates/git_ui/src/git_panel.rs index e06787bd4a7c9ec8188ce9b5b6fd26c63566bae9..5ba4d290797abb8247b27dc9be0c90b4c9d70c8a 100644 --- a/crates/git_ui/src/git_panel.rs +++ b/crates/git_ui/src/git_panel.rs @@ -3,14 +3,17 @@ use util::TryFutureExt; use db::kvp::KEY_VALUE_STORE; use gpui::*; -use project::Fs; +use project::{Fs, Project}; use serde::{Deserialize, Serialize}; use settings::Settings as _; -use ui::{prelude::*, Checkbox, Divider, DividerColor, ElevationIndex}; +use ui::{prelude::*, Checkbox, Divider, DividerColor, ElevationIndex, Tooltip}; use workspace::dock::{DockPosition, Panel, PanelEvent}; use workspace::Workspace; use crate::settings::GitPanelSettings; +use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll}; + +actions!(git_panel, [ToggleFocus]); const GIT_PANEL_KEY: &str = "GitPanel"; @@ -30,14 +33,15 @@ struct SerializedGitPanel { width: Option, } -actions!(git_panel, [Deploy, ToggleFocus]); - pub struct GitPanel { _workspace: WeakView, - pending_serialization: Task>, - fs: Arc, focus_handle: FocusHandle, + fs: Arc, + pending_serialization: Task>, + project: Model, width: Option, + + current_modifiers: Modifiers, } impl GitPanel { @@ -53,14 +57,19 @@ impl GitPanel { } pub fn new(workspace: &mut Workspace, cx: &mut ViewContext) -> View { + let project = workspace.project().clone(); let fs = workspace.app_state().fs.clone(); let weak_workspace = workspace.weak_handle(); cx.new_view(|cx| Self { - fs, _workspace: weak_workspace, - pending_serialization: Task::ready(None), focus_handle: cx.focus_handle(), + fs, + pending_serialization: Task::ready(None), + project, + + current_modifiers: cx.modifiers(), + width: Some(px(360.)), }) } @@ -81,7 +90,84 @@ impl GitPanel { ); } + fn dispatch_context(&self) -> KeyContext { + let mut dispatch_context = KeyContext::new_with_defaults(); + dispatch_context.add("GitPanel"); + dispatch_context.add("menu"); + + dispatch_context + } + + fn handle_modifiers_changed( + &mut self, + event: &ModifiersChangedEvent, + cx: &mut ViewContext, + ) { + self.current_modifiers = event.modifiers; + cx.notify(); + } +} + +impl GitPanel { + fn stage_all(&mut self, _: &StageAll, _cx: &mut ViewContext) { + // todo!(): Implement stage all + println!("Stage all triggered"); + } + + fn unstage_all(&mut self, _: &UnstageAll, _cx: &mut ViewContext) { + // todo!(): Implement unstage all + println!("Unstage all triggered"); + } + + fn discard_all(&mut self, _: &DiscardAll, _cx: &mut ViewContext) { + // todo!(): Implement discard all + println!("Discard all triggered"); + } + + /// Commit all staged changes + fn commit_staged_changes(&mut self, _: &CommitStagedChanges, _cx: &mut ViewContext) { + // todo!(): Implement commit all staged + println!("Commit staged changes triggered"); + } + + /// Commit all changes, regardless of whether they are staged or not + fn commit_all_changes(&mut self, _: &CommitAllChanges, _cx: &mut ViewContext) { + // todo!(): Implement commit all changes + println!("Commit all changes triggered"); + } + + fn all_staged(&self) -> bool { + // todo!(): Implement all_staged + true + } +} + +impl GitPanel { + pub fn panel_button( + &self, + id: impl Into, + label: impl Into, + ) -> Button { + let id = id.into().clone(); + let label = label.into().clone(); + + Button::new(id, label) + .label_size(LabelSize::Small) + .layer(ElevationIndex::ElevatedSurface) + .size(ButtonSize::Compact) + .style(ButtonStyle::Filled) + } + + pub fn render_divider(&self, _cx: &mut ViewContext) -> impl IntoElement { + h_flex() + .items_center() + .h(px(8.)) + .child(Divider::horizontal_dashed().color(DividerColor::Border)) + } + pub fn render_panel_header(&self, cx: &mut ViewContext) -> impl IntoElement { + let focus_handle = self.focus_handle(cx).clone(); + h_flex() .h(px(32.)) .items_center() @@ -99,21 +185,65 @@ impl GitPanel { .gap_2() .child( IconButton::new("discard-changes", IconName::Undo) + .tooltip(move |cx| { + let focus_handle = focus_handle.clone(); + + Tooltip::for_action_in( + "Discard all changes", + &DiscardAll, + &focus_handle, + cx, + ) + }) .icon_size(IconSize::Small) .disabled(true), ) - .child( - Button::new("stage-all", "Stage All") - .label_size(LabelSize::Small) - .layer(ElevationIndex::ElevatedSurface) - .size(ButtonSize::Compact) - .style(ButtonStyle::Filled) - .disabled(true), - ), + .child(if self.all_staged() { + self.panel_button("unstage-all", "Unstage All").on_click( + cx.listener(move |_, _, cx| cx.dispatch_action(Box::new(DiscardAll))), + ) + } else { + self.panel_button("stage-all", "Stage All").on_click( + cx.listener(move |_, _, cx| cx.dispatch_action(Box::new(StageAll))), + ) + }), ) } pub fn render_commit_editor(&self, cx: &ViewContext) -> impl IntoElement { + let focus_handle_1 = self.focus_handle(cx).clone(); + let focus_handle_2 = self.focus_handle(cx).clone(); + + let commit_staged_button = self + .panel_button("commit-staged-changes", "Commit") + .tooltip(move |cx| { + let focus_handle = focus_handle_1.clone(); + Tooltip::for_action_in( + "Commit all staged changes", + &CommitStagedChanges, + &focus_handle, + cx, + ) + }) + .on_click(cx.listener(|this, _: &ClickEvent, cx| { + this.commit_staged_changes(&CommitStagedChanges, cx) + })); + + let commit_all_button = self + .panel_button("commit-all-changes", "Commit All") + .tooltip(move |cx| { + let focus_handle = focus_handle_2.clone(); + Tooltip::for_action_in( + "Commit all changes, including unstaged changes", + &CommitAllChanges, + &focus_handle, + cx, + ) + }) + .on_click(cx.listener(|this, _: &ClickEvent, cx| { + this.commit_all_changes(&CommitAllChanges, cx) + })); + div().w_full().h(px(140.)).px_2().pt_1().pb_2().child( v_flex() .h_full() @@ -126,16 +256,13 @@ impl GitPanel { .child("Add a message") .gap_1() .child(div().flex_grow()) - .child( - h_flex().child(div().gap_1().flex_grow()).child( - Button::new("commit", "Commit") - .label_size(LabelSize::Small) - .layer(ElevationIndex::ElevatedSurface) - .size(ButtonSize::Compact) - .style(ButtonStyle::Filled) - .disabled(true), - ), - ) + .child(h_flex().child(div().gap_1().flex_grow()).child( + if self.current_modifiers.alt { + commit_all_button + } else { + commit_staged_button + }, + )) .cursor(CursorStyle::OperationNotAllowed) .opacity(0.5), ) @@ -160,29 +287,37 @@ impl GitPanel { impl Render for GitPanel { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { + let project = self.project.read(cx); + v_flex() - .key_context("GitPanel") - .font_buffer(cx) - .py_1() .id("git_panel") + .key_context(self.dispatch_context()) .track_focus(&self.focus_handle) + .on_modifiers_changed(cx.listener(Self::handle_modifiers_changed)) + .when(!project.is_read_only(cx), |this| { + this.on_action(cx.listener(|this, &StageAll, cx| this.stage_all(&StageAll, cx))) + .on_action( + cx.listener(|this, &UnstageAll, cx| this.unstage_all(&UnstageAll, cx)), + ) + .on_action( + cx.listener(|this, &DiscardAll, cx| this.discard_all(&DiscardAll, cx)), + ) + .on_action(cx.listener(|this, &CommitStagedChanges, cx| { + this.commit_staged_changes(&CommitStagedChanges, cx) + })) + .on_action(cx.listener(|this, &CommitAllChanges, cx| { + this.commit_all_changes(&CommitAllChanges, cx) + })) + }) .size_full() .overflow_hidden() + .font_buffer(cx) + .py_1() .bg(ElevationIndex::Surface.bg(cx)) .child(self.render_panel_header(cx)) - .child( - h_flex() - .items_center() - .h(px(8.)) - .child(Divider::horizontal_dashed().color(DividerColor::Border)), - ) + .child(self.render_divider(cx)) .child(self.render_empty_state(cx)) - .child( - h_flex() - .items_center() - .h(px(8.)) - .child(Divider::horizontal_dashed().color(DividerColor::Border)), - ) + .child(self.render_divider(cx)) .child(self.render_commit_editor(cx)) } } diff --git a/crates/git_ui/src/git_ui.rs b/crates/git_ui/src/git_ui.rs index a61733f8534717840b44a3e92e0b0b808b425f8a..dabe23ce92e85de315fce33d1455598340e0f5a6 100644 --- a/crates/git_ui/src/git_ui.rs +++ b/crates/git_ui/src/git_ui.rs @@ -1,10 +1,21 @@ use ::settings::Settings; -use gpui::AppContext; +use gpui::{actions, AppContext}; use settings::GitPanelSettings; pub mod git_panel; mod settings; +actions!( + git_ui, + [ + StageAll, + UnstageAll, + DiscardAll, + CommitStagedChanges, + CommitAllChanges + ] +); + pub fn init(cx: &mut AppContext) { GitPanelSettings::register(cx); }