use unicode_segmentation::UnicodeSegmentation;
pub(crate) fn format_and_truncate_tool_result(
text: &str,
max_lines: usize,
line_width: usize,
) -> String {
let max_graphemes = (max_lines * line_width).saturating_sub(max_lines);
if let Some(formatted_json) = format_json_compact(text) {
truncate_text(&formatted_json, max_graphemes)
} else {
truncate_text(text, max_graphemes)
}
}
pub(crate) fn format_json_compact(text: &str) -> Option<String> {
let json = serde_json::from_str::<serde_json::Value>(text).ok()?;
let json_pretty = serde_json::to_string_pretty(&json).unwrap_or_else(|_| json.to_string());
let mut result = String::new();
let mut chars = json_pretty.chars().peekable();
let mut in_string = false;
let mut escape_next = false;
while let Some(ch) = chars.next() {
match ch {
'"' if !escape_next => {
in_string = !in_string;
result.push(ch);
}
'\\' if in_string => {
escape_next = !escape_next;
result.push(ch);
}
'\n' | '\r' if !in_string => {
}
' ' | '\t' if !in_string => {
if let Some(&next_ch) = chars.peek()
&& let Some(last_ch) = result.chars().last()
&& (last_ch == ':' || last_ch == ',')
&& !matches!(next_ch, '}' | ']')
{
result.push(' ');
}
}
_ => {
if escape_next && in_string {
escape_next = false;
}
result.push(ch);
}
}
}
Some(result)
}
pub(crate) fn truncate_text(text: &str, max_graphemes: usize) -> String {
let mut graphemes = text.grapheme_indices(true);
if let Some((byte_index, _)) = graphemes.nth(max_graphemes) {
if max_graphemes >= 3 {
let mut truncate_graphemes = text.grapheme_indices(true);
if let Some((truncate_byte_index, _)) = truncate_graphemes.nth(max_graphemes - 3) {
let truncated = &text[..truncate_byte_index];
format!("{truncated}...")
} else {
text.to_string()
}
} else {
let truncated = &text[..byte_index];
truncated.to_string()
}
} else {
text.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_truncate_text() {
let text = "Hello, world!";
let truncated = truncate_text(text, 8);
assert_eq!(truncated, "Hello...");
}
#[test]
fn test_truncate_empty_string() {
let text = "";
let truncated = truncate_text(text, 5);
assert_eq!(truncated, "");
}
#[test]
fn test_truncate_max_graphemes_zero() {
let text = "Hello";
let truncated = truncate_text(text, 0);
assert_eq!(truncated, "");
}
#[test]
fn test_truncate_max_graphemes_one() {
let text = "Hello";
let truncated = truncate_text(text, 1);
assert_eq!(truncated, "H");
}
#[test]
fn test_truncate_max_graphemes_two() {
let text = "Hello";
let truncated = truncate_text(text, 2);
assert_eq!(truncated, "He");
}
#[test]
fn test_truncate_max_graphemes_three_boundary() {
let text = "Hello";
let truncated = truncate_text(text, 3);
assert_eq!(truncated, "...");
}
#[test]
fn test_truncate_text_shorter_than_limit() {
let text = "Hi";
let truncated = truncate_text(text, 10);
assert_eq!(truncated, "Hi");
}
#[test]
fn test_truncate_text_exact_length() {
let text = "Hello";
let truncated = truncate_text(text, 5);
assert_eq!(truncated, "Hello");
}
#[test]
fn test_truncate_emoji() {
let text = "👋🌍🚀✨💫";
let truncated = truncate_text(text, 3);
assert_eq!(truncated, "...");
let truncated_longer = truncate_text(text, 4);
assert_eq!(truncated_longer, "👋...");
}
#[test]
fn test_truncate_unicode_combining_characters() {
let text = "é́ñ̃"; let truncated = truncate_text(text, 2);
assert_eq!(truncated, "é́ñ̃");
}
#[test]
fn test_truncate_very_long_text() {
let text = "a".repeat(1000);
let truncated = truncate_text(&text, 10);
assert_eq!(truncated, "aaaaaaa...");
assert_eq!(truncated.len(), 10); }
#[test]
fn test_format_json_compact_simple_object() {
let json = r#"{ "name": "John", "age": 30 }"#;
let result = format_json_compact(json).unwrap();
assert_eq!(result, r#"{"name": "John", "age": 30}"#);
}
#[test]
fn test_format_json_compact_nested_object() {
let json = r#"{ "user": { "name": "John", "details": { "age": 30, "city": "NYC" } } }"#;
let result = format_json_compact(json).unwrap();
assert_eq!(
result,
r#"{"user": {"name": "John", "details": {"age": 30, "city": "NYC"}}}"#
);
}
#[test]
fn test_format_json_compact_array() {
let json = r#"[ 1, 2, { "key": "value" }, "string" ]"#;
let result = format_json_compact(json).unwrap();
assert_eq!(result, r#"[1, 2, {"key": "value"}, "string"]"#);
}
#[test]
fn test_format_json_compact_already_compact() {
let json = r#"{"compact":true}"#;
let result = format_json_compact(json).unwrap();
assert_eq!(result, r#"{"compact": true}"#);
}
#[test]
fn test_format_json_compact_with_whitespace() {
let json = r#"
{
"name": "John",
"hobbies": [
"reading",
"coding"
]
}
"#;
let result = format_json_compact(json).unwrap();
assert_eq!(
result,
r#"{"name": "John", "hobbies": ["reading", "coding"]}"#
);
}
#[test]
fn test_format_json_compact_invalid_json() {
let invalid_json = r#"{"invalid": json syntax}"#;
let result = format_json_compact(invalid_json);
assert!(result.is_none());
}
#[test]
fn test_format_json_compact_empty_object() {
let json = r#"{}"#;
let result = format_json_compact(json).unwrap();
assert_eq!(result, "{}");
}
#[test]
fn test_format_json_compact_empty_array() {
let json = r#"[]"#;
let result = format_json_compact(json).unwrap();
assert_eq!(result, "[]");
}
#[test]
fn test_format_json_compact_primitive_values() {
assert_eq!(format_json_compact("42").unwrap(), "42");
assert_eq!(format_json_compact("true").unwrap(), "true");
assert_eq!(format_json_compact("false").unwrap(), "false");
assert_eq!(format_json_compact("null").unwrap(), "null");
assert_eq!(format_json_compact(r#""string""#).unwrap(), r#""string""#);
}
}