use std::ops::Range;
use textwrap::Options;
pub(crate) fn wrap_ranges<'a, O>(text: &str, width_or_options: O) -> Vec<Range<usize>>
where
O: Into<Options<'a>>,
{
let opts = width_or_options.into();
let mut lines: Vec<Range<usize>> = Vec::new();
let mut cursor = 0usize;
for (line_index, line) in textwrap::wrap(text, &opts).iter().enumerate() {
match line {
std::borrow::Cow::Borrowed(slice) => {
let start = unsafe { slice.as_ptr().offset_from(text.as_ptr()) as usize };
let end = start + slice.len();
let trailing_spaces = text[end..].chars().take_while(|c| *c == ' ').count();
lines.push(start..end + trailing_spaces + 1);
cursor = end + trailing_spaces;
}
std::borrow::Cow::Owned(slice) => {
let synthetic_prefix = if line_index == 0 {
opts.initial_indent
} else {
opts.subsequent_indent
};
let mapped = map_owned_wrapped_line_to_range(text, cursor, slice, synthetic_prefix);
let trailing_spaces = text[mapped.end..].chars().take_while(|c| *c == ' ').count();
lines.push(mapped.start..mapped.end + trailing_spaces + 1);
cursor = mapped.end + trailing_spaces;
}
}
}
lines
}
fn map_owned_wrapped_line_to_range(
text: &str,
cursor: usize,
wrapped: &str,
synthetic_prefix: &str,
) -> Range<usize> {
let wrapped = if synthetic_prefix.is_empty() {
wrapped
} else {
wrapped.strip_prefix(synthetic_prefix).unwrap_or(wrapped)
};
let mut start = cursor;
while start < text.len() && !wrapped.starts_with(' ') {
let Some(ch) = text[start..].chars().next() else {
break;
};
if ch != ' ' {
break;
}
start += ch.len_utf8();
}
let mut end = start;
let mut saw_source_char = false;
let mut chars = wrapped.chars().peekable();
while let Some(ch) = chars.next() {
if end < text.len() {
let Some(src) = text[end..].chars().next() else {
unreachable!("checked end < text.len()");
};
if ch == src {
end += src.len_utf8();
saw_source_char = true;
continue;
}
}
if ch == '-' && chars.peek().is_none() {
continue;
}
if !saw_source_char {
continue;
}
tracing::warn!(
wrapped = %wrapped,
cursor,
end,
"wrap_ranges: could not fully map owned line; returning partial source range"
);
break;
}
start..end
}