pub fn truncate_str(s: &str, max_bytes: usize) -> &str {
if s.len() <= max_bytes {
return s;
}
let mut end = max_bytes;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
&s[..end]
}
pub fn looks_like_file_path(s: &str) -> bool {
if !s.starts_with('/') {
return false;
}
if s[1..].contains('/') {
return true;
}
let first_word = s.split_whitespace().next().unwrap_or(s);
if let Some(ext) = std::path::Path::new(first_word).extension()
&& !ext.is_empty()
{
return true;
}
false
}
pub fn tilde_home(s: &str) -> String {
let home = match dirs::home_dir() {
Some(h) => h,
None => return s.to_string(),
};
let home_str = home.to_string_lossy();
if home_str.is_empty() {
return s.to_string();
}
s.replace(home_str.as_ref(), "~")
}
pub fn truncate_middle(s: &str, max_bytes: usize) -> String {
if s.len() <= max_bytes {
return s.to_string();
}
const ELLIPSIS: &str = "…"; if max_bytes <= ELLIPSIS.len() + 2 {
return truncate_str(s, max_bytes).to_string();
}
let budget = max_bytes - ELLIPSIS.len();
let tail_bytes = budget.div_ceil(2);
let head_bytes = budget - tail_bytes;
let mut head_end = head_bytes;
while head_end > 0 && !s.is_char_boundary(head_end) {
head_end -= 1;
}
let mut tail_start = s.len() - tail_bytes;
while tail_start < s.len() && !s.is_char_boundary(tail_start) {
tail_start += 1;
}
if tail_start <= head_end {
return truncate_str(s, max_bytes).to_string();
}
format!("{}{}{}", &s[..head_end], ELLIPSIS, &s[tail_start..])
}
fn format_token_count(tokens: u32) -> String {
let tokens = tokens as f64;
if tokens >= 1_000_000.0 {
format!("{:.1}M", tokens / 1_000_000.0)
} else if tokens >= 1_000.0 {
format!("{:.0}K", tokens / 1_000.0)
} else if tokens > 0.0 {
format!("{}", tokens as u32)
} else {
"0".to_string()
}
}
pub fn format_ctx_footer(used: u32, max: u32, tps: Option<f64>) -> String {
let pct = if max > 0 {
(used as f64 / max as f64) * 100.0
} else {
0.0
};
let base = format!(
"ctx: {}/{} {:.0}%",
format_token_count(used),
format_token_count(max),
pct
);
if let Some(rate) = tps {
format!("{} | {:.0} tok/s", base, rate)
} else {
base
}
}
pub fn md_table(headers: &[&str], rows: &[Vec<String>]) -> String {
if rows.is_empty() {
return String::new();
}
let clean = |s: &str| s.replace(['|', '\n'], " ");
let mut out = String::new();
out.push_str("| ");
out.push_str(
&headers
.iter()
.map(|h| clean(h))
.collect::<Vec<_>>()
.join(" | "),
);
out.push_str(" |\n|");
for _ in headers {
out.push_str(" --- |");
}
out.push('\n');
for row in rows {
out.push_str("| ");
out.push_str(&row.iter().map(|c| clean(c)).collect::<Vec<_>>().join(" | "));
out.push_str(" |\n");
}
out
}
pub fn strip_ctx_footer(text: &str) -> String {
text.lines()
.filter(|line| {
let trimmed = line.trim();
!trimmed.starts_with("ctx:") || !trimmed.contains('/') || !trimmed.contains('%')
})
.collect::<Vec<_>>()
.join("\n")
.trim_end_matches('\n')
.to_string()
}