mod app;
mod completion;
mod engine;
mod ir;
mod keys;
mod lsp_client;
mod ui;
use clap::Parser as ClapParser;
use crossterm::{
event::{KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use ratatui::{Terminal, backend::CrosstermBackend};
use std::io::{self, stdout};
use std::panic;
use std::path::PathBuf;
#[derive(ClapParser)]
#[command(name = "seqr")]
#[command(version = env!("CARGO_PKG_VERSION"))]
#[command(about = "TUI REPL for Seq with IR visualization", long_about = None)]
struct Args {
file: Option<PathBuf>,
}
fn main() {
let args = Args::parse();
if let Err(e) = run(args.file.as_deref()) {
eprintln!("Error: {}", e);
std::process::exit(1);
}
}
fn run(file: Option<&std::path::Path>) -> Result<(), String> {
let original_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen);
original_hook(panic_info);
}));
enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen)
.map_err(|e| format!("Failed to enter alternate screen: {}", e))?;
let keyboard_enhancement_enabled = execute!(
stdout,
PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES)
)
.is_ok();
let backend = CrosstermBackend::new(stdout);
let mut terminal =
Terminal::new(backend).map_err(|e| format!("Failed to create terminal: {}", e))?;
let app_state = if let Some(path) = file {
app::App::with_file(path.to_path_buf())?
} else {
app::App::new()?
};
let result = run_app(&mut terminal, app_state);
let _ = disable_raw_mode();
if keyboard_enhancement_enabled {
let _ = execute!(terminal.backend_mut(), PopKeyboardEnhancementFlags);
}
let _ = execute!(terminal.backend_mut(), LeaveAlternateScreen);
let _ = terminal.show_cursor();
result.map_err(|e| format!("Application error: {}", e))
}
fn run_app(
terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>,
mut app: app::App,
) -> io::Result<()> {
use crossterm::event::{self, Event};
use std::time::Duration;
loop {
terminal.draw(|frame| app.render(frame))?;
if event::poll(Duration::from_millis(100))?
&& let Event::Key(key) = event::read()?
{
app.handle_key(key);
}
if app.should_quit {
break;
}
if app.should_edit {
app.should_edit = false;
open_in_editor(terminal, &app.session_path)?;
}
}
app.save_history();
Ok(())
}
fn open_in_editor(
terminal: &mut Terminal<CrosstermBackend<std::io::Stdout>>,
path: &std::path::Path,
) -> io::Result<()> {
use std::process::Command;
let _ = disable_raw_mode();
let _ = execute!(terminal.backend_mut(), LeaveAlternateScreen);
let _ = terminal.show_cursor();
let editor = std::env::var("EDITOR").unwrap_or_else(|_| "vi".to_string());
let parts = match shlex::split(&editor) {
Some(p) if !p.is_empty() => p,
_ => vec!["vi".to_string()],
};
let status = Command::new(&parts[0]).args(&parts[1..]).arg(path).status();
if let Err(e) = status {
eprintln!("Failed to open editor: {}", e);
std::thread::sleep(std::time::Duration::from_secs(1));
}
enable_raw_mode()?;
execute!(terminal.backend_mut(), EnterAlternateScreen)?;
terminal.hide_cursor()?;
terminal.clear()?;
Ok(())
}