aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/Cargo.toml6
-rw-r--r--helix-term/src/application.rs84
-rw-r--r--helix-term/src/commands.rs8
-rw-r--r--helix-term/src/compositor.rs17
-rw-r--r--helix-term/src/keymap.rs1
5 files changed, 103 insertions, 13 deletions
diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml
index 1fc14ad2..0e2baae3 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 5f350671..9e659fb0 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -18,6 +18,10 @@ use crossterm::{
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<syntax::Loader>,
+ #[cfg(not(windows))]
+ signals: Signals,
jobs: Jobs,
lsp_progress: LspProgressMap,
}
@@ -102,6 +108,9 @@ impl Application {
editor.set_theme(theme);
+ #[cfg(not(windows))]
+ let signals = Signals::new(&[signal::SIGTSTP, signal::SIGCONT])?;
+
let app = Self {
compositor,
editor,
@@ -111,6 +120,8 @@ impl Application {
theme_loader,
syn_loader,
+ #[cfg(not(windows))]
+ signals,
jobs: Jobs::new(),
lsp_progress: LspProgressMap::new(),
};
@@ -147,6 +158,51 @@ 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.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 @@ impl Application {
}
}
- 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 @@ impl Application {
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 cc2c2cca..5cbab34f 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -291,7 +291,8 @@ impl Command {
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 c2cfa3a7..628c4e13 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -68,7 +68,7 @@ pub trait Component: Any + AnyComponent {
use anyhow::Error;
use std::io::stdout;
-use tui::backend::CrosstermBackend;
+use tui::backend::{Backend, CrosstermBackend};
type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
pub struct Compositor {
@@ -99,6 +99,21 @@ impl Compositor {
.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>) {
let size = self.size();
// trigger required_size on init
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index 053b92e6..5fe730a1 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -502,6 +502,7 @@ impl Default for 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