use std::{collections::BTreeSet, fmt::Write};
use orfail::OrFail;
use tuinix::TerminalStyle;
use unicode_width::UnicodeWidthChar;
use crate::{buffer::TextPosition, editor::Editor, tuinix_ext::TerminalFrame};
#[derive(Debug)]
pub struct TextView {
scroll_offset: TextPosition,
}
impl TextView {
pub fn new() -> Self {
Self {
scroll_offset: TextPosition::default(),
}
}
pub fn render(&mut self, editor: &Editor, frame: &mut TerminalFrame) -> orfail::Result<()> {
let terminal_size = frame.size();
self.adjust_scroll_offset_for_cursor(editor, terminal_size.rows, terminal_size.cols);
let marked_positions: BTreeSet<TextPosition> = editor
.marker
.as_ref()
.map(|marker| marker.marked_positions().collect())
.unwrap_or_default();
for (line_index, line) in editor
.buffer
.lines()
.skip(self.scroll_offset.row)
.take(terminal_size.rows)
.enumerate()
{
let current_row = self.scroll_offset.row + line_index;
let mut current_col = self.scroll_offset.col;
for c in line
.chars()
.skip(self.scroll_offset.col)
.take(terminal_size.cols)
{
let c = editor.buffer.filter.apply(c);
let position = TextPosition {
row: current_row,
col: current_col,
};
let (c, is_clipboard) = editor
.clipboard
.as_ref()
.and_then(|cb| cb.get(position).map(|c| (c, true)))
.unwrap_or((c, false));
current_col += c.width().unwrap_or_default();
if is_clipboard {
let style = TerminalStyle::new().reverse().underline();
let reset = TerminalStyle::RESET;
write!(frame, "{}{}{}", style, c, reset).or_fail()?;
} else if marked_positions.contains(&position) {
let style = TerminalStyle::new().reverse();
let reset = TerminalStyle::RESET;
write!(frame, "{}{}{}", style, c, reset).or_fail()?;
} else {
write!(frame, "{}", c).or_fail()?;
}
}
writeln!(frame).or_fail()?;
}
Ok(())
}
fn adjust_scroll_offset_for_cursor(
&mut self,
editor: &Editor,
terminal_rows: usize,
terminal_cols: usize,
) {
let cursor = editor.cursor;
if cursor.row < self.scroll_offset.row {
self.scroll_offset.row = cursor.row;
} else if cursor.row >= self.scroll_offset.row + terminal_rows {
self.scroll_offset.row = cursor.row.saturating_sub(terminal_rows.saturating_sub(1));
}
if cursor.col < self.scroll_offset.col {
self.scroll_offset.col = cursor.col;
} else if cursor.col >= self.scroll_offset.col + terminal_cols {
self.scroll_offset.col = cursor.col.saturating_sub(terminal_cols.saturating_sub(1));
}
}
pub fn cursor_terminal_position(&self, editor: &Editor) -> tuinix::TerminalPosition {
tuinix::TerminalPosition {
row: editor.cursor.row.saturating_sub(self.scroll_offset.row),
col: editor.cursor.col.saturating_sub(self.scroll_offset.col),
}
}
}