i18n, a11y, IME · palette copy reads i18n with builtin en/zh; dialog and palette Enter run on the field's key-down action, so an IME-kept Enter never submits; Button, IconButton, TextInput, Tabs, EditorTabs, StatusBarItem and palette rows name their role and label
This commit is contained in:
1 parent
8fd7b8ab87
commit
4580e3b075
15 files changed
+257
-75
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
App, ClickEvent, ElementId, FocusHandle, FontWeight, Hsla, IntoElement, MouseButton,
|
App, ClickEvent, ElementId, FocusHandle, FontWeight, Hsla, IntoElement, MouseButton,
|
||||||
RenderOnce, SharedString, Window, div, prelude::*, transparent_black,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement, Window, div, prelude::*,
|
||||||
|
transparent_black,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -232,6 +233,7 @@ impl RenderOnce for Button {
|
|||||||
let platform = cx.theme().platform;
|
let platform = cx.theme().platform;
|
||||||
let hint = self.shortcut.map(|source| shortcut_text(&source, platform));
|
let hint = self.shortcut.map(|source| shortcut_text(&source, platform));
|
||||||
let spinner = (self.id.clone(), "spinner");
|
let spinner = (self.id.clone(), "spinner");
|
||||||
|
let name = self.label.clone();
|
||||||
let parts = div()
|
let parts = div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
@@ -251,6 +253,8 @@ impl RenderOnce for Button {
|
|||||||
|
|
||||||
div()
|
div()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
|
.role(Role::Button)
|
||||||
|
.when(!name.is_empty(), |el| el.aria_label(name))
|
||||||
.flex()
|
.flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
App, ClickEvent, ElementId, IntoElement, MouseButton, RenderOnce, SharedString, Window, div,
|
App, ClickEvent, ElementId, IntoElement, MouseButton, RenderOnce, Role, SharedString, Window,
|
||||||
prelude::*,
|
div, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::button::{ButtonVariant, ClickHandler, label_size, tone};
|
use super::button::{ButtonVariant, ClickHandler, label_size, tone};
|
||||||
@@ -70,9 +70,15 @@ impl RenderOnce for IconButton {
|
|||||||
let tone = tone(self.variant, &theme.colors);
|
let tone = tone(self.variant, &theme.colors);
|
||||||
let (_, icon_size) = label_size(self.size);
|
let (_, icon_size) = label_size(self.size);
|
||||||
let side = theme.control_height(self.size);
|
let side = theme.control_height(self.size);
|
||||||
|
let name: SharedString = self
|
||||||
|
.tooltip
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| self.icon.name().into());
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
|
.role(Role::Button)
|
||||||
|
.aria_label(name)
|
||||||
.flex()
|
.flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.items_center()
|
.items_center()
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use gpui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
buttons::SegmentedControl,
|
buttons::SegmentedControl,
|
||||||
|
i18n,
|
||||||
lists::{Tree, TreeNode},
|
lists::{Tree, TreeNode},
|
||||||
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
||||||
primitives::IconName,
|
primitives::IconName,
|
||||||
@@ -258,7 +259,12 @@ impl GoToSymbol {
|
|||||||
|
|
||||||
impl RenderOnce for GoToSymbol {
|
impl RenderOnce for GoToSymbol {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let input = query_field(&self.id, "Go to a symbol", window, cx);
|
let input = query_field(
|
||||||
|
&self.id,
|
||||||
|
i18n::text(cx, "palette.symbol.placeholder", &[]),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let query = input.read(cx).text().trim().to_string();
|
let query = input.read(cx).text().trim().to_string();
|
||||||
let colors = cx.theme().colors.clone();
|
let colors = cx.theme().colors.clone();
|
||||||
let mut found: Vec<(i32, Row)> = flattened(&self.symbols)
|
let mut found: Vec<(i32, Row)> = flattened(&self.symbols)
|
||||||
@@ -278,6 +284,7 @@ impl RenderOnce for GoToSymbol {
|
|||||||
});
|
});
|
||||||
let row = Row {
|
let row = Row {
|
||||||
value: symbol.line.to_string().into(),
|
value: symbol.line.to_string().into(),
|
||||||
|
name: symbol.name.clone(),
|
||||||
icon: Some(symbol.kind.icon()),
|
icon: Some(symbol.kind.icon()),
|
||||||
label: marked(symbol.name.clone(), hits, cx),
|
label: marked(symbol.name.clone(), hits, cx),
|
||||||
detail,
|
detail,
|
||||||
@@ -310,7 +317,7 @@ impl RenderOnce for GoToSymbol {
|
|||||||
rows: found.into_iter().map(|(_, row)| row).collect(),
|
rows: found.into_iter().map(|(_, row)| row).collect(),
|
||||||
}],
|
}],
|
||||||
start: 0,
|
start: 0,
|
||||||
empty: "No matching symbols".into(),
|
empty: i18n::text(cx, "palette.symbol.empty", &[]),
|
||||||
on_pick,
|
on_pick,
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-14
@@ -9,6 +9,7 @@ use gpui::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
buttons::{ButtonVariant, IconButton},
|
buttons::{ButtonVariant, IconButton},
|
||||||
forms::Checkbox,
|
forms::Checkbox,
|
||||||
|
i18n,
|
||||||
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
||||||
primitives::{Icon, IconName},
|
primitives::{Icon, IconName},
|
||||||
theme::{ActiveTheme, ControlSize, IconSize, Radius, TextSize},
|
theme::{ActiveTheme, ControlSize, IconSize, Radius, TextSize},
|
||||||
@@ -83,7 +84,7 @@ impl RenderOnce for RecentProjects {
|
|||||||
);
|
);
|
||||||
let button = |key: &'static str,
|
let button = |key: &'static str,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
words: &'static str,
|
words: SharedString,
|
||||||
action: Option<OnIndex>| {
|
action: Option<OnIndex>| {
|
||||||
IconButton::new((self.id.clone(), format!("{key}-{ix}")), icon)
|
IconButton::new((self.id.clone(), format!("{key}-{ix}")), icon)
|
||||||
.variant(ButtonVariant::Ghost)
|
.variant(ButtonVariant::Ghost)
|
||||||
@@ -96,6 +97,15 @@ impl RenderOnce for RecentProjects {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
let (pin_icon, pin_tip) = if project.pinned {
|
||||||
|
(
|
||||||
|
IconName::PinOff,
|
||||||
|
i18n::text(cx, "palette.project.unpin", &[]),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(IconName::Pin, i18n::text(cx, "palette.project.pin", &[]))
|
||||||
|
};
|
||||||
|
let remove_tip = i18n::text(cx, "palette.project.remove", &[]);
|
||||||
div()
|
div()
|
||||||
.id((self.id.clone(), format!("project-{ix}")))
|
.id((self.id.clone(), format!("project-{ix}")))
|
||||||
.flex()
|
.flex()
|
||||||
@@ -157,17 +167,8 @@ impl RenderOnce for RecentProjects {
|
|||||||
.text_color(colors.fg_subtle)
|
.text_color(colors.fg_subtle)
|
||||||
.child(project.opened.clone()),
|
.child(project.opened.clone()),
|
||||||
)
|
)
|
||||||
.child(button(
|
.child(button("pin", pin_icon, pin_tip, pin))
|
||||||
"pin",
|
.child(button("remove", IconName::X, remove_tip, remove))
|
||||||
if project.pinned {
|
|
||||||
IconName::PinOff
|
|
||||||
} else {
|
|
||||||
IconName::Pin
|
|
||||||
},
|
|
||||||
if project.pinned { "Unpin" } else { "Pin" },
|
|
||||||
pin,
|
|
||||||
))
|
|
||||||
.child(button("remove", IconName::X, "Remove from recent", remove))
|
|
||||||
});
|
});
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
@@ -212,7 +213,12 @@ impl ProjectSwitcher {
|
|||||||
|
|
||||||
impl RenderOnce for ProjectSwitcher {
|
impl RenderOnce for ProjectSwitcher {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let input = query_field(&self.id, "Switch to a project", window, cx);
|
let input = query_field(
|
||||||
|
&self.id,
|
||||||
|
i18n::text(cx, "palette.project.placeholder", &[]),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let query = input.read(cx).text().trim().to_string();
|
let query = input.read(cx).text().trim().to_string();
|
||||||
let colors = cx.theme().colors.clone();
|
let colors = cx.theme().colors.clone();
|
||||||
let mut found: Vec<(i32, Row)> = ordered(&self.projects)
|
let mut found: Vec<(i32, Row)> = ordered(&self.projects)
|
||||||
@@ -227,6 +233,7 @@ impl RenderOnce for ProjectSwitcher {
|
|||||||
score,
|
score,
|
||||||
Row {
|
Row {
|
||||||
value: ix.to_string().into(),
|
value: ix.to_string().into(),
|
||||||
|
name: project.name.clone(),
|
||||||
icon: Some(if project.pinned {
|
icon: Some(if project.pinned {
|
||||||
IconName::Pin
|
IconName::Pin
|
||||||
} else {
|
} else {
|
||||||
@@ -271,7 +278,7 @@ impl RenderOnce for ProjectSwitcher {
|
|||||||
rows: found.into_iter().map(|(_, row)| row).collect(),
|
rows: found.into_iter().map(|(_, row)| row).collect(),
|
||||||
}],
|
}],
|
||||||
start: 0,
|
start: 0,
|
||||||
empty: "No matching projects".into(),
|
empty: i18n::text(cx, "palette.project.empty", &[]),
|
||||||
on_pick,
|
on_pick,
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
App, ClipboardItem, Context, CursorStyle, InteractiveElement, IntoElement, KeyBinding,
|
App, ClipboardItem, Context, CursorStyle, InteractiveElement, IntoElement, KeyBinding,
|
||||||
MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Styled, Window, actions,
|
MouseButton, MouseDownEvent, MouseMoveEvent, ParentElement, Render, Role,
|
||||||
div,
|
StatefulInteractiveElement, Styled, Window, actions, div, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -359,6 +359,11 @@ impl Render for TextInput {
|
|||||||
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
div()
|
div()
|
||||||
.id("ely-text-input")
|
.id("ely-text-input")
|
||||||
|
.role(Role::TextInput)
|
||||||
|
.when(!self.placeholder.is_empty(), |el| {
|
||||||
|
el.aria_label(self.placeholder.clone())
|
||||||
|
})
|
||||||
|
.when(!self.masked, |el| el.aria_value(self.text.clone()))
|
||||||
.key_context(CONTEXT)
|
.key_context(CONTEXT)
|
||||||
.track_focus(&self.focus)
|
.track_focus(&self.focus)
|
||||||
.cursor(if self.disabled {
|
.cursor(if self.disabled {
|
||||||
|
|||||||
+16
-11
@@ -7,6 +7,7 @@ use gpui::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
buttons::{ButtonVariant, IconButton},
|
buttons::{ButtonVariant, IconButton},
|
||||||
|
i18n,
|
||||||
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
||||||
primitives::{Icon, IconName},
|
primitives::{Icon, IconName},
|
||||||
theme::{ActiveTheme, ControlSize, IconSize, Radius, TextSize},
|
theme::{ActiveTheme, ControlSize, IconSize, Radius, TextSize},
|
||||||
@@ -87,7 +88,7 @@ impl RenderOnce for BranchSelector {
|
|||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let input = query_field(
|
let input = query_field(
|
||||||
&self.id,
|
&self.id,
|
||||||
"Switch to a branch, or name a new one",
|
i18n::text(cx, "palette.branch.placeholder", &[]),
|
||||||
window,
|
window,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
@@ -104,6 +105,7 @@ impl RenderOnce for BranchSelector {
|
|||||||
score,
|
score,
|
||||||
Row {
|
Row {
|
||||||
value: branch.name.clone(),
|
value: branch.name.clone(),
|
||||||
|
name: branch.name.clone(),
|
||||||
icon: Some(if branch.current {
|
icon: Some(if branch.current {
|
||||||
IconName::Check
|
IconName::Check
|
||||||
} else {
|
} else {
|
||||||
@@ -125,7 +127,7 @@ impl RenderOnce for BranchSelector {
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
let group = |title: &'static str, remote: bool, cx: &App| {
|
let group = |title: SharedString, remote: bool, cx: &App| {
|
||||||
let mut rows: Vec<(i32, Row)> = self
|
let mut rows: Vec<(i32, Row)> = self
|
||||||
.branches
|
.branches
|
||||||
.iter()
|
.iter()
|
||||||
@@ -136,26 +138,29 @@ impl RenderOnce for BranchSelector {
|
|||||||
rows.sort_by(|(a, _), (b, _)| b.cmp(a));
|
rows.sort_by(|(a, _), (b, _)| b.cmp(a));
|
||||||
}
|
}
|
||||||
Group {
|
Group {
|
||||||
title: Some(title.into()),
|
title: Some(title),
|
||||||
rows: rows.into_iter().map(|(_, row)| row).collect(),
|
rows: rows.into_iter().map(|(_, row)| row).collect(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut groups = vec![group("Local", false, cx), group("Remote", true, cx)];
|
let mut groups = vec![
|
||||||
|
group(i18n::text(cx, "palette.branch.local", &[]), false, cx),
|
||||||
|
group(i18n::text(cx, "palette.branch.remote", &[]), true, cx),
|
||||||
|
];
|
||||||
let named = self
|
let named = self
|
||||||
.branches
|
.branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|branch| branch.name.as_ref() == query);
|
.any(|branch| branch.name.as_ref() == query);
|
||||||
if !query.is_empty() && !named {
|
if !query.is_empty() && !named {
|
||||||
|
let create = i18n::text(cx, "palette.branch.create", &[("query", &query)]);
|
||||||
groups.insert(
|
groups.insert(
|
||||||
0,
|
0,
|
||||||
Group {
|
Group {
|
||||||
title: None,
|
title: None,
|
||||||
rows: vec![Row {
|
rows: vec![Row {
|
||||||
value: format!("{CREATE}{query}").into(),
|
value: format!("{CREATE}{query}").into(),
|
||||||
|
name: create.clone(),
|
||||||
icon: Some(IconName::Plus),
|
icon: Some(IconName::Plus),
|
||||||
label: div()
|
label: div().child(create).into_any_element(),
|
||||||
.child(format!("Create branch “{query}”"))
|
|
||||||
.into_any_element(),
|
|
||||||
detail: None,
|
detail: None,
|
||||||
end: None,
|
end: None,
|
||||||
}],
|
}],
|
||||||
@@ -184,7 +189,7 @@ impl RenderOnce for BranchSelector {
|
|||||||
input,
|
input,
|
||||||
groups,
|
groups,
|
||||||
start: 0,
|
start: 0,
|
||||||
empty: "No branch fits".into(),
|
empty: i18n::text(cx, "palette.branch.empty", &[]),
|
||||||
on_pick: Some(on_pick),
|
on_pick: Some(on_pick),
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
}
|
}
|
||||||
@@ -242,7 +247,7 @@ pub(super) fn listed(
|
|||||||
pub(super) fn action(
|
pub(super) fn action(
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
icon: IconName,
|
icon: IconName,
|
||||||
words: &'static str,
|
words: impl Into<SharedString>,
|
||||||
run: Option<Run>,
|
run: Option<Run>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
IconButton::new(id, icon)
|
IconButton::new(id, icon)
|
||||||
@@ -320,13 +325,13 @@ impl RenderOnce for BranchList {
|
|||||||
action(
|
action(
|
||||||
(self.id.clone(), format!("switch-{}", branch.name)).into(),
|
(self.id.clone(), format!("switch-{}", branch.name)).into(),
|
||||||
IconName::ArrowRight,
|
IconName::ArrowRight,
|
||||||
"Switch to it",
|
i18n::text(cx, "palette.branch.switch", &[]),
|
||||||
bind(&self.on_switch),
|
bind(&self.on_switch),
|
||||||
),
|
),
|
||||||
action(
|
action(
|
||||||
(self.id.clone(), format!("delete-{}", branch.name)).into(),
|
(self.id.clone(), format!("delete-{}", branch.name)).into(),
|
||||||
IconName::Trash2,
|
IconName::Trash2,
|
||||||
"Delete",
|
i18n::text(cx, "palette.branch.delete", &[]),
|
||||||
bind(&self.on_delete),
|
bind(&self.on_delete),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
//! Component copy in English and Chinese; `text` reads it when the app's catalogs lack the key.
|
||||||
|
|
||||||
|
/// English, the canonical set; zh-CN holds the same keys, a test keeps parity.
|
||||||
|
const EN: &[(&str, &str)] = &[
|
||||||
|
("palette.command.placeholder", "Type a command"),
|
||||||
|
("palette.command.empty", "No matching commands"),
|
||||||
|
("palette.recent", "Recent"),
|
||||||
|
("palette.open.placeholder", "Go to file"),
|
||||||
|
("palette.open.idle", "Type part of a file name"),
|
||||||
|
("palette.open.empty", "No matching files"),
|
||||||
|
("palette.switch.placeholder", "Switch to"),
|
||||||
|
("palette.switch.empty", "Nothing open by that name"),
|
||||||
|
("palette.search.placeholder", "Search everything"),
|
||||||
|
("palette.search.idle", "Type to search"),
|
||||||
|
("palette.search.empty", "Nothing found for “{query}”"),
|
||||||
|
("palette.symbol.placeholder", "Go to a symbol"),
|
||||||
|
("palette.symbol.empty", "No matching symbols"),
|
||||||
|
("palette.project.placeholder", "Switch to a project"),
|
||||||
|
("palette.project.empty", "No matching projects"),
|
||||||
|
("palette.project.pin", "Pin"),
|
||||||
|
("palette.project.unpin", "Unpin"),
|
||||||
|
("palette.project.remove", "Remove from recent"),
|
||||||
|
(
|
||||||
|
"palette.branch.placeholder",
|
||||||
|
"Switch to a branch, or name a new one",
|
||||||
|
),
|
||||||
|
("palette.branch.local", "Local"),
|
||||||
|
("palette.branch.remote", "Remote"),
|
||||||
|
("palette.branch.create", "Create branch “{query}”"),
|
||||||
|
("palette.branch.empty", "No branch fits"),
|
||||||
|
("palette.branch.switch", "Switch to it"),
|
||||||
|
("palette.branch.delete", "Delete"),
|
||||||
|
("palette.history.placeholder", "Search past commands"),
|
||||||
|
("palette.history.empty", "No past command fits"),
|
||||||
|
];
|
||||||
|
|
||||||
|
const ZH_CN: &[(&str, &str)] = &[
|
||||||
|
("palette.command.placeholder", "输入命令"),
|
||||||
|
("palette.command.empty", "没有匹配的命令"),
|
||||||
|
("palette.recent", "最近"),
|
||||||
|
("palette.open.placeholder", "转到文件"),
|
||||||
|
("palette.open.idle", "输入文件名的一部分"),
|
||||||
|
("palette.open.empty", "没有匹配的文件"),
|
||||||
|
("palette.switch.placeholder", "切换到"),
|
||||||
|
("palette.switch.empty", "没有以此名字打开的"),
|
||||||
|
("palette.search.placeholder", "搜索全部"),
|
||||||
|
("palette.search.idle", "输入以搜索"),
|
||||||
|
("palette.search.empty", "没有找到“{query}”"),
|
||||||
|
("palette.symbol.placeholder", "转到符号"),
|
||||||
|
("palette.symbol.empty", "没有匹配的符号"),
|
||||||
|
("palette.project.placeholder", "切换项目"),
|
||||||
|
("palette.project.empty", "没有匹配的项目"),
|
||||||
|
("palette.project.pin", "置顶"),
|
||||||
|
("palette.project.unpin", "取消置顶"),
|
||||||
|
("palette.project.remove", "从最近中移除"),
|
||||||
|
("palette.branch.placeholder", "切换分支,或输入新分支名"),
|
||||||
|
("palette.branch.local", "本地"),
|
||||||
|
("palette.branch.remote", "远程"),
|
||||||
|
("palette.branch.create", "创建分支“{query}”"),
|
||||||
|
("palette.branch.empty", "没有匹配的分支"),
|
||||||
|
("palette.branch.switch", "切换过去"),
|
||||||
|
("palette.branch.delete", "删除"),
|
||||||
|
("palette.history.placeholder", "搜索历史命令"),
|
||||||
|
("palette.history.empty", "没有匹配的历史命令"),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Ely's own message for `key` in `locale`, English for a locale Ely does not ship; fails on an unknown key.
|
||||||
|
pub(crate) fn builtin(locale: &str, key: &str) -> &'static str {
|
||||||
|
let table = if locale == "zh-CN" { ZH_CN } else { EN };
|
||||||
|
table
|
||||||
|
.iter()
|
||||||
|
.find_map(|(k, v)| (*k == key).then_some(*v))
|
||||||
|
.or_else(|| EN.iter().find_map(|(k, v)| (*k == key).then_some(*v)))
|
||||||
|
.unwrap_or_else(|| panic!("no builtin message {key:?}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{EN, ZH_CN};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zh_cn_holds_every_english_key() {
|
||||||
|
let zh: std::collections::HashSet<_> = ZH_CN.iter().map(|(key, _)| key).collect();
|
||||||
|
assert_eq!(EN.len(), ZH_CN.len());
|
||||||
|
assert!(EN.iter().all(|(key, _)| zh.contains(key)));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
mod builtin;
|
||||||
mod locale;
|
mod locale;
|
||||||
mod skip;
|
mod skip;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -73,6 +74,14 @@ impl I18n {
|
|||||||
cx.refresh_windows();
|
cx.refresh_windows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The locale's own message for `key`, nothing when its catalog lacks it.
|
||||||
|
pub(crate) fn find(&self, key: &str) -> Option<&'static str> {
|
||||||
|
self.catalogs
|
||||||
|
.iter()
|
||||||
|
.find(|(tag, _)| *tag == self.locale.tag)
|
||||||
|
.and_then(|(_, catalog)| catalog.get(key).copied())
|
||||||
|
}
|
||||||
|
|
||||||
/// The shown locale's message for `key`, each `{name}` filled from `args`; fails on a missing key or argument. gpui sets no paragraph direction, so a right-to-left message leads with a right-to-left mark: CoreText would lay out one that starts with Latin letters or a signed number left to right.
|
/// The shown locale's message for `key`, each `{name}` filled from `args`; fails on a missing key or argument. gpui sets no paragraph direction, so a right-to-left message leads with a right-to-left mark: CoreText would lay out one that starts with Latin letters or a signed number left to right.
|
||||||
pub fn text(&self, key: &str, args: &[(&str, &str)]) -> SharedString {
|
pub fn text(&self, key: &str, args: &[(&str, &str)]) -> SharedString {
|
||||||
let (_, catalog) = self
|
let (_, catalog) = self
|
||||||
@@ -92,6 +101,24 @@ impl I18n {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A component's message: the app's catalog first, Ely's builtin copy after. Without an `I18n` global it reads the English builtin.
|
||||||
|
pub fn text(cx: &App, key: &str, args: &[(&str, &str)]) -> SharedString {
|
||||||
|
let (message, direction) = if cx.has_global::<I18n>() {
|
||||||
|
let i18n = cx.global::<I18n>();
|
||||||
|
let message = i18n
|
||||||
|
.find(key)
|
||||||
|
.unwrap_or_else(|| builtin::builtin(i18n.locale().tag, key));
|
||||||
|
(message, i18n.locale().direction)
|
||||||
|
} else {
|
||||||
|
(builtin::builtin("en-US", key), Direction::Ltr)
|
||||||
|
};
|
||||||
|
let text = fill(message, args).unwrap_or_else(|error| panic!("{key:?}: {error}"));
|
||||||
|
match direction {
|
||||||
|
Direction::Ltr => text.into(),
|
||||||
|
Direction::Rtl => format!("\u{200f}{text}").into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `message` with each `{name}` replaced by its argument.
|
/// `message` with each `{name}` replaced by its argument.
|
||||||
fn fill(message: &str, args: &[(&str, &str)]) -> Result<String, String> {
|
fn fill(message: &str, args: &[(&str, &str)]) -> Result<String, String> {
|
||||||
let mut out = String::with_capacity(message.len());
|
let mut out = String::with_capacity(message.len());
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, ElementId, EntityId, InteractiveElement, IntoElement, MouseButton,
|
AnyElement, App, ElementId, EntityId, InteractiveElement, IntoElement, MouseButton,
|
||||||
ParentElement, Pixels, RenderOnce, ScrollHandle, SharedString, StatefulInteractiveElement,
|
ParentElement, Pixels, RenderOnce, Role, ScrollHandle, SharedString,
|
||||||
Styled, Window, canvas, div, prelude::*,
|
StatefulInteractiveElement, Styled, Window, canvas, div, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::TabOverflowMenu;
|
use super::TabOverflowMenu;
|
||||||
@@ -246,6 +246,9 @@ impl RenderOnce for EditorTabs {
|
|||||||
let (title, icon, edge) = (tab.title.clone(), tab.icon, colors.focus);
|
let (title, icon, edge) = (tab.title.clone(), tab.icon, colors.focus);
|
||||||
div()
|
div()
|
||||||
.id(tab.id.clone())
|
.id(tab.id.clone())
|
||||||
|
.role(Role::Tab)
|
||||||
|
.aria_selected(on)
|
||||||
|
.aria_label(tab.title.clone())
|
||||||
.group(group)
|
.group(group)
|
||||||
.relative()
|
.relative()
|
||||||
.flex()
|
.flex()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use gpui::{App, ElementId, IntoElement, RenderOnce, SharedString, Window};
|
|||||||
use super::{Fit, Group, Palette, Row, fuzzy, marked, query_field};
|
use super::{Fit, Group, Palette, Row, fuzzy, marked, query_field};
|
||||||
use crate::{
|
use crate::{
|
||||||
forms::{Choice, OnValue, Run},
|
forms::{Choice, OnValue, Run},
|
||||||
|
i18n,
|
||||||
primitives::IconName,
|
primitives::IconName,
|
||||||
typography::{Highlight, KbdCombo},
|
typography::{Highlight, KbdCombo},
|
||||||
};
|
};
|
||||||
@@ -60,6 +61,7 @@ impl Command {
|
|||||||
fn row(&self, hits: Vec<Range<usize>>, cx: &App) -> Row {
|
fn row(&self, hits: Vec<Range<usize>>, cx: &App) -> Row {
|
||||||
Row {
|
Row {
|
||||||
value: self.value.clone(),
|
value: self.value.clone(),
|
||||||
|
name: self.label.clone(),
|
||||||
icon: self.icon,
|
icon: self.icon,
|
||||||
label: marked(self.label.clone(), hits, cx),
|
label: marked(self.label.clone(), hits, cx),
|
||||||
detail: None,
|
detail: None,
|
||||||
@@ -123,7 +125,8 @@ impl CommandPalette {
|
|||||||
|
|
||||||
impl RenderOnce for CommandPalette {
|
impl RenderOnce for CommandPalette {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let input = query_field(&self.id, "Type a command", window, cx);
|
let placeholder = i18n::text(cx, "palette.command.placeholder", &[]);
|
||||||
|
let input = query_field(&self.id, placeholder, window, cx);
|
||||||
let query = input.read(cx).text().trim().to_string();
|
let query = input.read(cx).text().trim().to_string();
|
||||||
let groups = if query.is_empty() {
|
let groups = if query.is_empty() {
|
||||||
let find = |value: &SharedString| {
|
let find = |value: &SharedString| {
|
||||||
@@ -134,7 +137,7 @@ impl RenderOnce for CommandPalette {
|
|||||||
.unwrap_or_else(|| panic!("recent command {value} is not in the palette"))
|
.unwrap_or_else(|| panic!("recent command {value} is not in the palette"))
|
||||||
};
|
};
|
||||||
let recent = Group {
|
let recent = Group {
|
||||||
title: Some("Recent".into()),
|
title: Some(i18n::text(cx, "palette.recent", &[])),
|
||||||
rows: self
|
rows: self
|
||||||
.recent
|
.recent
|
||||||
.iter()
|
.iter()
|
||||||
@@ -182,7 +185,7 @@ impl RenderOnce for CommandPalette {
|
|||||||
input,
|
input,
|
||||||
groups,
|
groups,
|
||||||
start: 0,
|
start: 0,
|
||||||
empty: "No matching commands".into(),
|
empty: i18n::text(cx, "palette.command.empty", &[]),
|
||||||
on_pick: self.on_run,
|
on_pick: self.on_run,
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
}
|
}
|
||||||
@@ -246,6 +249,7 @@ fn file_row(path: &SharedString, hits: &[Range<usize>], cx: &App) -> Row {
|
|||||||
let folder = &path[..name_at.saturating_sub(1)];
|
let folder = &path[..name_at.saturating_sub(1)];
|
||||||
Row {
|
Row {
|
||||||
value: path.clone(),
|
value: path.clone(),
|
||||||
|
name: path[name_at..].to_string().into(),
|
||||||
icon: Some(IconName::File),
|
icon: Some(IconName::File),
|
||||||
label: marked(
|
label: marked(
|
||||||
path[name_at..].to_string().into(),
|
path[name_at..].to_string().into(),
|
||||||
@@ -260,7 +264,8 @@ fn file_row(path: &SharedString, hits: &[Range<usize>], cx: &App) -> Row {
|
|||||||
|
|
||||||
impl RenderOnce for QuickOpen {
|
impl RenderOnce for QuickOpen {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let input = query_field(&self.id, "Go to file", window, cx);
|
let placeholder = i18n::text(cx, "palette.open.placeholder", &[]);
|
||||||
|
let input = query_field(&self.id, placeholder, window, cx);
|
||||||
let query = input.read(cx).text().trim().to_string();
|
let query = input.read(cx).text().trim().to_string();
|
||||||
let (group, empty) = if query.is_empty() {
|
let (group, empty) = if query.is_empty() {
|
||||||
let rows = self
|
let rows = self
|
||||||
@@ -270,10 +275,10 @@ impl RenderOnce for QuickOpen {
|
|||||||
.collect();
|
.collect();
|
||||||
(
|
(
|
||||||
Group {
|
Group {
|
||||||
title: Some("Recent".into()),
|
title: Some(i18n::text(cx, "palette.recent", &[])),
|
||||||
rows,
|
rows,
|
||||||
},
|
},
|
||||||
"Type part of a file name",
|
i18n::text(cx, "palette.open.idle", &[]),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
let rows = ranked(self.paths.iter(), &query, |path| &path[..])
|
let rows = ranked(self.paths.iter(), &query, |path| &path[..])
|
||||||
@@ -281,14 +286,17 @@ impl RenderOnce for QuickOpen {
|
|||||||
.take(SHOWN)
|
.take(SHOWN)
|
||||||
.map(|(path, fit)| file_row(path, &fit.hits, cx))
|
.map(|(path, fit)| file_row(path, &fit.hits, cx))
|
||||||
.collect();
|
.collect();
|
||||||
(Group { title: None, rows }, "No matching files")
|
(
|
||||||
|
Group { title: None, rows },
|
||||||
|
i18n::text(cx, "palette.open.empty", &[]),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
Palette {
|
Palette {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
input,
|
input,
|
||||||
groups: vec![group],
|
groups: vec![group],
|
||||||
start: 0,
|
start: 0,
|
||||||
empty: empty.into(),
|
empty,
|
||||||
on_pick: self.on_open,
|
on_pick: self.on_open,
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
}
|
}
|
||||||
@@ -336,10 +344,12 @@ impl QuickSwitcher {
|
|||||||
|
|
||||||
impl RenderOnce for QuickSwitcher {
|
impl RenderOnce for QuickSwitcher {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let input = query_field(&self.id, "Switch to", window, cx);
|
let placeholder = i18n::text(cx, "palette.switch.placeholder", &[]);
|
||||||
|
let input = query_field(&self.id, placeholder, window, cx);
|
||||||
let query = input.read(cx).text().trim().to_string();
|
let query = input.read(cx).text().trim().to_string();
|
||||||
let row = |item: &Choice, hits: Vec<Range<usize>>| Row {
|
let row = |item: &Choice, hits: Vec<Range<usize>>| Row {
|
||||||
value: item.value.clone(),
|
value: item.value.clone(),
|
||||||
|
name: item.label.clone(),
|
||||||
icon: item.icon,
|
icon: item.icon,
|
||||||
label: marked(item.label.clone(), hits, cx),
|
label: marked(item.label.clone(), hits, cx),
|
||||||
detail: None,
|
detail: None,
|
||||||
@@ -367,7 +377,7 @@ impl RenderOnce for QuickSwitcher {
|
|||||||
input,
|
input,
|
||||||
groups: vec![Group { title: None, rows }],
|
groups: vec![Group { title: None, rows }],
|
||||||
start,
|
start,
|
||||||
empty: "Nothing open by that name".into(),
|
empty: i18n::text(cx, "palette.switch.empty", &[]),
|
||||||
on_pick: self.on_switch,
|
on_pick: self.on_switch,
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
}
|
}
|
||||||
@@ -424,7 +434,8 @@ impl SearchPalette {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn palette(self, window: &mut Window, cx: &mut App) -> Palette {
|
pub(crate) fn palette(self, window: &mut Window, cx: &mut App) -> Palette {
|
||||||
let input = query_field(&self.id, "Search everything", window, cx);
|
let placeholder = i18n::text(cx, "palette.search.placeholder", &[]);
|
||||||
|
let input = query_field(&self.id, placeholder, window, cx);
|
||||||
let query = input.read(cx).text().trim().to_string();
|
let query = input.read(cx).text().trim().to_string();
|
||||||
let groups = (self.search)(&query)
|
let groups = (self.search)(&query)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -441,7 +452,8 @@ impl SearchPalette {
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map(|result| Row {
|
.map(|result| Row {
|
||||||
value: result.value,
|
value: result.value.clone(),
|
||||||
|
name: result.label.clone(),
|
||||||
icon: result.icon,
|
icon: result.icon,
|
||||||
label: Highlight::matching(result.label, &query).into_any_element(),
|
label: Highlight::matching(result.label, &query).into_any_element(),
|
||||||
detail: result
|
detail: result
|
||||||
@@ -458,9 +470,9 @@ impl SearchPalette {
|
|||||||
groups,
|
groups,
|
||||||
start: 0,
|
start: 0,
|
||||||
empty: if query.is_empty() {
|
empty: if query.is_empty() {
|
||||||
"Type to search".into()
|
i18n::text(cx, "palette.search.idle", &[])
|
||||||
} else {
|
} else {
|
||||||
format!("Nothing found for “{query}”").into()
|
i18n::text(cx, "palette.search.empty", &[("query", &query)])
|
||||||
},
|
},
|
||||||
on_pick: self.on_open,
|
on_pick: self.on_open,
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use std::{ops::Range, rc::Rc};
|
|||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Animation, AnimationExt, AnyElement, App, ElementId, Entity, FontWeight, HighlightStyle,
|
Animation, AnimationExt, AnyElement, App, ElementId, Entity, FontWeight, HighlightStyle,
|
||||||
InteractiveElement, IntoElement, MouseButton, ParentElement, Point, ScrollHandle, SharedString,
|
InteractiveElement, IntoElement, MouseButton, ParentElement, Point, Role, ScrollHandle,
|
||||||
StatefulInteractiveElement, Styled, StyledText, Window, div, prelude::*,
|
SharedString, StatefulInteractiveElement, Styled, StyledText, Window, div, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -102,6 +102,8 @@ pub(crate) fn marked(text: SharedString, hits: Vec<Range<usize>>, cx: &App) -> A
|
|||||||
/// One result: the value a pick hands back, then what the row shows.
|
/// One result: the value a pick hands back, then what the row shows.
|
||||||
pub(crate) struct Row {
|
pub(crate) struct Row {
|
||||||
pub value: SharedString,
|
pub value: SharedString,
|
||||||
|
/// The row's plain name, for assistive technology.
|
||||||
|
pub name: SharedString,
|
||||||
pub icon: Option<IconName>,
|
pub icon: Option<IconName>,
|
||||||
pub label: AnyElement,
|
pub label: AnyElement,
|
||||||
pub detail: Option<AnyElement>,
|
pub detail: Option<AnyElement>,
|
||||||
@@ -125,7 +127,7 @@ struct Cursor {
|
|||||||
/// The query field kept for palette `id`.
|
/// The query field kept for palette `id`.
|
||||||
pub(crate) fn query_field(
|
pub(crate) fn query_field(
|
||||||
id: &ElementId,
|
id: &ElementId,
|
||||||
placeholder: &'static str,
|
placeholder: SharedString,
|
||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) -> Entity<TextInput> {
|
) -> Entity<TextInput> {
|
||||||
@@ -250,6 +252,9 @@ impl Palette {
|
|||||||
children.push(
|
children.push(
|
||||||
div()
|
div()
|
||||||
.id(("row", ix))
|
.id(("row", ix))
|
||||||
|
.role(Role::ListBoxOption)
|
||||||
|
.aria_selected(ix == at)
|
||||||
|
.aria_label(row.name.clone())
|
||||||
.flex()
|
.flex()
|
||||||
.flex_none()
|
.flex_none()
|
||||||
.items_center()
|
.items_center()
|
||||||
@@ -328,11 +333,9 @@ impl Palette {
|
|||||||
cx.stop_propagation();
|
cx.stop_propagation();
|
||||||
down(1, cx);
|
down(1, cx);
|
||||||
})
|
})
|
||||||
.capture_action(|_: &Enter, _, cx| cx.stop_propagation())
|
.capture_action(move |_: &Enter, window, cx| {
|
||||||
.on_key_up(move |event, window, cx| {
|
cx.stop_propagation();
|
||||||
let stroke = &event.keystroke;
|
if count > 0 {
|
||||||
if stroke.key == "enter" && !stroke.modifiers.modified() && count > 0 {
|
|
||||||
cx.stop_propagation();
|
|
||||||
enter(chosen.read(cx).at.min(count - 1), window, cx);
|
enter(chosen.read(cx).at.min(count - 1), window, cx);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -363,6 +366,7 @@ impl Palette {
|
|||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.id((self.id.clone(), "list"))
|
.id((self.id.clone(), "list"))
|
||||||
|
.role(Role::ListBox)
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Animation, AnimationExt, AnyElement, App, ElementId, FontWeight, InteractiveElement,
|
Animation, AnimationExt, AnyElement, App, ElementId, FontWeight, InteractiveElement,
|
||||||
IntoElement, MouseButton, ParentElement, RenderOnce, SharedString, StatefulInteractiveElement,
|
IntoElement, MouseButton, ParentElement, RenderOnce, Role, SharedString,
|
||||||
Styled, Window, div, prelude::*,
|
StatefulInteractiveElement, Styled, Window, div, prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -150,6 +150,9 @@ impl RenderOnce for Tabs {
|
|||||||
let pick = pick.clone();
|
let pick = pick.clone();
|
||||||
div()
|
div()
|
||||||
.id(("tab", ix))
|
.id(("tab", ix))
|
||||||
|
.role(Role::Tab)
|
||||||
|
.aria_selected(on)
|
||||||
|
.aria_label(tab.label.clone())
|
||||||
.relative()
|
.relative()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
@@ -195,6 +198,7 @@ impl RenderOnce for Tabs {
|
|||||||
let (keys, key_pick) = (tabs.clone(), pick.clone());
|
let (keys, key_pick) = (tabs.clone(), pick.clone());
|
||||||
let strip = div()
|
let strip = div()
|
||||||
.id((self.id.clone(), "strip"))
|
.id((self.id.clone(), "strip"))
|
||||||
|
.role(Role::TabList)
|
||||||
.track_focus(&focus)
|
.track_focus(&focus)
|
||||||
.relative()
|
.relative()
|
||||||
.flex()
|
.flex()
|
||||||
|
|||||||
+7
-13
@@ -69,7 +69,7 @@ impl Dialog {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs on an unmodified Enter release while the `focus_first` field holds focus; `build` gets the dialog's close.
|
/// Runs on an unmodified Enter while the `focus_first` field holds focus; `build` gets the dialog's close. The field's key binding fires on key-down, so an Enter an IME takes never reaches it.
|
||||||
pub(crate) fn on_enter(mut self, build: impl FnOnce(Close) -> Run + 'static) -> Self {
|
pub(crate) fn on_enter(mut self, build: impl FnOnce(Close) -> Run + 'static) -> Self {
|
||||||
self.enter = Some(Box::new(build));
|
self.enter = Some(Box::new(build));
|
||||||
self
|
self
|
||||||
@@ -254,18 +254,12 @@ impl RenderOnce for Dialog {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.when_some(enter, |layer, (field, enter)| {
|
.when_some(enter, |layer, (field, enter)| {
|
||||||
layer
|
layer.capture_action(move |_: &Enter, window, cx| {
|
||||||
.capture_action(|_: &Enter, _, cx| cx.stop_propagation())
|
cx.stop_propagation();
|
||||||
.on_key_up(move |event, window, cx| {
|
if field.is_focused(window) {
|
||||||
let stroke = &event.keystroke;
|
enter(window, cx);
|
||||||
if stroke.key == "enter"
|
}
|
||||||
&& !stroke.modifiers.modified()
|
})
|
||||||
&& field.is_focused(window)
|
|
||||||
{
|
|
||||||
cx.stop_propagation();
|
|
||||||
enter(window, cx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
FocusScope::new(&focus)
|
FocusScope::new(&focus)
|
||||||
|
|||||||
+12
-2
@@ -1,7 +1,7 @@
|
|||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, ClickEvent, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
|
AnyElement, App, ClickEvent, Div, ElementId, InteractiveElement, IntoElement, ParentElement,
|
||||||
RenderOnce, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window, div,
|
RenderOnce, Role, SharedString, StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
||||||
prelude::*,
|
div, prelude::*,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
@@ -220,6 +220,11 @@ impl RenderOnce for StatusBarItem {
|
|||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let theme = cx.theme();
|
let theme = cx.theme();
|
||||||
let hover = theme.colors.hover;
|
let hover = theme.colors.hover;
|
||||||
|
let name = self
|
||||||
|
.label
|
||||||
|
.clone()
|
||||||
|
.or_else(|| self.tooltip.clone())
|
||||||
|
.or_else(|| self.icon.map(|icon| icon.name().into()));
|
||||||
div()
|
div()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.flex()
|
.flex()
|
||||||
@@ -239,6 +244,11 @@ impl RenderOnce for StatusBarItem {
|
|||||||
.when_some(self.label, |item, label| item.child(label))
|
.when_some(self.label, |item, label| item.child(label))
|
||||||
.when_some(self.tooltip, |item, text| item.tooltip(Tooltip::text(text)))
|
.when_some(self.tooltip, |item, text| item.tooltip(Tooltip::text(text)))
|
||||||
.when_some(self.on_click, |item, handler| {
|
.when_some(self.on_click, |item, handler| {
|
||||||
|
let item = item.role(Role::Button);
|
||||||
|
let item = match name {
|
||||||
|
Some(name) => item.aria_label(name),
|
||||||
|
None => item,
|
||||||
|
};
|
||||||
item.cursor_pointer()
|
item.cursor_pointer()
|
||||||
.hover(|style| style.bg(hover))
|
.hover(|style| style.bg(hover))
|
||||||
.on_click(handler)
|
.on_click(handler)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use gpui::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
i18n,
|
||||||
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
navigation::{Group, Palette, Row, fuzzy, marked, query_field},
|
||||||
primitives::IconName,
|
primitives::IconName,
|
||||||
theme::ActiveTheme,
|
theme::ActiveTheme,
|
||||||
@@ -57,7 +58,12 @@ impl CommandHistory {
|
|||||||
|
|
||||||
impl RenderOnce for CommandHistory {
|
impl RenderOnce for CommandHistory {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let input = query_field(&self.id, "Search past commands", window, cx);
|
let input = query_field(
|
||||||
|
&self.id,
|
||||||
|
i18n::text(cx, "palette.history.placeholder", &[]),
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
let query = input.read(cx).text().trim().to_string();
|
let query = input.read(cx).text().trim().to_string();
|
||||||
let colors = cx.theme().colors.clone();
|
let colors = cx.theme().colors.clone();
|
||||||
let mut found: Vec<(i32, Row)> = self
|
let mut found: Vec<(i32, Row)> = self
|
||||||
@@ -79,6 +85,7 @@ impl RenderOnce for CommandHistory {
|
|||||||
score,
|
score,
|
||||||
Row {
|
Row {
|
||||||
value: past.command.clone(),
|
value: past.command.clone(),
|
||||||
|
name: past.command.clone(),
|
||||||
icon: Some(icon),
|
icon: Some(icon),
|
||||||
label: marked(past.command.clone(), hits, cx),
|
label: marked(past.command.clone(), hits, cx),
|
||||||
detail: Some(
|
detail: Some(
|
||||||
@@ -108,7 +115,7 @@ impl RenderOnce for CommandHistory {
|
|||||||
rows: found.into_iter().map(|(_, row)| row).collect(),
|
rows: found.into_iter().map(|(_, row)| row).collect(),
|
||||||
}],
|
}],
|
||||||
start: 0,
|
start: 0,
|
||||||
empty: "No past command fits".into(),
|
empty: i18n::text(cx, "palette.history.empty", &[]),
|
||||||
on_pick: self.on_pick,
|
on_pick: self.on_pick,
|
||||||
on_close: self.on_close,
|
on_close: self.on_close,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user