mirror of
https://github.com/openai/codex.git
synced 2026-11-08 20:40:33 +00:00
fix(tui): harden windows vt output
This commit is contained in:
Generated
+1
@@ -3712,6 +3712,7 @@ version = "0.0.0"
|
||||
dependencies = [
|
||||
"pretty_assertions",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -57,7 +57,9 @@ use codex_protocol::protocol::AskForApproval;
|
||||
use codex_terminal_detection::Multiplexer;
|
||||
use codex_terminal_detection::TerminalInfo;
|
||||
use codex_terminal_detection::TerminalName;
|
||||
use codex_terminal_detection::WindowsStdoutVtState;
|
||||
use codex_terminal_detection::terminal_info;
|
||||
use codex_terminal_detection::windows_stdout_vt_state;
|
||||
use codex_tui::Cli as TuiCli;
|
||||
use codex_utils_cli::CliConfigOverrides;
|
||||
use http::HeaderMap;
|
||||
@@ -1580,6 +1582,7 @@ struct TerminalCheckInputs {
|
||||
stream_supports_color: bool,
|
||||
terminal_size: Result<(u16, u16), String>,
|
||||
tmux_details: Vec<String>,
|
||||
windows_stdout_vt_state: Option<WindowsStdoutVtState>,
|
||||
}
|
||||
|
||||
impl TerminalCheckInputs {
|
||||
@@ -1604,6 +1607,7 @@ impl TerminalCheckInputs {
|
||||
stream_supports_color: supports_color::on(Stream::Stdout).is_some(),
|
||||
terminal_size,
|
||||
tmux_details,
|
||||
windows_stdout_vt_state: cfg!(windows).then(windows_stdout_vt_state),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1643,6 +1647,9 @@ fn terminal_check_from_inputs(inputs: TerminalCheckInputs) -> DoctorCheck {
|
||||
Ok((columns, rows)) => details.push(format!("terminal size: {columns}x{rows}")),
|
||||
Err(err) => details.push(format!("terminal size: unavailable ({err})")),
|
||||
}
|
||||
if let Some(windows_stdout_vt_state) = inputs.windows_stdout_vt_state {
|
||||
push_windows_stdout_vt_details(&mut details, windows_stdout_vt_state);
|
||||
}
|
||||
push_terminal_env_values(&mut details, &inputs, TERMINAL_DIMENSION_ENV_VARS);
|
||||
details.push(format!("color output: {}", color_output_summary(&inputs)));
|
||||
push_terminal_env_values(&mut details, &inputs, COLOR_ENV_VARS);
|
||||
@@ -1711,6 +1718,26 @@ fn terminal_check_from_inputs(inputs: TerminalCheckInputs) -> DoctorCheck {
|
||||
check
|
||||
}
|
||||
|
||||
fn push_windows_stdout_vt_details(
|
||||
details: &mut Vec<String>,
|
||||
windows_stdout_vt_state: WindowsStdoutVtState,
|
||||
) {
|
||||
match windows_stdout_vt_state {
|
||||
WindowsStdoutVtState::Enabled { console_mode } => {
|
||||
details.push(format!("Windows stdout console mode: {console_mode}"));
|
||||
details.push("Windows stdout VT processing: enabled".to_string());
|
||||
}
|
||||
WindowsStdoutVtState::Disabled { console_mode } => {
|
||||
details.push(format!("Windows stdout console mode: {console_mode}"));
|
||||
details.push("Windows stdout VT processing: disabled".to_string());
|
||||
}
|
||||
WindowsStdoutVtState::Unavailable => {
|
||||
details.push("Windows stdout console mode: unavailable".to_string());
|
||||
details.push("Windows stdout VT processing: unavailable".to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn terminal_name(info: &TerminalInfo) -> &'static str {
|
||||
match info.name {
|
||||
TerminalName::AppleTerminal => "Apple Terminal",
|
||||
@@ -3724,6 +3751,7 @@ mod tests {
|
||||
stream_supports_color: true,
|
||||
terminal_size: Ok((120, 40)),
|
||||
tmux_details: Vec::new(),
|
||||
windows_stdout_vt_state: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3868,6 +3896,63 @@ mod tests {
|
||||
assert_eq!(check.summary, "terminal metadata was detected");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_check_reports_windows_stdout_vt_processing_when_enabled() {
|
||||
let mut inputs = terminal_inputs();
|
||||
inputs.windows_stdout_vt_state = Some(WindowsStdoutVtState::Enabled { console_mode: 7 });
|
||||
|
||||
let check = terminal_check_from_inputs(inputs);
|
||||
|
||||
assert!(
|
||||
check
|
||||
.details
|
||||
.contains(&"Windows stdout console mode: 7".to_string())
|
||||
);
|
||||
assert!(
|
||||
check
|
||||
.details
|
||||
.contains(&"Windows stdout VT processing: enabled".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_check_reports_windows_stdout_vt_processing_when_disabled() {
|
||||
let mut inputs = terminal_inputs();
|
||||
inputs.windows_stdout_vt_state = Some(WindowsStdoutVtState::Disabled { console_mode: 3 });
|
||||
|
||||
let check = terminal_check_from_inputs(inputs);
|
||||
|
||||
assert!(
|
||||
check
|
||||
.details
|
||||
.contains(&"Windows stdout console mode: 3".to_string())
|
||||
);
|
||||
assert!(
|
||||
check
|
||||
.details
|
||||
.contains(&"Windows stdout VT processing: disabled".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn terminal_check_reports_unavailable_windows_stdout_vt_processing() {
|
||||
let mut inputs = terminal_inputs();
|
||||
inputs.windows_stdout_vt_state = Some(WindowsStdoutVtState::Unavailable);
|
||||
|
||||
let check = terminal_check_from_inputs(inputs);
|
||||
|
||||
assert!(
|
||||
check
|
||||
.details
|
||||
.contains(&"Windows stdout console mode: unavailable".to_string())
|
||||
);
|
||||
assert!(
|
||||
check
|
||||
.details
|
||||
.contains(&"Windows stdout VT processing: unavailable".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn color_output_summary_reports_disabled_reasons() {
|
||||
let mut inputs = terminal_inputs();
|
||||
|
||||
@@ -15,5 +15,11 @@ workspace = true
|
||||
[dependencies]
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Console",
|
||||
] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
@@ -5,6 +5,128 @@
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
/// Visibility into the Windows stdout console VT-processing mode.
|
||||
///
|
||||
/// Windows console handles expose a mode bit that controls whether emitted VT
|
||||
/// sequences are interpreted by the terminal. PTY-style stdout handles do not
|
||||
/// necessarily support this console-mode inspection; those land in
|
||||
/// [`WindowsStdoutVtState::Unavailable`] instead of being treated as broken.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum WindowsStdoutVtState {
|
||||
/// Stdout is an inspectable Windows console handle and VT processing is enabled.
|
||||
Enabled {
|
||||
/// Console mode bits reported for stdout.
|
||||
console_mode: u32,
|
||||
},
|
||||
/// Stdout is an inspectable Windows console handle and VT processing is disabled.
|
||||
Disabled {
|
||||
/// Console mode bits reported for stdout.
|
||||
console_mode: u32,
|
||||
},
|
||||
/// Stdout is not an inspectable Windows console handle.
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
impl WindowsStdoutVtState {
|
||||
/// Returns the inspected stdout console mode, if one is visible.
|
||||
pub fn console_mode(self) -> Option<u32> {
|
||||
match self {
|
||||
Self::Enabled { console_mode } | Self::Disabled { console_mode } => Some(console_mode),
|
||||
Self::Unavailable => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether stdout VT processing is enabled when the mode is visible.
|
||||
pub fn vt_processing_enabled(self) -> Option<bool> {
|
||||
match self {
|
||||
Self::Enabled { .. } => Some(true),
|
||||
Self::Disabled { .. } => Some(false),
|
||||
Self::Unavailable => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inspects the Windows stdout console VT-processing mode without mutating it.
|
||||
///
|
||||
/// On non-Windows targets, and for stdout handles that do not support Windows
|
||||
/// console-mode inspection, this returns [`WindowsStdoutVtState::Unavailable`].
|
||||
pub fn windows_stdout_vt_state() -> WindowsStdoutVtState {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let Some((_handle, console_mode)) = windows_stdout_console_mode() else {
|
||||
return WindowsStdoutVtState::Unavailable;
|
||||
};
|
||||
windows_stdout_vt_state_from_mode(console_mode)
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
WindowsStdoutVtState::Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempts to enable Windows stdout VT processing and returns the verified mode state.
|
||||
///
|
||||
/// This preserves the inspection result for PTY-style or otherwise unavailable
|
||||
/// stdout handles instead of assuming those handles cannot interpret ANSI.
|
||||
pub fn enable_windows_stdout_vt_processing() -> WindowsStdoutVtState {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use windows_sys::Win32::System::Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
use windows_sys::Win32::System::Console::SetConsoleMode;
|
||||
|
||||
let Some((handle, console_mode)) = windows_stdout_console_mode() else {
|
||||
return WindowsStdoutVtState::Unavailable;
|
||||
};
|
||||
let state = windows_stdout_vt_state_from_mode(console_mode);
|
||||
if matches!(state, WindowsStdoutVtState::Disabled { .. }) {
|
||||
// Safety: `handle` and `console_mode` came from `GetConsoleMode` for stdout.
|
||||
let _ = unsafe {
|
||||
SetConsoleMode(handle, console_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
};
|
||||
}
|
||||
windows_stdout_vt_state()
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
WindowsStdoutVtState::Unavailable
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn windows_stdout_vt_state_from_mode(console_mode: u32) -> WindowsStdoutVtState {
|
||||
use windows_sys::Win32::System::Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
|
||||
if console_mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
|
||||
WindowsStdoutVtState::Enabled { console_mode }
|
||||
} else {
|
||||
WindowsStdoutVtState::Disabled { console_mode }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn windows_stdout_console_mode() -> Option<(windows_sys::Win32::Foundation::HANDLE, u32)> {
|
||||
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
||||
use windows_sys::Win32::System::Console::GetConsoleMode;
|
||||
use windows_sys::Win32::System::Console::GetStdHandle;
|
||||
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
|
||||
|
||||
// Safety: requesting the process stdout handle does not transfer ownership.
|
||||
let handle = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) };
|
||||
if handle == INVALID_HANDLE_VALUE || handle == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut console_mode = 0;
|
||||
// Safety: `console_mode` is a valid output pointer for the stdout handle query.
|
||||
if unsafe { GetConsoleMode(handle, &mut console_mode) } == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((handle, console_mode))
|
||||
}
|
||||
|
||||
/// Structured terminal identification data.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct TerminalInfo {
|
||||
|
||||
@@ -237,6 +237,7 @@ impl ChatWidget {
|
||||
tracing::debug!(error = %err, "failed to clear terminal title");
|
||||
}
|
||||
}
|
||||
Ok(SetTerminalTitleResult::SkippedUnsupported) => {}
|
||||
Err(err) => {
|
||||
tracing::debug!(error = %err, "failed to set terminal title");
|
||||
}
|
||||
|
||||
@@ -160,6 +160,7 @@ mod oss_selection;
|
||||
mod pager_overlay;
|
||||
mod permission_compat;
|
||||
pub(crate) mod public_widgets;
|
||||
mod raw_ansi;
|
||||
mod render;
|
||||
mod resize_reflow_cap;
|
||||
mod resume_picker;
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
//! Gate forced-ANSI TUI output that can leak raw bytes on Windows.
|
||||
|
||||
use std::io;
|
||||
use std::io::stdout;
|
||||
|
||||
use codex_terminal_detection::WindowsStdoutVtState;
|
||||
use crossterm::SynchronizedUpdate;
|
||||
|
||||
/// Whether raw ANSI writes should be emitted through the current stdout path.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum RawAnsiCapability {
|
||||
/// Raw ANSI should follow the existing TUI write path.
|
||||
Available,
|
||||
/// Stdout is an inspectable Windows console handle whose VT mode is still disabled.
|
||||
Unavailable,
|
||||
}
|
||||
|
||||
impl RawAnsiCapability {
|
||||
pub(crate) fn is_available(self) -> bool {
|
||||
matches!(self, Self::Available)
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables and verifies Windows stdout VT processing when that mode is inspectable.
|
||||
///
|
||||
/// Handles that do not expose Windows console mode inspection preserve the
|
||||
/// existing PTY behavior and continue to allow raw ANSI output.
|
||||
pub(crate) fn stdout_capability() -> RawAnsiCapability {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let state = codex_terminal_detection::enable_windows_stdout_vt_processing();
|
||||
let capability = capability_for_windows_stdout_vt_state(state);
|
||||
if !capability.is_available() {
|
||||
warn_windows_stdout_vt_disabled(state);
|
||||
}
|
||||
capability
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
{
|
||||
capability_for_windows_stdout_vt_state(WindowsStdoutVtState::Unavailable)
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a draw update with synchronized-update wrappers only when raw ANSI is safe.
|
||||
pub(crate) fn synchronized_update<T>(update: impl FnOnce() -> T) -> io::Result<T> {
|
||||
match stdout_capability() {
|
||||
RawAnsiCapability::Available => stdout().sync_update(|_| update()),
|
||||
RawAnsiCapability::Unavailable => Ok(update()),
|
||||
}
|
||||
}
|
||||
|
||||
fn capability_for_windows_stdout_vt_state(state: WindowsStdoutVtState) -> RawAnsiCapability {
|
||||
match state {
|
||||
WindowsStdoutVtState::Disabled { .. } => RawAnsiCapability::Unavailable,
|
||||
WindowsStdoutVtState::Enabled { .. } | WindowsStdoutVtState::Unavailable => {
|
||||
RawAnsiCapability::Available
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn warn_windows_stdout_vt_disabled(state: WindowsStdoutVtState) {
|
||||
use std::sync::Once;
|
||||
|
||||
static WARNED: Once = Once::new();
|
||||
|
||||
WARNED.call_once(|| {
|
||||
tracing::warn!(
|
||||
?state,
|
||||
"Windows stdout VT processing is disabled; skipping raw ANSI TUI output"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn unavailable_windows_stdout_state_keeps_raw_ansi_available() {
|
||||
assert_eq!(
|
||||
capability_for_windows_stdout_vt_state(WindowsStdoutVtState::Unavailable),
|
||||
RawAnsiCapability::Available
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn disabled_windows_stdout_state_blocks_raw_ansi() {
|
||||
assert_eq!(
|
||||
capability_for_windows_stdout_vt_state(WindowsStdoutVtState::Disabled {
|
||||
console_mode: 3,
|
||||
}),
|
||||
RawAnsiCapability::Unavailable
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ use std::io::stdout;
|
||||
use crossterm::Command;
|
||||
use ratatui::crossterm::execute;
|
||||
|
||||
use crate::raw_ansi;
|
||||
|
||||
/// Practical upper bound on title length, measured in Rust `char`s.
|
||||
///
|
||||
/// Most terminals silently truncate titles beyond a few hundred characters.
|
||||
@@ -41,6 +43,8 @@ pub(crate) enum SetTerminalTitleResult {
|
||||
/// empty post-sanitization value should result in no-op behavior, clearing
|
||||
/// the title Codex manages, or some other fallback.
|
||||
NoVisibleContent,
|
||||
/// The title would require a raw ANSI write while stdout cannot safely emit one.
|
||||
SkippedUnsupported,
|
||||
}
|
||||
|
||||
/// Writes a sanitized OSC window-title sequence to stdout.
|
||||
@@ -57,6 +61,9 @@ pub(crate) fn set_terminal_title(title: &str) -> io::Result<SetTerminalTitleResu
|
||||
if !stdout().is_terminal() {
|
||||
return Ok(SetTerminalTitleResult::Applied);
|
||||
}
|
||||
if !title_output_supported(raw_ansi::stdout_capability()) {
|
||||
return Ok(SetTerminalTitleResult::SkippedUnsupported);
|
||||
}
|
||||
|
||||
let title = sanitize_terminal_title(title);
|
||||
if title.is_empty() {
|
||||
@@ -75,10 +82,17 @@ pub(crate) fn clear_terminal_title() -> io::Result<()> {
|
||||
if !stdout().is_terminal() {
|
||||
return Ok(());
|
||||
}
|
||||
if !title_output_supported(raw_ansi::stdout_capability()) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
execute!(stdout(), SetWindowTitle(String::new()))
|
||||
}
|
||||
|
||||
fn title_output_supported(raw_ansi_capability: raw_ansi::RawAnsiCapability) -> bool {
|
||||
raw_ansi_capability.is_available()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct SetWindowTitle(String);
|
||||
|
||||
@@ -181,6 +195,8 @@ mod tests {
|
||||
use super::MAX_TERMINAL_TITLE_CHARS;
|
||||
use super::SetWindowTitle;
|
||||
use super::sanitize_terminal_title;
|
||||
use super::title_output_supported;
|
||||
use crate::raw_ansi::RawAnsiCapability;
|
||||
use crossterm::Command;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
@@ -222,4 +238,9 @@ mod tests {
|
||||
.expect("encode terminal title");
|
||||
assert_eq!(out, "\x1b]0;hello\x07");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_ansi_disabled_skips_terminal_title_output() {
|
||||
assert!(!title_output_supported(RawAnsiCapability::Unavailable));
|
||||
}
|
||||
}
|
||||
|
||||
+91
-14
@@ -14,7 +14,6 @@ use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use crossterm::Command;
|
||||
use crossterm::SynchronizedUpdate;
|
||||
use crossterm::cursor::SetCursorStyle;
|
||||
use crossterm::event::DisableBracketedPaste;
|
||||
use crossterm::event::DisableFocusChange;
|
||||
@@ -43,6 +42,7 @@ use crate::custom_terminal::Terminal as CustomTerminal;
|
||||
use crate::insert_history::HistoryLineWrapPolicy;
|
||||
use crate::notifications::DesktopNotificationBackend;
|
||||
use crate::notifications::detect_backend;
|
||||
use crate::raw_ansi;
|
||||
use crate::tui::event_stream::EventBroker;
|
||||
use crate::tui::event_stream::TuiEventStream;
|
||||
#[cfg(unix)]
|
||||
@@ -91,9 +91,13 @@ impl Drop for Tui {
|
||||
mod tests {
|
||||
use std::io::Write as _;
|
||||
|
||||
use super::PendingHistoryLines;
|
||||
use super::Tui;
|
||||
use super::clear_for_viewport_change;
|
||||
use super::should_emit_notification;
|
||||
use crate::custom_terminal::Terminal as CustomTerminal;
|
||||
use crate::insert_history::HistoryLineWrapPolicy;
|
||||
use crate::raw_ansi::RawAnsiCapability;
|
||||
use crate::test_backend::VT100Backend;
|
||||
use codex_config::types::NotificationCondition;
|
||||
use ratatui::layout::Position;
|
||||
@@ -163,10 +167,45 @@ mod tests {
|
||||
"expected stale cells inside the new viewport to be cleared, rows: {rows:?}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_ansi_disabled_skips_pending_history_writes() {
|
||||
let width = 12;
|
||||
let backend = VT100Backend::new(width, /*height*/ 4);
|
||||
let mut terminal =
|
||||
CustomTerminal::with_options_and_cursor_position(backend, Position { x: 0, y: 1 })
|
||||
.expect("terminal");
|
||||
let mut pending_history_lines = vec![PendingHistoryLines {
|
||||
lines: vec![ratatui::text::Line::from("history")],
|
||||
wrap_policy: HistoryLineWrapPolicy::PreWrap,
|
||||
}];
|
||||
|
||||
Tui::flush_pending_history_lines_with_raw_ansi_capability(
|
||||
&mut terminal,
|
||||
&mut pending_history_lines,
|
||||
RawAnsiCapability::Unavailable,
|
||||
)
|
||||
.expect("skip history writes");
|
||||
|
||||
assert!(pending_history_lines.is_empty());
|
||||
let rows: Vec<String> = terminal
|
||||
.backend()
|
||||
.vt100()
|
||||
.screen()
|
||||
.rows(/*start*/ 0, width)
|
||||
.collect();
|
||||
assert!(
|
||||
!rows.iter().any(|row| row.contains("history")),
|
||||
"expected no history writes while raw ANSI is unavailable, rows: {rows:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_modes() -> Result<()> {
|
||||
execute!(stdout(), EnableBracketedPaste)?;
|
||||
let raw_ansi_capability = raw_ansi::stdout_capability();
|
||||
if raw_ansi_capability.is_available() {
|
||||
execute!(stdout(), EnableBracketedPaste)?;
|
||||
}
|
||||
|
||||
enable_raw_mode()?;
|
||||
// Enable keyboard enhancement flags so modifiers for keys like Enter are disambiguated.
|
||||
@@ -177,7 +216,9 @@ pub fn set_modes() -> Result<()> {
|
||||
// gracefully if unsupported.
|
||||
keyboard_modes::enable_keyboard_enhancement();
|
||||
|
||||
let _ = execute!(stdout(), EnableFocusChange);
|
||||
if raw_ansi_capability.is_available() {
|
||||
let _ = execute!(stdout(), EnableFocusChange);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -244,8 +285,15 @@ fn restore_common(
|
||||
KeyboardRestore::ResetAfterExit => keyboard_modes::reset_keyboard_reporting_after_exit(),
|
||||
}
|
||||
|
||||
let mut first_error = execute!(stdout(), DisableBracketedPaste).err();
|
||||
let _ = execute!(stdout(), DisableFocusChange);
|
||||
let raw_ansi_capability = raw_ansi::stdout_capability();
|
||||
let mut first_error = if raw_ansi_capability.is_available() {
|
||||
execute!(stdout(), DisableBracketedPaste).err()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if raw_ansi_capability.is_available() {
|
||||
let _ = execute!(stdout(), DisableFocusChange);
|
||||
}
|
||||
if matches!(raw_mode_restore, RawModeRestore::Disable)
|
||||
&& let Err(err) = disable_raw_mode()
|
||||
{
|
||||
@@ -659,6 +707,10 @@ impl Tui {
|
||||
if !self.alt_screen_enabled {
|
||||
return Ok(());
|
||||
}
|
||||
let raw_ansi_capability = raw_ansi::stdout_capability();
|
||||
if !raw_ansi_capability.is_available() {
|
||||
return Ok(());
|
||||
}
|
||||
let _ = execute!(self.terminal.backend_mut(), EnterAlternateScreen);
|
||||
// Enable "alternate scroll" so terminals may translate wheel to arrows
|
||||
let _ = execute!(self.terminal.backend_mut(), EnableAlternateScroll);
|
||||
@@ -681,9 +733,12 @@ impl Tui {
|
||||
if !self.alt_screen_enabled {
|
||||
return Ok(());
|
||||
}
|
||||
let raw_ansi_capability = raw_ansi::stdout_capability();
|
||||
// Disable alternate scroll when leaving alt-screen
|
||||
let _ = execute!(self.terminal.backend_mut(), DisableAlternateScroll);
|
||||
let _ = execute!(self.terminal.backend_mut(), LeaveAlternateScreen);
|
||||
if raw_ansi_capability.is_available() {
|
||||
let _ = execute!(self.terminal.backend_mut(), DisableAlternateScroll);
|
||||
let _ = execute!(self.terminal.backend_mut(), LeaveAlternateScreen);
|
||||
}
|
||||
if let Some(saved) = self.alt_saved_viewport.take() {
|
||||
self.terminal.set_viewport_area(saved);
|
||||
}
|
||||
@@ -762,13 +817,35 @@ impl Tui {
|
||||
}
|
||||
|
||||
/// Write any buffered history lines above the viewport and clear the buffer.
|
||||
fn flush_pending_history_lines(
|
||||
terminal: &mut Terminal,
|
||||
fn flush_pending_history_lines<B>(
|
||||
terminal: &mut CustomTerminal<B>,
|
||||
pending_history_lines: &mut Vec<PendingHistoryLines>,
|
||||
) -> Result<()> {
|
||||
) -> Result<()>
|
||||
where
|
||||
B: Backend + Write,
|
||||
{
|
||||
Self::flush_pending_history_lines_with_raw_ansi_capability(
|
||||
terminal,
|
||||
pending_history_lines,
|
||||
raw_ansi::stdout_capability(),
|
||||
)
|
||||
}
|
||||
|
||||
fn flush_pending_history_lines_with_raw_ansi_capability<B>(
|
||||
terminal: &mut CustomTerminal<B>,
|
||||
pending_history_lines: &mut Vec<PendingHistoryLines>,
|
||||
raw_ansi_capability: raw_ansi::RawAnsiCapability,
|
||||
) -> Result<()>
|
||||
where
|
||||
B: Backend + Write,
|
||||
{
|
||||
if pending_history_lines.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
if !raw_ansi_capability.is_available() {
|
||||
pending_history_lines.clear();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
for batch in pending_history_lines.iter() {
|
||||
crate::insert_history::insert_history_lines_with_wrap_policy(
|
||||
@@ -797,7 +874,7 @@ impl Tui {
|
||||
// the synchronized update, to avoid racing with the event reader.
|
||||
let mut pending_viewport_area = self.pending_viewport_area()?;
|
||||
|
||||
stdout().sync_update(|_| {
|
||||
raw_ansi::synchronized_update(|| {
|
||||
#[cfg(unix)]
|
||||
if let Some(prepared) = prepared_resume.take() {
|
||||
prepared.apply(&mut self.terminal)?;
|
||||
@@ -856,7 +933,7 @@ impl Tui {
|
||||
) -> std::result::Result<(), crate::pets::PetImageRenderError> {
|
||||
let terminal = &mut self.terminal;
|
||||
let state = &mut self.ambient_pet_image_state;
|
||||
stdout().sync_update(|_| {
|
||||
raw_ansi::synchronized_update(|| {
|
||||
match crate::pets::render_ambient_pet_image(terminal.backend_mut(), state, request) {
|
||||
Ok(()) => Ok(Ok(())),
|
||||
Err(crate::pets::PetImageRenderError::Terminal(err)) => Err(err),
|
||||
@@ -871,7 +948,7 @@ impl Tui {
|
||||
) -> std::result::Result<(), crate::pets::PetImageRenderError> {
|
||||
let terminal = &mut self.terminal;
|
||||
let state = &mut self.pet_picker_preview_image_state;
|
||||
stdout().sync_update(|_| {
|
||||
raw_ansi::synchronized_update(|| {
|
||||
match crate::pets::render_pet_picker_preview_image(
|
||||
terminal.backend_mut(),
|
||||
state,
|
||||
@@ -911,7 +988,7 @@ impl Tui {
|
||||
.suspend_context
|
||||
.prepare_resume_action(&mut self.terminal, &mut self.alt_saved_viewport);
|
||||
|
||||
stdout().sync_update(|_| {
|
||||
raw_ansi::synchronized_update(|| {
|
||||
#[cfg(unix)]
|
||||
if let Some(prepared) = prepared_resume.take() {
|
||||
prepared.apply(&mut self.terminal)?;
|
||||
|
||||
Reference in New Issue
Block a user