use crate::tui::{render, TuiState}; use crossterm::{ event::{self, Event, KeyCode, KeyEventKind}, terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, ExecutableCommand, }; use ratatui::{backend::CrosstermBackend, Terminal}; use std::io::Stdout; use std::path::PathBuf; use crate::models::TechDebtItem; pub struct TuiApp { state: TuiState, terminal: Terminal>, } impl TuiApp { pub fn new(items: Vec, path: PathBuf) -> Self { let state = TuiState::new(items, path); let terminal = Terminal::new(CrosstermBackend::new(std::io::stdout())) .expect("Failed to create terminal"); Self { state, terminal } } pub fn run(&mut self) -> anyhow::Result<()> { enable_raw_mode()?; std::io::stdout().execute(Clear(ClearType::All))?; loop { self.terminal.draw(|f| render(f, &self.state))?; if let Event::Key(key) = event::read()? { if key.kind == KeyEventKind::Press { match self.handle_input(key.code) { Ok(should_exit) => { if should_exit { break; } } Err(e) => { eprintln!("Error handling input: {}", e); } } } } } disable_raw_mode()?; std::io::stdout().execute(Clear(ClearType::All))?; Ok(()) } fn handle_input(&mut self, code: KeyCode) -> anyhow::Result { let mut should_exit = false; match code { KeyCode::Char('q') | KeyCode::Esc => { should_exit = true; } KeyCode::Tab => { self.state.current_view = match self.state.current_view { crate::tui::View::Dashboard => crate::tui::View::List, crate::tui::View::List => crate::tui::View::Dashboard, crate::tui::View::Detail => crate::tui::View::List, crate::tui::View::Export => crate::tui::View::Dashboard, }; } KeyCode::Up => { if self.state.selected_index > 0 { self.state.selected_index -= 1; } } KeyCode::Down => { let filtered_len = self.state.filtered_items().len(); if filtered_len > 0 && self.state.selected_index < filtered_len - 1 { self.state.selected_index += 1; } } KeyCode::Enter => { if self.state.current_view == crate::tui::View::List { self.state.current_view = crate::tui::View::Detail; } } KeyCode::Char('/') => { self.enable_filter_mode()?; } KeyCode::Char('f') => { self.enable_filter_mode()?; } KeyCode::Char('s') => { self.state.sort_order = match self.state.sort_order { crate::tui::SortOrder::Priority => crate::tui::SortOrder::File, crate::tui::SortOrder::File => crate::tui::SortOrder::Line, crate::tui::SortOrder::Line => crate::tui::SortOrder::Keyword, crate::tui::SortOrder::Keyword => crate::tui::SortOrder::Priority, }; } KeyCode::Char('c') => { self.state.filter_priority = None; self.state.filter_text.clear(); } KeyCode::Char('1') => { self.state.filter_priority = Some(crate::models::Priority::Critical); } KeyCode::Char('2') => { self.state.filter_priority = Some(crate::models::Priority::High); } KeyCode::Char('3') => { self.state.filter_priority = Some(crate::models::Priority::Medium); } KeyCode::Char('4') => { self.state.filter_priority = Some(crate::models::Priority::Low); } _ => {} } Ok(should_exit) } fn enable_filter_mode(&mut self) -> anyhow::Result<()> { disable_raw_mode()?; println!("Enter filter text (or press ESC to cancel): "); std::io::stdout().flush()?; let mut input = String::new(); std::io::stdin().read_line(&mut input)?; input = input.trim_end().to_string(); if input.is_empty() || input.as_bytes().first() == Some(&0x1b) { self.state.filter_text.clear(); } else { self.state.filter_text = input; } enable_raw_mode()?; std::io::stdout().execute(Clear(ClearType::All))?; Ok(()) } }