on master help sign in
Add ctrl-z to suspend
821565eIvan Tham
Cargo.lock+14
@@ -378,6 +378,8 @@ dependencies = [
378 "pulldown-cmark",
378 "pulldown-cmark",
379 "serde",
379 "serde",
380 "serde_json",
380 "serde_json",
381+ "signal-hook",
382+ "signal-hook-tokio",
381 "tokio",
383 "tokio",
382 "toml",
384 "toml",
383]
385]
@@ -865,6 +867,18 @@ dependencies = [
865 "libc",
867 "libc",
866]
868]
867
869
870+[[package]]
871+name = "signal-hook-tokio"
872+version = "0.3.0"
873+source = "registry+https://github.com/rust-lang/crates.io-index"
874+checksum = "f6c5d32165ff8b94e68e7b3bdecb1b082e958c22434b363482cfb89dcd6f3ff8"
875+dependencies = [
876+ "futures-core",
877+ "libc",
878+ "signal-hook",
879+ "tokio",
880+]
881+
868[[package]]
882[[package]]
869name = "similar"
883name = "similar"
870version = "1.3.0"
884version = "1.3.0"
helix-lsp/Cargo.toml+1−1
@@ -23,5 +23,5 @@ lsp-types = { version = "0.89", features = ["proposed"] }
23serde = { version = "1.0", features = ["derive"] }
23serde = { version = "1.0", features = ["derive"] }
24serde_json = "1.0"
24serde_json = "1.0"
25thiserror = "1.0"
25thiserror = "1.0"
26−tokio = { version = "1.9", features = ["full"] }
26+tokio = { version = "1.9", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
27tokio-stream = "0.1.7"
27tokio-stream = "0.1.7"
helix-term/Cargo.toml+5−1
@@ -28,10 +28,11 @@ helix-lsp = { version = "0.3", path = "../helix-lsp" }
28anyhow = "1"
28anyhow = "1"
29once_cell = "1.8"
29once_cell = "1.8"
30
30
31−tokio = { version = "1", features = ["full"] }
31+tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
32num_cpus = "1"
32num_cpus = "1"
33tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
33tui = { path = "../helix-tui", package = "helix-tui", default-features = false, features = ["crossterm"] }
34crossterm = { version = "0.20", features = ["event-stream"] }
34crossterm = { version = "0.20", features = ["event-stream"] }
35+signal-hook = "0.3"
35
36
36futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
37futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
37
38
@@ -53,3 +54,6 @@ toml = "0.5"
53
54
54serde_json = "1.0"
55serde_json = "1.0"
55serde = { version = "1.0", features = ["derive"] }
56serde = { version = "1.0", features = ["derive"] }
57+
58+[target.'cfg(not(windows))'.dependencies] # https://github.com/vorner/signal-hook/issues/100
59+signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] }
helix-term/src/application.rs+74−10
@@ -18,6 +18,10 @@ use crossterm::{
18 event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream},
18 event::{DisableMouseCapture, EnableMouseCapture, Event, EventStream},
19 execute, terminal,
19 execute, terminal,
20};
20};
21+#[cfg(not(windows))]
22+use signal_hook::{consts::signal, low_level};
23+#[cfg(not(windows))]
24+use signal_hook_tokio::Signals;
21
25
22pub struct Application {
26pub struct Application {
23 compositor: Compositor,
27 compositor: Compositor,
@@ -36,6 +40,8 @@ pub struct Application {
36 #[allow(dead_code)]
40 #[allow(dead_code)]
37 syn_loader: Arc<syntax::Loader>,
41 syn_loader: Arc<syntax::Loader>,
38
42
43+ #[cfg(not(windows))]
44+ signals: Signals,
39 jobs: Jobs,
45 jobs: Jobs,
40 lsp_progress: LspProgressMap,
46 lsp_progress: LspProgressMap,
41}
47}
@@ -102,6 +108,9 @@ impl Application {
102
108
103 editor.set_theme(theme);
109 editor.set_theme(theme);
104
110
111+ #[cfg(not(windows))]
112+ let signals = Signals::new(&[signal::SIGTSTP, signal::SIGCONT])?;
113+
105 let app = Self {
114 let app = Self {
106 compositor,
115 compositor,
107 editor,
116 editor,
@@ -111,6 +120,8 @@ impl Application {
111 theme_loader,
120 theme_loader,
112 syn_loader,
121 syn_loader,
113
122
123+ #[cfg(not(windows))]
124+ signals,
114 jobs: Jobs::new(),
125 jobs: Jobs::new(),
115 lsp_progress: LspProgressMap::new(),
126 lsp_progress: LspProgressMap::new(),
116 };
127 };
@@ -147,6 +158,51 @@ impl Application {
147
158
148 use futures_util::StreamExt;
159 use futures_util::StreamExt;
149
160
161+ #[cfg(not(windows))]
162+ tokio::select! {
163+ biased;
164+
165+ event = reader.next() => {
166+ self.handle_terminal_events(event)
167+ }
168+ Some(signal) = self.signals.next() => {
169+ use helix_view::graphics::Rect;
170+ match signal {
171+ signal::SIGTSTP => {
172+ self.compositor.save_cursor();
173+ self.restore_term().unwrap();
174+ low_level::emulate_default_handler(signal::SIGTSTP).unwrap();
175+ }
176+ signal::SIGCONT => {
177+ self.claim_term().await.unwrap();
178+ // redraw the terminal
179+ let Rect { width, height, .. } = self.compositor.size();
180+ self.compositor.resize(width, height);
181+ self.compositor.load_cursor();
182+ self.render();
183+ }
184+ _ => unreachable!(),
185+ }
186+ }
187+ Some((id, call)) = self.editor.language_servers.incoming.next() => {
188+ self.handle_language_server_message(call, id).await;
189+ // limit render calls for fast language server messages
190+ let last = self.editor.language_servers.incoming.is_empty();
191+ if last || last_render.elapsed() > deadline {
192+ self.render();
193+ last_render = Instant::now();
194+ }
195+ }
196+ Some(callback) = self.jobs.futures.next() => {
197+ self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
198+ self.render();
199+ }
200+ Some(callback) = self.jobs.wait_futures.next() => {
201+ self.jobs.handle_callback(&mut self.editor, &mut self.compositor, callback);
202+ self.render();
203+ }
204+ }
205+ #[cfg(windows)]
150 tokio::select! {
206 tokio::select! {
151 biased;
207 biased;
152
208
@@ -443,15 +499,29 @@ impl Application {
443 }
499 }
444 }
500 }
445
501
446− pub async fn run(&mut self) -> Result<(), Error> {
502+ async fn claim_term(&mut self) -> Result<(), Error> {
447 terminal::enable_raw_mode()?;
503 terminal::enable_raw_mode()?;
448−
449 let mut stdout = stdout();
504 let mut stdout = stdout();
450−
451 execute!(stdout, terminal::EnterAlternateScreen)?;
505 execute!(stdout, terminal::EnterAlternateScreen)?;
506+ self.editor.close_language_servers(None).await?;
452 if self.config.terminal.mouse {
507 if self.config.terminal.mouse {
453 execute!(stdout, EnableMouseCapture)?;
508 execute!(stdout, EnableMouseCapture)?;
454 }
509 }
510+ Ok(())
511+ }
512+
513+ fn restore_term(&mut self) -> Result<(), Error> {
514+ let mut stdout = stdout();
515+ // reset cursor shape
516+ write!(stdout, "\x1B[2 q")?;
517+ execute!(stdout, DisableMouseCapture)?;
518+ execute!(stdout, terminal::LeaveAlternateScreen)?;
519+ terminal::disable_raw_mode()?;
520+ Ok(())
521+ }
522+
523+ pub async fn run(&mut self) -> Result<(), Error> {
524+ self.claim_term().await?;
455
525
456 // Exit the alternate screen and disable raw mode before panicking
526 // Exit the alternate screen and disable raw mode before panicking
457 let hook = std::panic::take_hook();
527 let hook = std::panic::take_hook();
@@ -469,13 +539,7 @@ impl Application {
469
539
470 self.editor.close_language_servers(None).await?;
540 self.editor.close_language_servers(None).await?;
471
541
472− // reset cursor shape
542+ self.restore_term()?;
473− write!(stdout, "\x1B[2 q")?;
474−
475− execute!(stdout, DisableMouseCapture)?;
476− execute!(stdout, terminal::LeaveAlternateScreen)?;
477−
478− terminal::disable_raw_mode()?;
479
543
480 Ok(())
544 Ok(())
481 }
545 }
helix-term/src/commands.rs+7−1
@@ -291,7 +291,8 @@ impl Command {
291 surround_replace, "Surround replace",
291 surround_replace, "Surround replace",
292 surround_delete, "Surround delete",
292 surround_delete, "Surround delete",
293 select_textobject_around, "Select around object",
293 select_textobject_around, "Select around object",
294− select_textobject_inner, "Select inside object"
294+ select_textobject_inner, "Select inside object",
295+ suspend, "Suspend"
295 );
296 );
296}
297}
297
298
@@ -3894,3 +3895,8 @@ fn surround_delete(cx: &mut Context) {
3894 }
3895 }
3895 })
3896 })
3896}
3897}
3898+
3899+fn suspend(_cx: &mut Context) {
3900+ #[cfg(not(windows))]
3901+ signal_hook::low_level::raise(signal_hook::consts::signal::SIGTSTP).unwrap();
3902+}
helix-term/src/compositor.rs+16−1
@@ -68,7 +68,7 @@ pub trait Component: Any + AnyComponent {
68
68
69use anyhow::Error;
69use anyhow::Error;
70use std::io::stdout;
70use std::io::stdout;
71−use tui::backend::CrosstermBackend;
71+use tui::backend::{Backend, CrosstermBackend};
72type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
72type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
73
73
74pub struct Compositor {
74pub struct Compositor {
@@ -99,6 +99,21 @@ impl Compositor {
99 .expect("Unable to resize terminal")
99 .expect("Unable to resize terminal")
100 }
100 }
101
101
102+ pub fn save_cursor(&mut self) {
103+ if self.terminal.cursor_kind() == CursorKind::Hidden {
104+ self.terminal
105+ .backend_mut()
106+ .show_cursor(CursorKind::Block)
107+ .ok();
108+ }
109+ }
110+
111+ pub fn load_cursor(&mut self) {
112+ if self.terminal.cursor_kind() == CursorKind::Hidden {
113+ self.terminal.backend_mut().hide_cursor().ok();
114+ }
115+ }
116+
102 pub fn push(&mut self, mut layer: Box<dyn Component>) {
117 pub fn push(&mut self, mut layer: Box<dyn Component>) {
103 let size = self.size();
118 let size = self.size();
104 // trigger required_size on init
119 // trigger required_size on init
helix-term/src/keymap.rs+1
@@ -502,6 +502,7 @@ impl Default for Keymaps {
502 },
502 },
503
503
504 "\"" => select_register,
504 "\"" => select_register,
505+ "C-z" => suspend,
505 });
506 });
506 // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
507 // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
507 // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
508 // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
helix-tui/src/terminal.rs+11−6
@@ -45,8 +45,8 @@ where
45 buffers: [Buffer; 2],
45 buffers: [Buffer; 2],
46 /// Index of the current buffer in the previous array
46 /// Index of the current buffer in the previous array
47 current: usize,
47 current: usize,
48− /// Whether the cursor is currently hidden
48+ /// Kind of cursor (hidden or others)
49− hidden_cursor: bool,
49+ cursor_kind: CursorKind,
50 /// Viewport
50 /// Viewport
51 viewport: Viewport,
51 viewport: Viewport,
52}
52}
@@ -57,7 +57,7 @@ where
57{
57{
58 fn drop(&mut self) {
58 fn drop(&mut self) {
59 // Attempt to restore the cursor state
59 // Attempt to restore the cursor state
60− if self.hidden_cursor {
60+ if self.cursor_kind == CursorKind::Hidden {
61 if let Err(err) = self.show_cursor(CursorKind::Block) {
61 if let Err(err) = self.show_cursor(CursorKind::Block) {
62 eprintln!("Failed to show the cursor: {}", err);
62 eprintln!("Failed to show the cursor: {}", err);
63 }
63 }
@@ -93,7 +93,7 @@ where
93 Buffer::empty(options.viewport.area),
93 Buffer::empty(options.viewport.area),
94 ],
94 ],
95 current: 0,
95 current: 0,
96− hidden_cursor: false,
96+ cursor_kind: CursorKind::Block,
97 viewport: options.viewport,
97 viewport: options.viewport,
98 })
98 })
99 }
99 }
@@ -185,15 +185,20 @@ where
185 Ok(())
185 Ok(())
186 }
186 }
187
187
188+ #[inline]
189+ pub fn cursor_kind(&self) -> CursorKind {
190+ self.cursor_kind
191+ }
192+
188 pub fn hide_cursor(&mut self) -> io::Result<()> {
193 pub fn hide_cursor(&mut self) -> io::Result<()> {
189 self.backend.hide_cursor()?;
194 self.backend.hide_cursor()?;
190− self.hidden_cursor = true;
195+ self.cursor_kind = CursorKind::Hidden;
191 Ok(())
196 Ok(())
192 }
197 }
193
198
194 pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
199 pub fn show_cursor(&mut self, kind: CursorKind) -> io::Result<()> {
195 self.backend.show_cursor(kind)?;
200 self.backend.show_cursor(kind)?;
196− self.hidden_cursor = false;
201+ self.cursor_kind = kind;
197 Ok(())
202 Ok(())
198 }
203 }
199
204
helix-view/Cargo.toml+1−1
@@ -24,7 +24,7 @@ crossterm = { version = "0.20", optional = true }
24once_cell = "1.8"
24once_cell = "1.8"
25url = "2"
25url = "2"
26
26
27−tokio = { version = "1", features = ["full"] }
27+tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "io-std", "time", "process", "macros", "fs", "parking_lot"] }
28futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
28futures-util = { version = "0.3", features = ["std", "async-await"], default-features = false }
29
29
30slotmap = "1"
30slotmap = "1"
helix-view/src/graphics.rs+1−1
@@ -1,7 +1,7 @@
1use bitflags::bitflags;
1use bitflags::bitflags;
2use std::cmp::{max, min};
2use std::cmp::{max, min};
3
3
4−#[derive(Debug)]
4+#[derive(Debug, Clone, Copy, PartialEq)]
5/// UNSTABLE
5/// UNSTABLE
6pub enum CursorKind {
6pub enum CursorKind {
7 /// █
7 /// █