#![allow(dead_code)]
use crossterm::{
cursor::MoveTo,
execute, queue,
terminal::{Clear, ClearType},
};
use std::{cmp::Ordering, convert::TryInto, io::Write};
use super::term;
use crate::screen::Row;
use crate::{error::MinusError, minus_core, LineNumbers, PagerState};
#[derive(Debug, PartialEq, Eq)]
pub enum AppendStyle<'a> {
PartialUpdate(&'a [Row]),
FullRedraw,
}
pub fn draw_for_change(
out: &mut impl Write,
ps: &mut PagerState,
new_upper_mark: &mut usize,
) -> Result<(), MinusError> {
let line_count = ps.screen.formatted_lines_count();
let writable_rows = ps.rows.saturating_sub(1);
let lower_bound = ps.upper_mark.saturating_add(writable_rows.min(line_count));
let new_lower_bound = new_upper_mark.saturating_add(writable_rows.min(line_count));
if new_lower_bound > line_count {
*new_upper_mark = line_count.saturating_sub(writable_rows);
}
let delta = new_upper_mark.abs_diff(ps.upper_mark);
let normalized_delta = delta.min(writable_rows);
let lines = match (*new_upper_mark).cmp(&ps.upper_mark) {
Ordering::Greater => {
queue!(
out,
crossterm::terminal::ScrollUp(normalized_delta.try_into().unwrap())
)?;
term::move_cursor(
out,
0,
ps.rows
.saturating_sub(normalized_delta + 1)
.try_into()
.unwrap(),
false,
)?;
queue!(out, Clear(ClearType::CurrentLine))?;
if delta < writable_rows {
ps.screen
.get_formatted_lines_with_bounds(lower_bound, new_lower_bound)
} else {
ps.screen.get_formatted_lines_with_bounds(
*new_upper_mark,
new_upper_mark.saturating_add(normalized_delta),
)
}
}
Ordering::Less => {
execute!(
out,
crossterm::terminal::ScrollDown(normalized_delta.try_into().unwrap())
)?;
term::move_cursor(out, 0, 0, false)?;
ps.screen.get_formatted_lines_with_bounds(
*new_upper_mark,
new_upper_mark.saturating_add(normalized_delta),
)
}
Ordering::Equal => return Ok(()),
};
write_lines(
out,
lines,
ps.cols,
ps.screen.line_wrapping,
ps.left_mark,
ps.line_numbers.is_on(),
ps.screen.line_count(),
)?;
ps.upper_mark = *new_upper_mark;
if ps.show_prompt {
super::display::write_prompt(out, &ps.displayed_prompt, ps.rows.try_into().unwrap())?;
}
out.flush()?;
Ok(())
}
pub fn write_prompt(out: &mut impl Write, text: &str, rows: u16) -> Result<(), MinusError> {
write!(out, "{mv}\r{prompt}", mv = MoveTo(0, rows), prompt = text)?;
out.flush()?;
Ok(())
}
pub fn draw_full(out: &mut impl Write, ps: &mut PagerState) -> Result<(), MinusError> {
super::term::move_cursor(out, 0, 0, false)?;
queue!(out, Clear(ClearType::All))?;
write_from_pagerstate(out, ps)?;
let pager_rows: u16 = ps.rows.try_into().map_err(|_| MinusError::Conversion)?;
if ps.show_prompt {
write_prompt(out, &ps.displayed_prompt, pager_rows)?;
}
out.flush().map_err(MinusError::Draw)
}
pub fn draw_append_text(
out: &mut impl Write,
rows: usize,
prev_unterminated: usize,
prev_fmt_lines_count: usize,
append_style: &AppendStyle,
) -> Result<(), MinusError> {
let AppendStyle::PartialUpdate(fmt_text) = append_style else {
unreachable!()
};
if prev_fmt_lines_count < rows {
term::move_cursor(
out,
0,
prev_fmt_lines_count
.saturating_sub(prev_unterminated)
.try_into()
.unwrap(),
false,
)?;
let available_rows = rows.saturating_sub(
prev_fmt_lines_count
.saturating_sub(prev_unterminated)
.saturating_add(1),
);
let num_appendable = fmt_text.len().min(available_rows);
if num_appendable >= 1 {
crossterm::execute!(out, crossterm::terminal::Clear(ClearType::CurrentLine))?;
}
write!(out, "{}", fmt_text[0..num_appendable].join("\n\r"))?;
out.flush()?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn write_text_checked(
out: &mut impl Write,
lines: &[String],
mut upper_mark: usize,
rows: usize,
cols: usize,
line_wrapping: bool,
left_mark: usize,
line_numbers: LineNumbers,
total_line_count: usize,
) -> Result<(), MinusError> {
let line_count = lines.len();
let writable_rows = rows.saturating_sub(1);
let mut lower_mark = upper_mark.saturating_add(writable_rows.min(line_count));
if lower_mark > line_count {
upper_mark = line_count.saturating_sub(writable_rows);
lower_mark = upper_mark.saturating_add(writable_rows.min(line_count));
}
let display_lines: &[String] = &lines[upper_mark..lower_mark];
term::move_cursor(out, 0, 0, false)?;
term::clear_entire_screen(out, false)?;
write_lines(
out,
display_lines,
cols,
line_wrapping,
left_mark,
line_numbers.is_on(),
total_line_count,
)
}
pub fn write_from_pagerstate(out: &mut impl Write, ps: &mut PagerState) -> Result<(), MinusError> {
let line_count = ps.screen.formatted_lines_count();
let writable_rows = ps.rows.saturating_sub(1);
let lower_mark = ps.upper_mark.saturating_add(writable_rows.min(line_count));
if lower_mark > line_count {
ps.upper_mark = line_count.saturating_sub(writable_rows);
}
let display_lines: &[String] = ps
.screen
.get_formatted_lines_with_bounds(ps.upper_mark, lower_mark);
write_lines(
out,
display_lines,
ps.cols,
ps.screen.line_wrapping,
ps.left_mark,
ps.line_numbers.is_on(),
ps.screen.line_count(),
)
}
pub fn write_lines(
out: &mut impl Write,
lines: &[String],
cols: usize,
line_wrapping: bool,
left_mark: usize,
line_numbers: bool,
line_count: usize,
) -> crate::Result {
if line_wrapping {
write_raw_lines(out, lines, Some("\r"))
} else {
write_lines_in_horizontal_scroll(out, lines, cols, left_mark, line_numbers, line_count)
}
}
pub fn write_lines_in_horizontal_scroll(
out: &mut impl Write,
lines: &[String],
cols: usize,
start: usize,
line_numbers: bool,
line_count: usize,
) -> crate::Result {
let line_number_ascii_seq_len = if line_numbers { 8 } else { 0 };
let line_number_padding = if line_numbers {
minus_core::utils::digits(line_count) + LineNumbers::EXTRA_PADDING + 3
} else {
0
};
let shifted_start = if line_numbers {
start + line_number_padding + line_number_ascii_seq_len
} else {
start
};
for line in lines {
let end = shifted_start + cols.min(line.len().saturating_sub(shifted_start))
- line_number_padding;
if start < line.len() {
if line_numbers {
writeln!(
out,
"\r{}{}",
&line[0..line_number_padding + line_number_ascii_seq_len],
&line[shifted_start..end]
)?;
} else {
writeln!(out, "\r{}", &line[shifted_start..end])?;
}
} else {
writeln!(out, "\r")?;
}
}
Ok(())
}
pub fn write_raw_lines(
out: &mut impl Write,
lines: &[String],
initial: Option<&str>,
) -> Result<(), MinusError> {
for line in lines {
writeln!(out, "{}{line}", initial.unwrap_or(""))?;
}
Ok(())
}
#[cfg(test)]
mod tests;