use std::time::Duration;
use anyhow::Result;
use crossterm::event::{Event, EventStream};
use futures_util::StreamExt;
use ratatui::DefaultTerminal;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use crate::app::{App, Msg};
use crate::{keys, ui};
const TICK: Duration = Duration::from_secs(1);
pub async fn run(
mut terminal: DefaultTerminal,
mut app: App,
mut rx: UnboundedReceiver<Msg>,
tx: UnboundedSender<Msg>,
) -> Result<()> {
let mut events = EventStream::new();
let mut tick = tokio::time::interval(TICK);
tick.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
#[cfg(unix)]
{
let tx = tx.clone();
tokio::spawn(async move {
use tokio::signal::unix::{SignalKind, signal};
if let Ok(mut s) = signal(SignalKind::terminate()) {
s.recv().await;
let _ = tx.send(Msg::Quit);
}
});
}
#[cfg(not(unix))]
let _ = &tx;
app.load_namespaces();
loop {
if let Some(request) = app.take_edit_request() {
ratatui::restore();
let outcome = run_editor(&request.path);
terminal = ratatui::init();
terminal.clear()?;
app.finish_edit(&request, outcome.err().map(|e| e.to_string()));
}
if app.dirty {
terminal.draw(|f| ui::render(f, &mut app))?;
app.dirty = false;
}
if app.should_quit {
return Ok(());
}
tokio::select! {
maybe_event = events.next() => match maybe_event {
Some(Ok(Event::Key(k))) => {
if let Some(chord) = keys::to_chord(k) {
app.handle(Msg::Key(chord));
}
}
Some(Ok(Event::Resize(_, _))) => app.handle(Msg::Redraw),
Some(Ok(_)) => {}
Some(Err(e)) => return Err(e.into()),
None => return Ok(()),
},
Some(msg) = rx.recv() => app.handle(msg),
_ = tick.tick() => app.handle(Msg::Tick),
}
}
}
fn run_editor(path: &std::path::Path) -> Result<()> {
let editor = std::env::var("VISUAL")
.or_else(|_| std::env::var("EDITOR"))
.unwrap_or_else(|_| "vi".to_string());
let mut parts = editor.split_whitespace();
let Some(program) = parts.next() else {
anyhow::bail!("$EDITOR is set but empty");
};
let status = std::process::Command::new(program)
.args(parts)
.arg(path)
.status()
.map_err(|e| anyhow::anyhow!("could not run `{program}`: {e}"))?;
if !status.success() {
anyhow::bail!("`{program}` exited with {status}");
}
Ok(())
}