use clap::Parser;
use 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() -> error::Result<()> {
let args = Arguments::parse();
if args.license {
print_license();
return Ok(());
}
init_hooks()?;
let mut terminal = init_terminal()?;
draw_loading_screen(&mut terminal)?;
let (mut app, errors) = app::App::new(args);
let mut current_error: Option<error::RucolaError> = errors.into_iter().last();
'main: loop {
terminal.draw(|frame: &mut Frame| {
let area = frame.size();
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);
})?;
let maybe_keypress = if event::poll(std::time::Duration::from_millis(500))? {
current_error = None;
match event::read()? {
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()?;
cmd.status()?;
terminal = init_terminal()?;
}
Err(e) => current_error = Some(e),
}
}
restore_terminal()?;
Ok(())
}
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@gmail.com>
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>,
) -> Result<ratatui::CompletedFrame, std::io::Error> {
terminal.draw(|frame| {
frame.render_widget(
ratatui::widgets::Paragraph::new("Indexing...").alignment(Alignment::Center),
Layout::vertical([
Constraint::Fill(1),
Constraint::Length(3),
Constraint::Fill(1),
])
.split(frame.size())[1],
);
})
}