diff --git a/crates/op-editor-ui/src/util.rs b/crates/op-editor-ui/src/util.rs index 566a1798a..5fc09558c 100644 --- a/crates/op-editor-ui/src/util.rs +++ b/crates/op-editor-ui/src/util.rs @@ -157,10 +157,30 @@ pub fn resize_bounds(start: Rect, handle: SelectionHandle, dx: f32, dy: f32) -> Rect::xywh(x, y, w, h) } +/// Truncate `s` to at most `max` characters, appending a `…` ellipsis when it +/// is shortened (the ellipsis counts toward `max`). Character-aware, so +/// multibyte glyphs are never split. +pub fn truncate_ellipsis(s: &str, max: usize) -> String { + if s.chars().count() <= max { + return s.to_string(); + } + let kept: String = s.chars().take(max.saturating_sub(1)).collect(); + format!("{kept}…") +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn truncate_ellipsis_appends_only_when_shortened() { + assert_eq!(truncate_ellipsis("hello", 10), "hello"); + assert_eq!(truncate_ellipsis("hello", 5), "hello"); + assert_eq!(truncate_ellipsis("hello", 3), "he…"); + // Saturating: max == 0 must not panic. + assert_eq!(truncate_ellipsis("hello", 0), "…"); + } + #[test] fn parse_hex_expands_shorthand_and_zero_pads() { assert_eq!( diff --git a/crates/op-editor-ui/src/widgets/component_browser_panel.rs b/crates/op-editor-ui/src/widgets/component_browser_panel.rs index e830a338e..abaea07af 100644 --- a/crates/op-editor-ui/src/widgets/component_browser_panel.rs +++ b/crates/op-editor-ui/src/widgets/component_browser_panel.rs @@ -676,10 +676,4 @@ impl<'a> ComponentBrowserPanel<'a> { } } -pub(in crate::widgets) fn truncate(s: &str, max: usize) -> String { - if s.chars().count() <= max { - return s.to_string(); - } - let kept: String = s.chars().take(max.saturating_sub(1)).collect(); - format!("{kept}…") -} +pub(in crate::widgets) use crate::util::truncate_ellipsis as truncate; diff --git a/crates/op-editor-ui/src/widgets/design_md_panel/helpers.rs b/crates/op-editor-ui/src/widgets/design_md_panel/helpers.rs index 1e241aa64..cb095e2dd 100644 --- a/crates/op-editor-ui/src/widgets/design_md_panel/helpers.rs +++ b/crates/op-editor-ui/src/widgets/design_md_panel/helpers.rs @@ -9,13 +9,7 @@ pub(super) fn label_char_w(ch: char) -> f32 { } } -pub(super) fn truncate(s: &str, max: usize) -> String { - if s.chars().count() <= max { - return s.to_string(); - } - let kept: String = s.chars().take(max.saturating_sub(1)).collect(); - format!("{kept}…") -} +pub(super) use crate::util::truncate_ellipsis as truncate; pub(super) fn hex_to_color(hex: &str) -> Color { match op_editor_core::parse_hex_rgb(hex) { diff --git a/crates/op-editor-ui/src/widgets/git_panel.rs b/crates/op-editor-ui/src/widgets/git_panel.rs index 1c716dd6b..75b961a20 100644 --- a/crates/op-editor-ui/src/widgets/git_panel.rs +++ b/crates/op-editor-ui/src/widgets/git_panel.rs @@ -919,10 +919,4 @@ impl<'a> GitPanel<'a> { } /// Char truncation with an ellipsis. -pub(super) fn truncate(s: &str, max: usize) -> String { - if s.chars().count() <= max { - return s.to_string(); - } - let kept: String = s.chars().take(max.saturating_sub(1)).collect(); - format!("{kept}…") -} +pub(super) use crate::util::truncate_ellipsis as truncate; diff --git a/crates/op-editor-ui/src/widgets/variables_panel/paint.rs b/crates/op-editor-ui/src/widgets/variables_panel/paint.rs index 4a2ecf99e..8915dcd66 100644 --- a/crates/op-editor-ui/src/widgets/variables_panel/paint.rs +++ b/crates/op-editor-ui/src/widgets/variables_panel/paint.rs @@ -749,11 +749,4 @@ fn scalar_to_label(s: &VariableScalar) -> String { } } -fn truncate(s: &str, max: usize) -> String { - if s.chars().count() <= max { - return s.to_string(); - } - let mut out: String = s.chars().take(max - 1).collect(); - out.push('…'); - out -} +use crate::util::truncate_ellipsis as truncate;