mod app;
mod ui;
use anyhow::Result;
use app::{SplitApp, SplitMode};
use crossterm::{
event::{self, Event, KeyCode, KeyModifiers},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io;
use std::time::Duration;
pub fn run() -> Result<()> {
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let result = SplitApp::new().and_then(|mut app| run_app(&mut terminal, &mut app));
disable_raw_mode()?;
execute!(terminal.backend_mut(), LeaveAlternateScreen)?;
terminal.show_cursor()?;
result
}
fn run_app(
terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
app: &mut SplitApp,
) -> Result<()> {
loop {
terminal.draw(|f| ui::render(f, app))?;
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
handle_key(app, key.code, key.modifiers)?;
}
}
if app.should_quit {
break;
}
if app.execute_requested {
app.execute_split()?;
break;
}
}
Ok(())
}
fn handle_key(app: &mut SplitApp, code: KeyCode, modifiers: KeyModifiers) -> Result<()> {
match &app.mode {
SplitMode::Normal => handle_normal_key(app, code, modifiers),
SplitMode::Naming => handle_naming_key(app, code),
SplitMode::Confirm => handle_confirm_key(app, code),
SplitMode::Help => handle_help_key(app, code),
}
}
fn handle_normal_key(app: &mut SplitApp, code: KeyCode, modifiers: KeyModifiers) -> Result<()> {
match code {
KeyCode::Char('q') | KeyCode::Esc => app.should_quit = true,
KeyCode::Char('?') => app.mode = SplitMode::Help,
KeyCode::Up | KeyCode::Char('k') => app.select_previous(),
KeyCode::Down | KeyCode::Char('j') => app.select_next(),
KeyCode::Char('s') => {
if app.can_split_at_current() {
app.input_buffer.clear();
app.input_cursor = 0;
app.mode = SplitMode::Naming;
} else {
app.status_message = Some("Cannot split here".to_string());
}
}
KeyCode::Char('d') => {
app.remove_split_at_current();
}
KeyCode::Enter => {
if !app.split_points.is_empty() {
app.mode = SplitMode::Confirm;
} else {
app.status_message = Some("No split points defined".to_string());
}
}
KeyCode::Char('K') if modifiers.contains(KeyModifiers::SHIFT) => {
app.move_split_up();
}
KeyCode::Char('J') if modifiers.contains(KeyModifiers::SHIFT) => {
app.move_split_down();
}
_ => {}
}
Ok(())
}
fn handle_naming_key(app: &mut SplitApp, code: KeyCode) -> Result<()> {
match code {
KeyCode::Esc => {
app.mode = SplitMode::Normal;
app.input_buffer.clear();
}
KeyCode::Enter => {
let name = app.input_buffer.trim().to_string();
if name.is_empty() {
app.status_message = Some("Branch name cannot be empty".to_string());
} else if app.branch_name_exists(&name) {
app.status_message = Some(format!("Branch '{}' already exists", name));
} else {
app.add_split_at_current(name);
app.mode = SplitMode::Normal;
}
app.input_buffer.clear();
}
KeyCode::Char(c) => {
app.input_buffer.insert(app.input_cursor, c);
app.input_cursor += 1;
}
KeyCode::Backspace => {
if app.input_cursor > 0 {
app.input_cursor -= 1;
app.input_buffer.remove(app.input_cursor);
}
}
KeyCode::Left => {
if app.input_cursor > 0 {
app.input_cursor -= 1;
}
}
KeyCode::Right => {
if app.input_cursor < app.input_buffer.len() {
app.input_cursor += 1;
}
}
_ => {}
}
Ok(())
}
fn handle_confirm_key(app: &mut SplitApp, code: KeyCode) -> Result<()> {
match code {
KeyCode::Char('y') | KeyCode::Char('Y') => {
app.execute_requested = true;
}
KeyCode::Char('n') | KeyCode::Char('N') | KeyCode::Esc => {
app.mode = SplitMode::Normal;
}
_ => {}
}
Ok(())
}
fn handle_help_key(app: &mut SplitApp, _code: KeyCode) -> Result<()> {
app.mode = SplitMode::Normal;
Ok(())
}