use std::io;
use crossterm::terminal::{enable_raw_mode, disable_raw_mode};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use super::state::{TuiState, ViewMode};
use super::input::{apply_search_filter, ensure_selection_in_filtered};
use crossterm::event::{poll, read, Event, KeyCode};
use crossterm::event::{KeyEventKind};
use super::views::{
draw_heatmap_view,
draw_statistics_view,
draw_filetypes_view,
draw_timeline_view,
draw_help_overlay,
};
use crate::cli::CommonArgs;
use crate::git::GitRepo;
use crate::cache::Cache;
use crate::heat::{aggregate_weeks, fetch_commit_stats};
pub fn run(common: &CommonArgs, path: Option<String>) -> io::Result<()> {
let repo = GitRepo::open(common.repo.as_ref()).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let mut cache = Cache::new(common.cache.as_deref(), repo.path()).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let range = repo.resolve_range(common.since.as_deref(), common.until.as_deref()).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let stats = fetch_commit_stats(&repo, &mut cache, &range, common.include_merges, common.binary)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let weeks = aggregate_weeks(&stats, &cache, path.as_deref());
enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
let mut state = TuiState::default();
state.filtered_indices = (0..weeks.len()).collect();
terminal.clear()?;
loop {
let draw_result = terminal.draw(|f| {
let size = f.size();
if state.show_help {
draw_help_overlay(f, size);
return;
}
let chunks = ratatui::layout::Layout::default()
.direction(ratatui::layout::Direction::Vertical)
.constraints([
ratatui::layout::Constraint::Length(3),
ratatui::layout::Constraint::Min(0),
])
.split(size);
let tabs = ratatui::widgets::Tabs::new(vec!["Heatmap", "Stats", "Files", "Timeline"])
.block(ratatui::widgets::Block::default().borders(ratatui::widgets::Borders::ALL).title("View Mode"))
.highlight_style(ratatui::style::Style::default().fg(ratatui::style::Color::Yellow).add_modifier(ratatui::style::Modifier::BOLD))
.select(state.tab_index);
f.render_widget(tabs, chunks[0]);
state.view_mode = match state.tab_index {
0 => ViewMode::Heatmap,
1 => ViewMode::Statistics,
2 => ViewMode::FileTypes,
3 => ViewMode::Timeline,
_ => ViewMode::Heatmap,
};
match state.view_mode {
ViewMode::Heatmap => draw_heatmap_view(f, chunks[1], &weeks, &state),
ViewMode::Statistics => draw_statistics_view(f, chunks[1], &weeks, &state),
ViewMode::FileTypes => draw_filetypes_view(f, chunks[1], &weeks, &state),
ViewMode::Timeline => draw_timeline_view(f, chunks[1], &weeks, &state),
}
});
if let Err(e) = draw_result {
eprintln!("TUI draw error: {}", e);
}
if poll(std::time::Duration::from_millis(200))? {
if let Event::Key(key_event) = read()? {
if key_event.kind != KeyEventKind::Press {
continue;
}
if state.search_mode {
match key_event.code {
KeyCode::Esc => {
state.search_mode = false;
state.search_query.clear();
state.filtered_indices = (0..weeks.len()).collect();
}
KeyCode::Enter => {
state.search_mode = false;
apply_search_filter(&weeks, &mut state);
}
KeyCode::Backspace => {
state.search_query.pop();
apply_search_filter(&weeks, &mut state);
}
KeyCode::Char(c) => {
state.search_query.push(c);
apply_search_filter(&weeks, &mut state);
}
_ => {}
}
} else {
match key_event.code {
KeyCode::Char('q') => break,
KeyCode::Char('h') | KeyCode::F(1) => state.show_help = !state.show_help,
KeyCode::Char('/') => {
state.search_mode = true;
state.search_query.clear();
}
KeyCode::Tab => {
state.tab_index = (state.tab_index + 1) % 4;
}
KeyCode::BackTab => {
state.tab_index = if state.tab_index == 0 { 3 } else { state.tab_index - 1 };
}
KeyCode::Left | KeyCode::Char('j') => {
if state.selected > 0 {
state.selected -= 1;
ensure_selection_in_filtered(&mut state);
}
}
KeyCode::Right | KeyCode::Char('k') => {
if state.selected + 1 < weeks.len() {
state.selected += 1;
ensure_selection_in_filtered(&mut state);
}
}
KeyCode::Home => {
state.selected = 0;
ensure_selection_in_filtered(&mut state);
}
KeyCode::End => {
state.selected = weeks.len().saturating_sub(1);
ensure_selection_in_filtered(&mut state);
}
KeyCode::PageUp => {
state.selected = state.selected.saturating_sub(10);
ensure_selection_in_filtered(&mut state);
}
KeyCode::PageDown => {
state.selected = std::cmp::min(state.selected + 10, weeks.len().saturating_sub(1));
ensure_selection_in_filtered(&mut state);
}
_ => {}
}
}
}
}
}
terminal.clear()?;
disable_raw_mode()?;
Ok(())
}