From f285e85b0cf02ed3b5fee4790070cf9e05682300 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Sat, 6 Apr 2024 07:42:05 +0200 Subject: [PATCH] Enable focus reporting only just before reading from stdin Some terminals send the focus-in sequences ("^[I") whenever focus reporting is enabled. We enable focus reporting whenever we are finished running a command. If we run two commands without reading in between, the focus sequences will show up on the terminal. Fix this by enabling focus-reporting as late as possible. This fixes the problem with `^[I` showing up when running "cat" in gnome-terminal https://github.com/fish-shell/fish-shell/issues/10411. This begs the question if we should do the same for CSI u and bracketed paste. It's difficult to answer that; let's hope we find motivating test cases. If we enable CSI u too late, we might misinterpret key presses, so for now we still enable those as early as possible. Also, since we now read immediately after enabling focus events, we can get rid of the hack where we defer enabling them until after the first prompt. When I start a fresh terminal, the ^[I no longer shows up. --- CHANGELOG.rst | 1 + build_tools/pexpect_helper.py | 1 - src/input.rs | 3 --- src/input_common.rs | 42 ++++++++++++++++++++--------------- src/reader.rs | 4 +++- src/tests/input.rs | 2 +- tests/filter-ctrlseqs.sh | 4 +--- tests/pexpects/read.py | 6 ++--- tests/pexpects/signals.py | 2 +- 9 files changed, 34 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 146bb745c..3f9705035 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -135,6 +135,7 @@ Improved terminal support - Fish now sets the terminal window title (via OSC 0) unconditionally instead of only for some terminals (:issue:`10037`). - Focus reporting is enabled unconditionally, not just inside tmux. To use it, define functions that handle events ``fish_focus_in`` and ``fish_focus_out``. +- Focus reporting is no longer disabled on the first prompt. Other improvements ------------------ diff --git a/build_tools/pexpect_helper.py b/build_tools/pexpect_helper.py index 649c5c40c..3ec241015 100644 --- a/build_tools/pexpect_helper.py +++ b/build_tools/pexpect_helper.py @@ -39,7 +39,6 @@ def get_prompt_re(counter): (?:\x1b[>4;1m) # XTerm's modifyOtherKeys (?:\x1b[>5u) # CSI u with kitty progressive enhancement (?:\x1b=) # set application keypad mode, so the keypad keys send unique codes - (?:\x1b[\?1004h)? # enable focus notify (?:\[.\]\ )? # optional vi mode prompt """ + (r"prompt\ %d>" % counter) # prompt with counter diff --git a/src/input.rs b/src/input.rs index 6236656ce..116618f2b 100644 --- a/src/input.rs +++ b/src/input.rs @@ -571,9 +571,6 @@ fn mapping_execute(&mut self, m: &InputMapping) { /// Enqueue a char event to the queue of unread characters that input_readch will return before /// actually reading from fd 0. pub fn queue_char(&mut self, ch: CharEvent) { - if ch.is_readline() { - self.function_push_args(ch.get_readline()); - } self.queue.push_back(ch); } diff --git a/src/input_common.rs b/src/input_common.rs index 762d340f4..6b18441c1 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -1,5 +1,4 @@ use libc::STDOUT_FILENO; -use once_cell::sync::OnceCell; use crate::common::{ fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked, shell_modes, ScopeGuard, @@ -458,18 +457,25 @@ pub fn terminal_protocols_disable_scoped() -> impl ScopeGuarding { }) } -pub struct TerminalProtocols {} +pub struct TerminalProtocols { + focus_events: bool, +} impl TerminalProtocols { fn new() -> Self { terminal_protocols_enable_impl(); - TerminalProtocols {} + Self { + focus_events: false, + } } } impl Drop for TerminalProtocols { fn drop(&mut self) { terminal_protocols_disable_impl(); + if self.focus_events { + let _ = write_to_fd("\x1b[?1004l".as_bytes(), STDOUT_FILENO); + } } } @@ -477,28 +483,17 @@ fn terminal_protocols_enable_impl() { // Interactive or inside builtin read. assert!(is_interactive_session() || reader_current_data().is_some()); - let mut sequences = concat!( + let sequences = concat!( "\x1b[?2004h", // Bracketed paste "\x1b[>4;1m", // XTerm's modifyOtherKeys "\x1b[>5u", // CSI u with kitty progressive enhancement "\x1b=", // set application keypad mode, so the keypad keys send unique codes - "\x1b[?1004h", // enable focus notify ); - // Note: Don't call this initially because, even though we're in a fish_prompt event, - // tmux reacts sooo quickly that we'll still get a sequence before we're prepared for it. - // So this means that we won't get focus events until you've run at least one command, - // but that's preferable to always seeing "^[[I" when starting fish. - static FIRST_CALL_HACK: OnceCell<()> = OnceCell::new(); - if FIRST_CALL_HACK.get().is_none() { - sequences = sequences.strip_suffix("\x1b[?1004h").unwrap(); - } - FIRST_CALL_HACK.get_or_init(|| ()); - FLOG!( term_protocols, format!( - "Enabling extended keys, bracketed paste and focus reporting: {:?}", + "Enabling extended keys and bracketed paste: {:?}", sequences ) ); @@ -513,18 +508,28 @@ fn terminal_protocols_disable_impl() { "\x1b[>4;0m", "\x1b[<1u", // Konsole breaks unless we pass an explicit number of entries to pop. "\x1b>", - "\x1b[?1004l", ); FLOG!( term_protocols, format!( - "Disabling extended keys, bracketed paste and focus reporting: {:?}", + "Disabling extended keys and bracketed paste: {:?}", sequences ) ); let _ = write_to_fd(sequences.as_bytes(), STDOUT_FILENO); } +pub(crate) fn focus_events_enable_ifn() { + let mut term_protocols = TERMINAL_PROTOCOLS.get().borrow_mut(); + let Some(term_protocols) = term_protocols.as_mut() else { + panic!() + }; + if !term_protocols.focus_events { + term_protocols.focus_events = true; + let _ = write_to_fd("\x1b[?1004h".as_bytes(), STDOUT_FILENO); + } +} + fn parse_mask(mask: u32) -> Modifiers { Modifiers { ctrl: (mask & 4) != 0, @@ -1011,6 +1016,7 @@ fn readch_timed(&mut self, wait_time_ms: usize) -> Option { if let Some(evt) = self.try_pop() { return Some(evt); } + focus_events_enable_ifn(); // We are not prepared to handle a signal immediately; we only want to know if we get input on // our fd before the timeout. Use pselect to block all signals; we will handle signals diff --git a/src/reader.rs b/src/reader.rs index fe9466d18..44e1b1cc3 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -70,7 +70,8 @@ use crate::input::init_input; use crate::input::Inputter; use crate::input_common::{ - terminal_protocols_enable_scoped, CharEvent, CharInputStyle, ReadlineCmd, + focus_events_enable_ifn, terminal_protocols_enable_scoped, CharEvent, CharInputStyle, + ReadlineCmd, }; use crate::io::IoChain; use crate::kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate}; @@ -2039,6 +2040,7 @@ fn read_normal_chars(&mut self, rls: &mut ReadlineLoopState) -> Option4;1m""\ ""$escape\[>5u""\ ""$escape=""\ -""($escape\[\?1004h)?""\ ""|""\ ""$escape\[\?2004l""\ ""$escape\[>4;0m""\ ""$escape\[<1u""\ -""$escape>""\ -""$escape\[\?1004l" +""$escape>" diff --git a/tests/pexpects/read.py b/tests/pexpects/read.py index 7927b39c9..952065ef6 100644 --- a/tests/pexpects/read.py +++ b/tests/pexpects/read.py @@ -13,7 +13,7 @@ send, sendline, sleep, expect_prompt, expect_re, expect_str = ( def expect_read_prompt(): - expect_re("\r\n?read> $") + expect_re(r"\r\n?read> \x1b\[\?1004h$") def expect_marker(text): @@ -56,12 +56,12 @@ print_var_contents("foo", "bar") # read -c (see #8633) sendline(r"read -c init_text somevar && echo $somevar") -expect_re("\r\n?read> init_text$") +expect_re(r"\r\n?read> init_text\x1b\[\?1004h$") sendline("someval") expect_prompt("someval\r\n") sendline(r"read --command='some other text' somevar && echo $somevar") -expect_re("\r\n?read> some other text$") +expect_re(r"\r\n?read> some other text\x1b\[\?1004h$") sendline("another value") expect_prompt("another value\r\n") diff --git a/tests/pexpects/signals.py b/tests/pexpects/signals.py index 026df3ff1..0b56e4bf8 100644 --- a/tests/pexpects/signals.py +++ b/tests/pexpects/signals.py @@ -44,7 +44,7 @@ expect_prompt() sendline("function postexec --on-event fish_postexec; echo fish_postexec spotted; end") expect_prompt() sendline("read") -expect_re("\r\n?read> $") +expect_re(r"\r\n?read> \x1b\[\?1004h$") sleep(0.200) os.kill(sp.spawn.pid, signal.SIGINT) expect_str("fish_postexec spotted")