use crossterm::{
event::{self, Event, KeyCode, KeyEvent},
terminal::{disable_raw_mode, enable_raw_mode},
};
use std::io::{self, Write};
const DEFAULT_TERM_HEIGHT: usize = 24;
pub fn page_text(content: &str) -> io::Result<()> {
let lines: Vec<&str> = content.lines().collect();
let line_count = lines.len();
let term_height = get_terminal_height();
if line_count <= term_height - 2 {
println!("{}", content);
return Ok(());
}
page_lines(&lines, term_height)
}
fn get_terminal_height() -> usize {
terminal_size::terminal_size()
.map(|(_, terminal_size::Height(h))| h as usize)
.unwrap_or(DEFAULT_TERM_HEIGHT)
}
fn page_lines(lines: &[&str], term_height: usize) -> io::Result<()> {
let page_size = term_height - 2; let mut current_line = 0;
let total_lines = lines.len();
let mut stdout = io::stdout();
enable_raw_mode().map_err(io::Error::other)?;
let result = (|| -> io::Result<()> {
while current_line < total_lines {
let end_line = (current_line + page_size).min(total_lines);
for line in &lines[current_line..end_line] {
write!(stdout, "{}\r\n", line)?;
}
stdout.flush()?;
current_line = end_line;
if current_line >= total_lines {
break;
}
let remaining = total_lines - current_line;
write!(
stdout,
"\x1b[7m-- More ({} lines remaining) -- Press Space to continue, q to quit \x1b[0m",
remaining
)?;
stdout.flush()?;
loop {
if let Event::Key(KeyEvent { code, .. }) =
event::read().map_err(io::Error::other)?
{
match code {
KeyCode::Char(' ') => {
break;
}
KeyCode::Char('q') | KeyCode::Char('Q') | KeyCode::Esc => {
write!(stdout, "\r\x1b[K")?;
return Ok(());
}
_ => {
continue;
}
}
}
}
write!(stdout, "\r\x1b[K")?;
}
Ok(())
})();
disable_raw_mode().map_err(io::Error::other)?;
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_get_terminal_height() {
let height = get_terminal_height();
assert!(height > 0);
assert!(height <= 200); }
#[test]
fn test_short_content_no_paging() {
let content = "Line 1\nLine 2\nLine 3";
let result = page_text(content);
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_empty_content() {
let result = page_text("");
assert!(result.is_ok());
}
#[test]
fn test_single_line() {
let result = page_text("Single line");
assert!(result.is_ok());
}
#[test]
fn test_content_with_newlines() {
let content = "Line 1\nLine 2\nLine 3\nLine 4\n";
let result = page_text(content);
assert!(result.is_ok());
}
}