pub(in crate::tui) mod graphics;
pub(in crate::tui) mod panic_hook;
pub(in crate::tui) mod synchronized_output;
use anyhow::Result;
use crossterm::{
cursor::SetCursorStyle,
event::{
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
},
execute,
terminal::{
EndSynchronizedUpdate, EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode,
enable_raw_mode,
},
};
use std::io;
const ENHANCED_KEYBOARD_FLAGS: KeyboardEnhancementFlags =
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
.union(KeyboardEnhancementFlags::REPORT_EVENT_TYPES)
.union(KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TerminalOp {
EnableRawMode,
EnterAlternateScreenEnableInput,
EnableEnhancedKeyboard,
DisableEnhancedKeyboard,
DisableInputAndLeaveAlternateScreen,
DisableRawMode,
}
trait TerminalOps {
fn run(&self, op: TerminalOp) -> Result<()>;
fn enhanced_keyboard_supported(&self) -> bool {
true
}
}
#[derive(Debug, Default)]
struct CrosstermTerminalOps;
pub(crate) struct TerminalGuard {
ops: Box<dyn TerminalOps>,
enhanced_keyboard_enabled: bool,
}
impl TerminalOps for CrosstermTerminalOps {
fn run(&self, op: TerminalOp) -> Result<()> {
match op {
TerminalOp::EnableRawMode => enable_raw_mode().map_err(Into::into),
TerminalOp::EnterAlternateScreenEnableInput => execute!(
io::stdout(),
EnterAlternateScreen,
EnableMouseCapture,
EnableBracketedPaste,
SetCursorStyle::SteadyBlock,
)
.map_err(Into::into),
TerminalOp::EnableEnhancedKeyboard => execute!(
io::stdout(),
PushKeyboardEnhancementFlags(ENHANCED_KEYBOARD_FLAGS)
)
.map_err(Into::into),
TerminalOp::DisableEnhancedKeyboard => {
execute!(io::stdout(), PopKeyboardEnhancementFlags).map_err(Into::into)
}
TerminalOp::DisableInputAndLeaveAlternateScreen => {
let _ = execute!(io::stdout(), EndSynchronizedUpdate);
graphics::cleanup();
execute!(
io::stdout(),
DisableBracketedPaste,
DisableMouseCapture,
SetCursorStyle::DefaultUserShape,
LeaveAlternateScreen
)
.map_err(Into::into)
}
TerminalOp::DisableRawMode => disable_raw_mode().map_err(Into::into),
}
}
fn enhanced_keyboard_supported(&self) -> bool {
!cfg!(windows)
}
}
impl TerminalGuard {
pub(crate) fn enter() -> Result<Self> {
let guard = Self::enter_with_ops(Box::new(CrosstermTerminalOps))?;
crate::output::hold_terminal_warnings();
Ok(guard)
}
fn enter_with_ops(ops: Box<dyn TerminalOps>) -> Result<Self> {
ops.run(TerminalOp::EnableRawMode)?;
if let Err(error) = ops.run(TerminalOp::EnterAlternateScreenEnableInput) {
let _ = ops.run(TerminalOp::DisableInputAndLeaveAlternateScreen);
let _ = ops.run(TerminalOp::DisableRawMode);
return Err(error);
}
let enhanced_keyboard_supported = ops.enhanced_keyboard_supported();
if enhanced_keyboard_supported
&& let Err(error) = ops.run(TerminalOp::EnableEnhancedKeyboard)
{
let _ = ops.run(TerminalOp::DisableEnhancedKeyboard);
let _ = ops.run(TerminalOp::DisableInputAndLeaveAlternateScreen);
let _ = ops.run(TerminalOp::DisableRawMode);
return Err(error);
}
Ok(Self {
ops,
enhanced_keyboard_enabled: enhanced_keyboard_supported,
})
}
}
pub(crate) fn restore_terminal_best_effort() {
let ops = CrosstermTerminalOps;
let _ = ops.run(TerminalOp::DisableEnhancedKeyboard);
let _ = ops.run(TerminalOp::DisableInputAndLeaveAlternateScreen);
let _ = ops.run(TerminalOp::DisableRawMode);
crate::output::release_terminal_warnings();
}
impl Drop for TerminalGuard {
fn drop(&mut self) {
if self.enhanced_keyboard_enabled {
let _ = self.ops.run(TerminalOp::DisableEnhancedKeyboard);
}
let _ = self
.ops
.run(TerminalOp::DisableInputAndLeaveAlternateScreen);
let _ = self.ops.run(TerminalOp::DisableRawMode);
crate::output::release_terminal_warnings();
}
}