Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cli): added Ctrl+Z (SIGTSTP) support #2836

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ ratatui = { version = "0.27.0", features = ["crossterm", "unstable"] }
crossterm = { version = "0.27.0", features = ["event-stream"] }
ansi-to-tui = "=5.0.0-rc.1"
ansi-to-html = "0.2.1"
signal-hook = "0.3.17"
signal-hook-tokio = { version = "0.3.1", features = ["futures-v0_3"] }

# on macos, we need to specify the vendored feature on ssl when cross compiling
# [target.'cfg(target_os = "macos")'.dependencies]
Expand Down
61 changes: 61 additions & 0 deletions packages/cli/src/serve/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use std::future::{poll_fn, Future, IntoFuture};
use std::ops::Deref;
use std::sync::{Arc, RwLock};
use std::task::Poll;

use crate::builder::{Stage, TargetPlatform, UpdateBuildProgress, UpdateStage};
Expand All @@ -7,6 +9,8 @@ use crate::dioxus_crate::DioxusCrate;
use crate::tracer::CLILogControl;
use crate::Result;
use futures_util::FutureExt;
use signal_hook::consts::SIGCONT;
use signal_hook_tokio::Signals;
use tokio::task::yield_now;

mod builder;
Expand Down Expand Up @@ -63,6 +67,37 @@ pub async fn serve_all(
let mut watcher = Watcher::start(&serve, &dioxus_crate);
let mut screen = Output::start(&serve, log_control).expect("Failed to open terminal logger");

let term = screen.term.clone();

let mut signals = Signals::new(vec![SIGCONT]).unwrap();
let signals_handle = signals.handle();

let signals_task = tokio::spawn(async move {
use crossterm::terminal::enable_raw_mode;
use crossterm::terminal::EnterAlternateScreen;
use crossterm::ExecutableCommand;
use futures_util::stream::StreamExt;

while let Some(signal) = signals.next().await {
match signal {
SIGCONT => {
tracing::info!("received CONT signal");
enable_raw_mode().unwrap();
std::io::stdout().execute(EnterAlternateScreen).unwrap();
tracing::info!("clearing screen");
term.deref()
.write()
.unwrap()
.as_mut()
.unwrap()
.clear()
.unwrap();
}
_ => unreachable!(),
}
}
});

let is_hot_reload = serve.server_arguments.hot_reload.unwrap_or(true);

loop {
Expand All @@ -72,6 +107,29 @@ pub async fn serve_all(
// Draw the state of the server to the screen
screen.render(&serve, &dioxus_crate, &builder, &server, &watcher);

{
let mut temporary_stop = TEMPORARY_STOP.lock().unwrap();
if *temporary_stop {
tracing::info!("STOP is true");
tracing::info!("setting resume to false");
use crossterm::terminal::disable_raw_mode;
use crossterm::terminal::LeaveAlternateScreen;
use crossterm::ExecutableCommand;
use signal_hook::consts::SIGTSTP;

tracing::info!("setting STOP to false");
*temporary_stop = false;

disable_raw_mode().unwrap();
std::io::stdout().execute(LeaveAlternateScreen).unwrap();
// Note: this probably only works on UNIX-like systems, so this
// maybe should be put under a cfg for unix.
signal_hook::low_level::emulate_default_handler(SIGTSTP).unwrap();
// This is the place where (at least) main tread is blocked.
tracing::info!("{}", dbg!("Back to work!"));
}
}

// And then wait for any updates before redrawing
tokio::select! {
// rebuild the project or hotreload it
Expand Down Expand Up @@ -201,6 +259,9 @@ pub async fn serve_all(
_ = server.shutdown().await;
builder.shutdown();

signals_handle.close();
signals_task.await.unwrap();

Ok(())
}

Expand Down
44 changes: 40 additions & 4 deletions packages/cli/src/serve/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ use crossterm::{
use dioxus_cli_config::{AddressArguments, Platform};
use dioxus_hot_reload::ClientMsg;
use futures_util::{future::select_all, Future, FutureExt, StreamExt};
use once_cell::sync::Lazy;
use ratatui::{prelude::*, widgets::*, TerminalOptions, Viewport};
use std::{
borrow::BorrowMut,
cell::RefCell,
collections::{HashMap, HashSet},
fmt::Display,
io::{self, stdout},
ops::Deref,
rc::Rc,
sync::atomic::Ordering,
sync::{atomic::Ordering, Arc, Mutex, RwLock},
time::{Duration, Instant},
};
use tokio::{
Expand Down Expand Up @@ -80,8 +83,15 @@ impl BuildProgress {
}
}

/// The way TSTP "signal" is handled. Instead of built-in signal the Ctrl+Z is
/// read directly from user input by crossterm.
///
/// See: [crossterm issue #494](https://github.com/crossterm-rs/crossterm/issues/494).
pub(crate) static TEMPORARY_STOP: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));

pub struct Output {
term: Rc<RefCell<Option<TerminalBackend>>>,
// term: Rc<RefCell<Option<TerminalBackend>>>,
pub(crate) term: Arc<RwLock<Option<TerminalBackend>>>,
log_control: CLILogControl,

// optional since when there's no tty there's no eventstream to read from - just stdin
Expand Down Expand Up @@ -166,7 +176,7 @@ impl Output {
let platform = cfg.build_arguments.platform.expect("To be resolved by now");

Ok(Self {
term: Rc::new(RefCell::new(term)),
term: Arc::new(RwLock::new(term)),
log_control,
events,
_rustc_version,
Expand Down Expand Up @@ -372,6 +382,18 @@ impl Output {
}
}

// Handle Ctrl+Z (SIGTSTP a.k.a. suspend job).
if let Event::Key(key) = input {
if let KeyCode::Char('z') = key.code {
if key.modifiers.contains(KeyModifiers::CONTROL) {
tracing::info!("got Ctrl+Z");
tracing::info!("setting STOP to true");
*TEMPORARY_STOP.lock().unwrap() = true;
return Ok(false);
}
}
}

if let Event::Key(key) = input {
if let KeyCode::Char('/') = key.code {
self.fly_modal_open = !self.fly_modal_open;
Expand Down Expand Up @@ -563,6 +585,18 @@ impl Output {
}
}

// TODO: need to call this from another task or some other way.
// pub fn clear(&self) {
// self.term
// .deref()
// .write()
// .unwrap()
// .as_mut()
// .unwrap()
// .clear()
// .unwrap();
// }

pub fn render(
&mut self,
_opts: &Serve,
Expand All @@ -586,7 +620,9 @@ impl Output {
_ = self
.term
.clone()
.borrow_mut()
.deref()
.write()
.unwrap()
.as_mut()
.unwrap()
.draw(|frame| {
Expand Down