147 lines
4.8 KiB
Rust
147 lines
4.8 KiB
Rust
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<CrosstermBackend<Stdout>>,
|
|
}
|
|
|
|
impl TuiApp {
|
|
pub fn new(items: Vec<TechDebtItem>, 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<bool> {
|
|
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(())
|
|
}
|
|
}
|