Git · StashList: words in i18n, rows pick, only handled actions drawn

This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 03:57:22 -04:00
1 parent 8a0344d2c4
commit c5799cc7e4
3 files changed
+91 -22

No files matched your search

+33 -1
View File
@@ -8,7 +8,8 @@ use gpui::{
};
use super::{
Blame, BlameView, Branch, BranchList, Commit, CommitList, FileHistory, GitTag, TagList,
Blame, BlameView, Branch, BranchList, Commit, CommitList, FileHistory, GitTag, Stash,
StashList, TagList,
};
use crate::{forms, primitives::FocusNext, theme::Theme};
@@ -89,6 +90,24 @@ impl Render for Lists {
tagged.borrow_mut().picked.push(name.to_string())
}))
}
"stashes" => {
let stash = |message: &str| Stash {
message: message.to_string().into(),
branch: "main".into(),
when: "today".into(),
};
let acted = self.heard.clone();
frame
.child(StashList::new("bare", [stash("unhandled")]))
.child(
StashList::new("stashes", [stash("one"), stash("two")])
.selected(1)
.on_pick(move |ix, _, _| pick.borrow_mut().picked.push(format!("{ix}")))
.on_action(move |ix, what, _, _| {
acted.borrow_mut().picked.push(format!("{what:?} {ix}"))
}),
)
}
_ => {
let blame = |commit: &str| Blame {
commit: commit.into(),
@@ -219,3 +238,16 @@ fn branches_and_tags_pick_from_the_keyboard(cx: &mut TestAppContext) {
"row actions answer: {picked:?}"
);
}
#[gpui::test]
fn stashes_pick_and_act_from_the_keyboard(cx: &mut TestAppContext) {
let (heard, cx) = shown(cx, 0, "stashes");
for _ in 0..4 {
press_first(cx);
}
assert_eq!(
heard.borrow().picked,
["0", "Apply 0", "Pop 0", "Drop 0"],
"a list with no owner draws no stops"
);
}
+50 -21
View File
@@ -7,11 +7,13 @@ use gpui::{
use super::refs::{OnName, Pick, Run, action, listed};
use crate::{
i18n,
primitives::IconName,
theme::{ActiveTheme, TextSize},
};
type OnStash = Rc<dyn Fn(usize, StashAction, &mut Window, &mut App)>;
type OnAt = Rc<dyn Fn(usize, &mut Window, &mut App)>;
/// A tag, its message if annotated, its commit, when.
#[derive(Clone, Debug, PartialEq)]
@@ -120,6 +122,8 @@ pub struct StashList {
id: ElementId,
stashes: Vec<Stash>,
on_action: Option<OnStash>,
selected: Option<usize>,
on_pick: Option<OnAt>,
}
impl StashList {
@@ -128,9 +132,23 @@ impl StashList {
id: id.into(),
stashes: stashes.into_iter().collect(),
on_action: None,
selected: None,
on_pick: None,
}
}
/// The stash shown as picked.
pub fn selected(mut self, ix: usize) -> Self {
self.selected = Some(ix);
self
}
/// A press on a row picks its stash.
pub fn on_pick(mut self, handler: impl Fn(usize, &mut Window, &mut App) + 'static) -> Self {
self.on_pick = Some(Rc::new(handler));
self
}
pub fn on_action(
mut self,
handler: impl Fn(usize, StashAction, &mut Window, &mut App) + 'static,
@@ -149,35 +167,46 @@ impl RenderOnce for StashList {
.iter()
.enumerate()
.map(|(ix, stash)| {
let act = |key: &str, icon, words: &'static str, what: StashAction| {
let run = self.on_action.clone().map(|on_action| {
Rc::new(move |window: &mut Window, cx: &mut App| {
on_action(ix, what, window, cx)
}) as Run
});
action(
(self.id.clone(), format!("{key}-{ix}")).into(),
icon,
words,
run,
)
};
let name = SharedString::from(format!("stash@{{{ix}}}"));
// Only actions the owner handles are drawn.
let actions: Vec<AnyElement> = [
("apply", IconName::Download, StashAction::Apply),
("pop", IconName::ArrowUp, StashAction::Pop),
("drop", IconName::Trash2, StashAction::Drop),
]
.into_iter()
.filter_map(|(key, icon, what)| {
let on_action = self.on_action.clone()?;
let run = Rc::new(move |window: &mut Window, cx: &mut App| {
on_action(ix, what, window, cx)
}) as Run;
let words = i18n::text(cx, &format!("git.stash.{key}"), &[]);
let id = (self.id.clone(), format!("{key}-{ix}")).into();
Some(action(id, icon, words, Some(run)))
})
.collect();
let detail = i18n::text(
cx,
"git.stash.on",
&[("message", &stash.message), ("branch", &stash.branch)],
);
let pick = self.on_pick.clone().map(|pick| Pick {
spoken: format!("{name} {detail}").into(),
selected: self.selected == Some(ix),
run: Rc::new(move |window: &mut Window, cx: &mut App| pick(ix, window, cx)),
});
listed(
(self.id.clone(), format!("stash-{ix}")).into(),
IconName::Archive,
div()
.font_family(theme.mono_family.clone())
.text_color(colors.fg)
.child(format!("stash@{{{ix}}}"))
.child(name)
.into_any_element(),
format!("{} · on {}", stash.message, stash.branch).into(),
detail,
vec![stash.when.clone()],
vec![
act("apply", IconName::Download, "Apply", StashAction::Apply),
act("pop", IconName::ArrowUp, "Pop", StashAction::Pop),
act("drop", IconName::Trash2, "Drop", StashAction::Drop),
],
None,
actions,
pick,
cx,
)
})
+8
View File
@@ -96,6 +96,10 @@ const EN: &[(&str, &str)] = &[
("git.merge.accept_current", "Accept current"),
("git.merge.accept_incoming", "Accept incoming"),
("git.merge.accept_both", "Accept both"),
("git.stash.apply", "Apply"),
("git.stash.pop", "Pop"),
("git.stash.drop", "Drop"),
("git.stash.on", "{message} · on {branch}"),
];
const ZH_CN: &[(&str, &str)] = &[
@@ -190,6 +194,10 @@ const ZH_CN: &[(&str, &str)] = &[
("git.merge.accept_current", "接受当前"),
("git.merge.accept_incoming", "接受传入"),
("git.merge.accept_both", "接受两者"),
("git.stash.apply", "应用"),
("git.stash.pop", "弹出"),
("git.stash.drop", "删除"),
("git.stash.on", "{message} · 在 {branch}"),
];
/// Ely's message for `key`; English if unshipped, fails if unknown.