use std::io;
use anyhow::Result;
use ratatui::crossterm::{
cursor::{RestorePosition, SavePosition},
event::{
DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
EnableFocusChange, EnableMouseCapture,
},
execute,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use crate::options::FullscreenInteractionSettings;
#[derive(Debug, Clone)]
pub(super) struct TerminalModeState {
bracketed_paste_enabled: bool,
raw_mode_enabled: bool,
mouse_capture_enabled: bool,
focus_change_enabled: bool,
keyboard_enhancements_pushed: bool,
cursor_position_saved: bool,
alternate_screen_active: bool,
}
impl TerminalModeState {
fn new() -> Self {
Self {
bracketed_paste_enabled: false,
raw_mode_enabled: false,
mouse_capture_enabled: false,
focus_change_enabled: false,
keyboard_enhancements_pushed: false,
cursor_position_saved: false,
alternate_screen_active: false,
}
}
pub(super) fn save_cursor_position(&mut self, stderr: &mut io::Stderr) {
match execute!(stderr, SavePosition) {
Ok(_) => self.cursor_position_saved = true,
Err(error) => {
tracing::debug!(%error, "failed to save cursor position for inline session");
}
}
}
pub(super) fn enter_alternate_screen(&mut self, stderr: &mut io::Stderr) -> Result<()> {
execute!(stderr, EnterAlternateScreen)
.map_err(|error| anyhow::anyhow!("failed to enter alternate inline screen: {error}"))?;
self.alternate_screen_active = true;
Ok(())
}
}
impl Default for TerminalModeState {
fn default() -> Self {
Self::new()
}
}
pub(super) fn enable_terminal_modes(
stderr: &mut io::Stderr,
keyboard_flags: crossterm::event::KeyboardEnhancementFlags,
fullscreen: &FullscreenInteractionSettings,
) -> Result<TerminalModeState> {
use ratatui::crossterm::event::PushKeyboardEnhancementFlags;
let mut state = TerminalModeState::new();
match execute!(stderr, EnableBracketedPaste) {
Ok(_) => state.bracketed_paste_enabled = true,
Err(error) => {
tracing::warn!(%error, "failed to enable bracketed paste");
}
}
match enable_raw_mode() {
Ok(_) => state.raw_mode_enabled = true,
Err(error) => {
return Err(anyhow::anyhow!("failed to enable raw mode: {}", error));
}
}
if fullscreen.mouse_capture {
match execute!(stderr, EnableMouseCapture) {
Ok(_) => state.mouse_capture_enabled = true,
Err(error) => {
tracing::warn!(%error, "failed to enable mouse capture");
}
}
}
match execute!(stderr, EnableFocusChange) {
Ok(_) => state.focus_change_enabled = true,
Err(error) => {
tracing::debug!(%error, "failed to enable focus change events");
}
}
if !keyboard_flags.is_empty() {
match execute!(stderr, PushKeyboardEnhancementFlags(keyboard_flags)) {
Ok(_) => {
state.keyboard_enhancements_pushed = true;
crate::ui::tui::panic_hook::mark_keyboard_enhancements_pushed(true);
}
Err(error) => {
tracing::debug!(%error, "failed to push keyboard enhancement flags");
}
}
}
Ok(state)
}
pub(super) fn restore_terminal_modes(state: &TerminalModeState) -> Result<()> {
use ratatui::crossterm::event::PopKeyboardEnhancementFlags;
let mut stderr = io::stderr();
let mut errors = Vec::new();
if state.alternate_screen_active
&& let Err(error) = execute!(stderr, LeaveAlternateScreen)
{
tracing::debug!(%error, "failed to leave alternate screen");
errors.push(format!("alternate screen: {}", error));
}
if state.keyboard_enhancements_pushed {
crate::ui::tui::panic_hook::mark_keyboard_enhancements_pushed(false);
if let Err(error) = execute!(stderr, PopKeyboardEnhancementFlags) {
tracing::debug!(%error, "failed to pop keyboard enhancement flags");
errors.push(format!("keyboard enhancements: {}", error));
}
}
if state.focus_change_enabled
&& let Err(error) = execute!(stderr, DisableFocusChange)
{
tracing::debug!(%error, "failed to disable focus change events");
errors.push(format!("focus change: {}", error));
}
if state.mouse_capture_enabled
&& let Err(error) = execute!(stderr, DisableMouseCapture)
{
tracing::debug!(%error, "failed to disable mouse capture");
errors.push(format!("mouse capture: {}", error));
}
if state.bracketed_paste_enabled
&& let Err(error) = execute!(stderr, DisableBracketedPaste)
{
tracing::debug!(%error, "failed to disable bracketed paste");
errors.push(format!("bracketed paste: {}", error));
}
if state.raw_mode_enabled
&& let Err(error) = disable_raw_mode()
{
tracing::debug!(%error, "failed to disable raw mode");
errors.push(format!("raw mode: {}", error));
}
if state.cursor_position_saved
&& let Err(error) = execute!(stderr, RestorePosition)
{
tracing::debug!(%error, "failed to restore cursor position for inline session");
errors.push(format!("cursor position: {}", error));
}
if errors.is_empty() {
Ok(())
} else {
tracing::warn!(
errors = ?errors,
"some terminal modes failed to restore"
);
Ok(()) }
}