#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChunkSpan {
pub start: usize,
pub end: usize,
}
pub fn chunk_text(text: &str, target_bytes: usize) -> Vec<ChunkSpan> {
let target = target_bytes.max(1);
if text.is_empty() {
return Vec::new();
}
let mut soft_spans = Vec::new();
let mut chunk_start = 0usize;
let mut last_soft: Option<usize> = None;
let mut pos = 0usize;
for line in text.split_inclusive('\n') {
let line_start = pos;
pos += line.len();
if line.starts_with('#') && line_start > chunk_start {
last_soft = Some(line_start);
}
if line.trim().is_empty() {
last_soft = Some(pos);
}
if pos - chunk_start >= target
&& let Some(cut) = last_soft.filter(|s| *s > chunk_start)
{
soft_spans.push(ChunkSpan {
start: chunk_start,
end: cut,
});
chunk_start = cut;
last_soft = None;
}
}
if chunk_start < text.len() {
soft_spans.push(ChunkSpan {
start: chunk_start,
end: text.len(),
});
}
let mut spans = Vec::with_capacity(soft_spans.len());
for span in soft_spans {
if span.end - span.start <= 2 * target {
spans.push(span);
continue;
}
let mut piece_start = span.start;
let mut last_boundary = span.start;
for (off, ch) in text[span.start..span.end].char_indices() {
let abs = span.start + off;
if abs - piece_start >= target {
spans.push(ChunkSpan {
start: piece_start,
end: abs,
});
piece_start = abs;
}
last_boundary = abs + ch.len_utf8();
}
if piece_start < last_boundary {
spans.push(ChunkSpan {
start: piece_start,
end: span.end,
});
}
}
spans
}