Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions src/annotate/converters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -277,8 +277,15 @@ fn truncate_summary(summary: &str, max_len: usize) -> String {
if trimmed.len() <= max_len {
trimmed.to_string()
} else {
// Find the last space before max_len to avoid cutting words
let truncate_at = trimmed[..max_len].rfind(' ').unwrap_or(max_len);
// max_len is a byte count but may land inside a multi-byte UTF-8
// character (e.g. box-drawing '─' is 3 bytes) - back off to the
// nearest preceding char boundary before slicing.
let mut boundary = max_len;
while boundary > 0 && !trimmed.is_char_boundary(boundary) {
boundary -= 1;
}
// Find the last space before the boundary to avoid cutting words
let truncate_at = trimmed[..boundary].rfind(' ').unwrap_or(boundary);
format!("{}...", &trimmed[..truncate_at])
}
}
Expand Down Expand Up @@ -306,6 +313,17 @@ mod tests {
);
}

#[test]
fn test_truncate_summary_multibyte_char_at_boundary() {
// '─' (U+2500) is 3 bytes in UTF-8, so a naive byte-offset slice at
// max_len can land inside the character and panic. This string is
// constructed so byte 100 falls inside a '─' run.
let summary =
"─── Config ─────────────────────────────────────────────────────────────────";
// Must not panic, and the result must be valid UTF-8.
let _ = truncate_summary(summary, 100);
}

#[test]
fn test_parsed_documentation_getters() {
let mut doc = ParsedDocumentation::new();
Expand Down