Files
chkr/src/main.rs
2026-01-23 23:15:49 +01:00

118 lines
3.7 KiB
Rust

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<dyn std::error::Error>> {
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<String> = 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<ListItem> = 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(())
}