use std::io::{self, Read, Stdout}; use std::time::Duration; use crossterm::event::{self, Event, KeyCode}; use crossterm::execute; use crossterm::terminal::{ EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode, }; use ratatui::Terminal; use ratatui::backend::CrosstermBackend; use ratatui::layout::{Constraint, Direction, Layout}; use ratatui::style::{Color, Modifier, Style}; use ratatui::widgets::{Block, Borders, List, ListItem, ListState}; fn main() -> Result<(), Box> { let mut input = String::new(); io::stdin().read_to_string(&mut input)?; if input.trim().is_empty() { eprintln!("Provide text via stdin (pipe or heredoc). Example: \n cat file.txt | chkr"); return Ok(()); } let lines: Vec = input.lines().map(|s| s.to_string()).collect(); let mut marked = vec![false; lines.len()]; enable_raw_mode()?; let mut stdout = std::io::stdout(); execute!(stdout, EnterAlternateScreen)?; let backend = CrosstermBackend::new(stdout); let mut terminal = Terminal::new(backend)?; let mut state = ListState::default(); if !lines.is_empty() { state.select(Some(0)); } loop { terminal.draw(|f| { let size = f.area(); let chunks = Layout::default() .direction(Direction::Vertical) .constraints([Constraint::Min(0)].as_ref()) .split(size); let items: Vec = lines .iter() .enumerate() .map(|(i, l)| { let prefix = if marked[i] { "[x]" } else { "[ ]" }; ListItem::new(format!("{} {}", prefix, l)) }) .collect(); let list = List::new(items) .block( Block::default() .borders(Borders::ALL) .title("Lines (space to mark, q to quit)"), ) .highlight_style( Style::default() .fg(Color::White) .bg(Color::Blue) .add_modifier(Modifier::BOLD), ); f.render_stateful_widget(list, chunks[0], &mut state); })?; // Event handling if event::poll(Duration::from_millis(100))? && let Event::Key(key) = event::read()? { match key.code { KeyCode::Char('q') => break, KeyCode::Up => { if let Some(i) = state.selected() && i > 0 { state.select(Some(i - 1)); } } KeyCode::Down => { if let Some(i) = state.selected() && i + 1 < lines.len() { state.select(Some(i + 1)); } } KeyCode::Char(' ') => { if let Some(i) = state.selected() && i < marked.len() { assert!(i < marked.len()); if marked[i] { marked[i] = false; } else { marked[i] = true; // move cursor down let next = lines.len().min(i + 1); state.select(Some(next)); } } } _ => {} } } } disable_raw_mode()?; let mut stdout: Stdout = std::io::stdout(); execute!(stdout, LeaveAlternateScreen)?; terminal.show_cursor()?; Ok(()) }