use crate::domain as domain_models;
use crossterm::{
cursor::{MoveTo, position},
execute,
style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
terminal::{ScrollUp, size},
};
use std::io::{Result, Stdout, Write, stdout};
fn ensure_space(stdout: &mut Stdout, y: u16, lines_needed: u16, height: u16) -> Result<u16> {
let available_rows = height.saturating_sub(1); if y + lines_needed > available_rows {
let scroll_lines = y + lines_needed - available_rows;
execute!(stdout, ScrollUp(scroll_lines))?;
let new_y = y.saturating_sub(scroll_lines);
Ok(new_y)
} else {
Ok(y)
}
}
fn draw_box_around_lines(stdout: &mut impl Write, x: u16, y: u16, lines: &[String]) -> Result<()> {
let max_len = lines.iter().map(|l| l.len()).max().unwrap_or(0) as u16;
let width = max_len + 2;
execute!(stdout, MoveTo(x, y), Print("┌"))?;
for _ in 0..width {
execute!(stdout, Print("─"))?;
}
execute!(stdout, Print("┐"))?;
for (i, line) in lines.iter().enumerate() {
let line_y = y + 1 + i as u16;
execute!(stdout, MoveTo(x, line_y), Print("│ "))?;
execute!(
stdout,
MoveTo(x + 2, line_y),
SetForegroundColor(Color::White),
SetAttribute(Attribute::Bold),
Print(line),
ResetColor,
SetAttribute(Attribute::Reset),
)?;
let padding = (max_len as usize).saturating_sub(line.len());
let right_x = x + 2 + max_len;
execute!(stdout, MoveTo(right_x, line_y), Print(" │"))?;
if padding > 0 {
execute!(stdout, MoveTo(x + 2 + line.len() as u16, line_y))?;
for _ in 0..padding {
execute!(stdout, Print(" "))?;
}
}
}
execute!(stdout, MoveTo(x, y + 1 + lines.len() as u16), Print("└"))?;
for _ in 0..width {
execute!(stdout, Print("─"))?;
}
execute!(stdout, Print("┘"))?;
Ok(())
}
fn wrap_text(text: &str, max_width: usize) -> Vec<String> {
let mut lines = Vec::new();
let mut current_line = String::new();
for word in text.split_whitespace() {
let word_len = word.len();
let line_len = current_line.len();
if line_len + if line_len > 0 { 1 } else { 0 } + word_len > max_width
&& !current_line.is_empty()
{
lines.push(current_line);
current_line = String::new();
}
if !current_line.is_empty() {
current_line.push(' ');
}
current_line.push_str(word);
}
if !current_line.is_empty() {
lines.push(current_line);
}
lines
}
pub fn render_page(page: &domain_models::Page) -> Result<()> {
let mut stdout = stdout();
let (x, mut y) = position()?; let (term_width, term_height) = size()?; let wrap_width = ((term_width as f32) * 0.7).floor() as usize;
let header_line = format!(
"📆 {} - Found {} entr{}",
page.date.date(),
page.entries.len(),
if page.entries.len() == 1 { "y" } else { "ies" }
);
let header_lines = wrap_text(&header_line, wrap_width);
let header_box_height = header_lines.len() as u16 + 2;
y = ensure_space(&mut stdout, y, header_box_height, term_height)?;
draw_box_around_lines(&mut stdout, x, y, &header_lines)?;
y += header_box_height + 1;
let mut first = true;
for entry in &page.entries {
if !first {
y += 1;
}
first = false;
let mut display_title = entry.title.clone();
if entry.starred {
display_title = format!("✨ {}", display_title);
}
display_title = format!("[{}] {}", entry.date.format("%H:%M:%S"), display_title);
let title_lines = wrap_text(&display_title, wrap_width);
let body_lines = wrap_text(&entry.body, wrap_width);
let total_lines_needed = title_lines.len() as u16 + body_lines.len() as u16 + 1;
y = ensure_space(&mut stdout, y, total_lines_needed, term_height)?;
for line in &title_lines {
execute!(
stdout,
MoveTo(x, y),
SetForegroundColor(Color::Cyan),
SetAttribute(Attribute::Bold),
Print(line),
ResetColor,
SetAttribute(Attribute::Reset)
)?;
y += 1;
}
for line in &body_lines {
execute!(stdout, MoveTo(x, y), Print("| "), Print(line))?;
y += 1;
}
if !entry.tags.is_empty() {
execute!(stdout, MoveTo(x, y), Print("|"))?;
y += 1;
for tag in &entry.tags {
let tag_line = format!("#{}", tag);
execute!(
stdout,
MoveTo(x, y),
Print("| "),
SetForegroundColor(Color::Yellow),
SetAttribute(Attribute::Bold),
Print(tag_line),
ResetColor,
SetAttribute(Attribute::Reset)
)?;
y += 1;
}
execute!(stdout, MoveTo(x, y), Print("|"))?;
y += 1;
} else {
y += 1;
}
}
y = ensure_space(&mut stdout, y, 2, term_height)?;
execute!(stdout, MoveTo(x, y),)?;
writeln!(stdout)?;
stdout.flush()?;
Ok(())
}