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.
This commit is contained in:
Johannes Altmanninger 2024-04-06 07:42:05 +02:00
parent 7ffe023735
commit f285e85b0c
9 changed files with 34 additions and 31 deletions

View File

@ -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
------------------

View File

@ -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

View File

@ -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);
}

View File

@ -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<Target = ()> {
})
}
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

View File

@ -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);

View File

@ -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::*;

View File

@ -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>"

View File

@ -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")

View File

@ -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")