#![expect(
clippy::string_slice,
unused_results,
reason = "Formatting uses ASCII delimiters and intentionally ignores infallible String mutation results."
)]
#[inline]
pub fn contains_ignore_ascii_case(haystack: &str, needle: &str) -> bool {
let needle = needle.as_bytes();
if needle.is_empty() {
return true;
}
haystack
.as_bytes()
.windows(needle.len())
.any(|window| window.eq_ignore_ascii_case(needle))
}
pub fn format_size(size: u64) -> String {
const KB: u64 = 1024;
const MB: u64 = KB * 1024;
const GB: u64 = MB * 1024;
if size >= GB {
format!("{:.1}GB", size as f64 / GB as f64)
} else if size >= MB {
format!("{:.1}MB", size as f64 / MB as f64)
} else if size >= KB {
format!("{:.1}KB", size as f64 / KB as f64)
} else {
format!("{size}B")
}
}
#[must_use]
pub fn humanize_duration_compact(seconds: u64) -> String {
if seconds < 60 {
format!("{seconds}s")
} else if seconds < 3600 {
format!("{}m", seconds / 60)
} else if seconds < 86400 {
format!("{}h", seconds / 3600)
} else {
format!("{}d", seconds / 86400)
}
}
pub fn indent_block(text: &str, indent: &str) -> String {
if indent.is_empty() || text.is_empty() {
return text.to_string();
}
let mut indented = String::with_capacity(text.len() + indent.len() * text.lines().count());
for (idx, line) in text.split('\n').enumerate() {
if idx > 0 {
indented.push('\n');
}
if !line.is_empty() {
indented.push_str(indent);
}
indented.push_str(line);
}
indented
}
pub fn truncate_text(text: &str, max_len: usize, ellipsis: &str) -> String {
if text.chars().count() <= max_len {
return text.to_string();
}
let mut truncated = text.chars().take(max_len).collect::<String>();
truncated.push_str(ellipsis);
truncated
}
pub fn truncate_within(text: &str, max_len: usize, ellipsis: &str) -> String {
if text.chars().count() <= max_len {
return text.to_string();
}
let keep = max_len.saturating_sub(ellipsis.chars().count());
let mut truncated = text.chars().take(keep).collect::<String>();
truncated.push_str(ellipsis);
truncated
}
pub fn truncate_middle(text: &str, max_len: usize) -> String {
if max_len == 0 {
return String::new();
}
let sanitized: String = text
.chars()
.map(|c| if matches!(c, '\n' | '\r' | '\t') { ' ' } else { c })
.collect();
let char_count = sanitized.chars().count();
if char_count <= max_len {
return sanitized;
}
if max_len <= 1 {
return "…".to_string();
}
let head_len = max_len / 2;
let tail_len = max_len.saturating_sub(head_len + 1);
let head: String = sanitized.chars().take(head_len).collect();
let mut result = String::with_capacity(head.len() + tail_len + 1);
result.push_str(&head);
result.push('…');
if tail_len > 0 {
let mut tail_rev: Vec<char> = sanitized.chars().rev().take(tail_len).collect();
tail_rev.reverse();
let tail: String = tail_rev.into_iter().collect();
result.push_str(&tail);
}
result
}
pub fn truncate_path_middle(path: &str, max_len: usize) -> String {
if max_len == 0 {
return String::new();
}
let char_count = path.chars().count();
if char_count <= max_len {
return path.to_string();
}
if max_len <= 1 {
return "…".to_string();
}
let chars: Vec<char> = path.chars().collect();
let head_budget = max_len / 2;
let tail_budget = max_len.saturating_sub(head_budget + 1);
let head_break = chars.iter().take(head_budget).rposition(|c| *c == '/').unwrap_or(head_budget);
let tail: Vec<char> = chars.iter().rev().take(tail_budget).rev().copied().collect();
let tail_len = tail.iter().position(|c| *c == '/').map_or(tail_budget, |pos| tail_budget - pos);
let head: String = chars.iter().take(head_break).collect();
let tail: String = tail.iter().skip(tail_budget - tail_len).collect();
format!("{head}…{tail}")
}
pub fn head_tail_truncate(value: &str, max_chars: usize, marker: &str) -> (String, bool) {
const SUFFIX: &str = " [truncated]";
let total_chars = value.chars().count();
if total_chars <= max_chars {
return (value.to_string(), false);
}
let marker_chars = marker.chars().count();
if max_chars <= marker_chars + 16 {
let suffix_len = SUFFIX.chars().count();
let truncated = if max_chars > suffix_len {
let available = max_chars - suffix_len;
let mut result = value.chars().take(available).collect::<String>();
result.push_str(SUFFIX);
result
} else {
value.chars().take(max_chars).collect::<String>()
};
return (truncated, true);
}
let available = max_chars.saturating_sub(marker_chars);
let head_chars = (available * 2) / 3;
let tail_chars = available.saturating_sub(head_chars);
let head = value.chars().take(head_chars).collect::<String>();
let tail = value.chars().skip(total_chars.saturating_sub(tail_chars)).collect::<String>();
let mut truncated = String::with_capacity(max_chars + 20);
truncated.push_str(&head);
truncated.push_str(marker);
truncated.push_str(&tail);
(truncated, true)
}
pub fn wrap_text_words(text: &str, first_width: usize, continuation_width: usize) -> Vec<String> {
let trimmed = text.trim();
if trimmed.is_empty() {
return Vec::new();
}
let mut result = Vec::new();
let mut remaining = trimmed;
let mut width = first_width.max(1);
while remaining.chars().take(width + 1).count() > width {
let split = split_at_word_boundary(remaining, width);
let (head, tail) = remaining.split_at(split);
let head = head.trim();
if head.is_empty() {
break;
}
result.push(head.to_string());
remaining = tail.trim_start();
if remaining.is_empty() {
break;
}
width = continuation_width.max(1);
}
if !remaining.is_empty() {
result.push(remaining.to_string());
}
result
}
fn split_at_word_boundary(input: &str, width: usize) -> usize {
let mut last_space: Option<usize> = None;
for (seen, (idx, ch)) in input.char_indices().enumerate() {
if seen > width {
break;
}
if ch.is_whitespace() {
last_space = Some(idx);
}
}
match last_space {
Some(pos) => pos,
None => byte_index_for_char_count(input, width),
}
}
fn byte_index_for_char_count(input: &str, chars: usize) -> usize {
if chars == 0 {
return 0;
}
let mut seen = 0usize;
for (idx, ch) in input.char_indices() {
seen += 1;
if seen == chars {
return idx + ch.len_utf8();
}
}
input.len()
}
fn split_shell_words(text: &str) -> Vec<String> {
let mut words = Vec::new();
let mut current = String::new();
let mut in_single = false;
let mut in_double = false;
let mut escaped = false;
for ch in text.chars() {
if escaped {
current.push(ch);
escaped = false;
continue;
}
if ch == '\\' && !in_single {
current.push(ch);
escaped = true;
continue;
}
if ch == '\'' && !in_double {
in_single = !in_single;
current.push(ch);
continue;
}
if ch == '"' && !in_single {
in_double = !in_double;
current.push(ch);
continue;
}
if ch.is_whitespace() && !in_single && !in_double {
if !current.is_empty() {
words.push(std::mem::take(&mut current));
}
continue;
}
current.push(ch);
}
if !current.is_empty() {
words.push(current);
}
words
}
pub const RAN_COMMAND_FIRST_WIDTH: usize = 62;
pub const RAN_COMMAND_CONTINUATION_WIDTH: usize = 58;
pub fn wrap_shell_command(text: &str, first_width: usize, continuation_width: usize) -> Vec<String> {
let trimmed = text.trim();
if trimmed.is_empty() {
return Vec::new();
}
let words = split_shell_words(trimmed);
if words.is_empty() {
return Vec::new();
}
let mut lines = Vec::with_capacity(words.len());
let mut current = String::new();
let mut width = first_width.max(1);
for word in words {
let word_len = word.chars().count();
if word_len > width {
if !current.is_empty() {
lines.push(std::mem::take(&mut current));
width = continuation_width.max(1);
}
current = push_split_word(&mut lines, &word, width, continuation_width);
width = continuation_width.max(1);
continue;
}
let current_len = current.chars().count();
let need = if current_len == 0 {
word_len
} else {
current_len + 1 + word_len
};
if need <= width {
if current_len > 0 {
current.push(' ');
}
current.push_str(&word);
} else {
lines.push(std::mem::take(&mut current));
width = continuation_width.max(1);
if word_len > width {
current = push_split_word(&mut lines, &word, width, continuation_width);
} else {
current = word;
}
}
}
if !current.is_empty() {
lines.push(current);
}
lines
}
pub fn wrap_shell_command_with_continuations(text: &str, first_width: usize, continuation_width: usize) -> Vec<String> {
let lines = wrap_shell_command_lines(text, first_width, continuation_width);
let total = lines.len();
if total <= 1 {
return lines;
}
lines
.into_iter()
.enumerate()
.map(|(index, mut line)| {
if index + 1 < total {
line.push_str(" \\");
}
line
})
.collect()
}
pub fn wrap_shell_command_lines(text: &str, first_width: usize, continuation_width: usize) -> Vec<String> {
let trimmed = text.trim();
if trimmed.is_empty() {
return Vec::new();
}
let single = wrap_shell_command(trimmed, first_width.max(1), continuation_width.max(1));
if single.len() <= 1 {
return single;
}
let chunks = split_shell_operator_chunks(trimmed);
let mut lines: Vec<String> = Vec::new();
for (chunk_idx, chunk) in chunks.iter().enumerate() {
let width = if lines.is_empty() {
first_width.max(1)
} else {
continuation_width.max(1)
};
let wrapped = if chunk_idx == 0 {
wrap_shell_command(chunk, first_width.max(1), continuation_width.max(1))
} else {
wrap_shell_command(chunk, width, continuation_width.max(1))
};
if wrapped.is_empty() {
continue;
}
lines.extend(wrapped);
}
lines
}
fn is_shell_list_separator(word: &str) -> bool {
matches!(word, "&&" | "||" | "|" | "|&" | ";" | ";;" | ";&" | ";;&" | "&")
|| (word.len() > 1 && word.ends_with(';') && !word.ends_with(";;"))
}
fn split_shell_operator_chunks(command: &str) -> Vec<String> {
let words = split_shell_words(command);
if words.is_empty() {
return Vec::new();
}
let mut chunks: Vec<String> = Vec::new();
let mut current = String::new();
for word in words {
if !current.is_empty() {
current.push(' ');
}
current.push_str(&word);
if is_shell_list_separator(&word) {
chunks.push(std::mem::take(&mut current));
}
}
if !current.is_empty() {
chunks.push(current);
}
chunks
}
fn push_split_word(lines: &mut Vec<String>, word: &str, width: usize, continuation_width: usize) -> String {
let mut width = width.max(1);
let mut rest = word;
while rest.chars().count() > width {
let idx = byte_index_for_char_count(rest, width);
let (head, tail) = rest.split_at(idx);
lines.push(head.to_string());
rest = tail;
width = continuation_width.max(1);
}
rest.to_string()
}
#[allow(
clippy::cast_possible_truncation,
clippy::float_cmp,
reason = "Narrowing `f64` to `f32` and comparing exactly is the detection mechanism: only exact round-trips collapse."
)]
pub fn format_float_display(value: f64) -> String {
if !value.is_finite() {
return value.to_string();
}
let narrowed = value as f32;
if (narrowed as f64) == value {
return narrowed.to_string();
}
const GRID_EPSILON: f64 = 1e-7;
for precision in [1i32, 2i32] {
let factor = 10f64.powi(precision);
let snapped = (value * factor).round() / factor;
if (snapped - value).abs() < GRID_EPSILON {
return snapped.to_string();
}
}
value.to_string()
}
#[inline]
pub fn truncate_utf8_prefix(text: &str, max_bytes: usize) -> &str {
let mut end = max_bytes.min(text.len());
while end > 0 && !text.is_char_boundary(end) {
end -= 1;
}
&text[..end]
}
pub fn truncate_byte_budget(text: &str, max_bytes: usize, suffix: &str) -> String {
if text.len() <= max_bytes {
return text.to_string();
}
format!("{}{suffix}", truncate_utf8_prefix(text, max_bytes))
}
#[inline]
pub fn is_markdown_fence_delimiter(line: &str) -> bool {
let indent = line.len() - line.trim_start().len();
if indent > 3 {
return false;
}
let trimmed = line.trim_start();
trimmed.starts_with("```") || trimmed.starts_with("~~~")
}
pub fn lowercase_leading_word(text: &str) -> String {
let mut chars = text.chars();
let Some(first) = chars.next() else {
return String::new();
};
let second = chars.clone().next();
let plain_word =
first.is_uppercase() && first != 'I' && second.is_none_or(|c| c.is_lowercase() || c.is_whitespace());
if plain_word {
let mut lowered: String = first.to_lowercase().collect();
lowered.push_str(chars.as_str());
lowered
} else {
text.to_string()
}
}
#[inline]
pub fn collapse_whitespace(text: &str) -> String {
let mut result = String::with_capacity(text.len());
let mut pending_space = false;
for ch in text.chars() {
if ch.is_whitespace() {
pending_space = true;
} else {
if pending_space && !result.is_empty() {
result.push(' ');
}
result.push(ch);
pending_space = false;
}
}
result
}
#[inline]
pub fn strip_optional_word_prefix<'a>(input: &'a str, word: &str) -> &'a str {
let Some(prefix) = input.get(..word.len()) else {
return input;
};
if !prefix.eq_ignore_ascii_case(word) {
return input;
}
let remainder = &input[word.len()..];
if remainder.chars().next().is_some_and(char::is_whitespace) {
remainder.trim_start()
} else {
input
}
}
#[inline]
pub fn trim_wrapping_quotes_and_punctuation(target: &str) -> &str {
let mut normalized = target.trim();
loop {
let previous = normalized;
normalized = normalized.trim();
normalized = normalized.strip_prefix('"').unwrap_or(normalized);
normalized = normalized.strip_suffix('"').unwrap_or(normalized);
normalized = normalized.strip_prefix('\'').unwrap_or(normalized);
normalized = normalized.strip_suffix('\'').unwrap_or(normalized);
normalized = normalized.trim_end_matches(['.', ',', ';', '!', '?']).trim();
if normalized == previous {
return normalized;
}
}
}
pub fn clean_reasoning_text(text: &str) -> String {
text.lines()
.map(str::trim_end)
.filter(|line| !line.trim().is_empty())
.collect::<Vec<_>>()
.join("\n")
}
pub fn compact_reasoning_text(text: &str) -> String {
let mut out: Vec<&str> = Vec::with_capacity(text.lines().count());
let mut prev_blank = false;
for line in text.lines() {
let trimmed = line.trim();
let is_blank = trimmed.is_empty();
if is_blank {
if prev_blank {
continue;
}
out.push("");
prev_blank = true;
} else {
out.push(trimmed);
prev_blank = false;
}
}
while out.first().is_some_and(|l| l.trim().is_empty()) {
out.remove(0);
}
while out.last().is_some_and(|l| l.trim().is_empty()) {
out.pop();
}
out.join("\n")
}
#[cfg(test)]
mod generative_tests;
#[cfg(test)]
mod tests;