use clap::Parser;
use ratatui::crossterm::{event, terminal, ExecutableCommand};
use ratatui::prelude::*;
use std::panic;
mod app;
mod config;
mod data;
mod error;
mod io;
mod ui;
pub use config::Config;
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Arguments {
target_folder: Option<String>,
#[arg(short, long)]
style: Option<String>,
#[arg(short, long)]
license: bool,
}
fn main() {
let args = Arguments::parse();
if args.license {
print_license();
}
init_hooks().expect("Error in hook initialization.");
let mut terminal = init_terminal().expect("Error in terminal initialization.");
let (mut app, errors) =
app::App::new(args, |message| draw_loading_screen(&mut terminal, message));
let mut current_error: Option<error::RucolaError> = errors.into_iter().next_back();
'main: loop {
terminal
.draw(|frame: &mut Frame| {
let area = frame.area();
let buf = frame.buffer_mut();
if area.width < 90 || area.height < 25 {
current_error = Some(error::RucolaError::SmallArea);
}
let app_area = match ¤t_error {
Some(e) => {
let areas = Layout::vertical([Constraint::Fill(1), Constraint::Length(1)])
.split(area);
Widget::render(e.to_ratatui(), areas[1], buf);
areas[0]
}
None => area,
};
Widget::render(ratatui::widgets::Clear, app_area, buf);
app.draw(app_area, buf);
})
.expect("Error in terminal drawing.");
let maybe_keypress = if event::poll(std::time::Duration::from_millis(500))
.expect("Error in event polling.")
{
current_error = None;
match event::read().expect("Error in event reading.") {
event::Event::Key(key) if key.kind == event::KeyEventKind::Press => Some(key),
_ => None,
}
} else {
None
};
match app.update(maybe_keypress) {
Ok(ui::TerminalMessage::Quit) => {
break 'main;
}
Ok(ui::TerminalMessage::None) => {}
Ok(ui::TerminalMessage::OpenExternalCommand(mut cmd)) => {
restore_terminal().expect("Error in terminal restoration.");
cmd.status().expect("Error in command execution.");
terminal = init_terminal().expect("Error in terminal re-initialization.");
}
Err(e) => current_error = Some(e),
}
}
restore_terminal().expect("Error in terminal restoration.");
}
fn init_hooks() -> error::Result<()> {
let original_hook = panic::take_hook();
panic::set_hook(Box::new(move |panic_info| {
let _ = restore_terminal();
original_hook(panic_info);
}));
Ok(())
}
fn init_terminal() -> std::io::Result<Terminal<impl ratatui::backend::Backend>> {
std::io::stdout().execute(terminal::EnterAlternateScreen)?;
terminal::enable_raw_mode()?;
let mut terminal = Terminal::new(CrosstermBackend::new(std::io::stdout()))?;
terminal.clear()?;
Ok(terminal)
}
fn restore_terminal() -> std::io::Result<()> {
std::io::stdout().execute(terminal::LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
Ok(())
}
fn print_license() {
print!("Rucola is released under the GNU General Public License v3, available at <https://www.gnu.org/licenses/gpl-3.0>.
Copyright (C) 2024 Linus Mußmächer <linus@mussmaecher.de>
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
");
}
fn draw_loading_screen(terminal: &mut Terminal<impl ratatui::backend::Backend>, message: &str) {
let _ = terminal.draw(|frame| {
frame.render_widget(
ratatui::widgets::Paragraph::new(message).alignment(Alignment::Center),
Layout::vertical([
Constraint::Fill(1),
Constraint::Length(5),
Constraint::Fill(1),
])
.split(frame.area())[1],
);
});
}