use crate::Result;
use crate::cli::CliArgs;
use crate::exec::{ExecActionEvent, ExecStatusEvent, ExecutorSender};
use crate::hub::{HubEvent, get_hub};
use crate::tui::hub_event_handler::handle_hub_event;
use crate::tui::in_reader::InReader;
use crate::tui::support::safer_println;
use crossterm::cursor::MoveUp;
use crossterm::event::{KeyCode, KeyEventKind, KeyModifiers};
use crossterm::execute;
use crossterm::terminal::{Clear, ClearType};
use tokio::sync::broadcast::Receiver;
use tokio::sync::oneshot;
#[derive(Debug)]
pub struct TuiApp {
executor_sender: ExecutorSender,
}
impl TuiApp {
pub fn new(executor_sender: ExecutorSender) -> Self {
Self { executor_sender }
}
}
impl TuiApp {
fn executor_sender(&self) -> ExecutorSender {
self.executor_sender.clone()
}
}
impl TuiApp {
pub async fn start_with_args(self, cli_args: CliArgs) -> Result<()> {
let hub_rx_for_exit = get_hub().subscriber();
let interactive = cli_args.cmd.is_interactive();
let in_reader = self.start_app(interactive)?;
self.exec_cli_args(cli_args)?;
self.wait_for_exit(hub_rx_for_exit, interactive).await?;
if let Some(in_reader) = in_reader {
in_reader.close()
}
Ok(())
}
fn start_app(&self, interactive: bool) -> Result<Option<InReader>> {
self.handle_hub_event(interactive);
let in_reader = self.handle_in_event(interactive);
Ok(in_reader)
}
}
impl TuiApp {
fn handle_in_event(&self, interactive: bool) -> Option<InReader> {
if interactive {
let (in_reader, in_rx) = InReader::new_and_rx();
in_reader.start();
let exec_sender = self.executor_sender();
tokio::spawn(async move {
let hub = get_hub();
while let Ok(key_event) = in_rx.recv_async().await {
match key_event.code {
KeyCode::Char('r') => {
if key_event.kind == KeyEventKind::Press {
safer_println("\n-- R pressed - Redo\n", interactive);
exec_sender.send(ExecActionEvent::Redo).await;
}
}
KeyCode::Char('q') => {
if key_event.kind == KeyEventKind::Press {
hub.publish(HubEvent::Quit).await
}
}
KeyCode::Char('a') => {
if key_event.kind == KeyEventKind::Press {
exec_sender.send(ExecActionEvent::OpenAgent).await;
}
}
KeyCode::Char('c')
if key_event.modifiers.contains(KeyModifiers::CONTROL)
&& key_event.kind == KeyEventKind::Press =>
{
hub.publish(HubEvent::Quit).await;
}
_ => (),
}
}
});
Some(in_reader)
} else {
None
}
}
fn handle_hub_event(&self, interactive: bool) {
let exec_sender = self.executor_sender();
tokio::spawn(async move {
let mut rx = get_hub().subscriber();
loop {
let evt_res = rx.recv().await;
match evt_res {
Ok(event) => {
if let Err(err) = handle_hub_event(event, &exec_sender, interactive).await {
println!("Tui ERROR while handling handle_hub_event. Cause {err}")
}
}
Err(err) => {
println!("TuiApp handle_hub_event event error: {err}");
break;
}
}
}
});
}
}
impl TuiApp {
fn exec_cli_args(&self, cli_args: CliArgs) -> Result<oneshot::Receiver<()>> {
let exec_cmd: ExecActionEvent = cli_args.cmd.into();
let executor_tx = self.executor_sender();
let (done_tx, done_rx) = oneshot::channel();
tokio::spawn(async move {
let _ = executor_tx.send(exec_cmd).await;
let _ = done_tx.send(());
});
Ok(done_rx)
}
async fn wait_for_exit(&self, mut hub_rx: Receiver<HubEvent>, interactive: bool) -> Result<()> {
loop {
if let Ok(hub_event) = hub_rx.recv().await {
match (hub_event, interactive) {
(HubEvent::Quit, _) => break,
(HubEvent::Executor(ExecStatusEvent::EndExec), false) => break,
_ => (),
}
}
}
Ok(())
}
}
#[allow(unused)]
fn clear_last_n_lines(n: u16) {
let mut stdout = std::io::stdout();
execute!(stdout, MoveUp(n)).expect("Cannot MoveUp Cursor");
for _ in 0..n {
execute!(stdout, Clear(ClearType::CurrentLine)).expect("Cannot Clear Current Line");
}
}