diff --git a/Cargo.lock b/Cargo.lock index 5688fd15b..5a5fcf4b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -378,6 +378,8 @@ dependencies = [ "pulldown-cmark", "serde", "serde_json", + "signal-hook", + "signal-hook-tokio", "tokio", "toml", ] @@ -865,6 +867,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 cf1f30a7b..ef9feb947 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.9", features = ["full"] } +tokio = { version = "1.9", 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 1fc14ad21..0e2baae37 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -28,10 +28,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 } @@ -53,3 +54,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 5f3506714..9e659fb0d 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -18,6 +18,10 @@ event::{DisableMouseCapture, EnableMouseCapture, 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, } @@ -102,6 +108,9 @@ pub fn new(args: Args, mut config: Config) -> Result { editor.set_theme(theme); + #[cfg(not(windows))] + let signals = Signals::new(&[signal::SIGTSTP, signal::SIGCONT])?; + let app = Self { compositor, editor, @@ -111,6 +120,8 @@ pub fn new(args: Args, mut config: Config) -> Result { theme_loader, syn_loader, + #[cfg(not(windows))] + signals, jobs: Jobs::new(), lsp_progress: LspProgressMap::new(), }; @@ -147,6 +158,51 @@ pub async fn event_loop(&mut self) { 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.compositor.save_cursor(); + 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.compositor.load_cursor(); + 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 + 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(); + } + Some(callback) = self.jobs.wait_futures.next() => { + self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback); + self.render(); + } + } + #[cfg(windows)] tokio::select! { biased; @@ -443,15 +499,29 @@ pub async fn handle_language_server_message( } } - pub async fn run(&mut self) -> Result<(), Error> { + async fn claim_term(&mut self) -> Result<(), Error> { terminal::enable_raw_mode()?; - let mut stdout = stdout(); - execute!(stdout, terminal::EnterAlternateScreen)?; + self.editor.close_language_servers(None).await?; if self.config.terminal.mouse { execute!(stdout, EnableMouseCapture)?; } + Ok(()) + } + + fn restore_term(&mut self) -> Result<(), Error> { + let mut stdout = stdout(); + // reset cursor shape + write!(stdout, "\x1B[2 q")?; + execute!(stdout, DisableMouseCapture)?; + execute!(stdout, terminal::LeaveAlternateScreen)?; + terminal::disable_raw_mode()?; + Ok(()) + } + + 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(); @@ -469,13 +539,7 @@ pub async fn run(&mut self) -> Result<(), Error> { self.editor.close_language_servers(None).await?; - // reset cursor shape - write!(stdout, "\x1B[2 q")?; - - execute!(stdout, DisableMouseCapture)?; - 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 cc2c2cca3..5cbab34ff 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -291,7 +291,8 @@ pub fn doc(&self) -> &'static str { surround_replace, "Surround replace", surround_delete, "Surround delete", select_textobject_around, "Select around object", - select_textobject_inner, "Select inside object" + select_textobject_inner, "Select inside object", + suspend, "Suspend" ); } @@ -3894,3 +3895,8 @@ fn surround_delete(cx: &mut Context) { } }) } + +fn suspend(_cx: &mut Context) { + #[cfg(not(windows))] + signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap(); +} diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index c2cfa3a72..628c4e13c 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -68,7 +68,7 @@ fn type_name(&self) -> &'static str { use anyhow::Error; use std::io::stdout; -use tui::backend::CrosstermBackend; +use tui::backend::{Backend, CrosstermBackend}; type Terminal = tui::terminal::Terminal>; pub struct Compositor { @@ -99,6 +99,21 @@ pub fn resize(&mut self, width: u16, height: u16) { .expect("Unable to resize terminal") } + pub fn save_cursor(&mut self) { + if self.terminal.cursor_kind() == CursorKind::Hidden { + self.terminal + .backend_mut() + .show_cursor(CursorKind::Block) + .ok(); + } + } + + pub fn load_cursor(&mut self) { + if self.terminal.cursor_kind() == CursorKind::Hidden { + self.terminal.backend_mut().hide_cursor().ok(); + } + } + pub fn push(&mut self, mut layer: Box) { let size = self.size(); // trigger required_size on init diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index 053b92e62..5fe730a16 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -502,6 +502,7 @@ fn default() -> Keymaps { }, "\"" => select_register, + "C-z" => 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-tui/src/terminal.rs b/helix-tui/src/terminal.rs index 4637eb71d..22e9232f3 100644 --- a/helix-tui/src/terminal.rs +++ b/helix-tui/src/terminal.rs @@ -45,8 +45,8 @@ pub struct Terminal buffers: [Buffer; 2], /// Index of the current buffer in the previous array current: usize, - /// Whether the cursor is currently hidden - hidden_cursor: bool, + /// Kind of cursor (hidden or others) + cursor_kind: CursorKind, /// Viewport viewport: Viewport, } @@ -57,7 +57,7 @@ impl Drop for Terminal { fn drop(&mut self) { // Attempt to restore the cursor state - if self.hidden_cursor { + if self.cursor_kind == CursorKind::Hidden { if let Err(err) = self.show_cursor(CursorKind::Block) { eprintln!("Failed to show the cursor: {}", err); } @@ -93,7 +93,7 @@ pub fn with_options(backend: B, options: TerminalOptions) -> io::Result CursorKind { + self.cursor_kind + } + pub fn hide_cursor(&mut self) -> io::Result<()> { self.backend.hide_cursor()?; - self.hidden_cursor = true; + self.cursor_kind = CursorKind::Hidden; Ok(()) } pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { self.backend.show_cursor(kind)?; - self.hidden_cursor = false; + self.cursor_kind = kind; Ok(()) } diff --git a/helix-view/Cargo.toml b/helix-view/Cargo.toml index 89e183f01..3617506fc 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" diff --git a/helix-view/src/graphics.rs b/helix-view/src/graphics.rs index e14ce2b91..5138e9231 100644 --- a/helix-view/src/graphics.rs +++ b/helix-view/src/graphics.rs @@ -1,7 +1,7 @@ use bitflags::bitflags; use std::cmp::{max, min}; -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq)] /// UNSTABLE pub enum CursorKind { /// █