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::{ 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}; use crate::{forms, primitives::FocusNext, theme::Theme};
@@ -89,6 +90,24 @@ impl Render for Lists {
tagged.borrow_mut().picked.push(name.to_string()) 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 { let blame = |commit: &str| Blame {
commit: commit.into(), commit: commit.into(),
@@ -219,3 +238,16 @@ fn branches_and_tags_pick_from_the_keyboard(cx: &mut TestAppContext) {
"row actions answer: {picked:?}" "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 super::refs::{OnName, Pick, Run, action, listed};
use crate::{ use crate::{
i18n,
primitives::IconName, primitives::IconName,
theme::{ActiveTheme, TextSize}, theme::{ActiveTheme, TextSize},
}; };
type OnStash = Rc<dyn Fn(usize, StashAction, &mut Window, &mut App)>; 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. /// A tag, its message if annotated, its commit, when.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@@ -120,6 +122,8 @@ pub struct StashList {
id: ElementId, id: ElementId,
stashes: Vec<Stash>, stashes: Vec<Stash>,
on_action: Option<OnStash>, on_action: Option<OnStash>,
selected: Option<usize>,
on_pick: Option<OnAt>,
} }
impl StashList { impl StashList {
@@ -128,9 +132,23 @@ impl StashList {
id: id.into(), id: id.into(),
stashes: stashes.into_iter().collect(), stashes: stashes.into_iter().collect(),
on_action: None, 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( pub fn on_action(
mut self, mut self,
handler: impl Fn(usize, StashAction, &mut Window, &mut App) + 'static, handler: impl Fn(usize, StashAction, &mut Window, &mut App) + 'static,
@@ -149,35 +167,46 @@ impl RenderOnce for StashList {
.iter() .iter()
.enumerate() .enumerate()
.map(|(ix, stash)| { .map(|(ix, stash)| {
let act = |key: &str, icon, words: &'static str, what: StashAction| { let name = SharedString::from(format!("stash@{{{ix}}}"));
let run = self.on_action.clone().map(|on_action| { // Only actions the owner handles are drawn.
Rc::new(move |window: &mut Window, cx: &mut App| { let actions: Vec<AnyElement> = [
on_action(ix, what, window, cx) ("apply", IconName::Download, StashAction::Apply),
}) as Run ("pop", IconName::ArrowUp, StashAction::Pop),
}); ("drop", IconName::Trash2, StashAction::Drop),
action( ]
(self.id.clone(), format!("{key}-{ix}")).into(), .into_iter()
icon, .filter_map(|(key, icon, what)| {
words, let on_action = self.on_action.clone()?;
run, 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( listed(
(self.id.clone(), format!("stash-{ix}")).into(), (self.id.clone(), format!("stash-{ix}")).into(),
IconName::Archive, IconName::Archive,
div() div()
.font_family(theme.mono_family.clone()) .font_family(theme.mono_family.clone())
.text_color(colors.fg) .text_color(colors.fg)
.child(format!("stash@{{{ix}}}")) .child(name)
.into_any_element(), .into_any_element(),
format!("{} · on {}", stash.message, stash.branch).into(), detail,
vec![stash.when.clone()], vec![stash.when.clone()],
vec![ actions,
act("apply", IconName::Download, "Apply", StashAction::Apply), pick,
act("pop", IconName::ArrowUp, "Pop", StashAction::Pop),
act("drop", IconName::Trash2, "Drop", StashAction::Drop),
],
None,
cx, cx,
) )
}) })
+8
View File
@@ -96,6 +96,10 @@ const EN: &[(&str, &str)] = &[
("git.merge.accept_current", "Accept current"), ("git.merge.accept_current", "Accept current"),
("git.merge.accept_incoming", "Accept incoming"), ("git.merge.accept_incoming", "Accept incoming"),
("git.merge.accept_both", "Accept both"), ("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)] = &[ const ZH_CN: &[(&str, &str)] = &[
@@ -190,6 +194,10 @@ const ZH_CN: &[(&str, &str)] = &[
("git.merge.accept_current", "接受当前"), ("git.merge.accept_current", "接受当前"),
("git.merge.accept_incoming", "接受传入"), ("git.merge.accept_incoming", "接受传入"),
("git.merge.accept_both", "接受两者"), ("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. /// Ely's message for `key`; English if unshipped, fails if unknown.