Split text into paragraphs at double-newline (`\n\n`) boundaries.
(text: &str)
| 214 | |
| 215 | /// Split text into paragraphs at double-newline (`\n\n`) boundaries. |
| 216 | fn split_paragraphs(text: &str) -> Vec<(usize, String)> { |
| 217 | let mut segments = Vec::new(); |
| 218 | let mut char_offset = 0usize; |
| 219 | |
| 220 | // Split on \n\n (or \r\n\r\n). |
| 221 | let mut remaining = text; |
| 222 | while let Some(pos) = find_paragraph_break(remaining) { |
| 223 | let para = &remaining[..pos]; |
| 224 | let para_chars: Vec<char> = para.chars().collect(); |
| 225 | if !para_chars.is_empty() { |
| 226 | segments.push((char_offset, para.to_string())); |
| 227 | } |
| 228 | char_offset += para.chars().count(); |
| 229 | |
| 230 | // Skip the paragraph break characters. |
| 231 | let break_str = &remaining[pos..]; |
| 232 | let break_len = if break_str.starts_with("\r\n\r\n") { |
| 233 | 4 |
| 234 | } else { |
| 235 | 2 // \n\n |
| 236 | }; |
| 237 | let break_chars = remaining[pos..pos + break_len].chars().count(); |
| 238 | char_offset += break_chars; |
| 239 | remaining = &remaining[pos + break_len..]; |
| 240 | } |
| 241 | |
| 242 | // Remaining text. |
| 243 | if !remaining.is_empty() { |
| 244 | segments.push((char_offset, remaining.to_string())); |
| 245 | } |
| 246 | |
| 247 | segments |
| 248 | } |
| 249 | |
| 250 | /// Find the byte position of the next paragraph break (\n\n or \r\n\r\n). |
| 251 | fn find_paragraph_break(text: &str) -> Option<usize> { |
no test coverage detected