use crate::background::Warmer;
use anyhow::Context;
use crossterm::event::{self, Event};
use mandible_extract::{default_tiers, resolve_tool, Runner};
use mandible_tui::app::App;
use mandible_tui::{clipboard, event as tui_event, layout, render, terminal, Effect};
use std::sync::Arc;
use std::time::Duration;
pub fn run(mut app: App) -> anyhow::Result<()> {
let mut term = terminal::init().context("failed to initialize the terminal")?;
let result = run_loop(&mut term, &mut app);
let restore_result = terminal::restore().context("failed to restore the terminal");
result.and(restore_result)
}
fn run_loop(term: &mut terminal::Term, app: &mut App) -> anyhow::Result<()> {
let runner = Arc::new(Runner::new(default_tiers()));
let resolved = resolve_tool(&app.tool);
let warmer = Warmer::new();
{
let root_path = vec![app.root.name.clone()];
app.mark_pending(root_path.clone());
warmer.submit(
Arc::clone(&runner),
resolved.clone(),
root_path,
app.root.clone(),
);
}
loop {
for warmed in warmer.drain() {
let node = warmed.result.node.clone();
let queued = warmer.warm_children(&runner, &resolved, &node, &warmed.path);
app.splice_filled_node(&warmed.path, node);
for path in queued {
app.mark_pending(path);
}
}
app.expire_status(std::time::Instant::now());
app.tick_search(10);
app.ensure_rows_fresh();
{
let size = term.size()?;
let area = ratatui::layout::Rect::new(0, 0, size.width, size.height);
let regions = layout::compute(area, app.focus);
app.tree_viewport = regions
.tree
.map(|rect| {
ratatui::widgets::Block::default()
.borders(ratatui::widgets::Borders::ALL)
.inner(rect)
.height as usize
})
.unwrap_or(0);
app.follow_selection();
}
term.draw(|frame| render::render(frame, app))?;
if !event::poll(Duration::from_millis(100))? {
continue;
}
match event::read()? {
Event::Key(key) => {
if let Some(effect) = tui_event::handle_key(app, key) {
if !apply_effect(app, effect, &runner, &resolved, &warmer) {
warmer.cancel();
return Ok(());
}
}
}
Event::Mouse(mouse) => {
let size = term.size()?;
let area = ratatui::layout::Rect::new(0, 0, size.width, size.height);
let regions = layout::compute(area, app.focus);
if let Some(effect) = tui_event::handle_mouse(app, mouse, ®ions) {
if !apply_effect(app, effect, &runner, &resolved, &warmer) {
warmer.cancel();
return Ok(());
}
}
}
Event::Resize(_, _) => {
}
_ => {}
}
}
}
fn apply_effect(
app: &mut App,
effect: Effect,
runner: &Arc<Runner>,
resolved: &mandible_extract::ResolvedTool,
warmer: &Warmer,
) -> bool {
match effect {
Effect::Quit => return false,
Effect::Copy(text) => {
let status = match clipboard::copy(&text) {
Ok(()) => format!("copied: {text}"),
Err(_) => format!("copy failed (clipboard unavailable): {text}"),
};
app.set_status(status);
}
Effect::Refresh => {
let loaded = crate::pipeline::load(&app.tool);
match loaded.root {
Some(root) => {
let tool = app.tool.clone();
*app = App::new(tool, root);
app.set_status("re-extracted");
}
None => {
app.set_status("re-extraction failed: no tier produced a result");
}
}
}
Effect::Fill(path) => {
if let Some(existing) = mandible_core::resolve(&app.root, &path).cloned() {
app.mark_pending(path.clone());
warmer.submit(Arc::clone(runner), resolved.clone(), path, existing);
}
}
}
true
}