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
This commit is contained in:
Nate Butler 2024-12-12 12:21:08 -05:00 committed by GitHub
parent ee6f834028
commit 573e096fc5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 230 additions and 42 deletions

42
crates/git_ui/TODO.md Normal file
View file

@ -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

View file

@ -3,14 +3,17 @@ use util::TryFutureExt;
use db::kvp::KEY_VALUE_STORE; use db::kvp::KEY_VALUE_STORE;
use gpui::*; use gpui::*;
use project::Fs; use project::{Fs, Project};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings as _; 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::dock::{DockPosition, Panel, PanelEvent};
use workspace::Workspace; use workspace::Workspace;
use crate::settings::GitPanelSettings; use crate::settings::GitPanelSettings;
use crate::{CommitAllChanges, CommitStagedChanges, DiscardAll, StageAll, UnstageAll};
actions!(git_panel, [ToggleFocus]);
const GIT_PANEL_KEY: &str = "GitPanel"; const GIT_PANEL_KEY: &str = "GitPanel";
@ -30,14 +33,15 @@ struct SerializedGitPanel {
width: Option<Pixels>, width: Option<Pixels>,
} }
actions!(git_panel, [Deploy, ToggleFocus]);
pub struct GitPanel { pub struct GitPanel {
_workspace: WeakView<Workspace>, _workspace: WeakView<Workspace>,
pending_serialization: Task<Option<()>>,
fs: Arc<dyn Fs>,
focus_handle: FocusHandle, focus_handle: FocusHandle,
fs: Arc<dyn Fs>,
pending_serialization: Task<Option<()>>,
project: Model<Project>,
width: Option<Pixels>, width: Option<Pixels>,
current_modifiers: Modifiers,
} }
impl GitPanel { impl GitPanel {
@ -53,14 +57,19 @@ impl GitPanel {
} }
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> { pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
let project = workspace.project().clone();
let fs = workspace.app_state().fs.clone(); let fs = workspace.app_state().fs.clone();
let weak_workspace = workspace.weak_handle(); let weak_workspace = workspace.weak_handle();
cx.new_view(|cx| Self { cx.new_view(|cx| Self {
fs,
_workspace: weak_workspace, _workspace: weak_workspace,
pending_serialization: Task::ready(None),
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
fs,
pending_serialization: Task::ready(None),
project,
current_modifiers: cx.modifiers(),
width: Some(px(360.)), 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>,
) {
self.current_modifiers = event.modifiers;
cx.notify();
}
}
impl GitPanel {
fn stage_all(&mut self, _: &StageAll, _cx: &mut ViewContext<Self>) {
// todo!(): Implement stage all
println!("Stage all triggered");
}
fn unstage_all(&mut self, _: &UnstageAll, _cx: &mut ViewContext<Self>) {
// todo!(): Implement unstage all
println!("Unstage all triggered");
}
fn discard_all(&mut self, _: &DiscardAll, _cx: &mut ViewContext<Self>) {
// todo!(): Implement discard all
println!("Discard all triggered");
}
/// Commit all staged changes
fn commit_staged_changes(&mut self, _: &CommitStagedChanges, _cx: &mut ViewContext<Self>) {
// 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<Self>) {
// 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<SharedString>,
label: impl Into<SharedString>,
) -> 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<Self>) -> 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<Self>) -> impl IntoElement { pub fn render_panel_header(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let focus_handle = self.focus_handle(cx).clone();
h_flex() h_flex()
.h(px(32.)) .h(px(32.))
.items_center() .items_center()
@ -99,21 +185,65 @@ impl GitPanel {
.gap_2() .gap_2()
.child( .child(
IconButton::new("discard-changes", IconName::Undo) 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) .icon_size(IconSize::Small)
.disabled(true), .disabled(true),
) )
.child( .child(if self.all_staged() {
Button::new("stage-all", "Stage All") self.panel_button("unstage-all", "Unstage All").on_click(
.label_size(LabelSize::Small) cx.listener(move |_, _, cx| cx.dispatch_action(Box::new(DiscardAll))),
.layer(ElevationIndex::ElevatedSurface) )
.size(ButtonSize::Compact) } else {
.style(ButtonStyle::Filled) self.panel_button("stage-all", "Stage All").on_click(
.disabled(true), cx.listener(move |_, _, cx| cx.dispatch_action(Box::new(StageAll))),
), )
}),
) )
} }
pub fn render_commit_editor(&self, cx: &ViewContext<Self>) -> impl IntoElement { pub fn render_commit_editor(&self, cx: &ViewContext<Self>) -> 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( div().w_full().h(px(140.)).px_2().pt_1().pb_2().child(
v_flex() v_flex()
.h_full() .h_full()
@ -126,16 +256,13 @@ impl GitPanel {
.child("Add a message") .child("Add a message")
.gap_1() .gap_1()
.child(div().flex_grow()) .child(div().flex_grow())
.child( .child(h_flex().child(div().gap_1().flex_grow()).child(
h_flex().child(div().gap_1().flex_grow()).child( if self.current_modifiers.alt {
Button::new("commit", "Commit") commit_all_button
.label_size(LabelSize::Small) } else {
.layer(ElevationIndex::ElevatedSurface) commit_staged_button
.size(ButtonSize::Compact) },
.style(ButtonStyle::Filled) ))
.disabled(true),
),
)
.cursor(CursorStyle::OperationNotAllowed) .cursor(CursorStyle::OperationNotAllowed)
.opacity(0.5), .opacity(0.5),
) )
@ -160,29 +287,37 @@ impl GitPanel {
impl Render for GitPanel { impl Render for GitPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let project = self.project.read(cx);
v_flex() v_flex()
.key_context("GitPanel")
.font_buffer(cx)
.py_1()
.id("git_panel") .id("git_panel")
.key_context(self.dispatch_context())
.track_focus(&self.focus_handle) .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() .size_full()
.overflow_hidden() .overflow_hidden()
.font_buffer(cx)
.py_1()
.bg(ElevationIndex::Surface.bg(cx)) .bg(ElevationIndex::Surface.bg(cx))
.child(self.render_panel_header(cx)) .child(self.render_panel_header(cx))
.child( .child(self.render_divider(cx))
h_flex()
.items_center()
.h(px(8.))
.child(Divider::horizontal_dashed().color(DividerColor::Border)),
)
.child(self.render_empty_state(cx)) .child(self.render_empty_state(cx))
.child( .child(self.render_divider(cx))
h_flex()
.items_center()
.h(px(8.))
.child(Divider::horizontal_dashed().color(DividerColor::Border)),
)
.child(self.render_commit_editor(cx)) .child(self.render_commit_editor(cx))
} }
} }

View file

@ -1,10 +1,21 @@
use ::settings::Settings; use ::settings::Settings;
use gpui::AppContext; use gpui::{actions, AppContext};
use settings::GitPanelSettings; use settings::GitPanelSettings;
pub mod git_panel; pub mod git_panel;
mod settings; mod settings;
actions!(
git_ui,
[
StageAll,
UnstageAll,
DiscardAll,
CommitStagedChanges,
CommitAllChanges
]
);
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
GitPanelSettings::register(cx); GitPanelSettings::register(cx);
} }