use crate::tui::events::{EventHandler, AppEvent};
use crate::tui::widgets::{FileExplorer, CodeViewer, AIChatWidget};
use crate::utils::code_analysis::CodeAnalyzer;
use anyhow::Result;
use ratatui::{
backend::CrosstermBackend,
layout::{Layout, Constraint, Direction},
Frame, Terminal,
};
use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use std::io;
#[derive(Clone)]
pub enum UIState {
FileExplorer,
CodeViewer,
AIChat,
}
pub struct StudioApp {
pub ui_state: UIState,
pub file_explorer: FileExplorer,
pub code_viewer: CodeViewer,
pub ai_chat: AIChatWidget,
pub code_analyzer: CodeAnalyzer,
pub should_quit: bool,
}
impl StudioApp {
pub fn new() -> Result<Self> {
let files = vec![
"main.rs".to_string(),
"Cargo.toml".to_string(),
"src/lib.rs".to_string(),
];
let code_content = "// Sample code content\nfn main() {\n println!(\"Hello, Kandil!\");\n}";
Ok(Self {
ui_state: UIState::FileExplorer,
file_explorer: FileExplorer::new(files),
code_viewer: CodeViewer::new(code_content),
ai_chat: AIChatWidget::new(),
code_analyzer: CodeAnalyzer::new()?,
should_quit: false,
})
}
pub async fn run(&mut self) -> Result<()> {
let backend = CrosstermBackend::new(io::stdout());
let mut terminal = Terminal::new(backend)?;
enable_raw_mode()?;
execute!(io::stdout(), EnterAlternateScreen)?;
let events = EventHandler::new(250);
loop {
terminal.draw(|f| self.ui(f))?;
match events.next().await? {
AppEvent::Tick => {}
AppEvent::Key(key_event) => {
if key_event.kind == crossterm::event::KeyEventKind::Press {
self.handle_key_events(key_event)?;
}
}
AppEvent::Mouse(mouse_event) => {
self.handle_mouse_events(mouse_event)?;
}
}
if self.should_quit {
break;
}
}
disable_raw_mode()?;
execute!(io::stdout(), LeaveAlternateScreen)?;
Ok(())
}
fn ui(&self, f: &mut Frame) {
let chunks = Layout::default()
.direction(Direction::Horizontal)
.constraints([
Constraint::Percentage(20), Constraint::Percentage(60), Constraint::Percentage(20), ])
.split(f.size());
f.render_widget(self.file_explorer.clone(), chunks[0]);
f.render_widget(self.code_viewer.clone(), chunks[1]);
f.render_widget(self.ai_chat.clone(), chunks[2]);
}
fn handle_key_events(&mut self, key_event: crossterm::event::KeyEvent) -> Result<()> {
match key_event.code {
crossterm::event::KeyCode::Char('q') | crossterm::event::KeyCode::Esc => {
self.should_quit = true;
}
crossterm::event::KeyCode::Tab => {
self.cycle_ui_state();
}
crossterm::event::KeyCode::Down => {
match self.ui_state {
UIState::FileExplorer => self.file_explorer.next(),
_ => {}
}
}
crossterm::event::KeyCode::Up => {
match self.ui_state {
UIState::FileExplorer => self.file_explorer.previous(),
_ => {}
}
}
crossterm::event::KeyCode::Enter => {
if let UIState::FileExplorer = self.ui_state {
self.ai_chat.add_message("File loaded!".to_string());
}
}
crossterm::event::KeyCode::Char('a') if key_event.modifiers.contains(crossterm::event::KeyModifiers::CONTROL) => {
self.ai_chat.add_message("Analyzing file with Tree-sitter...".to_string());
}
_ => {}
}
Ok(())
}
fn handle_mouse_events(&mut self, _mouse_event: crossterm::event::MouseEvent) -> Result<()> {
Ok(())
}
fn cycle_ui_state(&mut self) {
self.ui_state = match self.ui_state.clone() {
UIState::FileExplorer => UIState::CodeViewer,
UIState::CodeViewer => UIState::AIChat,
UIState::AIChat => UIState::FileExplorer,
};
}
}