use anyhow::Result;
use crossterm::{
cursor::SetCursorStyle,
event::{
DisableBracketedPaste, DisableMouseCapture, EnableBracketedPaste, EnableMouseCapture,
KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
},
terminal::{
disable_raw_mode, enable_raw_mode, supports_keyboard_enhancement, EnterAlternateScreen,
LeaveAlternateScreen,
},
ExecutableCommand,
};
use std::io::{stdout, Write};
#[derive(Debug, Clone)]
pub struct KeyboardConfig {
pub disambiguate_escape_codes: bool,
pub report_event_types: bool,
pub report_alternate_keys: bool,
pub report_all_keys_as_escape_codes: bool,
}
impl Default for KeyboardConfig {
fn default() -> Self {
Self {
disambiguate_escape_codes: true,
report_event_types: false,
report_alternate_keys: true,
report_all_keys_as_escape_codes: false,
}
}
}
impl KeyboardConfig {
pub fn to_flags(&self) -> KeyboardEnhancementFlags {
let mut flags = KeyboardEnhancementFlags::empty();
if self.disambiguate_escape_codes {
flags |= KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES;
}
if self.report_event_types {
flags |= KeyboardEnhancementFlags::REPORT_EVENT_TYPES;
}
if self.report_alternate_keys {
flags |= KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS;
}
if self.report_all_keys_as_escape_codes {
flags |= KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES;
}
flags
}
pub fn any_enabled(&self) -> bool {
self.disambiguate_escape_codes
|| self.report_event_types
|| self.report_alternate_keys
|| self.report_all_keys_as_escape_codes
}
}
#[derive(Debug, Default)]
pub struct TerminalModes {
raw_mode: bool,
alternate_screen: bool,
mouse_capture: bool,
keyboard_enhancement: bool,
bracketed_paste: bool,
}
impl TerminalModes {
pub fn new() -> Self {
Self::default()
}
pub fn enable(keyboard_config: Option<&KeyboardConfig>) -> Result<Self> {
let mut modes = Self::new();
let keyboard_config = keyboard_config.cloned().unwrap_or_default();
if let Err(e) = enable_raw_mode() {
tracing::error!("Failed to enable raw mode: {}", e);
return Err(e.into());
}
modes.raw_mode = true;
tracing::debug!("Enabled raw mode");
if let Err(e) = stdout().execute(EnterAlternateScreen) {
tracing::error!("Failed to enter alternate screen: {}", e);
modes.undo();
return Err(e.into());
}
modes.alternate_screen = true;
tracing::debug!("Entered alternate screen");
if keyboard_config.any_enabled() {
match supports_keyboard_enhancement() {
Ok(true) => {
let flags = keyboard_config.to_flags();
if let Err(e) = stdout().execute(PushKeyboardEnhancementFlags(flags)) {
tracing::warn!("Failed to enable keyboard enhancement: {}", e);
} else {
modes.keyboard_enhancement = true;
tracing::debug!("Enabled keyboard enhancement flags: {:?}", flags);
}
}
Ok(false) => {
tracing::info!("Keyboard enhancement not supported by terminal");
}
Err(e) => {
tracing::warn!("Failed to query keyboard enhancement support: {}", e);
}
}
} else {
tracing::debug!("Keyboard enhancement disabled by config");
}
if let Err(e) = stdout().execute(EnableMouseCapture) {
tracing::warn!("Failed to enable mouse capture: {}", e);
} else {
modes.mouse_capture = true;
tracing::debug!("Enabled mouse capture");
}
if let Err(e) = stdout().execute(EnableBracketedPaste) {
tracing::warn!("Failed to enable bracketed paste: {}", e);
} else {
modes.bracketed_paste = true;
tracing::debug!("Enabled bracketed paste mode");
}
Ok(modes)
}
pub fn undo(&mut self) {
if self.mouse_capture {
let _ = stdout().execute(DisableMouseCapture);
self.mouse_capture = false;
tracing::debug!("Disabled mouse capture");
}
if self.bracketed_paste {
let _ = stdout().execute(DisableBracketedPaste);
self.bracketed_paste = false;
tracing::debug!("Disabled bracketed paste");
}
let _ = stdout().execute(SetCursorStyle::DefaultUserShape);
crate::view::theme::Theme::reset_terminal_cursor_color();
if self.keyboard_enhancement {
let _ = stdout().execute(PopKeyboardEnhancementFlags);
self.keyboard_enhancement = false;
tracing::debug!("Popped keyboard enhancement flags");
}
if self.raw_mode {
let _ = disable_raw_mode();
self.raw_mode = false;
tracing::debug!("Disabled raw mode");
}
if self.alternate_screen {
let _ = stdout().execute(LeaveAlternateScreen);
self.alternate_screen = false;
tracing::debug!("Left alternate screen");
}
let _ = stdout().flush();
}
pub fn raw_mode_enabled(&self) -> bool {
self.raw_mode
}
pub fn keyboard_enhancement_enabled(&self) -> bool {
self.keyboard_enhancement
}
pub fn mouse_capture_enabled(&self) -> bool {
self.mouse_capture
}
pub fn bracketed_paste_enabled(&self) -> bool {
self.bracketed_paste
}
pub fn alternate_screen_enabled(&self) -> bool {
self.alternate_screen
}
}
impl Drop for TerminalModes {
fn drop(&mut self) {
self.undo();
}
}
pub fn emergency_cleanup() {
let _ = stdout().execute(DisableMouseCapture);
let _ = stdout().execute(DisableBracketedPaste);
let _ = stdout().execute(SetCursorStyle::DefaultUserShape);
crate::view::theme::Theme::reset_terminal_cursor_color();
let _ = stdout().execute(PopKeyboardEnhancementFlags);
let _ = disable_raw_mode();
let _ = stdout().execute(LeaveAlternateScreen);
let _ = stdout().flush();
}