git: Fully implement "all staged" checkbox (#23079)
Also includes some improvements to the "stage/unstage all" actions and buttons. Release Notes: - N/A
This commit is contained in:
parent
2179be1855
commit
bd3c7d6cbf
1 changed files with 109 additions and 58 deletions
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue