diff options
Diffstat (limited to 'src/ui/text.rs')
| -rw-r--r-- | src/ui/text.rs | 89 |
1 files changed, 89 insertions, 0 deletions
diff --git a/src/ui/text.rs b/src/ui/text.rs new file mode 100644 index 00000000..45f7983b --- /dev/null +++ b/src/ui/text.rs @@ -0,0 +1,89 @@ +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + +pub(crate) fn display_width(text: &str) -> usize { + UnicodeWidthStr::width(text) +} + +pub(crate) fn display_width_u16(text: &str) -> u16 { + display_width(text).min(u16::MAX as usize) as u16 +} + +pub(crate) fn truncate_end(text: &str, max_width: usize) -> String { + if display_width(text) <= max_width { + return text.to_string(); + } + if max_width == 0 { + return String::new(); + } + if max_width == 1 { + return "…".to_string(); + } + + let prefix = take_prefix_width(text, max_width.saturating_sub(1)); + format!("{prefix}…") +} + +pub(crate) fn middle_elide(text: &str, max_width: usize) -> String { + if display_width(text) <= max_width { + return text.to_string(); + } + if max_width <= 1 { + return "…".to_string(); + } + + let content_width = max_width.saturating_sub(1); + let left_width = content_width / 2; + let right_width = content_width.saturating_sub(left_width); + let prefix = take_prefix_width(text, left_width); + let suffix = take_suffix_width(text, right_width); + format!("{prefix}…{suffix}") +} + +fn take_prefix_width(text: &str, max_width: usize) -> String { + let mut output = String::new(); + let mut width = 0usize; + for ch in text.chars() { + let ch_width = UnicodeWidthChar::width(ch).unwrap_or(0); + if width + ch_width > max_width { + break; + } + output.push(ch); + width += ch_width; + } + output +} + +fn take_suffix_width(text: &str, max_width: usize) -> String { + let mut output = Vec::new(); + let mut width = 0usize; + for ch in text.chars().rev() { + let ch_width = UnicodeWidthChar::width(ch).unwrap_or(0); + if width + ch_width > max_width { + break; + } + output.push(ch); + width += ch_width; + } + output.into_iter().rev().collect() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn truncate_end_uses_display_width() { + let text = truncate_end("提交 herdr 的反馈", 16); + + assert_eq!(text, "提交 herdr 的反…"); + assert!(display_width(&text) <= 16); + } + + #[test] + fn middle_elide_uses_display_width() { + let text = middle_elide("重构用户认证模块并迁移到统一登录服务", 12); + + assert!(text.contains('…')); + assert!(display_width(&text) <= 12); + } +} |
