use crate::markdown::DocBlock;
use ratatui::text::Line;
use unicode_width::UnicodeWidthStr;
pub fn line_visual_rows(line: &Line, content_width: u16) -> u32 {
if content_width == 0 {
return 1;
}
let width: usize = line
.spans
.iter()
.map(|s| UnicodeWidthStr::width(s.content.as_ref()))
.sum();
if width == 0 {
return 1;
}
let cw = content_width as usize;
crate::cast::u32_sat(width.div_ceil(cw))
}
pub fn visual_row_to_logical_line(
blocks: &[DocBlock],
scroll_offset: u32,
visual_row: u32,
content_width: u16,
) -> u32 {
let mut remaining_visual = visual_row;
let mut block_offset = 0u32;
for block in blocks {
let block_height = block.height();
let block_end = block_offset + block_height;
if block_end <= scroll_offset {
block_offset = block_end;
continue;
}
let clip_start = scroll_offset.saturating_sub(block_offset) as usize;
match block {
DocBlock::Text { text, .. } => {
for (idx, line) in text.lines.iter().enumerate().skip(clip_start) {
let rows = line_visual_rows(line, content_width);
if remaining_visual < rows {
return block_offset + crate::cast::u32_sat(idx);
}
remaining_visual -= rows;
}
}
DocBlock::Mermaid { cell_height, .. } => {
let visible_rows = cell_height.get().saturating_sub(crate::cast::u32_sat(clip_start));
if remaining_visual < visible_rows {
return u32::MAX;
}
remaining_visual -= visible_rows;
}
DocBlock::Table(t) => {
let visible_rows = t.rendered_height.saturating_sub(crate::cast::u32_sat(clip_start));
if remaining_visual < visible_rows {
return u32::MAX;
}
remaining_visual -= visible_rows;
}
}
block_offset = block_end;
}
u32::MAX
}