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 @@ impl Inputter { /// 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 @@ pub trait InputEventQueuer { 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::history::{ 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 @@ impl ReaderData { let mut accumulated_chars = WString::new(); while accumulated_chars.len() < limit { + focus_events_enable_ifn(); let evt = self.inputter.read_char(); let CharEvent::Key(kevt) = &evt else { event_needing_handling = Some(evt); diff --git a/src/tests/input.rs b/src/tests/input.rs index 4a5906dbe..da742f3f9 100644 --- a/src/tests/input.rs +++ b/src/tests/input.rs @@ -1,5 +1,5 @@ use crate::input::{input_mappings, Inputter, DEFAULT_BIND_MODE}; -use crate::input_common::{CharEvent, ReadlineCmd}; +use crate::input_common::{terminal_protocols_enable_scoped, CharEvent, ReadlineCmd}; use crate::key::Key; use crate::parser::Parser; use crate::tests::prelude::*; diff --git a/tests/filter-ctrlseqs.sh b/tests/filter-ctrlseqs.sh index 9e3bf1cb6..ea7b04a61 100755 --- a/tests/filter-ctrlseqs.sh +++ b/tests/filter-ctrlseqs.sh @@ -7,10 +7,8 @@ escape=$(printf '\033') ""$escape\[>4;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")