@@ -69,7 +69,7 @@ pub struct GitListEntry {
display_name: String,
repo_path: RepoPath,
status: GitStatusPair,
- toggle_state: ToggleState,
+ is_staged: Option<bool>,
}
pub struct GitPanel {
@@ -91,18 +91,11 @@ pub struct GitPanel {
/// At this point it doesn't matter what repository the entry belongs to,
/// as only one repositories' entries are visible in the list at a time.
visible_entries: Vec<GitListEntry>,
+ all_staged: Option<bool>,
width: Option<Pixels>,
reveal_in_editor: Task<()>,
}
-fn status_to_toggle_state(status: &GitStatusPair) -> ToggleState {
- match status.is_staged() {
- Some(true) => ToggleState::Selected,
- Some(false) => ToggleState::Unselected,
- None => ToggleState::Indeterminate,
- }
-}
-
impl GitPanel {
pub fn load(
workspace: WeakView<Workspace>,
@@ -314,6 +307,7 @@ impl GitPanel {
fs,
pending_serialization: Task::ready(None),
visible_entries: Vec::new(),
+ all_staged: None,
current_modifiers: cx.modifiers(),
width: Some(px(360.)),
scrollbar_state: ScrollbarState::new(scroll_handle.clone()).parent_view(cx.view()),
@@ -602,10 +596,26 @@ impl GitPanel {
}
fn stage_all(&mut self, _: &StageAll, cx: &mut ViewContext<Self>) {
- self.git_state.update(cx, |state, _| state.stage_all());
+ let to_stage = self
+ .visible_entries
+ .iter_mut()
+ .filter_map(|entry| {
+ let is_unstaged = !entry.is_staged.unwrap_or(false);
+ entry.is_staged = Some(true);
+ is_unstaged.then(|| entry.repo_path.clone())
+ })
+ .collect();
+ self.all_staged = Some(true);
+ self.git_state
+ .update(cx, |state, _| state.stage_entries(to_stage));
}
fn unstage_all(&mut self, _: &UnstageAll, cx: &mut ViewContext<Self>) {
+ // This should only be called when all entries are staged.
+ for entry in &mut self.visible_entries {
+ entry.is_staged = Some(false);
+ }
+ self.all_staged = Some(false);
self.git_state.update(cx, |state, _| {
state.unstage_all();
});
@@ -639,11 +649,6 @@ impl GitPanel {
println!("Commit all changes triggered");
}
- fn all_staged(&self) -> bool {
- // TODO: Implement all_staged
- true
- }
-
fn no_entries(&self) -> bool {
self.visible_entries.is_empty()
}
@@ -678,7 +683,7 @@ impl GitPanel {
status,
depth: 0,
display_name: filename,
- toggle_state: entry.toggle_state,
+ is_staged: entry.is_staged,
};
callback(ix, details, cx);
@@ -705,10 +710,19 @@ impl GitPanel {
let path_set = HashSet::from_iter(repo.status().map(|entry| entry.repo_path));
// Second pass - create entries with proper depth calculation
- for entry in repo.status() {
+ let mut all_staged = None;
+ for (ix, entry) in repo.status().enumerate() {
let (depth, difference) =
Self::calculate_depth_and_difference(&entry.repo_path, &path_set);
- let toggle_state = status_to_toggle_state(&entry.status);
+ let is_staged = entry.status.is_staged();
+ all_staged = if ix == 0 {
+ is_staged
+ } else {
+ match (all_staged, is_staged) {
+ (None, _) | (_, None) => None,
+ (Some(a), Some(b)) => (a == b).then_some(a),
+ }
+ };
let display_name = if difference > 1 {
// Show partial path for deeply nested files
@@ -734,11 +748,12 @@ impl GitPanel {
display_name,
repo_path: entry.repo_path,
status: entry.status,
- toggle_state,
+ is_staged,
};
self.visible_entries.push(entry);
}
+ self.all_staged = all_staged;
// Sort entries by path to maintain consistent order
self.visible_entries
@@ -805,7 +820,11 @@ impl GitPanel {
.child(
h_flex()
.gap_2()
- .child(Checkbox::new("all-changes", true.into()).disabled(true))
+ .child(Checkbox::new(
+ "all-changes",
+ self.all_staged
+ .map_or(ToggleState::Indeterminate, ToggleState::from),
+ ))
.child(div().text_buffer(cx).text_ui_sm(cx).child(changes_string)),
)
.child(div().flex_grow())
@@ -814,27 +833,50 @@ impl GitPanel {
.gap_2()
.child(
IconButton::new("discard-changes", IconName::Undo)
- .tooltip(move |cx| {
+ .tooltip({
let focus_handle = focus_handle.clone();
-
- Tooltip::for_action_in(
- "Discard all changes",
- &RevertAll,
- &focus_handle,
- cx,
- )
+ move |cx| {
+ Tooltip::for_action_in(
+ "Discard all changes",
+ &RevertAll,
+ &focus_handle,
+ cx,
+ )
+ }
})
.icon_size(IconSize::Small)
.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(RevertAll))),
- )
+ .child(if self.all_staged.unwrap_or(false) {
+ self.panel_button("unstage-all", "Unstage All")
+ .tooltip({
+ let focus_handle = focus_handle.clone();
+ move |cx| {
+ Tooltip::for_action_in(
+ "Unstage all changes",
+ &UnstageAll,
+ &focus_handle,
+ cx,
+ )
+ }
+ })
+ .on_click(
+ cx.listener(move |this, _, cx| this.unstage_all(&UnstageAll, cx)),
+ )
} else {
- self.panel_button("stage-all", "Stage All").on_click(
- cx.listener(move |_, _, cx| cx.dispatch_action(Box::new(StageAll))),
- )
+ self.panel_button("stage-all", "Stage All")
+ .tooltip({
+ let focus_handle = focus_handle.clone();
+ move |cx| {
+ Tooltip::for_action_in(
+ "Stage all changes",
+ &StageAll,
+ &focus_handle,
+ cx,
+ )
+ }
+ })
+ .on_click(cx.listener(move |this, _, cx| this.stage_all(&StageAll, cx)))
}),
)
}
@@ -1049,30 +1091,39 @@ impl GitPanel {
entry = entry
.child(
- Checkbox::new(checkbox_id, entry_details.toggle_state)
- .fill()
- .elevation(ElevationIndex::Surface)
- .on_click({
- let handle = handle.clone();
- let repo_path = repo_path.clone();
- move |toggle, cx| {
- let Some(this) = handle.upgrade() else {
- return;
- };
- this.update(cx, |this, _| {
- this.visible_entries[ix].toggle_state = *toggle;
- });
- state.update(cx, {
- let repo_path = repo_path.clone();
- move |state, _| match toggle {
- ToggleState::Selected | ToggleState::Indeterminate => {
- state.stage_entry(repo_path);
- }
- ToggleState::Unselected => state.unstage_entry(repo_path),
+ Checkbox::new(
+ checkbox_id,
+ entry_details
+ .is_staged
+ .map_or(ToggleState::Indeterminate, ToggleState::from),
+ )
+ .fill()
+ .elevation(ElevationIndex::Surface)
+ .on_click({
+ let handle = handle.clone();
+ let repo_path = repo_path.clone();
+ move |toggle, cx| {
+ let Some(this) = handle.upgrade() else {
+ return;
+ };
+ this.update(cx, |this, _| {
+ this.visible_entries[ix].is_staged = match *toggle {
+ ToggleState::Selected => Some(true),
+ ToggleState::Unselected => Some(false),
+ ToggleState::Indeterminate => None,
+ }
+ });
+ state.update(cx, {
+ let repo_path = repo_path.clone();
+ move |state, _| match toggle {
+ ToggleState::Selected | ToggleState::Indeterminate => {
+ state.stage_entry(repo_path);
}
- });
- }
- }),
+ ToggleState::Unselected => state.unstage_entry(repo_path),
+ }
+ });
+ }
+ }),
)
.child(git_status_icon(status))
.child(