WIP: TUI actor
This commit is contained in:
parent
08d2377404
commit
4d19f9e98f
7 changed files with 174 additions and 19 deletions
|
@ -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;
|
||||
|
||||
|
@ -33,7 +36,11 @@ enum Command {
|
|||
#[derive(Parser, Debug)]
|
||||
enum Server {
|
||||
Init,
|
||||
Start,
|
||||
Start {
|
||||
/// Display a UI (experimental)
|
||||
#[arg(long, required = false)]
|
||||
ui: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
|
@ -50,9 +57,9 @@ fn main() -> Result<()> {
|
|||
Server::Init => {
|
||||
server::init(&fs)?;
|
||||
}
|
||||
Server::Start => {
|
||||
Server::Start { ui } => {
|
||||
let sleep_duration = std::time::Duration::from_secs(10);
|
||||
server::start(fs, net, repository_factory, sleep_duration)?;
|
||||
server::start(ui, fs, net, repository_factory, sleep_duration)?;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
mod actor;
|
||||
pub mod actor;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -10,7 +10,7 @@ use crate::{
|
|||
alerts::{AlertsActor, History},
|
||||
file_watcher::{watch_file, FileUpdated},
|
||||
};
|
||||
use actor::ServerActor;
|
||||
pub use actor::ServerActor;
|
||||
use git_next_core::git::RepositoryFactory;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
@ -38,26 +38,23 @@ pub fn init(fs: &FileSystem) -> Result<()> {
|
|||
}
|
||||
|
||||
pub fn start(
|
||||
ui: bool,
|
||||
fs: FileSystem,
|
||||
net: Network,
|
||||
repo: Box<dyn RepositoryFactory>,
|
||||
sleep_duration: std::time::Duration,
|
||||
) -> Result<()> {
|
||||
if !ui {
|
||||
init_logging();
|
||||
}
|
||||
|
||||
let execution = async move {
|
||||
info!("Starting Alert Dispatcher...");
|
||||
let alerts_addr = AlertsActor::new(None, History::new(A_DAY), net.clone()).start();
|
||||
|
||||
info!("Starting Server...");
|
||||
let server = ServerActor::new(
|
||||
fs.clone(),
|
||||
net.clone(),
|
||||
alerts_addr.clone(),
|
||||
repo,
|
||||
sleep_duration,
|
||||
)
|
||||
.start();
|
||||
let server =
|
||||
ServerActor::new(fs.clone(), net.clone(), alerts_addr, repo, sleep_duration).start();
|
||||
server.do_send(FileUpdated);
|
||||
|
||||
info!("Starting File Watcher...");
|
||||
|
@ -65,14 +62,28 @@ pub fn start(
|
|||
let fw_shutdown = watch_file("git-next-server.toml".into(), server.clone().recipient())
|
||||
.expect("file watcher");
|
||||
|
||||
if ui {
|
||||
let (tx, rx) = std::sync::mpsc::channel::<()>();
|
||||
actix_rt::task::spawn_blocking(|| {
|
||||
println!("Start Terminal...");
|
||||
// TODO: how does server send messages to Tui?
|
||||
crate::tui::Tui::new(tx).start().do_send(crate::tui::Tick);
|
||||
});
|
||||
println!("Waiting for shutdown...");
|
||||
let _ = rx.recv(); // block until shutdown is signaled
|
||||
} else {
|
||||
info!("Server running - Press Ctrl-C to stop...");
|
||||
let _ = actix_rt::signal::ctrl_c().await;
|
||||
info!("Ctrl-C received, shutting down...");
|
||||
}
|
||||
|
||||
// shutdown
|
||||
fw_shutdown.store(true, Ordering::Relaxed);
|
||||
server.do_send(crate::server::actor::messages::Shutdown);
|
||||
actix_rt::time::sleep(std::time::Duration::from_millis(200)).await;
|
||||
System::current().stop();
|
||||
fw_shutdown.store(true, Ordering::Relaxed);
|
||||
};
|
||||
|
||||
let system = System::new();
|
||||
Arbiter::current().spawn(execution);
|
||||
system.run()?;
|
||||
|
|
2
crates/cli/src/tui/actor/handlers/mod.rs
Normal file
2
crates/cli/src/tui/actor/handlers/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
//
|
||||
mod tick;
|
54
crates/cli/src/tui/actor/handlers/tick.rs
Normal file
54
crates/cli/src/tui/actor/handlers/tick.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
//
|
||||
use std::borrow::BorrowMut;
|
||||
|
||||
use actix::{ActorContext, AsyncContext, Handler};
|
||||
use ratatui::{
|
||||
crossterm::event::{self, KeyCode, KeyEventKind},
|
||||
style::Stylize as _,
|
||||
widgets::Paragraph,
|
||||
};
|
||||
|
||||
use crate::tui::actor::{messages::Tick, Tui};
|
||||
|
||||
impl Handler<Tick> for Tui {
|
||||
type Result = std::io::Result<()>;
|
||||
|
||||
fn handle(&mut self, _msg: Tick, ctx: &mut Self::Context) -> Self::Result {
|
||||
ctx.notify_later(Tick, std::time::Duration::from_millis(16));
|
||||
if let Some(terminal) = self.terminal.borrow_mut() {
|
||||
terminal.draw(|frame| {
|
||||
let area = frame.area();
|
||||
frame.render_widget(
|
||||
Paragraph::new("Hello Ratatui! (press 'q' to quit)")
|
||||
.white()
|
||||
.on_blue(),
|
||||
area,
|
||||
);
|
||||
})?;
|
||||
} else {
|
||||
eprintln!("No terminal setup");
|
||||
}
|
||||
if event::poll(std::time::Duration::from_millis(16))? {
|
||||
if let event::Event::Key(key) = event::read()? {
|
||||
if key.kind == KeyEventKind::Press {
|
||||
match key.code {
|
||||
KeyCode::Char('q') => {
|
||||
// execute!(stderr(), LeaveAlternateScreen)?;
|
||||
// disable_raw_mode()?;
|
||||
ctx.stop();
|
||||
if let Err(err) = self.signal_shutdown.send(()) {
|
||||
tracing::error!(?err, "Failed to signal shutdown");
|
||||
}
|
||||
}
|
||||
KeyCode::Esc => {
|
||||
//
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
4
crates/cli/src/tui/actor/messages.rs
Normal file
4
crates/cli/src/tui/actor/messages.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
//
|
||||
use git_next_core::message;
|
||||
|
||||
message!(Tick => std::io::Result<()>, "Start the TUI");
|
72
crates/cli/src/tui/actor/mod.rs
Normal file
72
crates/cli/src/tui/actor/mod.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
mod handlers;
|
||||
pub mod messages;
|
||||
|
||||
use std::{
|
||||
io::{stderr, Stderr},
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
|
||||
use actix::{Actor, Context};
|
||||
|
||||
use ratatui::{
|
||||
crossterm::{
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
},
|
||||
prelude::CrosstermBackend,
|
||||
Terminal,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tui {
|
||||
terminal: Option<Terminal<CrosstermBackend<Stderr>>>,
|
||||
signal_shutdown: Sender<()>,
|
||||
}
|
||||
impl Actor for Tui {
|
||||
type Context = Context<Self>;
|
||||
fn started(&mut self, _ctx: &mut Self::Context) {
|
||||
match init() {
|
||||
Err(err) => {
|
||||
eprintln!("Failed to enable raw mode: {err:?}");
|
||||
}
|
||||
Ok(terminal) => {
|
||||
self.terminal.replace(terminal);
|
||||
}
|
||||
}
|
||||
}
|
||||
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
||||
if let Err(err) = restore() {
|
||||
match std::env::consts::OS {
|
||||
"linux" | "macos" => {
|
||||
eprintln!(
|
||||
"Failed to restore terminal: Type `reset` to restore terminal: {err:?}"
|
||||
);
|
||||
}
|
||||
"windows" => {
|
||||
println!("Failed to restore terminal: Reopen a new terminal: {err:?}");
|
||||
}
|
||||
_ => println!("Failed to restore terminal: {err:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Tui {
|
||||
pub const fn new(signal_shutdown: Sender<()>) -> Self {
|
||||
Self {
|
||||
terminal: None,
|
||||
signal_shutdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn init() -> std::io::Result<Terminal<CrosstermBackend<Stderr>>> {
|
||||
execute!(stderr(), EnterAlternateScreen)?;
|
||||
enable_raw_mode()?;
|
||||
Terminal::new(CrosstermBackend::new(stderr()))
|
||||
}
|
||||
|
||||
fn restore() -> std::io::Result<()> {
|
||||
execute!(stderr(), LeaveAlternateScreen)?;
|
||||
disable_raw_mode()
|
||||
}
|
5
crates/cli/src/tui/mod.rs
Normal file
5
crates/cli/src/tui/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
//
|
||||
mod actor;
|
||||
|
||||
pub use actor::messages::Tick;
|
||||
pub use actor::Tui;
|
Loading…
Reference in a new issue