diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs index 8b8f021..2418f45 100644 --- a/crates/cli/src/main.rs +++ b/crates/cli/src/main.rs @@ -5,10 +5,13 @@ mod forge; mod init; mod repo; mod server; -mod webhook; + +#[cfg(feature = "tui")] +mod tui; #[cfg(test)] mod tests; +mod webhook; use git_next_core::git; diff --git a/crates/cli/src/server/mod.rs b/crates/cli/src/server/mod.rs index 8acabe4..a6d24f2 100644 --- a/crates/cli/src/server/mod.rs +++ b/crates/cli/src/server/mod.rs @@ -65,6 +65,15 @@ pub fn start( let fw_shutdown = watch_file("git-next-server.toml".into(), server.clone().recipient()) .expect("file watcher"); + // info!("Start Terminal..."); + // #[cfg(feature = "tui")] + // { + // info!("Start Terminal..."); + // let tui = crate::tui::Tui::default(); + // let tui_addr = tui.start(); + // tui_addr.do_send(crate::tui::Start); + // } + info!("Server running - Press Ctrl-C to stop..."); let _ = actix_rt::signal::ctrl_c().await; info!("Ctrl-C received, shutting down..."); diff --git a/crates/cli/src/tui/actor/handlers/mod.rs b/crates/cli/src/tui/actor/handlers/mod.rs new file mode 100644 index 0000000..fc60273 --- /dev/null +++ b/crates/cli/src/tui/actor/handlers/mod.rs @@ -0,0 +1,2 @@ +// +mod start; diff --git a/crates/cli/src/tui/actor/handlers/start.rs b/crates/cli/src/tui/actor/handlers/start.rs new file mode 100644 index 0000000..aee5a8f --- /dev/null +++ b/crates/cli/src/tui/actor/handlers/start.rs @@ -0,0 +1,58 @@ +use std::io::Stderr; + +// +use actix::{ContextFutureSpawner, Handler, WrapFuture}; +use ratatui::{ + crossterm::event::{self, KeyCode, KeyEventKind}, + prelude::CrosstermBackend, + style::Stylize, + widgets::Paragraph, + Terminal, +}; +use tracing::Instrument; + +use crate::tui::actor::{messages::Start, Tui}; + +impl Handler for Tui { + type Result = (); + + fn handle(&mut self, _msg: Start, ctx: &mut Self::Context) -> Self::Result { + let Some(mut terminal) = self.terminal.take() else { + tracing::error!("Unable to start without terminal"); + return; + }; + async move { terminal_loop_wrapper(&mut terminal) } + .in_current_span() + .into_actor(self) + .wait(ctx); + } +} + +fn terminal_loop_wrapper(terminal: &mut Terminal>) { + match terminal_loop(terminal) { + Ok(()) => tracing::info!("Terminal closed"), + Err(err) => tracing::error!(?err, "Terminal closed"), + } +} + +fn terminal_loop(terminal: &mut Terminal>) -> std::io::Result<()> { + loop { + terminal.draw(|frame| { + let area = frame.area(); + frame.render_widget( + Paragraph::new("Hello Ratatui! (press 'q' to quit)") + .white() + .on_blue(), + area, + ); + })?; + if event::poll(std::time::Duration::from_millis(16))? { + if let event::Event::Key(key) = event::read()? { + if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') { + break; + } + } + } + } + Ok(()) +} diff --git a/crates/cli/src/tui/actor/messages.rs b/crates/cli/src/tui/actor/messages.rs new file mode 100644 index 0000000..8366f31 --- /dev/null +++ b/crates/cli/src/tui/actor/messages.rs @@ -0,0 +1,4 @@ +// +use git_next_core::message; + +message!(Start, "Start the TUI"); diff --git a/crates/cli/src/tui/actor/mod.rs b/crates/cli/src/tui/actor/mod.rs new file mode 100644 index 0000000..85180f1 --- /dev/null +++ b/crates/cli/src/tui/actor/mod.rs @@ -0,0 +1,62 @@ +// +mod handlers; +pub mod messages; + +use std::io::{stderr, Stderr}; + +use actix::{Actor, Context}; + +use ratatui::{ + crossterm::{ + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, + }, + prelude::CrosstermBackend, + Terminal, +}; + +#[derive(Debug, Default)] +pub struct Tui { + terminal: Option>>, +} + +impl Actor for Tui { + type Context = Context; + + fn started(&mut self, _ctx: &mut Self::Context) { + match init() { + Ok(terminal) => { + self.terminal.replace(terminal); + } + Err(err) => tracing::error!(?err, "Failed to start terminal UI"), + } + } + + fn stopped(&mut self, _ctx: &mut Self::Context) { + if let Err(err) = restore() { + match std::env::consts::OS { + "linux" | "macos" => { + tracing::error!( + ?err, + "Failed to restore terminal: Type `reset` to restore terminal" + ); + } + "windows" => { + tracing::error!(?err, "Failed to restore terminal: Reopen a new terminal"); + } + _ => tracing::error!(?err, "Failed to restore terminal"), + } + } + } +} + +fn init() -> std::io::Result>> { + execute!(stderr(), EnterAlternateScreen)?; + enable_raw_mode()?; + Terminal::new(CrosstermBackend::new(stderr())) +} + +fn restore() -> std::io::Result<()> { + execute!(stderr(), LeaveAlternateScreen)?; + disable_raw_mode() +} diff --git a/crates/cli/src/tui/mod.rs b/crates/cli/src/tui/mod.rs new file mode 100644 index 0000000..db4de36 --- /dev/null +++ b/crates/cli/src/tui/mod.rs @@ -0,0 +1,5 @@ +// +mod actor; + +// pub use actor::messages::Start; +// pub use actor::Tui;