Move terminal claim/restore code to helix-tui
This moves the `Application::claim_term` and `helix-term::application::restore_term` functions into the helix-tui crate. How the terminal should be claimed and restored is a TUI concern and is implemented differently through different TUI backends. This cleans out a lot of crossterm and TUI code in Application and makes it easier to modify claim/restore based on information we query from the terminal host. The child commit will take advantage of this to cache the check for whether the host terminal supports the keyboard enhancement protocol. Without this change, caching that information takes much more code which is not easily reusable for anything else. The code to restore the terminal is somewhat duplicated by this patch: we want to restore the terminal in cases of panics. Panic handler hooks must live for `'static` and the Application's terminal does not.
This commit is contained in:
parent
48b6aa9a69
commit
3d85024717
7 changed files with 131 additions and 79 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1201,6 +1201,7 @@ dependencies = [
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"helix-core",
|
"helix-core",
|
||||||
"helix-view",
|
"helix-view",
|
||||||
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"termini",
|
"termini",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
|
|
|
@ -30,22 +30,14 @@ use crate::{
|
||||||
|
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use std::{
|
use std::{
|
||||||
io::{stdin, stdout, Write},
|
io::{stdin, stdout},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{Context, Error};
|
use anyhow::{Context, Error};
|
||||||
|
|
||||||
use crossterm::{
|
use crossterm::{event::Event as CrosstermEvent, tty::IsTty};
|
||||||
event::{
|
|
||||||
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
|
|
||||||
EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, KeyboardEnhancementFlags,
|
|
||||||
PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
|
|
||||||
},
|
|
||||||
execute, terminal,
|
|
||||||
tty::IsTty,
|
|
||||||
};
|
|
||||||
#[cfg(not(windows))]
|
#[cfg(not(windows))]
|
||||||
use {
|
use {
|
||||||
signal_hook::{consts::signal, low_level},
|
signal_hook::{consts::signal, low_level},
|
||||||
|
@ -63,10 +55,12 @@ use tui::backend::CrosstermBackend;
|
||||||
use tui::backend::TestBackend;
|
use tui::backend::TestBackend;
|
||||||
|
|
||||||
#[cfg(not(feature = "integration"))]
|
#[cfg(not(feature = "integration"))]
|
||||||
type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
|
type TerminalBackend = CrosstermBackend<std::io::Stdout>;
|
||||||
|
|
||||||
#[cfg(feature = "integration")]
|
#[cfg(feature = "integration")]
|
||||||
type Terminal = tui::terminal::Terminal<TestBackend>;
|
type TerminalBackend = TestBackend;
|
||||||
|
|
||||||
|
type Terminal = tui::terminal::Terminal<TerminalBackend>;
|
||||||
|
|
||||||
pub struct Application {
|
pub struct Application {
|
||||||
compositor: Compositor,
|
compositor: Compositor,
|
||||||
|
@ -108,26 +102,6 @@ fn setup_integration_logging() {
|
||||||
.apply();
|
.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn restore_term() -> Result<(), Error> {
|
|
||||||
let mut stdout = stdout();
|
|
||||||
// reset cursor shape
|
|
||||||
write!(stdout, "\x1B[0 q")?;
|
|
||||||
if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
|
|
||||||
execute!(stdout, PopKeyboardEnhancementFlags)?;
|
|
||||||
}
|
|
||||||
// Ignore errors on disabling, this might trigger on windows if we call
|
|
||||||
// disable without calling enable previously
|
|
||||||
let _ = execute!(stdout, DisableMouseCapture);
|
|
||||||
execute!(
|
|
||||||
stdout,
|
|
||||||
DisableBracketedPaste,
|
|
||||||
DisableFocusChange,
|
|
||||||
terminal::LeaveAlternateScreen
|
|
||||||
)?;
|
|
||||||
terminal::disable_raw_mode()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Application {
|
impl Application {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
args: Args,
|
args: Args,
|
||||||
|
@ -472,13 +446,7 @@ impl Application {
|
||||||
pub async fn handle_signals(&mut self, signal: i32) {
|
pub async fn handle_signals(&mut self, signal: i32) {
|
||||||
match signal {
|
match signal {
|
||||||
signal::SIGTSTP => {
|
signal::SIGTSTP => {
|
||||||
// restore cursor
|
self.restore_term().unwrap();
|
||||||
use helix_view::graphics::CursorKind;
|
|
||||||
self.terminal
|
|
||||||
.backend_mut()
|
|
||||||
.show_cursor(CursorKind::Block)
|
|
||||||
.ok();
|
|
||||||
restore_term().unwrap();
|
|
||||||
low_level::emulate_default_handler(signal::SIGTSTP).unwrap();
|
low_level::emulate_default_handler(signal::SIGTSTP).unwrap();
|
||||||
}
|
}
|
||||||
signal::SIGCONT => {
|
signal::SIGCONT => {
|
||||||
|
@ -1054,37 +1022,19 @@ impl Application {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn claim_term(&mut self) -> Result<(), Error> {
|
async fn claim_term(&mut self) -> std::io::Result<()> {
|
||||||
use helix_view::graphics::CursorKind;
|
let terminal_config = self.config.load().editor.clone().into();
|
||||||
terminal::enable_raw_mode()?;
|
self.terminal.claim(terminal_config)
|
||||||
if self.terminal.cursor_kind() == CursorKind::Hidden {
|
}
|
||||||
self.terminal.backend_mut().hide_cursor().ok();
|
|
||||||
}
|
|
||||||
let mut stdout = stdout();
|
|
||||||
execute!(
|
|
||||||
stdout,
|
|
||||||
terminal::EnterAlternateScreen,
|
|
||||||
EnableBracketedPaste,
|
|
||||||
EnableFocusChange
|
|
||||||
)?;
|
|
||||||
execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
|
|
||||||
if self.config.load().editor.mouse {
|
|
||||||
execute!(stdout, EnableMouseCapture)?;
|
|
||||||
}
|
|
||||||
if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
|
|
||||||
log::debug!("The enhanced keyboard protocol is supported on this terminal");
|
|
||||||
execute!(
|
|
||||||
stdout,
|
|
||||||
PushKeyboardEnhancementFlags(
|
|
||||||
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
|
||||||
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
log::debug!("The enhanced keyboard protocol is not supported on this terminal");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
fn restore_term(&mut self) -> std::io::Result<()> {
|
||||||
|
let terminal_config = self.config.load().editor.clone().into();
|
||||||
|
use helix_view::graphics::CursorKind;
|
||||||
|
self.terminal
|
||||||
|
.backend_mut()
|
||||||
|
.show_cursor(CursorKind::Block)
|
||||||
|
.ok();
|
||||||
|
self.terminal.restore(terminal_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
|
pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
|
||||||
|
@ -1099,7 +1049,7 @@ impl Application {
|
||||||
// We can't handle errors properly inside this closure. And it's
|
// We can't handle errors properly inside this closure. And it's
|
||||||
// probably not a good idea to `unwrap()` inside a panic handler.
|
// probably not a good idea to `unwrap()` inside a panic handler.
|
||||||
// So we just ignore the `Result`.
|
// So we just ignore the `Result`.
|
||||||
let _ = restore_term();
|
let _ = TerminalBackend::force_restore();
|
||||||
hook(info);
|
hook(info);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -1107,13 +1057,7 @@ impl Application {
|
||||||
|
|
||||||
let close_errs = self.close().await;
|
let close_errs = self.close().await;
|
||||||
|
|
||||||
// restore cursor
|
self.restore_term()?;
|
||||||
use helix_view::graphics::CursorKind;
|
|
||||||
self.terminal
|
|
||||||
.backend_mut()
|
|
||||||
.show_cursor(CursorKind::Block)
|
|
||||||
.ok();
|
|
||||||
restore_term()?;
|
|
||||||
|
|
||||||
for err in close_errs {
|
for err in close_errs {
|
||||||
self.editor.exit_code = 1;
|
self.editor.exit_code = 1;
|
||||||
|
|
|
@ -22,5 +22,6 @@ unicode-segmentation = "1.10"
|
||||||
crossterm = { version = "0.26", optional = true }
|
crossterm = { version = "0.26", optional = true }
|
||||||
termini = "0.1"
|
termini = "0.1"
|
||||||
serde = { version = "1", "optional" = true, features = ["derive"]}
|
serde = { version = "1", "optional" = true, features = ["derive"]}
|
||||||
|
log = "~0.4"
|
||||||
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
|
helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
|
||||||
helix-core = { version = "0.6", path = "../helix-core" }
|
helix-core = { version = "0.6", path = "../helix-core" }
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use crate::{backend::Backend, buffer::Cell};
|
use crate::{backend::Backend, buffer::Cell, terminal::Config};
|
||||||
use crossterm::{
|
use crossterm::{
|
||||||
cursor::{Hide, MoveTo, SetCursorStyle, Show},
|
cursor::{Hide, MoveTo, SetCursorStyle, Show},
|
||||||
|
event::{
|
||||||
|
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
|
||||||
|
EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags,
|
||||||
|
PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
|
||||||
|
},
|
||||||
execute, queue,
|
execute, queue,
|
||||||
style::{
|
style::{
|
||||||
Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
|
Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
|
||||||
|
@ -83,6 +88,69 @@ impl<W> Backend for CrosstermBackend<W>
|
||||||
where
|
where
|
||||||
W: Write,
|
W: Write,
|
||||||
{
|
{
|
||||||
|
fn claim(&mut self, config: Config) -> io::Result<()> {
|
||||||
|
terminal::enable_raw_mode()?;
|
||||||
|
execute!(
|
||||||
|
self.buffer,
|
||||||
|
terminal::EnterAlternateScreen,
|
||||||
|
EnableBracketedPaste,
|
||||||
|
EnableFocusChange
|
||||||
|
)?;
|
||||||
|
execute!(self.buffer, terminal::Clear(terminal::ClearType::All))?;
|
||||||
|
if config.enable_mouse_capture {
|
||||||
|
execute!(self.buffer, EnableMouseCapture)?;
|
||||||
|
}
|
||||||
|
if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
|
||||||
|
log::debug!("The enhanced keyboard protocol is supported on this terminal");
|
||||||
|
execute!(
|
||||||
|
self.buffer,
|
||||||
|
PushKeyboardEnhancementFlags(
|
||||||
|
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
|
||||||
|
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
|
||||||
|
)
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
log::debug!("The enhanced keyboard protocol is not supported on this terminal");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, config: Config) -> io::Result<()> {
|
||||||
|
// reset cursor shape
|
||||||
|
write!(self.buffer, "\x1B[0 q")?;
|
||||||
|
if config.enable_mouse_capture {
|
||||||
|
execute!(self.buffer, DisableMouseCapture)?;
|
||||||
|
}
|
||||||
|
if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
|
||||||
|
execute!(self.buffer, PopKeyboardEnhancementFlags)?;
|
||||||
|
}
|
||||||
|
execute!(
|
||||||
|
self.buffer,
|
||||||
|
DisableBracketedPaste,
|
||||||
|
DisableFocusChange,
|
||||||
|
terminal::LeaveAlternateScreen
|
||||||
|
)?;
|
||||||
|
terminal::disable_raw_mode()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_restore() -> io::Result<()> {
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
|
||||||
|
// reset cursor shape
|
||||||
|
write!(stdout, "\x1B[0 q")?;
|
||||||
|
// Ignore errors on disabling, this might trigger on windows if we call
|
||||||
|
// disable without calling enable previously
|
||||||
|
let _ = execute!(stdout, DisableMouseCapture);
|
||||||
|
let _ = execute!(stdout, PopKeyboardEnhancementFlags);
|
||||||
|
execute!(
|
||||||
|
stdout,
|
||||||
|
DisableBracketedPaste,
|
||||||
|
DisableFocusChange,
|
||||||
|
terminal::LeaveAlternateScreen
|
||||||
|
)?;
|
||||||
|
terminal::disable_raw_mode()
|
||||||
|
}
|
||||||
|
|
||||||
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::buffer::Cell;
|
use crate::{buffer::Cell, terminal::Config};
|
||||||
|
|
||||||
use helix_view::graphics::{CursorKind, Rect};
|
use helix_view::graphics::{CursorKind, Rect};
|
||||||
|
|
||||||
|
@ -13,6 +13,9 @@ mod test;
|
||||||
pub use self::test::TestBackend;
|
pub use self::test::TestBackend;
|
||||||
|
|
||||||
pub trait Backend {
|
pub trait Backend {
|
||||||
|
fn claim(&mut self, config: Config) -> Result<(), io::Error>;
|
||||||
|
fn restore(&mut self, config: Config) -> Result<(), io::Error>;
|
||||||
|
fn force_restore() -> Result<(), io::Error>;
|
||||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (u16, u16, &'a Cell)>;
|
I: Iterator<Item = (u16, u16, &'a Cell)>;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
backend::Backend,
|
backend::Backend,
|
||||||
buffer::{Buffer, Cell},
|
buffer::{Buffer, Cell},
|
||||||
|
terminal::Config,
|
||||||
};
|
};
|
||||||
use helix_core::unicode::width::UnicodeWidthStr;
|
use helix_core::unicode::width::UnicodeWidthStr;
|
||||||
use helix_view::graphics::{CursorKind, Rect};
|
use helix_view::graphics::{CursorKind, Rect};
|
||||||
|
@ -106,6 +107,18 @@ impl TestBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backend for TestBackend {
|
impl Backend for TestBackend {
|
||||||
|
fn claim(&mut self, _config: Config) -> Result<(), io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&mut self, _config: Config) -> Result<(), io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn force_restore() -> Result<(), io::Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
I: Iterator<Item = (u16, u16, &'a Cell)>,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::{backend::Backend, buffer::Buffer};
|
use crate::{backend::Backend, buffer::Buffer};
|
||||||
|
use helix_view::editor::Config as EditorConfig;
|
||||||
use helix_view::graphics::{CursorKind, Rect};
|
use helix_view::graphics::{CursorKind, Rect};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
|
@ -16,6 +17,19 @@ pub struct Viewport {
|
||||||
resize_behavior: ResizeBehavior,
|
resize_behavior: ResizeBehavior,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub enable_mouse_capture: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EditorConfig> for Config {
|
||||||
|
fn from(config: EditorConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
enable_mouse_capture: config.mouse,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Viewport {
|
impl Viewport {
|
||||||
/// UNSTABLE
|
/// UNSTABLE
|
||||||
pub fn fixed(area: Rect) -> Viewport {
|
pub fn fixed(area: Rect) -> Viewport {
|
||||||
|
@ -98,6 +112,14 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn claim(&mut self, config: Config) -> io::Result<()> {
|
||||||
|
self.backend.claim(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore(&mut self, config: Config) -> io::Result<()> {
|
||||||
|
self.backend.restore(config)
|
||||||
|
}
|
||||||
|
|
||||||
// /// Get a Frame object which provides a consistent view into the terminal state for rendering.
|
// /// Get a Frame object which provides a consistent view into the terminal state for rendering.
|
||||||
// pub fn get_frame(&mut self) -> Frame<B> {
|
// pub fn get_frame(&mut self) -> Frame<B> {
|
||||||
// Frame {
|
// Frame {
|
||||||
|
|
Loading…
Add table
Reference in a new issue