cc_switch_lib/cli/terminal.rs
1use std::io::{self, IsTerminal, Write};
2use std::sync::Once;
3
4static DISABLE_BRACKETED_PASTE: Once = Once::new();
5
6/// Disables bracketed paste mode to work around inquire dropping paste events.
7///
8/// When bracketed paste mode is enabled (common in zsh/fish, tmux/zellij, and some terminals),
9/// paste events are sent as `Event::Paste(String)` by crossterm. However, inquire's event
10/// handler only processes `Event::Key(...)` and drops all other events, causing paste to
11/// appear broken.
12///
13/// This function sends the ANSI escape sequence `CSI ?2004 l` to disable bracketed paste mode,
14/// causing the terminal to send paste content as regular character events that inquire can handle.
15///
16/// **Side effects:**
17/// - This changes terminal state and may affect bracketed paste behavior after the program exits.
18/// - Most shells (zsh/fish) will re-enable it on the next prompt, but this is not guaranteed.
19///
20/// **Implementation notes:**
21/// - Uses `Once` to ensure the sequence is only sent once per process.
22/// - This is a best-effort operation that silently fails if stderr is not a terminal.
23/// - On Windows, the ANSI sequence may appear as garbage in legacy consoles that don't support VT.
24pub fn disable_bracketed_paste_mode_best_effort() {
25 DISABLE_BRACKETED_PASTE.call_once(|| {
26 if !io::stderr().is_terminal() {
27 return;
28 }
29
30 let mut stderr = io::stderr();
31 // CSI ?2004 l - Disable bracketed paste mode
32 if let Err(e) = stderr.write_all(b"\x1b[?2004l") {
33 log::debug!("Failed to disable bracketed paste mode: {}", e);
34 return;
35 }
36 if let Err(e) = stderr.flush() {
37 log::debug!(
38 "Failed to flush stderr after disabling bracketed paste: {}",
39 e
40 );
41 }
42 });
43}