Preserve grapheme clusters when shortening desktop labels
This commit is contained in:
Generated
+1
@@ -2176,6 +2176,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -18,6 +18,7 @@ serde_json.workspace = true
|
||||
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
|
||||
tokio = { version = "1", features = ["rt", "time"] }
|
||||
tempfile = "3"
|
||||
unicode-segmentation = "1.13.3"
|
||||
|
||||
[dev-dependencies]
|
||||
iced_runtime = "0.14"
|
||||
|
||||
@@ -12,6 +12,7 @@ use iced::{
|
||||
},
|
||||
};
|
||||
use oa_core::*;
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
pub fn label<'a>(s: impl Into<String>) -> widget::Text<'a> {
|
||||
text(s.into()).size(11).color(style::MUTED)
|
||||
@@ -24,10 +25,12 @@ pub fn action<'a>(s: &str, m: Message) -> widget::Button<'a, Message> {
|
||||
}
|
||||
pub fn short(s: &str, limit: usize) -> String {
|
||||
let s = s.split_whitespace().collect::<Vec<_>>().join(" ");
|
||||
if s.chars().count() > limit {
|
||||
if s.graphemes(true).count() > limit {
|
||||
format!(
|
||||
"{}…",
|
||||
s.chars().take(limit.saturating_sub(1)).collect::<String>()
|
||||
s.graphemes(true)
|
||||
.take(limit.saturating_sub(1))
|
||||
.collect::<String>()
|
||||
)
|
||||
} else {
|
||||
s
|
||||
@@ -1290,3 +1293,57 @@ fn checks(app: &App) -> View<'_, Message> {
|
||||
}
|
||||
scrollable(out.padding(24)).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::short;
|
||||
|
||||
#[test]
|
||||
fn short_preserves_native_lifeline_clusters_at_cutoff() {
|
||||
let oa = "abcdefghijklmnope\u{0301}尾";
|
||||
assert_eq!(short(oa, 17), "abcdefghijklmnop…");
|
||||
assert_eq!(short(oa, 18), oa);
|
||||
assert_eq!(short(oa, 19), oa);
|
||||
|
||||
let sa = "abcdefghijklmnop👩\u{200d}🔬 tail";
|
||||
assert_eq!(short(sa, 17), "abcdefghijklmnop…");
|
||||
assert_eq!(short(sa, 18), "abcdefghijklmnop👩\u{200d}🔬…");
|
||||
assert_eq!(short(sa, 19), "abcdefghijklmnop👩\u{200d}🔬 …");
|
||||
assert_eq!(short(sa, 22), sa);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_never_splits_retained_extended_graphemes() {
|
||||
for cluster in ["e\u{0301}", "👩\u{200d}🔬", "👍🏽", "🇫🇷"] {
|
||||
let value = format!("A{cluster}BC");
|
||||
assert_eq!(short(&value, 2), "A…");
|
||||
assert_eq!(short(&value, 3), format!("A{cluster}…"));
|
||||
assert_eq!(short(&value, 4), value);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_preserves_ascii_cjk_and_whitespace_policy() {
|
||||
assert_eq!(short("abcd", 3), "ab…");
|
||||
assert_eq!(short("abcd", 4), "abcd");
|
||||
assert_eq!(short("abcd", 5), "abcd");
|
||||
assert_eq!(short("甲乙丙丁", 3), "甲乙…");
|
||||
assert_eq!(short("甲乙丙丁", 4), "甲乙丙丁");
|
||||
assert_eq!(short(" \tA\r\n e\u{0301}\u{2003}B ", 5), "A e\u{0301} B");
|
||||
assert_eq!(short(" \tA\r\n e\u{0301}\u{2003}B ", 4), "A e\u{0301}…");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn short_preserves_empty_and_small_limit_policy() {
|
||||
for limit in [0, 1, 2] {
|
||||
assert_eq!(short("", limit), "");
|
||||
assert_eq!(short(" \t\r\n", limit), "");
|
||||
}
|
||||
assert_eq!(short("A", 0), "…");
|
||||
assert_eq!(short("A", 1), "A");
|
||||
assert_eq!(short("AB", 1), "…");
|
||||
assert_eq!(short("e\u{0301}", 0), "…");
|
||||
assert_eq!(short("e\u{0301}", 1), "e\u{0301}");
|
||||
assert_eq!(short("e\u{0301}B", 1), "…");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user