Preserve grapheme clusters when shortening desktop labels
This commit is contained in:
@@ -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