From ba74174c21d4e6f1560f9edc05312ef9a97127b6 Mon Sep 17 00:00:00 2001 From: toyamagu2021 Date: Sun, 8 Jun 2025 06:43:40 +0900 Subject: [PATCH 1/3] Refactor string truncation logic into reusable utility function Extract repeated string truncation code from project.rs and session.rs into a new utils module with safe_truncate function. Also fix trailing whitespace in configure.rs. Signed-off-by: toyamagu2021 --- crates/goose-cli/src/commands/configure.rs | 2 +- crates/goose-cli/src/commands/project.rs | 7 +-- crates/goose-cli/src/commands/session.rs | 13 ++---- crates/goose-cli/src/lib.rs | 1 + crates/goose-cli/src/utils.rs | 54 ++++++++++++++++++++++ 5 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 crates/goose-cli/src/utils.rs diff --git a/crates/goose-cli/src/commands/configure.rs b/crates/goose-cli/src/commands/configure.rs index 7e49c50c70f4..663dc5ec0fad 100644 --- a/crates/goose-cli/src/commands/configure.rs +++ b/crates/goose-cli/src/commands/configure.rs @@ -887,7 +887,7 @@ pub fn configure_goose_mode_dialog() -> Result<(), Box> { let mode = cliclack::select("Which Goose mode would you like to configure?") .item( "auto", - "Auto Mode", + "Auto Mode", "Full file modification, extension usage, edit, create and delete files freely" ) .item( diff --git a/crates/goose-cli/src/commands/project.rs b/crates/goose-cli/src/commands/project.rs index 07ed8c2e94df..17e63e412baf 100644 --- a/crates/goose-cli/src/commands/project.rs +++ b/crates/goose-cli/src/commands/project.rs @@ -4,6 +4,7 @@ use cliclack::{self, intro, outro}; use std::path::Path; use crate::project_tracker::ProjectTracker; +use crate::utils::safe_truncate; /// Format a DateTime for display fn format_date(date: DateTime) -> String { @@ -199,11 +200,7 @@ pub fn handle_projects_interactive() -> Result<()> { .last_instruction .as_ref() .map_or(String::new(), |instr| { - let truncated = if instr.len() > 40 { - format!("{}...", &instr[0..37]) - } else { - instr.clone() - }; + let truncated = safe_truncate(instr, 40); format!(" [{}]", truncated) }); diff --git a/crates/goose-cli/src/commands/session.rs b/crates/goose-cli/src/commands/session.rs index f3fb97e7304d..3d160ae279a6 100644 --- a/crates/goose-cli/src/commands/session.rs +++ b/crates/goose-cli/src/commands/session.rs @@ -1,4 +1,5 @@ use crate::session::message_to_markdown; +use crate::utils::safe_truncate; use anyhow::{Context, Result}; use cliclack::{confirm, multiselect, select}; use goose::session::info::{get_session_info, SessionInfo, SortOrder}; @@ -50,11 +51,7 @@ fn prompt_interactive_session_removal(sessions: &[SessionInfo]) -> Result TRUNCATED_DESC_LENGTH { - format!("{}...", &desc[..TRUNCATED_DESC_LENGTH - 3]) - } else { - desc.to_string() - }; + let truncated_desc = safe_truncate(desc, TRUNCATED_DESC_LENGTH); let display_text = format!("{} - {} ({})", s.modified, truncated_desc, s.id); (display_text, s.clone()) }) @@ -315,11 +312,7 @@ pub fn prompt_interactive_session_selection() -> Result { }; // Truncate description if too long - let truncated_desc = if desc.len() > 40 { - format!("{}...", &desc[..37]) - } else { - desc.to_string() - }; + let truncated_desc = safe_truncate(desc, 40); let display_text = format!("{} - {} ({})", s.modified, truncated_desc, s.id); (display_text, s.clone()) diff --git a/crates/goose-cli/src/lib.rs b/crates/goose-cli/src/lib.rs index 68f2357f5ee0..055f38b9033a 100644 --- a/crates/goose-cli/src/lib.rs +++ b/crates/goose-cli/src/lib.rs @@ -7,6 +7,7 @@ pub mod project_tracker; pub mod recipes; pub mod session; pub mod signal; +pub mod utils; // Re-export commonly used types pub use session::Session; diff --git a/crates/goose-cli/src/utils.rs b/crates/goose-cli/src/utils.rs new file mode 100644 index 000000000000..74b5b22728ec --- /dev/null +++ b/crates/goose-cli/src/utils.rs @@ -0,0 +1,54 @@ +/// Utility functions for safe string handling and other common operations + +/// Safely truncate a string at character boundaries, not byte boundaries +/// +/// This function ensures that multi-byte UTF-8 characters (like Japanese, emoji, etc.) +/// are not split in the middle, which would cause a panic. +/// +/// # Arguments +/// * `s` - The string to truncate +/// * `max_chars` - Maximum number of characters to keep +/// +/// # Returns +/// A truncated string with "..." appended if truncation occurred +pub fn safe_truncate(s: &str, max_chars: usize) -> String { + if s.chars().count() <= max_chars { + s.to_string() + } else { + let truncated: String = s.chars().take(max_chars.saturating_sub(3)).collect(); + format!("{}...", truncated) + } +} + + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_safe_truncate_ascii() { + assert_eq!(safe_truncate("hello world", 20), "hello world"); + assert_eq!(safe_truncate("hello world", 8), "hello..."); + assert_eq!(safe_truncate("hello", 5), "hello"); + assert_eq!(safe_truncate("hello", 3), "..."); + } + + #[test] + fn test_safe_truncate_japanese() { + // Japanese characters: "こんにちは世界" (Hello World) + let japanese = "こんにちは世界"; + assert_eq!(safe_truncate(japanese, 10), japanese); + assert_eq!(safe_truncate(japanese, 5), "こん..."); + assert_eq!(safe_truncate(japanese, 7), japanese); + } + + #[test] + fn test_safe_truncate_mixed() { + // Mixed ASCII and Japanese + let mixed = "Hello こんにちは"; + assert_eq!(safe_truncate(mixed, 20), mixed); + assert_eq!(safe_truncate(mixed, 8), "Hello..."); + } + +} From 06e9f9f5469bda40271e5c68298c41ae64cc37de Mon Sep 17 00:00:00 2001 From: toyamagu2021 Date: Thu, 3 Jul 2025 23:33:41 +0900 Subject: [PATCH 2/3] fix: lint Signed-off-by: toyamagu2021 --- crates/goose-cli/src/utils.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/goose-cli/src/utils.rs b/crates/goose-cli/src/utils.rs index 74b5b22728ec..6db82964a80f 100644 --- a/crates/goose-cli/src/utils.rs +++ b/crates/goose-cli/src/utils.rs @@ -20,8 +20,6 @@ pub fn safe_truncate(s: &str, max_chars: usize) -> String { } } - - #[cfg(test)] mod tests { use super::*; @@ -50,5 +48,4 @@ mod tests { assert_eq!(safe_truncate(mixed, 20), mixed); assert_eq!(safe_truncate(mixed, 8), "Hello..."); } - } From 2b835ebb1d0a165bd78c2bca4a9b4aad1b79c2ac Mon Sep 17 00:00:00 2001 From: toyamagu2021 Date: Fri, 4 Jul 2025 00:09:10 +0900 Subject: [PATCH 3/3] fix: lint and pick https://github.com/block/goose/pull/3243 Signed-off-by: toyamagu2021 --- crates/goose-cli/src/utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/goose-cli/src/utils.rs b/crates/goose-cli/src/utils.rs index 6db82964a80f..69daddf1d2a1 100644 --- a/crates/goose-cli/src/utils.rs +++ b/crates/goose-cli/src/utils.rs @@ -1,5 +1,4 @@ /// Utility functions for safe string handling and other common operations - /// Safely truncate a string at character boundaries, not byte boundaries /// /// This function ensures that multi-byte UTF-8 characters (like Japanese, emoji, etc.)