Add ctrl-z to suspend

This commit is contained in:
Ivan Tham 2021-07-17 22:47:08 +08:00 committed by Blaž Hrastnik
parent adb5d842ba
commit 821565e4ef
10 changed files with 131 additions and 22 deletions

14
Cargo.lock generated
View File

@ -378,6 +378,8 @@ dependencies = [
"pulldown-cmark", "pulldown-cmark",
"serde", "serde",
"serde_json", "serde_json",
"signal-hook",
"signal-hook-tokio",
"tokio", "tokio",
"toml", "toml",
] ]
@ -865,6 +867,18 @@ dependencies = [
"libc", "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]] [[package]]
name = "similar" name = "similar"
version = "1.3.0" version = "1.3.0"

View File

@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
thiserror = "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" tokio-stream = "0.1.7"

View File

@ -28,10 +28,11 @@ helix-lsp = { version = "0.3", path = "../helix-lsp" }
anyhow = "1" anyhow = "1"
once_cell = "1.8" 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" num_cpus = "1"
tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] } tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
crossterm = { version = "0.20", features = ["event-stream"] } crossterm = { version = "0.20", features = ["event-stream"] }
signal-hook = "0.3"
futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false } futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
@ -53,3 +54,6 @@ toml = "0.5"
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } 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"] }

View File

@ -18,6 +18,10 @@ use crossterm::{
event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream}, event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream},
execute, terminal, execute, terminal,
}; };
#[cfg(not(windows))]
use signal_hook::{consts::signal, low_level};
#[cfg(not(windows))]
use signal_hook_tokio::Signals;
pub struct Application { pub struct Application {
compositor: Compositor, compositor: Compositor,
@ -36,6 +40,8 @@ pub struct Application {
#[allow(dead_code)] #[allow(dead_code)]
syn_loader: Arc<syntax::Loader>, syn_loader: Arc<syntax::Loader>,
#[cfg(not(windows))]
signals: Signals,
jobs: Jobs, jobs: Jobs,
lsp_progress: LspProgressMap, lsp_progress: LspProgressMap,
} }
@ -102,6 +108,9 @@ impl Application {
editor.set_theme(theme); editor.set_theme(theme);
#[cfg(not(windows))]
let signals = Signals::new(&[signal::SIGTSTP, signal::SIGCONT])?;
let app = Self { let app = Self {
compositor, compositor,
editor, editor,
@ -111,6 +120,8 @@ impl Application {
theme_loader, theme_loader,
syn_loader, syn_loader,
#[cfg(not(windows))]
signals,
jobs: Jobs::new(), jobs: Jobs::new(),
lsp_progress: LspProgressMap::new(), lsp_progress: LspProgressMap::new(),
}; };
@ -147,6 +158,51 @@ impl Application {
use futures_util::StreamExt; 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! { tokio::select! {
biased; biased;
@ -443,15 +499,29 @@ impl Application {
} }
} }
pub async fn run(&mut self) -> Result<(), Error> { async fn claim_term(&mut self) -> Result<(), Error> {
terminal::enable_raw_mode()?; terminal::enable_raw_mode()?;
let mut stdout = stdout(); let mut stdout = stdout();
execute!(stdout, terminal::EnterAlternateScreen)?; execute!(stdout, terminal::EnterAlternateScreen)?;
self.editor.close_language_servers(None).await?;
if self.config.terminal.mouse { if self.config.terminal.mouse {
execute!(stdout, EnableMouseCapture)?; 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 // Exit the alternate screen and disable raw mode before panicking
let hook = std::panic::take_hook(); let hook = std::panic::take_hook();
@ -469,13 +539,7 @@ impl Application {
self.editor.close_language_servers(None).await?; self.editor.close_language_servers(None).await?;
// reset cursor shape self.restore_term()?;
write!(stdout, "\x1B[2 q")?;
execute!(stdout, DisableMouseCapture)?;
execute!(stdout, terminal::LeaveAlternateScreen)?;
terminal::disable_raw_mode()?;
Ok(()) Ok(())
} }

View File

@ -291,7 +291,8 @@ impl Command {
surround_replace, "Surround replace", surround_replace, "Surround replace",
surround_delete, "Surround delete", surround_delete, "Surround delete",
select_textobject_around, "Select around object", 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();
}

View File

@ -68,7 +68,7 @@ pub trait Component: Any + AnyComponent {
use anyhow::Error; use anyhow::Error;
use std::io::stdout; use std::io::stdout;
use tui::backend::CrosstermBackend; use tui::backend::{Backend, CrosstermBackend};
type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>; type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
pub struct Compositor { pub struct Compositor {
@ -99,6 +99,21 @@ impl Compositor {
.expect("Unable to resize terminal") .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<dyn Component>) { pub fn push(&mut self, mut layer: Box<dyn Component>) {
let size = self.size(); let size = self.size();
// trigger required_size on init // trigger required_size on init

View File

@ -502,6 +502,7 @@ impl Default for Keymaps {
}, },
"\"" => select_register, "\"" => select_register,
"C-z" => suspend,
}); });
// TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether // 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 // we keep this separate select mode. More keys can fit into normal mode then, but it's weird

View File

@ -45,8 +45,8 @@ where
buffers: [Buffer; 2], buffers: [Buffer; 2],
/// Index of the current buffer in the previous array /// Index of the current buffer in the previous array
current: usize, current: usize,
/// Whether the cursor is currently hidden /// Kind of cursor (hidden or others)
hidden_cursor: bool, cursor_kind: CursorKind,
/// Viewport /// Viewport
viewport: Viewport, viewport: Viewport,
} }
@ -57,7 +57,7 @@ where
{ {
fn drop(&mut self) { fn drop(&mut self) {
// Attempt to restore the cursor state // 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) { if let Err(err) = self.show_cursor(CursorKind::Block) {
eprintln!("Failed to show the cursor: {}", err); eprintln!("Failed to show the cursor: {}", err);
} }
@ -93,7 +93,7 @@ where
Buffer::empty(options.viewport.area), Buffer::empty(options.viewport.area),
], ],
current: 0, current: 0,
hidden_cursor: false, cursor_kind: CursorKind::Block,
viewport: options.viewport, viewport: options.viewport,
}) })
} }
@ -185,15 +185,20 @@ where
Ok(()) Ok(())
} }
#[inline]
pub fn cursor_kind(&self) -> CursorKind {
self.cursor_kind
}
pub fn hide_cursor(&mut self) -> io::Result<()> { pub fn hide_cursor(&mut self) -> io::Result<()> {
self.backend.hide_cursor()?; self.backend.hide_cursor()?;
self.hidden_cursor = true; self.cursor_kind = CursorKind::Hidden;
Ok(()) Ok(())
} }
pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> { pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
self.backend.show_cursor(kind)?; self.backend.show_cursor(kind)?;
self.hidden_cursor = false; self.cursor_kind = kind;
Ok(()) Ok(())
} }

View File

@ -24,7 +24,7 @@ crossterm = { version = "0.20", optional = true }
once_cell = "1.8" once_cell = "1.8"
url = "2" 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 } futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
slotmap = "1" slotmap = "1"

View File

@ -1,7 +1,7 @@
use bitflags::bitflags; use bitflags::bitflags;
use std::cmp::{max, min}; use std::cmp::{max, min};
#[derive(Debug)] #[derive(Debug, Clone, Copy, PartialEq)]
/// UNSTABLE /// UNSTABLE
pub enum CursorKind { pub enum CursorKind {
/// █ /// █