mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
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:
parent
7ffe023735
commit
f285e85b0c
9 changed files with 34 additions and 31 deletions
|
@ -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`).
|
- 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.
|
- Focus reporting is enabled unconditionally, not just inside tmux.
|
||||||
To use it, define functions that handle events ``fish_focus_in`` and ``fish_focus_out``.
|
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
|
Other improvements
|
||||||
------------------
|
------------------
|
||||||
|
|
|
@ -39,7 +39,6 @@ def get_prompt_re(counter):
|
||||||
(?:\x1b[>4;1m) # XTerm's modifyOtherKeys
|
(?:\x1b[>4;1m) # XTerm's modifyOtherKeys
|
||||||
(?:\x1b[>5u) # CSI u with kitty progressive enhancement
|
(?:\x1b[>5u) # CSI u with kitty progressive enhancement
|
||||||
(?:\x1b=) # set application keypad mode, so the keypad keys send unique codes
|
(?:\x1b=) # set application keypad mode, so the keypad keys send unique codes
|
||||||
(?:\x1b[\?1004h)? # enable focus notify
|
|
||||||
(?:\[.\]\ )? # optional vi mode prompt
|
(?:\[.\]\ )? # optional vi mode prompt
|
||||||
"""
|
"""
|
||||||
+ (r"prompt\ %d>" % counter) # prompt with counter
|
+ (r"prompt\ %d>" % counter) # prompt with counter
|
||||||
|
|
|
@ -571,9 +571,6 @@ impl Inputter {
|
||||||
/// Enqueue a char event to the queue of unread characters that input_readch will return before
|
/// Enqueue a char event to the queue of unread characters that input_readch will return before
|
||||||
/// actually reading from fd 0.
|
/// actually reading from fd 0.
|
||||||
pub fn queue_char(&mut self, ch: CharEvent) {
|
pub fn queue_char(&mut self, ch: CharEvent) {
|
||||||
if ch.is_readline() {
|
|
||||||
self.function_push_args(ch.get_readline());
|
|
||||||
}
|
|
||||||
self.queue.push_back(ch);
|
self.queue.push_back(ch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use libc::STDOUT_FILENO;
|
use libc::STDOUT_FILENO;
|
||||||
use once_cell::sync::OnceCell;
|
|
||||||
|
|
||||||
use crate::common::{
|
use crate::common::{
|
||||||
fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked, shell_modes, ScopeGuard,
|
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 {
|
impl TerminalProtocols {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
terminal_protocols_enable_impl();
|
terminal_protocols_enable_impl();
|
||||||
TerminalProtocols {}
|
Self {
|
||||||
|
focus_events: false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for TerminalProtocols {
|
impl Drop for TerminalProtocols {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
terminal_protocols_disable_impl();
|
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.
|
// Interactive or inside builtin read.
|
||||||
assert!(is_interactive_session() || reader_current_data().is_some());
|
assert!(is_interactive_session() || reader_current_data().is_some());
|
||||||
|
|
||||||
let mut sequences = concat!(
|
let sequences = concat!(
|
||||||
"\x1b[?2004h", // Bracketed paste
|
"\x1b[?2004h", // Bracketed paste
|
||||||
"\x1b[>4;1m", // XTerm's modifyOtherKeys
|
"\x1b[>4;1m", // XTerm's modifyOtherKeys
|
||||||
"\x1b[>5u", // CSI u with kitty progressive enhancement
|
"\x1b[>5u", // CSI u with kitty progressive enhancement
|
||||||
"\x1b=", // set application keypad mode, so the keypad keys send unique codes
|
"\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!(
|
FLOG!(
|
||||||
term_protocols,
|
term_protocols,
|
||||||
format!(
|
format!(
|
||||||
"Enabling extended keys, bracketed paste and focus reporting: {:?}",
|
"Enabling extended keys and bracketed paste: {:?}",
|
||||||
sequences
|
sequences
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -513,18 +508,28 @@ fn terminal_protocols_disable_impl() {
|
||||||
"\x1b[>4;0m",
|
"\x1b[>4;0m",
|
||||||
"\x1b[<1u", // Konsole breaks unless we pass an explicit number of entries to pop.
|
"\x1b[<1u", // Konsole breaks unless we pass an explicit number of entries to pop.
|
||||||
"\x1b>",
|
"\x1b>",
|
||||||
"\x1b[?1004l",
|
|
||||||
);
|
);
|
||||||
FLOG!(
|
FLOG!(
|
||||||
term_protocols,
|
term_protocols,
|
||||||
format!(
|
format!(
|
||||||
"Disabling extended keys, bracketed paste and focus reporting: {:?}",
|
"Disabling extended keys and bracketed paste: {:?}",
|
||||||
sequences
|
sequences
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
let _ = write_to_fd(sequences.as_bytes(), STDOUT_FILENO);
|
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 {
|
fn parse_mask(mask: u32) -> Modifiers {
|
||||||
Modifiers {
|
Modifiers {
|
||||||
ctrl: (mask & 4) != 0,
|
ctrl: (mask & 4) != 0,
|
||||||
|
@ -1011,6 +1016,7 @@ pub trait InputEventQueuer {
|
||||||
if let Some(evt) = self.try_pop() {
|
if let Some(evt) = self.try_pop() {
|
||||||
return Some(evt);
|
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
|
// 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
|
// our fd before the timeout. Use pselect to block all signals; we will handle signals
|
||||||
|
|
|
@ -70,7 +70,8 @@ use crate::history::{
|
||||||
use crate::input::init_input;
|
use crate::input::init_input;
|
||||||
use crate::input::Inputter;
|
use crate::input::Inputter;
|
||||||
use crate::input_common::{
|
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::io::IoChain;
|
||||||
use crate::kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate};
|
use crate::kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate};
|
||||||
|
@ -2039,6 +2040,7 @@ impl ReaderData {
|
||||||
let mut accumulated_chars = WString::new();
|
let mut accumulated_chars = WString::new();
|
||||||
|
|
||||||
while accumulated_chars.len() < limit {
|
while accumulated_chars.len() < limit {
|
||||||
|
focus_events_enable_ifn();
|
||||||
let evt = self.inputter.read_char();
|
let evt = self.inputter.read_char();
|
||||||
let CharEvent::Key(kevt) = &evt else {
|
let CharEvent::Key(kevt) = &evt else {
|
||||||
event_needing_handling = Some(evt);
|
event_needing_handling = Some(evt);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::input::{input_mappings, Inputter, DEFAULT_BIND_MODE};
|
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::key::Key;
|
||||||
use crate::parser::Parser;
|
use crate::parser::Parser;
|
||||||
use crate::tests::prelude::*;
|
use crate::tests::prelude::*;
|
||||||
|
|
|
@ -7,10 +7,8 @@ escape=$(printf '\033')
|
||||||
""$escape\[>4;1m""\
|
""$escape\[>4;1m""\
|
||||||
""$escape\[>5u""\
|
""$escape\[>5u""\
|
||||||
""$escape=""\
|
""$escape=""\
|
||||||
""($escape\[\?1004h)?""\
|
|
||||||
""|""\
|
""|""\
|
||||||
""$escape\[\?2004l""\
|
""$escape\[\?2004l""\
|
||||||
""$escape\[>4;0m""\
|
""$escape\[>4;0m""\
|
||||||
""$escape\[<1u""\
|
""$escape\[<1u""\
|
||||||
""$escape>""\
|
""$escape>"
|
||||||
""$escape\[\?1004l"
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ send, sendline, sleep, expect_prompt, expect_re, expect_str = (
|
||||||
|
|
||||||
|
|
||||||
def expect_read_prompt():
|
def expect_read_prompt():
|
||||||
expect_re("\r\n?read> $")
|
expect_re(r"\r\n?read> \x1b\[\?1004h$")
|
||||||
|
|
||||||
|
|
||||||
def expect_marker(text):
|
def expect_marker(text):
|
||||||
|
@ -56,12 +56,12 @@ print_var_contents("foo", "bar")
|
||||||
|
|
||||||
# read -c (see #8633)
|
# read -c (see #8633)
|
||||||
sendline(r"read -c init_text somevar && echo $somevar")
|
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")
|
sendline("someval")
|
||||||
expect_prompt("someval\r\n")
|
expect_prompt("someval\r\n")
|
||||||
|
|
||||||
sendline(r"read --command='some other text' somevar && echo $somevar")
|
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")
|
sendline("another value")
|
||||||
expect_prompt("another value\r\n")
|
expect_prompt("another value\r\n")
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ expect_prompt()
|
||||||
sendline("function postexec --on-event fish_postexec; echo fish_postexec spotted; end")
|
sendline("function postexec --on-event fish_postexec; echo fish_postexec spotted; end")
|
||||||
expect_prompt()
|
expect_prompt()
|
||||||
sendline("read")
|
sendline("read")
|
||||||
expect_re("\r\n?read> $")
|
expect_re(r"\r\n?read> \x1b\[\?1004h$")
|
||||||
sleep(0.200)
|
sleep(0.200)
|
||||||
os.kill(sp.spawn.pid, signal.SIGINT)
|
os.kill(sp.spawn.pid, signal.SIGINT)
|
||||||
expect_str("fish_postexec spotted")
|
expect_str("fish_postexec spotted")
|
||||||
|
|
Loading…
Reference in a new issue