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