use std::fmt::Write;
use orfail::OrFail;
use tuinix::TerminalStyle;
use crate::{buffer::TextLine, buffer::TextPosition, mame::TerminalFrame, state::State};
#[derive(Debug)]
pub struct TextAreaRenderer;
impl TextAreaRenderer {
pub fn render(&self, state: &State, frame: &mut TerminalFrame) -> orfail::Result<()> {
let size = frame.size();
let available_rows = size.rows;
let start_row = state.viewport.row;
let end_row = (start_row + available_rows).min(state.buffer.text.len());
for (screen_row, buffer_row) in (start_row..end_row).enumerate() {
if screen_row > 0 {
writeln!(frame).or_fail()?;
}
if let Some(line) = state.buffer.text.get(buffer_row) {
self.render_line(line, state.viewport.col, frame, state, buffer_row)?;
}
}
let rendered_rows = end_row.saturating_sub(start_row);
for _ in rendered_rows..available_rows {
writeln!(frame).or_fail()?;
}
Ok(())
}
fn render_line(
&self,
line: &TextLine,
start_col: usize,
frame: &mut TerminalFrame,
state: &State,
line_row: usize,
) -> orfail::Result<()> {
let marked_region = if let Some(mark_pos) = state.mark {
let cursor_pos = state.cursor_position();
self.calculate_line_marked_region(mark_pos, cursor_pos, line_row)
} else {
None
};
for (current_col, ch) in line.char_cols() {
if current_col >= start_col {
let is_marked = marked_region.as_ref().map_or(false, |(start, end)| {
current_col >= *start && current_col < *end
});
if is_marked {
let style = TerminalStyle::new().reverse();
let reset = TerminalStyle::RESET;
write!(frame, "{style}{ch}{reset}").or_fail()?;
} else {
write!(frame, "{ch}").or_fail()?;
}
}
}
Ok(())
}
fn calculate_line_marked_region(
&self,
mark_pos: TextPosition,
cursor_pos: TextPosition,
line_row: usize,
) -> Option<(usize, usize)> {
let (start_pos, end_pos) = if mark_pos <= cursor_pos {
(mark_pos, cursor_pos)
} else {
(cursor_pos, mark_pos)
};
if line_row < start_pos.row || line_row > end_pos.row {
return None;
}
let start_col = if line_row == start_pos.row {
start_pos.col
} else {
0
};
let end_col = if line_row == end_pos.row {
end_pos.col
} else {
usize::MAX
};
if start_col < end_col {
Some((start_col, end_col))
} else {
None
}
}
}