Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions crates/tui/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3304,7 +3304,7 @@ async fn run_doctor(config: &Config, workspace: &Path, config_path_override: Opt
}
if crate::settings::detected_legacy_windows_console_host() {
println!(
" {} legacy Windows console host → low_motion + fancy_animations=false + synchronized_output=off (auto)",
" {} legacy Windows console host → low_motion + fancy_animations=false + bracketed_paste=false + synchronized_output=off (auto)",
"•".truecolor(sky_r, sky_g, sky_b)
);
any_quirk = true;
Expand Down Expand Up @@ -6026,8 +6026,8 @@ async fn run_interactive(
let use_alt_screen = should_use_alt_screen(cli, config);
let use_mouse_capture = should_use_mouse_capture(cli, config, use_alt_screen);
let use_bracketed_paste = crate::settings::Settings::load()
.map(|s| s.bracketed_paste)
.unwrap_or(true);
.map(|s| s.effective_bracketed_paste())
.unwrap_or_else(|_| !crate::settings::detected_legacy_windows_console_host());

// Auto-install bundled system skills (e.g. skill-creator) on first launch.
// Errors are non-fatal: log a warning and continue.
Expand Down
20 changes: 20 additions & 0 deletions crates/tui/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1105,6 +1105,18 @@ impl Settings {
pub fn synchronized_output_enabled(&self) -> bool {
!self.synchronized_output.eq_ignore_ascii_case("off")
}

/// Runtime bracketed-paste mode after terminal-host quirks are applied.
///
/// This deliberately does not mutate [`Settings::bracketed_paste`]:
/// `apply_env_overrides()` can run before saving settings, and a legacy
/// conhost runtime fallback must not permanently disable bracketed paste
/// when the same config is later used in Windows Terminal or another
/// modern terminal.
#[must_use]
pub fn effective_bracketed_paste(&self) -> bool {
self.bracketed_paste && !detected_legacy_windows_console_host()
}
}

fn resolve_settings_path_from_candidates(
Expand Down Expand Up @@ -2222,6 +2234,14 @@ mod tests {
settings.apply_env_overrides();
assert!(settings.low_motion);
assert!(!settings.fancy_animations);
assert!(
settings.bracketed_paste,
"env-only conhost fallback must not persistently mutate bracketed_paste (#1102)"
);
assert!(
!settings.effective_bracketed_paste(),
"legacy Windows console hosts do not support crossterm bracketed paste (#1102)"
);
assert_eq!(settings.synchronized_output, "off");

// SAFETY: cleanup under the guard.
Expand Down
28 changes: 22 additions & 6 deletions crates/tui/src/tui/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -872,7 +872,7 @@ pub async fn run_tui(config: &Config, options: TuiOptions) -> Result<()> {
execute!(terminal.backend_mut(), DisableMouseCapture)?;
}
if use_bracketed_paste {
execute!(terminal.backend_mut(), DisableBracketedPaste)?;
disable_bracketed_paste_mode(terminal.backend_mut());
}
terminal.show_cursor()?;
drop(terminal);
Expand Down Expand Up @@ -1061,7 +1061,7 @@ impl Drop for TerminalCleanupGuard {
let _ = execute!(stdout, DisableMouseCapture);
}
if self.use_bracketed_paste {
let _ = execute!(stdout, DisableBracketedPaste);
disable_bracketed_paste_mode(&mut stdout);
}
let _ = execute!(stdout, crossterm::cursor::Show);
}
Expand Down Expand Up @@ -9766,7 +9766,7 @@ fn pause_terminal(
execute!(terminal.backend_mut(), DisableMouseCapture)?;
}
if use_bracketed_paste {
execute!(terminal.backend_mut(), DisableBracketedPaste)?;
disable_bracketed_paste_mode(terminal.backend_mut());
}
Ok(())
}
Expand Down Expand Up @@ -9928,7 +9928,7 @@ pub fn emergency_restore_terminal() {
pop_keyboard_enhancement_flags(&mut stdout);
disable_alternate_scroll_mode(&mut stdout);
let _ = execute!(stdout, DisableFocusChange);
let _ = execute!(stdout, DisableBracketedPaste);
disable_bracketed_paste_mode(&mut stdout);
let _ = execute!(stdout, DisableMouseCapture);
let _ = disable_raw_mode();
let _ = execute!(stdout, LeaveAlternateScreen);
Expand Down Expand Up @@ -9991,14 +9991,30 @@ fn recover_terminal_modes<W: Write>(
if use_mouse_capture && let Err(err) = execute!(writer, EnableMouseCapture) {
tracing::debug!(?err, "EnableMouseCapture ignored");
}
if use_bracketed_paste && let Err(err) = execute!(writer, EnableBracketedPaste) {
tracing::debug!(?err, "EnableBracketedPaste ignored");
if use_bracketed_paste {
try_enable_bracketed_paste_mode(writer);
}
if let Err(err) = execute!(writer, EnableFocusChange) {
tracing::debug!(?err, "EnableFocusChange ignored");
}
}

fn try_enable_bracketed_paste_mode<W: Write>(writer: &mut W) -> bool {
match execute!(writer, EnableBracketedPaste) {
Ok(()) => true,
Err(err) => {
tracing::debug!(?err, "EnableBracketedPaste ignored");
false
}
}
}

fn disable_bracketed_paste_mode<W: Write>(writer: &mut W) {
if let Err(err) = execute!(writer, DisableBracketedPaste) {
tracing::debug!(?err, "DisableBracketedPaste ignored");
}
}

fn terminal_event_needs_viewport_recapture(evt: &Event) -> bool {
matches!(evt, Event::FocusGained)
}
Expand Down
24 changes: 24 additions & 0 deletions crates/tui/src/tui/ui/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,30 @@ fn recover_terminal_modes_emits_expected_csi_sequences_with_gating() {
);
}

#[cfg(not(windows))]
#[test]
fn bracketed_paste_mode_helpers_ignore_writer_errors() {
struct FailingWriter;

impl std::io::Write for FailingWriter {
fn write(&mut self, _buf: &[u8]) -> std::io::Result<usize> {
Err(std::io::Error::other("terminal mode unsupported"))
}

fn flush(&mut self) -> std::io::Result<()> {
Err(std::io::Error::other("terminal mode unsupported"))
}
}

let mut writer = FailingWriter;

assert!(
!try_enable_bracketed_paste_mode(&mut writer),
"unsupported bracketed paste must be reported without bubbling an error"
);
disable_bracketed_paste_mode(&mut writer);
}

#[cfg(windows)]
#[test]
fn recover_terminal_modes_runs_without_panic_on_windows() {
Expand Down
Loading