mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-24 18:36:18 +04:00
Add ctrl-z to suspend
This commit is contained in:
parent
adb5d842ba
commit
821565e4ef
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"] }
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
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 @@ pub fn new(args: Args, mut config: Config) -> Result<Self, Error> {
|
|||||||
|
|
||||||
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 @@ pub fn new(args: Args, mut config: Config) -> Result<Self, Error> {
|
|||||||
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 @@ pub async fn event_loop(&mut self) {
|
|||||||
|
|
||||||
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 @@ 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()?;
|
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 @@ pub async fn run(&mut self) -> Result<(), Error> {
|
|||||||
|
|
||||||
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(())
|
||||||
}
|
}
|
||||||
|
@ -291,7 +291,8 @@ pub fn doc(&self) -> &'static str {
|
|||||||
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();
|
||||||
|
}
|
||||||
|
@ -68,7 +68,7 @@ fn type_name(&self) -> &'static str {
|
|||||||
|
|
||||||
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 @@ pub fn resize(&mut self, width: u16, height: u16) {
|
|||||||
.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
|
||||||
|
@ -502,6 +502,7 @@ fn default() -> 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
|
||||||
|
@ -45,8 +45,8 @@ pub struct Terminal<B>
|
|||||||
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 @@ impl<B> Drop for Terminal<B>
|
|||||||
{
|
{
|
||||||
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 @@ pub fn with_options(backend: B, options: TerminalOptions) -> io::Result<Terminal
|
|||||||
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 @@ pub fn draw(
|
|||||||
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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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"
|
||||||
|
@ -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 {
|
||||||
/// █
|
/// █
|
||||||
|
Loading…
Reference in New Issue
Block a user