use crate::cli::CliArgs;
use crate::exec::{ExecCommand, ExecEvent};
use crate::hub::{get_hub, HubEvent};
use crate::tui::in_reader::InReader;
use crate::tui::tui_elem;
use crate::{Error, Result};
use crossterm::cursor::MoveUp;
use crossterm::event::{KeyCode, KeyModifiers};
use crossterm::terminal::{Clear, ClearType};
use crossterm::{cursor, execute, terminal};
use std::io::Write as _;
use tokio::sync::broadcast::Receiver;
use tokio::sync::{mpsc, oneshot};
#[derive(Debug)]
pub struct TuiApp {
executor_tx: mpsc::Sender<ExecCommand>,
}
impl TuiApp {
pub fn new(executor_tx: mpsc::Sender<ExecCommand>) -> Self {
Self { executor_tx }
}
}
impl TuiApp {
fn executor_tx(&self) -> mpsc::Sender<ExecCommand> {
self.executor_tx.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_tx = self.executor_tx();
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') => {
safer_println("\n-- R pressed - Redo\n", interactive);
send_to_executor(&exec_tx, ExecCommand::Redo).await;
}
KeyCode::Char('q') => hub.publish(HubEvent::Quit).await,
KeyCode::Char('a') => {
send_to_executor(&exec_tx, ExecCommand::OpenAgent).await;
}
KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
hub.publish(HubEvent::Quit).await;
}
_ => (),
}
}
});
Some(in_reader)
} else {
None
}
}
fn handle_hub_event(&self, interactive: bool) {
let exec_tx = self.executor_tx();
tokio::spawn(async move {
let mut rx = get_hub().subscriber();
while let Ok(event) = rx.recv().await {
match event {
HubEvent::Message(msg) => {
safer_println(&format!("{msg}"), interactive);
}
HubEvent::Error { error } => {
safer_println(&format!("Error: {error}"), interactive);
}
HubEvent::LuaPrint(text) => safer_println(&text, interactive),
HubEvent::Executor(exec_event) => {
if let (ExecEvent::RunEnd, true) = (exec_event, interactive) {
tui_elem::print_bottom_bar();
}
}
HubEvent::DoExecRedo => send_to_executor(&exec_tx, ExecCommand::Redo).await,
HubEvent::Quit => {
}
}
}
});
}
}
impl TuiApp {
fn exec_cli_args(&self, cli_args: CliArgs) -> Result<oneshot::Receiver<()>> {
let exec_cmd: ExecCommand = cli_args.cmd.into();
let executor_tx = self.executor_tx();
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(ExecEvent::EndExec), false) => break,
_ => (),
}
}
}
Ok(())
}
}
fn safer_println(msg: &str, interactive: bool) {
if interactive {
let stdout = std::io::stdout();
let mut stdout_lock = stdout.lock();
for line in msg.split("\n") {
execute!(
stdout_lock,
terminal::Clear(ClearType::CurrentLine),
cursor::MoveToColumn(0)
)
.expect("Failed to clear line and reset cursor");
println!("{line}");
stdout_lock.flush().expect("Failed to flush stdout");
stdout_lock.flush().expect("Failed to flush stdout");
}
} else {
println!("{msg}");
}
}
async fn send_to_executor(exec_tx: &mpsc::Sender<ExecCommand>, exec_cmd: ExecCommand) {
if let Err(err) = exec_tx.send(exec_cmd).await {
get_hub()
.publish(Error::cc("start_app - cannot send ExecCommand::Redo", err))
.await;
};
}
#[allow(unused)]
fn clear_last_n_lines(n: u16) {
let mut stdout = std::io::stdout();
execute!(stdout, MoveUp(n)).expect("Cannot MoveUp Cursort");
for _ in 0..n {
execute!(stdout, Clear(ClearType::CurrentLine)).expect("Cannot Clear Current Line");
}
}