mod app;
mod browser;
mod form;
mod theme;
mod view;
use std::io::{self, Stdout};
use std::panic;
use anyhow::{bail, Context, Result};
use appimg_core::{list, Paths};
use ratatui::backend::CrosstermBackend;
use ratatui::crossterm::event::{self, Event};
use ratatui::crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use ratatui::crossterm::{execute, ExecutableCommand};
use ratatui::Terminal;
use crate::ui::Ui;
use crate::Outcome;
use self::app::{Action, App};
type Tui = Terminal<CrosstermBackend<Stdout>>;
pub fn run(paths: &Paths, ui: &Ui) -> Result<Outcome> {
if !ui.is_interactive() {
bail!("the terminal interface needs a terminal, try `appimg list`");
}
let mut app = App::new(paths.clone())?;
let mut terminal = enter().context("cannot set up the terminal")?;
let result = event_loop(&mut terminal, &mut app);
leave(&mut terminal)?;
result?;
Ok(Outcome::Done)
}
fn event_loop(terminal: &mut Tui, app: &mut App) -> Result<()> {
loop {
terminal.draw(|frame| view::draw(frame, app))?;
if app.quit {
return Ok(());
}
match event::read()? {
Event::Key(key) => app.on_key(key),
Event::Resize(_, _) => continue,
_ => continue,
}
if let Some(action) = app.take_pending() {
terminal.draw(|frame| view::draw(frame, app))?;
match action {
Action::Edit(slug) => run_editor(terminal, app, &slug)?,
action => {
if let Err(error) = app.run_action(action) {
app.status = Some(format!("{error:#}"));
}
}
}
}
}
}
fn run_editor(terminal: &mut Tui, app: &mut App, slug: &str) -> Result<()> {
let installed = list::find(&app.paths, slug)?;
leave(terminal)?;
let result = crate::commands::edit::edit_entry(&app.paths, &installed.desktop_entry_path);
*terminal = enter()?;
terminal.clear()?;
app.reload()?;
app.select_slug(slug);
app.status = Some(match result {
Ok(edited) if !edited.changed => "Nothing changed.".to_string(),
Ok(edited) => match edited.warnings.first() {
Some(warning) => {
format!("Updated {}. desktop-file-validate: {warning}", installed.name)
}
None => format!("Updated the entry of {}.", installed.name),
},
Err(error) => format!("{error:#}"),
});
Ok(())
}
fn enter() -> Result<Tui> {
install_panic_hook();
enable_raw_mode()?;
io::stdout().execute(EnterAlternateScreen)?;
let terminal = Terminal::new(CrosstermBackend::new(io::stdout()))?;
Ok(terminal)
}
fn leave(terminal: &mut Tui) -> Result<()> {
restore();
terminal.show_cursor()?;
Ok(())
}
fn restore() {
let _ = disable_raw_mode();
let _ = execute!(io::stdout(), LeaveAlternateScreen);
}
fn install_panic_hook() {
static HOOK: std::sync::Once = std::sync::Once::new();
HOOK.call_once(|| {
let previous = panic::take_hook();
panic::set_hook(Box::new(move |info| {
restore();
previous(info);
}));
});
}