i18n: the find box, sort and filter builders and password toggle in Chinese
The find counts, toggles, replace buttons, the builders' add, join and direction labels, the filter tests and the password tooltip read the builtin catalog. Test::label becomes Test::key, the choice's value; its label is the catalog's. The shipped-copy scan checks button, segment and match-arm text on every match, and zh-CN render tests cover both. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
79e94f7e24
commit
a4bf6965f9
11 files changed
+303
-79
No files matched your search
+49
-28
@@ -149,6 +149,26 @@ impl FindWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// The count shown, and whether it reads as a miss.
|
||||
pub(crate) fn count(
|
||||
matches: &Result<(Option<usize>, usize), SharedString>,
|
||||
cx: &App,
|
||||
) -> (SharedString, bool) {
|
||||
match matches {
|
||||
Ok((_, 0)) => (i18n::text(cx, "find.none", &[]), true),
|
||||
Ok((Some(current), total)) => {
|
||||
let (at, total) = ((current + 1).to_string(), total.to_string());
|
||||
let args = [("at", at.as_str()), ("total", total.as_str())];
|
||||
(i18n::text(cx, "find.at", &args), false)
|
||||
}
|
||||
Ok((None, total)) => {
|
||||
let total = total.to_string();
|
||||
(i18n::text(cx, "find.found", &[("total", &total)]), false)
|
||||
}
|
||||
Err(message) => (message.clone(), true),
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderOnce for FindWidget {
|
||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
@@ -156,7 +176,7 @@ impl RenderOnce for FindWidget {
|
||||
let options = self.options;
|
||||
let toggle = |key: &'static str,
|
||||
icon: IconName,
|
||||
words: &'static str,
|
||||
words: SharedString,
|
||||
on: bool,
|
||||
set: fn(&mut FindOptions, bool)| {
|
||||
let on_options = self.on_options.clone();
|
||||
@@ -175,16 +195,11 @@ impl RenderOnce for FindWidget {
|
||||
}
|
||||
})
|
||||
};
|
||||
let (count, missing) = match &self.matches {
|
||||
Ok((_, 0)) => ("No results".to_string(), true),
|
||||
Ok((Some(current), total)) => (format!("{} of {total}", current + 1), false),
|
||||
Ok((None, total)) => (format!("{total} found"), false),
|
||||
Err(message) => (message.to_string(), true),
|
||||
};
|
||||
let (count, missing) = count(&self.matches, cx);
|
||||
let found = matches!(self.matches, Ok((_, total)) if total > 0);
|
||||
let icon_button = |key: &'static str,
|
||||
icon: IconName,
|
||||
words: &'static str,
|
||||
words: SharedString,
|
||||
enabled: bool,
|
||||
action: Option<OnBool>,
|
||||
value: bool| {
|
||||
@@ -230,7 +245,7 @@ impl RenderOnce for FindWidget {
|
||||
} else {
|
||||
IconName::ChevronRight
|
||||
},
|
||||
"Toggle replace",
|
||||
i18n::text(cx, "find.fold", &[]),
|
||||
true,
|
||||
Some(fold),
|
||||
!showing,
|
||||
@@ -246,21 +261,21 @@ impl RenderOnce for FindWidget {
|
||||
row.child(toggle(
|
||||
"case",
|
||||
IconName::CaseSensitive,
|
||||
"Match case",
|
||||
i18n::text(cx, "find.case", &[]),
|
||||
options.case,
|
||||
|next, on| next.case = on,
|
||||
))
|
||||
.child(toggle(
|
||||
"word",
|
||||
IconName::WholeWord,
|
||||
"Whole word",
|
||||
i18n::text(cx, "find.word", &[]),
|
||||
options.word,
|
||||
|next, on| next.word = on,
|
||||
))
|
||||
.child(toggle(
|
||||
"regex",
|
||||
IconName::Regex,
|
||||
"Regular expression",
|
||||
i18n::text(cx, "find.regex", &[]),
|
||||
options.regex,
|
||||
|next, on| next.regex = on,
|
||||
))
|
||||
@@ -279,7 +294,7 @@ impl RenderOnce for FindWidget {
|
||||
.child(icon_button(
|
||||
"previous",
|
||||
IconName::ArrowUp,
|
||||
"Previous match",
|
||||
i18n::text(cx, "find.previous", &[]),
|
||||
found,
|
||||
self.on_step.clone(),
|
||||
false,
|
||||
@@ -287,7 +302,7 @@ impl RenderOnce for FindWidget {
|
||||
.child(icon_button(
|
||||
"next",
|
||||
IconName::ArrowDown,
|
||||
"Next match",
|
||||
i18n::text(cx, "find.next", &[]),
|
||||
found,
|
||||
self.on_step.clone(),
|
||||
true,
|
||||
@@ -313,22 +328,28 @@ impl RenderOnce for FindWidget {
|
||||
.child(Input::new(field).size(ControlSize::Sm)),
|
||||
)
|
||||
.child(
|
||||
Button::new((self.id.clone(), "replace"), "Replace")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.disabled(!found)
|
||||
.when_some(one, |button, one| {
|
||||
button.on_click(move |_, window, cx| one(false, window, cx))
|
||||
}),
|
||||
Button::new(
|
||||
(self.id.clone(), "replace"),
|
||||
i18n::text(cx, "find.replace", &[]),
|
||||
)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.disabled(!found)
|
||||
.when_some(one, |button, one| {
|
||||
button.on_click(move |_, window, cx| one(false, window, cx))
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Button::new((self.id.clone(), "all"), "Replace all")
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.disabled(!found)
|
||||
.when_some(all, |button, all| {
|
||||
button.on_click(move |_, window, cx| all(true, window, cx))
|
||||
}),
|
||||
Button::new(
|
||||
(self.id.clone(), "all"),
|
||||
i18n::text(cx, "find.replace_all", &[]),
|
||||
)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.disabled(!found)
|
||||
.when_some(all, |button, all| {
|
||||
button.on_click(move |_, window, cx| all(true, window, cx))
|
||||
}),
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//! The find box reads in Chinese under zh-CN.
|
||||
|
||||
use gpui::{Entity, IntoElement, Render, TestAppContext, Window, div, prelude::*};
|
||||
|
||||
use crate::{forms::TextInput, i18n::I18n, theme::Theme};
|
||||
|
||||
struct Host(Entity<TextInput>, Entity<TextInput>);
|
||||
|
||||
impl Render for Host {
|
||||
fn render(&mut self, _: &mut Window, _: &mut gpui::Context<Self>) -> impl IntoElement {
|
||||
let find = super::super::FindWidget::new("find", &self.0, Some(0), 2)
|
||||
.replace(&self.1)
|
||||
.on_options(|_, _, _| {})
|
||||
.on_step(|_, _, _| {})
|
||||
.on_toggle_replace(|_, _, _| {})
|
||||
.on_close(|_, _| {});
|
||||
div().size_full().child(find)
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_find_box_reads_in_chinese(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
cx.set_global(I18n::new("zh-CN").catalog("zh-CN", &[]));
|
||||
});
|
||||
cx.update(|cx| {
|
||||
let count = |matches| super::super::find::count(&matches, cx);
|
||||
assert_eq!(count(Ok((None, 0))), ("无结果".into(), true));
|
||||
assert_eq!(count(Ok((Some(0), 2))), ("第 1 项,共 2 项".into(), false));
|
||||
assert_eq!(count(Ok((None, 3))), ("找到 3 项".into(), false));
|
||||
});
|
||||
let (_, cx) = cx.add_window_view(|window, cx| {
|
||||
Host(
|
||||
cx.new(|cx| TextInput::new(window, cx)),
|
||||
cx.new(|cx| TextInput::new(window, cx)),
|
||||
)
|
||||
});
|
||||
cx.run_until_parked();
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
mod chinese;
|
||||
mod history;
|
||||
mod stale;
|
||||
|
||||
|
||||
+9
-5
@@ -199,11 +199,15 @@ impl RenderOnce for PasswordInput {
|
||||
},
|
||||
)
|
||||
.size(ControlSize::Sm)
|
||||
.tooltip(if masked {
|
||||
"Show password"
|
||||
} else {
|
||||
"Hide password"
|
||||
})
|
||||
.tooltip(i18n::text(
|
||||
cx,
|
||||
if masked {
|
||||
"input.show_password"
|
||||
} else {
|
||||
"input.hide_password"
|
||||
},
|
||||
&[],
|
||||
))
|
||||
.on_click(move |_, _, cx| {
|
||||
toggle.update(cx, |input, cx| {
|
||||
let masked = input.is_masked();
|
||||
|
||||
@@ -117,9 +117,34 @@ const EN: &[(&str, &str)] = &[
|
||||
("theme.system", "System"),
|
||||
("select.placeholder", "Choose…"),
|
||||
("input.clear", "Clear"),
|
||||
("input.show_password", "Show password"),
|
||||
("input.hide_password", "Hide password"),
|
||||
("completion.empty", "No suggestions"),
|
||||
("builder.remove", "Remove"),
|
||||
("tabs.unpin", "Unpin"),
|
||||
("find.none", "No results"),
|
||||
("find.at", "{at} of {total}"),
|
||||
("find.found", "{total} found"),
|
||||
("find.fold", "Toggle replace"),
|
||||
("find.case", "Match case"),
|
||||
("find.word", "Whole word"),
|
||||
("find.regex", "Regular expression"),
|
||||
("find.previous", "Previous match"),
|
||||
("find.next", "Next match"),
|
||||
("find.replace", "Replace"),
|
||||
("find.replace_all", "Replace all"),
|
||||
("builder.add_rule", "Add rule"),
|
||||
("builder.all", "Match all"),
|
||||
("builder.any", "Match any"),
|
||||
("builder.add_sort", "Add sort"),
|
||||
("builder.up", "Ascending"),
|
||||
("builder.down", "Descending"),
|
||||
("builder.test.contains", "contains"),
|
||||
("builder.test.equals", "is"),
|
||||
("builder.test.not_equals", "is not"),
|
||||
("builder.test.above", "is above"),
|
||||
("builder.test.below", "is below"),
|
||||
("builder.test.empty", "is empty"),
|
||||
("theme.size.reset", "Back to the default size"),
|
||||
(
|
||||
"theme.size.sample",
|
||||
@@ -240,9 +265,34 @@ const ZH_CN: &[(&str, &str)] = &[
|
||||
("theme.system", "跟随系统"),
|
||||
("select.placeholder", "请选择…"),
|
||||
("input.clear", "清除"),
|
||||
("input.show_password", "显示密码"),
|
||||
("input.hide_password", "隐藏密码"),
|
||||
("completion.empty", "没有建议"),
|
||||
("builder.remove", "移除"),
|
||||
("tabs.unpin", "取消固定"),
|
||||
("find.none", "无结果"),
|
||||
("find.at", "第 {at} 项,共 {total} 项"),
|
||||
("find.found", "找到 {total} 项"),
|
||||
("find.fold", "切换替换"),
|
||||
("find.case", "区分大小写"),
|
||||
("find.word", "全字匹配"),
|
||||
("find.regex", "使用正则表达式"),
|
||||
("find.previous", "上一个匹配项"),
|
||||
("find.next", "下一个匹配项"),
|
||||
("find.replace", "替换"),
|
||||
("find.replace_all", "全部替换"),
|
||||
("builder.add_rule", "添加规则"),
|
||||
("builder.all", "满足全部"),
|
||||
("builder.any", "满足任一"),
|
||||
("builder.add_sort", "添加排序"),
|
||||
("builder.up", "升序"),
|
||||
("builder.down", "降序"),
|
||||
("builder.test.contains", "包含"),
|
||||
("builder.test.equals", "等于"),
|
||||
("builder.test.not_equals", "不等于"),
|
||||
("builder.test.above", "大于"),
|
||||
("builder.test.below", "小于"),
|
||||
("builder.test.empty", "为空"),
|
||||
("theme.size.reset", "恢复默认大小"),
|
||||
(
|
||||
"theme.size.sample",
|
||||
|
||||
@@ -10,6 +10,7 @@ const SHIPPED: &[(&str, &str)] = &[
|
||||
"tables/builders.rs",
|
||||
include_str!("../../tables/builders.rs"),
|
||||
),
|
||||
("tables/rules.rs", include_str!("../../tables/rules.rs")),
|
||||
(
|
||||
"navigation/editor_tabs.rs",
|
||||
include_str!("../../navigation/editor_tabs.rs"),
|
||||
@@ -22,17 +23,46 @@ const SHIPPED: &[(&str, &str)] = &[
|
||||
|
||||
#[test]
|
||||
fn shipped_components_show_no_fixed_english() {
|
||||
let shown = [".tooltip(\"", ".child(\"", "SharedString::from(\""];
|
||||
let shown = [".tooltip(\"", ".child(\"", "SharedString::from(\"", "=> \""];
|
||||
// After an id: words, not a lowercase id.
|
||||
let labels = ["), \"", "\", \"", "(\""];
|
||||
let mut found = Vec::new();
|
||||
for (path, source) in SHIPPED {
|
||||
let code = source.split("#[cfg(test)]").next().unwrap_or(source);
|
||||
for (no, line) in code.lines().enumerate() {
|
||||
for call in shown {
|
||||
let Some(at) = line.find(call) else { continue };
|
||||
let rest = &line[at + call.len()..];
|
||||
if rest.starts_with(|ch: char| ch.is_ascii_alphabetic()) {
|
||||
found.push(format!("{path}:{}: {}", no + 1, line.trim()));
|
||||
}
|
||||
let checks = ["assert", "panic!", "log::", ".expect("];
|
||||
if checks.iter().any(|check| line.contains(check)) {
|
||||
continue;
|
||||
}
|
||||
let literals = |calls: &[&str]| -> Vec<&str> {
|
||||
(calls.iter())
|
||||
.flat_map(|call| {
|
||||
line.match_indices(call)
|
||||
.map(|(at, _)| &line[at + call.len()..])
|
||||
})
|
||||
.map(|rest| rest.split('"').next().unwrap_or(rest))
|
||||
.filter(|literal| literal.starts_with(|ch: char| ch.is_ascii_alphabetic()))
|
||||
.collect()
|
||||
};
|
||||
let key = |literal: &str| literal.contains('.') && !literal.contains(' ');
|
||||
let words =
|
||||
|literal: &str| literal.contains(' ') || literal.starts_with(char::is_uppercase);
|
||||
let english = (literals(&shown).into_iter().filter(|literal| !key(literal)))
|
||||
.chain(
|
||||
literals(&labels)
|
||||
.into_iter()
|
||||
.filter(|literal| words(literal)),
|
||||
)
|
||||
.chain(
|
||||
line.trim()
|
||||
.strip_prefix('"')
|
||||
.and_then(|rest| rest.split('"').next()),
|
||||
)
|
||||
.filter(|literal| {
|
||||
literal.starts_with(char::is_uppercase) || !line.trim().starts_with('"')
|
||||
});
|
||||
if english.count() > 0 {
|
||||
found.push(format!("{path}:{}: {}", no + 1, line.trim()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+45
-31
@@ -152,7 +152,7 @@ impl RenderOnce for FilterBuilder {
|
||||
let emit = Rc::new(emit);
|
||||
let tests: Vec<Choice> = Test::ALL
|
||||
.iter()
|
||||
.map(|test| Choice::new(test.label(), test.label()))
|
||||
.map(|test| Choice::new(test.key(), i18n::text(cx, test.key(), &[])))
|
||||
.collect();
|
||||
let theme = cx.theme();
|
||||
let (rules, any, id) = (self.rules.clone(), self.any, self.id.clone());
|
||||
@@ -187,13 +187,13 @@ impl RenderOnce for FilterBuilder {
|
||||
.child(
|
||||
div().w(theme.label_width() * 0.75).child(
|
||||
Select::new((id.clone(), format!("test-{ix}")), tests.clone())
|
||||
.selected(rule.test.label())
|
||||
.selected(rule.test.key())
|
||||
.size(ControlSize::Sm)
|
||||
.on_change(move |label, window, cx| {
|
||||
let mut next = for_test.clone();
|
||||
next[ix].test = *Test::ALL
|
||||
.iter()
|
||||
.find(|test| test.label() == label.as_ref())
|
||||
.find(|test| test.key() == label.as_ref())
|
||||
.expect("a listed test");
|
||||
test(next, any, window, cx)
|
||||
}),
|
||||
@@ -224,18 +224,21 @@ impl RenderOnce for FilterBuilder {
|
||||
.items_center()
|
||||
.justify_between()
|
||||
.child(
|
||||
Button::new((self.id.clone(), "add"), "Add rule")
|
||||
.icon(IconName::Plus)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.on_click(move |_, window, cx| {
|
||||
let mut next = to_add.clone();
|
||||
next.push(FilterRule {
|
||||
column: first.clone(),
|
||||
..FilterRule::default()
|
||||
});
|
||||
add(next, any, window, cx)
|
||||
}),
|
||||
Button::new(
|
||||
(self.id.clone(), "add"),
|
||||
i18n::text(cx, "builder.add_rule", &[]),
|
||||
)
|
||||
.icon(IconName::Plus)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.on_click(move |_, window, cx| {
|
||||
let mut next = to_add.clone();
|
||||
next.push(FilterRule {
|
||||
column: first.clone(),
|
||||
..FilterRule::default()
|
||||
});
|
||||
add(next, any, window, cx)
|
||||
}),
|
||||
)
|
||||
.when(self.rules.len() > 1, |footer| {
|
||||
footer.child(
|
||||
@@ -243,8 +246,8 @@ impl RenderOnce for FilterBuilder {
|
||||
(self.id.clone(), "join"),
|
||||
if any { "any" } else { "all" },
|
||||
)
|
||||
.segment("all", "Match all", None)
|
||||
.segment("any", "Match any", None)
|
||||
.segment("all", i18n::text(cx, "builder.all", &[]), None)
|
||||
.segment("any", i18n::text(cx, "builder.any", &[]), None)
|
||||
.size(ControlSize::Sm)
|
||||
.on_change(move |value, window, cx| {
|
||||
join(to_join.clone(), value.as_ref() == "any", window, cx)
|
||||
@@ -365,8 +368,16 @@ impl RenderOnce for SortBuilder {
|
||||
(id.clone(), format!("sort-way-{ix}")),
|
||||
if sort.rising { "up" } else { "down" },
|
||||
)
|
||||
.segment("up", "Ascending", Some(IconName::ArrowUp))
|
||||
.segment("down", "Descending", Some(IconName::ArrowDown))
|
||||
.segment(
|
||||
"up",
|
||||
i18n::text(cx, "builder.up", &[]),
|
||||
Some(IconName::ArrowUp),
|
||||
)
|
||||
.segment(
|
||||
"down",
|
||||
i18n::text(cx, "builder.down", &[]),
|
||||
Some(IconName::ArrowDown),
|
||||
)
|
||||
.size(ControlSize::Sm)
|
||||
.on_change(move |way, window, cx| {
|
||||
let mut next = for_flip.clone();
|
||||
@@ -395,18 +406,21 @@ impl RenderOnce for SortBuilder {
|
||||
.gap_1()
|
||||
.child(list)
|
||||
.children(unused.map(|column| {
|
||||
Button::new((self.id.clone(), "add"), "Add sort")
|
||||
.icon(IconName::Plus)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.on_click(move |_, window, cx| {
|
||||
let mut next = add.1.clone();
|
||||
next.push(SortKey {
|
||||
column: column.clone(),
|
||||
rising: true,
|
||||
});
|
||||
(add.0)(&next, window, cx)
|
||||
})
|
||||
Button::new(
|
||||
(self.id.clone(), "add"),
|
||||
i18n::text(cx, "builder.add_sort", &[]),
|
||||
)
|
||||
.icon(IconName::Plus)
|
||||
.variant(ButtonVariant::Ghost)
|
||||
.size(ControlSize::Sm)
|
||||
.on_click(move |_, window, cx| {
|
||||
let mut next = add.1.clone();
|
||||
next.push(SortKey {
|
||||
column: column.clone(),
|
||||
rising: true,
|
||||
});
|
||||
(add.0)(&next, window, cx)
|
||||
})
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
+8
-7
@@ -26,14 +26,15 @@ impl Test {
|
||||
Test::Empty,
|
||||
];
|
||||
|
||||
pub fn label(self) -> &'static str {
|
||||
/// Its catalog key, which also names it.
|
||||
pub fn key(self) -> &'static str {
|
||||
match self {
|
||||
Self::Contains => "contains",
|
||||
Self::Equals => "is",
|
||||
Self::NotEquals => "is not",
|
||||
Self::Above => "is above",
|
||||
Self::Below => "is below",
|
||||
Self::Empty => "is empty",
|
||||
Self::Contains => "builder.test.contains",
|
||||
Self::Equals => "builder.test.equals",
|
||||
Self::NotEquals => "builder.test.not_equals",
|
||||
Self::Above => "builder.test.above",
|
||||
Self::Below => "builder.test.below",
|
||||
Self::Empty => "builder.test.empty",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
//! The sort and filter builders read in Chinese under zh-CN.
|
||||
|
||||
use gpui::{IntoElement, Render, TestAppContext, Window, div, prelude::*};
|
||||
|
||||
use crate::{
|
||||
i18n::{self, I18n},
|
||||
tables::{FilterBuilder, FilterRule, SortBuilder, SortKey, Test},
|
||||
theme::Theme,
|
||||
};
|
||||
|
||||
struct Builders;
|
||||
|
||||
impl Render for Builders {
|
||||
fn render(&mut self, _: &mut Window, _: &mut gpui::Context<Self>) -> impl IntoElement {
|
||||
let columns = [("a", "第一列"), ("b", "第二列")];
|
||||
let rule = FilterRule {
|
||||
column: "a".into(),
|
||||
..FilterRule::default()
|
||||
};
|
||||
let key = SortKey {
|
||||
column: "a".into(),
|
||||
rising: true,
|
||||
};
|
||||
div()
|
||||
.size_full()
|
||||
.child(FilterBuilder::new("filter", columns).rules([rule.clone(), rule], true))
|
||||
.child(SortBuilder::new("sort", columns).keys([key]))
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn the_builders_read_in_chinese(cx: &mut TestAppContext) {
|
||||
cx.update(|cx| {
|
||||
Theme::init(cx);
|
||||
cx.set_global(I18n::new("zh-CN").catalog("zh-CN", &[]));
|
||||
});
|
||||
cx.update(|cx| {
|
||||
for test in Test::ALL {
|
||||
let shown = i18n::text(cx, test.key(), &[]);
|
||||
assert!(
|
||||
!shown.chars().any(|ch| ch.is_ascii_alphabetic()),
|
||||
"{test:?}: {shown}"
|
||||
);
|
||||
}
|
||||
for key in [
|
||||
"builder.add_rule",
|
||||
"builder.all",
|
||||
"builder.any",
|
||||
"builder.add_sort",
|
||||
"builder.up",
|
||||
"builder.down",
|
||||
] {
|
||||
let shown = i18n::text(cx, key, &[]);
|
||||
assert!(
|
||||
!shown.chars().any(|ch| ch.is_ascii_alphabetic()),
|
||||
"{key}: {shown}"
|
||||
);
|
||||
}
|
||||
});
|
||||
let (_, cx) = cx.add_window_view(|_, _| Builders);
|
||||
cx.run_until_parked();
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use gpui::{
|
||||
Styled, TestAppContext, VisualTestContext, Window, div, point, px,
|
||||
};
|
||||
|
||||
mod chinese;
|
||||
mod clipboard;
|
||||
mod columns;
|
||||
mod stale;
|
||||
|
||||
Reference in new issue
Block a user