use std::ops::Range;
use unicode_segmentation::UnicodeSegmentation;
use crate::style::Role;
use crate::wysiwyg::{Glyph, VisualMap};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct TextCounts {
pub words: usize,
pub characters: usize,
pub characters_without_spaces: usize,
pub paragraphs: usize,
}
impl TextCounts {
fn add_block(&mut self, lines: &[String]) {
let mut has_text = false;
for line in lines {
for cluster in line.graphemes(true) {
self.characters += 1;
if !cluster.chars().all(char::is_whitespace) {
self.characters_without_spaces += 1;
has_text = true;
}
}
self.words += line.unicode_words().count();
}
if has_text {
self.paragraphs += 1;
}
}
}
pub(crate) fn tally(map: &VisualMap, range: Option<Range<usize>>) -> TextCounts {
let range = range.as_ref();
let mut counts = TextCounts::default();
let mut row = 0;
while row < map.rows.len() {
if let Some(table) = map.tables.iter().find(|t| t.rows_span.contains(&row)) {
let cells: Vec<String> = table
.grid
.iter()
.flat_map(|r| r.cells.iter())
.map(|c| visible(&c.glyphs, range))
.collect();
counts.add_block(&cells);
row = table.rows_span.end.max(row + 1);
continue;
}
if let Some(code) = map.code_blocks.iter().find(|c| c.rows_span.contains(&row)) {
let lines: Vec<String> = map.rows[code.rows_span.clone()]
.iter()
.map(|r| visible(&r.glyphs, range))
.collect();
counts.add_block(&lines);
row = code.rows_span.end.max(row + 1);
continue;
}
if !map.rows[row].decoration {
counts.add_block(&[visible(&map.rows[row].glyphs, range)]);
}
row += 1;
}
counts
}
fn visible(glyphs: &[Glyph], range: Option<&Range<usize>>) -> String {
glyphs
.iter()
.filter(|g| g.stop && is_text(g.style.role))
.filter(|g| range.is_none_or(|r| r.contains(&g.src)))
.map(|g| g.ch)
.collect()
}
fn is_text(role: Role) -> bool {
match role {
Role::Body | Role::Heading(_) | Role::Code | Role::Link | Role::Mark(_) => true,
Role::ListMarker | Role::ListIndent | Role::QuoteGutter | Role::Rule => false,
Role::Delimiter => false,
Role::Image => false,
Role::Math => false,
}
}