diff --git a/Cargo.toml b/Cargo.toml index c48709d..eb50acf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ scraper = "0.23.1" anyhow = "1.0.97" clap_complete = "4.5.47" thiserror = "2.0.12" +unicode-width = "0.1" [dependencies.diesel] version = "2.2.8" diff --git a/src/cache/models.rs b/src/cache/models.rs index 2f49eaf..9b28482 100644 --- a/src/cache/models.rs +++ b/src/cache/models.rs @@ -1,4 +1,6 @@ //! Leetcode data models +use unicode_width::UnicodeWidthStr; +use unicode_width::UnicodeWidthChar; use super::schemas::{problems, tags}; use crate::helper::HTML; use colored::Colorize; @@ -54,7 +56,7 @@ impl Problem { static DONE: &str = " ✔"; static ETC: &str = "..."; static LOCK: &str = "🔒"; -static NDONE: &str = "✘"; +static NDONE: &str = " ✘"; static SPACE: &str = " "; impl std::fmt::Display for Problem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -98,14 +100,27 @@ impl std::fmt::Display for Problem { } } - if self.name.len() < 60_usize { + let name_width = UnicodeWidthStr::width(self.name.as_str()); + let target_width = 60; + if name_width <= target_width { name.push_str(&self.name); - name.push_str(&SPACE.repeat(60 - &self.name.len())); + name.push_str(&SPACE.repeat(target_width - name_width)); } else { - name.push_str(&self.name[..49]); - name = name.trim_end().to_string(); - name.push_str(ETC); - name.push_str(&SPACE.repeat(60 - name.len())); + // truncate carefully to target width - 3 (because "..." will take some width) + let mut truncated = String::new(); + let mut current_width = 0; + for c in self.name.chars() { + let char_width = UnicodeWidthChar::width(c).unwrap_or(0); + if current_width + char_width > target_width - 3 { + break; + } + truncated.push(c); + current_width += char_width; + } + truncated.push_str(ETC); // add "..." + let truncated_width = UnicodeWidthStr::width(truncated.as_str()); + truncated.push_str(&SPACE.repeat(target_width - truncated_width)); + name = truncated; } level = match self.level {