use crossterm::{
cursor,
event::{read, Event, KeyCode, KeyEvent},
execute, style, terminal,
terminal::{disable_raw_mode, enable_raw_mode, ClearType},
};
use std::{
env,
error::Error,
io::{self, Write},
};
use txtv::{Channel, TextTvPage, MIN_PAGE};
fn print_status(channel: Channel) -> Result<(), Box<dyn Error>> {
execute!(
io::stdout(),
cursor::MoveToColumn(0),
cursor::MoveToNextLine(1),
)?;
println!(
"← prev → next g: go to page q: quit (now on {})",
channel
);
Ok(())
}
fn prompt_goto() -> Result<Option<Channel>, Box<dyn Error>> {
let mut input = String::new();
execute!(io::stdout(), cursor::MoveToColumn(0))?;
print!("Go to page (100–801): ");
io::stdout().flush()?;
loop {
if let Event::Key(KeyEvent { code, .. }) = read()? {
match code {
KeyCode::Char(c) if c.is_ascii_digit() => {
input.push(c);
print!("{}", c);
io::stdout().flush()?;
}
KeyCode::Backspace => {
if input.pop().is_some() {
execute!(
io::stdout(),
cursor::MoveLeft(1),
style::Print(" "),
cursor::MoveLeft(1),
)?;
io::stdout().flush()?;
}
}
KeyCode::Enter => break,
KeyCode::Esc => {
execute!(
io::stdout(),
cursor::MoveToColumn(0),
terminal::Clear(ClearType::CurrentLine),
)?;
io::stdout().flush()?;
return Ok(None);
}
_ => {}
}
}
}
if let Ok(n) = input.parse::<i32>() {
let channel = Channel::new(n);
return Ok(Some(channel));
}
Ok(None)
}
fn print_page_not_found(page: Channel) {
let _ = execute!(
io::stdout(),
style::SetForegroundColor(style::Color::Red),
style::Print(format!("Page {} not found\n", page)),
style::ResetColor,
);
}
fn print_version() {
println!("txtv {}", env!("CARGO_PKG_VERSION"));
}
fn print_help() {
print_version();
println!();
println!("{}", env!("CARGO_PKG_DESCRIPTION"));
println!();
println!("Usage: txtv [PAGE]");
println!();
println!("Arguments:");
println!(" [PAGE] Page number to open (100–801) [default: 100]");
println!();
println!("Options:");
println!(" -h, --help Print help");
println!(" -v, --version Print version");
println!();
println!("Keybindings:");
println!(" ← Previous page");
println!(" → Next page");
println!(" g Go to a specific page");
println!(" q Quit");
}
fn main() -> Result<(), Box<dyn Error>> {
if let Some(arg) = env::args().nth(1) {
match arg.as_str() {
"-v" | "--version" => {
print_version();
return Ok(());
}
"-h" | "--help" => {
print_help();
return Ok(());
}
_ => {}
}
}
execute!(io::stdout(), cursor::Hide)?;
let channel = env::args()
.nth(1)
.and_then(|s| s.parse::<i32>().ok())
.map(Channel::new)
.unwrap_or_else(|| Channel::new(MIN_PAGE));
let mut page = TextTvPage::fetch(channel)?;
page.clear_screen()?;
page.show()?;
print_status(page.channel())?;
enable_raw_mode()?;
loop {
if let Event::Key(KeyEvent { code, .. }) = read()? {
match code {
KeyCode::Left => {
if let Ok(new_page) = page.prev_page() {
page = new_page;
print_status(page.channel())?;
}
}
KeyCode::Right => {
if let Ok(new_page) = page.next_page() {
page = new_page;
print_status(page.channel())?;
}
}
KeyCode::Char('g') => {
if let Some(target_channel) = prompt_goto()? {
page.clear_screen()?;
if let Ok(new_page) = TextTvPage::fetch(target_channel) {
page = new_page;
page.show()?;
print_status(page.channel())?;
} else {
print_page_not_found(target_channel);
}
}
}
KeyCode::Char('q') => break,
_ => {}
}
}
}
disable_raw_mode()?;
execute!(io::stdout(), cursor::Show)?;
Ok(())
}