on master help sign in
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.

commit3d850247177f61601296fe5b1cdada8819137783 authorMichael Davis <mcarsondavis@gmail.com> dateSun 5 Mar 2023, 12:06 (4 years ago) parent48b6aa9
7 files changed+131−79
Cargo.lock+1 helix-term/src/application.rs+21−77 helix-tui/Cargo.toml+1 helix-tui/src/backend/crossterm.rs+69−1 helix-tui/src/backend/mod.rs+4−1 helix-tui/src/backend/test.rs+13 helix-tui/src/terminal.rs+22
Cargo.lock+1
@@ -1201,6 +1201,7 @@ dependencies = [
12011201 "crossterm",
12021202 "helix-core",
12031203 "helix-view",
1204+ "log",
12041205 "serde",
12051206 "termini",
12061207 "unicode-segmentation",
helix-term/src/application.rs+21−77
@@ -30,22 +30,14 @@ use crate::{
3030
3131use log::{debug, error, warn};
3232use std::{
33− io::{stdin, stdout, Write},
33+ io::{stdin, stdout},
3434 sync::Arc,
3535 time::{Duration, Instant},
3636};
3737
3838use anyhow::{Context, Error};
3939
40−use crossterm::{
41− event::{
42− DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
43− EnableFocusChange, EnableMouseCapture, Event as CrosstermEvent, KeyboardEnhancementFlags,
44− PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
45− },
46− execute, terminal,
47− tty::IsTty,
48−};
40+use crossterm::{event::Event as CrosstermEvent, tty::IsTty};
4941#[cfg(not(windows))]
5042use {
5143 signal_hook::{consts::signal, low_level},
@@ -63,10 +55,12 @@ use tui::backend::CrosstermBackend;
6355use tui::backend::TestBackend;
6456
6557#[cfg(not(feature = "integration"))]
66−type Terminal = tui::terminal::Terminal<CrosstermBackend<std::io::Stdout>>;
58+type TerminalBackend = CrosstermBackend<std::io::Stdout>;
6759
6860#[cfg(feature = "integration")]
69−type Terminal = tui::terminal::Terminal<TestBackend>;
61+type TerminalBackend = TestBackend;
62+
63+type Terminal = tui::terminal::Terminal<TerminalBackend>;
7064
7165pub struct Application {
7266 compositor: Compositor,
@@ -108,26 +102,6 @@ fn setup_integration_logging() {
108102 .apply();
109103}
110104
111−fn restore_term() -> Result<(), Error> {
112− let mut stdout = stdout();
113− // reset cursor shape
114− write!(stdout, "\x1B[0 q")?;
115− if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
116− execute!(stdout, PopKeyboardEnhancementFlags)?;
117− }
118− // Ignore errors on disabling, this might trigger on windows if we call
119− // disable without calling enable previously
120− let _ = execute!(stdout, DisableMouseCapture);
121− execute!(
122− stdout,
123− DisableBracketedPaste,
124− DisableFocusChange,
125− terminal::LeaveAlternateScreen
126− )?;
127− terminal::disable_raw_mode()?;
128− Ok(())
129−}
130−
131105impl Application {
132106 pub fn new(
133107 args: Args,
@@ -472,13 +446,7 @@ impl Application {
472446 pub async fn handle_signals(&mut self, signal: i32) {
473447 match signal {
474448 signal::SIGTSTP => {
475− // restore cursor
476− use helix_view::graphics::CursorKind;
477− self.terminal
478− .backend_mut()
479− .show_cursor(CursorKind::Block)
480− .ok();
481− restore_term().unwrap();
449+ self.restore_term().unwrap();
482450 low_level::emulate_default_handler(signal::SIGTSTP).unwrap();
483451 }
484452 signal::SIGCONT => {
@@ -1054,37 +1022,19 @@ impl Application {
10541022 }
10551023 }
10561024
1057− async fn claim_term(&mut self) -> Result<(), Error> {
1058− use helix_view::graphics::CursorKind;
1059− terminal::enable_raw_mode()?;
1060− if self.terminal.cursor_kind() == CursorKind::Hidden {
1061− self.terminal.backend_mut().hide_cursor().ok();
1062− }
1063− let mut stdout = stdout();
1064− execute!(
1065− stdout,
1066− terminal::EnterAlternateScreen,
1067− EnableBracketedPaste,
1068− EnableFocusChange
1069− )?;
1070− execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
1071− if self.config.load().editor.mouse {
1072− execute!(stdout, EnableMouseCapture)?;
1073− }
1074− if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
1075− log::debug!("The enhanced keyboard protocol is supported on this terminal");
1076− execute!(
1077− stdout,
1078− PushKeyboardEnhancementFlags(
1079− KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
1080− | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
1081− )
1082− )?;
1083− } else {
1084− log::debug!("The enhanced keyboard protocol is not supported on this terminal");
1085− }
1025+ async fn claim_term(&mut self) -> std::io::Result<()> {
1026+ let terminal_config = self.config.load().editor.clone().into();
1027+ self.terminal.claim(terminal_config)
1028+ }
10861029
1087− Ok(())
1030+ fn restore_term(&mut self) -> std::io::Result<()> {
1031+ let terminal_config = self.config.load().editor.clone().into();
1032+ use helix_view::graphics::CursorKind;
1033+ self.terminal
1034+ .backend_mut()
1035+ .show_cursor(CursorKind::Block)
1036+ .ok();
1037+ self.terminal.restore(terminal_config)
10881038 }
10891039
10901040 pub async fn run<S>(&mut self, input_stream: &mut S) -> Result<i32, Error>
@@ -1099,7 +1049,7 @@ impl Application {
10991049 // We can't handle errors properly inside this closure. And it's
11001050 // probably not a good idea to `unwrap()` inside a panic handler.
11011051 // So we just ignore the `Result`.
1102− let _ = restore_term();
1052+ let _ = TerminalBackend::force_restore();
11031053 hook(info);
11041054 }));
11051055
@@ -1107,13 +1057,7 @@ impl Application {
11071057
11081058 let close_errs = self.close().await;
11091059
1110− // restore cursor
1111− use helix_view::graphics::CursorKind;
1112− self.terminal
1113− .backend_mut()
1114− .show_cursor(CursorKind::Block)
1115− .ok();
1116− restore_term()?;
1060+ self.restore_term()?;
11171061
11181062 for err in close_errs {
11191063 self.editor.exit_code = 1;
helix-tui/Cargo.toml+1
@@ -22,5 +22,6 @@ unicode-segmentation = "1.10"
2222crossterm = { version = "0.26", optional = true }
2323termini = "0.1"
2424serde = { version = "1", "optional" = true, features = ["derive"]}
25+log = "~0.4"
2526helix-view = { version = "0.6", path = "../helix-view", features = ["term"] }
2627helix-core = { version = "0.6", path = "../helix-core" }
helix-tui/src/backend/crossterm.rs+69−1
@@ -1,6 +1,11 @@
1−use crate::{backend::Backend, buffer::Cell};
1+use crate::{backend::Backend, buffer::Cell, terminal::Config};
22use crossterm::{
33 cursor::{Hide, MoveTo, SetCursorStyle, Show},
4+ event::{
5+ DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
6+ EnableFocusChange, EnableMouseCapture, KeyboardEnhancementFlags,
7+ PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
8+ },
49 execute, queue,
510 style::{
611 Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
@@ -83,6 +88,69 @@ impl<W> Backend for CrosstermBackend<W>
8388where
8489 W: Write,
8590{
91+ fn claim(&mut self, config: Config) -> io::Result<()> {
92+ terminal::enable_raw_mode()?;
93+ execute!(
94+ self.buffer,
95+ terminal::EnterAlternateScreen,
96+ EnableBracketedPaste,
97+ EnableFocusChange
98+ )?;
99+ execute!(self.buffer, terminal::Clear(terminal::ClearType::All))?;
100+ if config.enable_mouse_capture {
101+ execute!(self.buffer, EnableMouseCapture)?;
102+ }
103+ if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
104+ log::debug!("The enhanced keyboard protocol is supported on this terminal");
105+ execute!(
106+ self.buffer,
107+ PushKeyboardEnhancementFlags(
108+ KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
109+ | KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
110+ )
111+ )?;
112+ } else {
113+ log::debug!("The enhanced keyboard protocol is not supported on this terminal");
114+ }
115+ Ok(())
116+ }
117+
118+ fn restore(&mut self, config: Config) -> io::Result<()> {
119+ // reset cursor shape
120+ write!(self.buffer, "\x1B[0 q")?;
121+ if config.enable_mouse_capture {
122+ execute!(self.buffer, DisableMouseCapture)?;
123+ }
124+ if matches!(terminal::supports_keyboard_enhancement(), Ok(true)) {
125+ execute!(self.buffer, PopKeyboardEnhancementFlags)?;
126+ }
127+ execute!(
128+ self.buffer,
129+ DisableBracketedPaste,
130+ DisableFocusChange,
131+ terminal::LeaveAlternateScreen
132+ )?;
133+ terminal::disable_raw_mode()
134+ }
135+
136+ fn force_restore() -> io::Result<()> {
137+ let mut stdout = io::stdout();
138+
139+ // reset cursor shape
140+ write!(stdout, "\x1B[0 q")?;
141+ // Ignore errors on disabling, this might trigger on windows if we call
142+ // disable without calling enable previously
143+ let _ = execute!(stdout, DisableMouseCapture);
144+ let _ = execute!(stdout, PopKeyboardEnhancementFlags);
145+ execute!(
146+ stdout,
147+ DisableBracketedPaste,
148+ DisableFocusChange,
149+ terminal::LeaveAlternateScreen
150+ )?;
151+ terminal::disable_raw_mode()
152+ }
153+
86154 fn draw<'a, I>(&mut self, content: I) -> io::Result<()>
87155 where
88156 I: Iterator<Item = (u16, u16, &'a Cell)>,
helix-tui/src/backend/mod.rs+4−1
@@ -1,6 +1,6 @@
11use std::io;
22
3−use crate::buffer::Cell;
3+use crate::{buffer::Cell, terminal::Config};
44
55use helix_view::graphics::{CursorKind, Rect};
66
@@ -13,6 +13,9 @@ mod test;
1313pub use self::test::TestBackend;
1414
1515pub trait Backend {
16+ fn claim(&mut self, config: Config) -> Result<(), io::Error>;
17+ fn restore(&mut self, config: Config) -> Result<(), io::Error>;
18+ fn force_restore() -> Result<(), io::Error>;
1619 fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
1720 where
1821 I: Iterator<Item = (u16, u16, &'a Cell)>;
helix-tui/src/backend/test.rs+13
@@ -1,6 +1,7 @@
11use crate::{
22 backend::Backend,
33 buffer::{Buffer, Cell},
4+ terminal::Config,
45};
56use helix_core::unicode::width::UnicodeWidthStr;
67use helix_view::graphics::{CursorKind, Rect};
@@ -106,6 +107,18 @@ impl TestBackend {
106107}
107108
108109impl Backend for TestBackend {
110+ fn claim(&mut self, _config: Config) -> Result<(), io::Error> {
111+ Ok(())
112+ }
113+
114+ fn restore(&mut self, _config: Config) -> Result<(), io::Error> {
115+ Ok(())
116+ }
117+
118+ fn force_restore() -> Result<(), io::Error> {
119+ Ok(())
120+ }
121+
109122 fn draw<'a, I>(&mut self, content: I) -> Result<(), io::Error>
110123 where
111124 I: Iterator<Item = (u16, u16, &'a Cell)>,
helix-tui/src/terminal.rs+22
@@ -1,4 +1,5 @@
11use crate::{backend::Backend, buffer::Buffer};
2+use helix_view::editor::Config as EditorConfig;
23use helix_view::graphics::{CursorKind, Rect};
34use std::io;
45
@@ -16,6 +17,19 @@ pub struct Viewport {
1617 resize_behavior: ResizeBehavior,
1718}
1819
20+#[derive(Debug)]
21+pub struct Config {
22+ pub enable_mouse_capture: bool,
23+}
24+
25+impl From<EditorConfig> for Config {
26+ fn from(config: EditorConfig) -> Self {
27+ Self {
28+ enable_mouse_capture: config.mouse,
29+ }
30+ }
31+}
32+
1933impl Viewport {
2034 /// UNSTABLE
2135 pub fn fixed(area: Rect) -> Viewport {
@@ -98,6 +112,14 @@ where
98112 })
99113 }
100114
115+ pub fn claim(&mut self, config: Config) -> io::Result<()> {
116+ self.backend.claim(config)
117+ }
118+
119+ pub fn restore(&mut self, config: Config) -> io::Result<()> {
120+ self.backend.restore(config)
121+ }
122+
101123 // /// Get a Frame object which provides a consistent view into the terminal state for rendering.
102124 // pub fn get_frame(&mut self) -> Frame<B> {
103125 // Frame {