diff --git a/Cargo.lock b/Cargo.lock index e262f0813e7c8..0539b016130c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,8 @@ dependencies = [ "pulldown-cmark", "serde", "serde_json", + "signal-hook", + "signal-hook-tokio", "tokio", "toml", ] @@ -899,6 +901,18 @@ dependencies = [ "libc", ] +[[package]] +name = "signal-hook-tokio" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6c5d32165ff8b94e68e7b3bdecb1b082e958c22434b363482cfb89dcd6f3ff8" +dependencies = [ + "futures-core", + "libc", + "signal-hook", + "tokio", +] + [[package]] name = "similar" version = "1.3.0" diff --git a/helix-lsp/Cargo.toml b/helix-lsp/Cargo.toml index be09982186ee1..aa631878aca81 100644 --- a/helix-lsp/Cargo.toml +++ b/helix-lsp/Cargo.toml @@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" thiserror = "1.0" -tokio = { version = "1.8", features = ["full"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } tokio-stream = "0.1.7" diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 1ff32276627ef..d35134780b308 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -29,10 +29,11 @@ helix-lsp = { version = "0.3", path = "../helix-lsp" } anyhow = "1" once_cell = "1.8" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } num_cpus = "1" tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } crossterm = { version = "0.20", features = ["event-stream"] } +signal-hook = "0.3" futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } @@ -54,3 +55,6 @@ toml = "0.5" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } + +[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100 +signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 79dd7c3ba352b..8927ad3d281e6 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -18,6 +18,10 @@ use crossterm::{ event::{Event, EventStream}, execute, terminal, }; +#[cfg(not(windows))] +use signal_hook::{consts::signal, low_level}; +#[cfg(not(windows))] +use signal_hook_tokio::Signals; pub struct Application { compositor: Compositor, @@ -36,6 +40,8 @@ pub struct Application { #[allow(dead_code)] syn_loader: Arc, + #[cfg(not(windows))] + signals: Signals, jobs: Jobs, lsp_progress: LspProgressMap, } @@ -99,6 +105,9 @@ impl Application { editor.set_theme(theme); + #[cfg(not(windows))] + let signals = Signals::new(&[signal::SIGTSTP, signal::SIGCONT])?; + let app = Self { compositor, editor, @@ -108,6 +117,8 @@ impl Application { theme_loader, syn_loader, + #[cfg(not(windows))] + signals, jobs: Jobs::new(), lsp_progress: LspProgressMap::new(), }; @@ -144,12 +155,29 @@ impl Application { use futures_util::StreamExt; + #[cfg(not(windows))] tokio::select! { biased; event = reader.next() => { self.handle_terminal_events(event) } + Some(signal) = self.signals.next() => { + use helix_view::graphics::Rect; + match signal { + signal::SIGTSTP => { + self.restore_term().unwrap(); + low_level::emulate_default_handler(signal::SIGTSTP).unwrap(); + } signal::SIGCONT => { + self.claim_term().await.unwrap(); + // redraw the terminal + let Rect { width, height, .. } = self.compositor.size(); + self.compositor.resize(width, height); + self.render(); + } + _ => unreachable!(), + } + } Some((id, call)) = self.editor.language_servers.incoming.next() => { self.handle_language_server_message(call, id).await; // limit render calls for fast language server messages @@ -157,9 +185,30 @@ impl Application { if last || last_render.elapsed() > deadline { self.render(); last_render = Instant::now(); - } + } } Some(callback) = self.jobs.futures.next() => { + self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); + self.render(); + } + Some(callback) = self.jobs.wait_futures.next() => { + self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); + self.render(); } - Some(callback) = self.jobs.futures.next() => { + } + #[cfg(windows)] + tokio::select! { + biased; + + event = reader.next() => { + self.handle_terminal_events(event) + } + Some((id, call)) = self.editor.language_servers.incoming.next() => { + self.handle_language_server_message(call, id).await; + // limit render calls for fast language server messages + let last = self.editor.language_servers.incoming.is_empty(); + if last || last_render.elapsed() > deadline { + self.render(); + last_render = Instant::now(); + } } Some(callback) = self.jobs.futures.next() => { self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); self.render(); } @@ -440,12 +489,23 @@ impl Application { } } - pub async fn run(&mut self) -> Result<(), Error> { + async fn claim_term(&mut self) -> Result<(), Error> { terminal::enable_raw_mode()?; + self.editor.close_language_servers(None).await?; + Ok(()) + } + fn restore_term(&mut self) -> Result<(), Error> { let mut stdout = stdout(); + // reset cursor shape + write!(stdout, "\x1B[2 q")?; + execute!(stdout, terminal::LeaveAlternateScreen)?; + terminal::disable_raw_mode()?; + Ok(()) + } - execute!(stdout, terminal::EnterAlternateScreen)?; + pub async fn run(&mut self) -> Result<(), Error> { + self.claim_term().await?; // Exit the alternate screen and disable raw mode before panicking let hook = std::panic::take_hook(); @@ -460,14 +520,7 @@ impl Application { self.event_loop().await; - self.editor.close_language_servers(None).await?; - - // reset cursor shape - write!(stdout, "\x1B[2 q")?; - - execute!(stdout, terminal::LeaveAlternateScreen)?; - - terminal::disable_raw_mode()?; + self.restore_term()?; Ok(()) } diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 7e769f4e1dad1..575bad9c575a2 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -50,6 +50,7 @@ use std::{ use once_cell::sync::{Lazy, OnceCell}; use serde::de::{self, Deserialize, Deserializer}; +use signal_hook::{consts::signal, low_level}; pub struct Context<'a> { pub selected_register: helix_view::RegisterSelection, @@ -279,7 +280,8 @@ impl Command { view_mode, left_bracket_mode, right_bracket_mode, - match_mode + match_mode, + suspend ); } @@ -3671,6 +3673,11 @@ fn surround_delete(cx: &mut Context) { }) } +fn suspend(_cx: &mut Context) { + #[cfg(not(windows))] + low_level::raise(signal::SIGTSTP).unwrap(); +} + /// Do nothing, just for modeinfo. fn noop(_cx: &mut Context) -> bool { false diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 32994c37ab383..050883024746c 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -193,6 +193,7 @@ impl Default for Keymaps { key!('z') => Command::view_mode, key!('"') => Command::select_register, + ctrl!('z') => Command::suspend, ); // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether // we keep this separate select mode. More keys can fit into normal mode then, but it's weird diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index cb2032de5457c..632702200f089 100644 --- a/helix-view/Cargo.toml +++ b/helix-view/Cargo.toml @@ -24,7 +24,7 @@ crossterm = { version = "0.20", optional = true } once_cell = "1.8" url = "2" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] } futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } slotmap = "1"