Refresh TTY timestamps again in most cases

See commit 081c3282b7 (Refresh TTY timestamps also in some rare cases,
2025-01-15) and others.
Fixes d27f5a5293 (Adopt TtyHandoff in remaining places, 2025-06-21)
Fixes #11671
This commit is contained in:
Johannes Altmanninger
2025-07-24 19:46:51 +02:00
parent e52cf2f6a7
commit eaa837effa
7 changed files with 35 additions and 24 deletions

View File

@@ -2,7 +2,7 @@
use crate::fds::make_fd_blocking;
use crate::proc::Pid;
use crate::reader::reader_write_title;
use crate::reader::{reader_save_screen_state, reader_write_title};
use crate::tokenizer::tok_command;
use crate::wutil::perror;
use crate::{env::EnvMode, tty_handoff::TtyHandoff};
@@ -139,7 +139,7 @@ pub fn fg(parser: &Parser, streams: &mut IoStreams, argv: &mut [&wstr]) -> Built
// Note if tty transfer fails, we still try running the job.
parser.job_promote_at(job_pos);
let mut handoff = TtyHandoff::new();
let mut handoff = TtyHandoff::new(reader_save_screen_state);
let _ = make_fd_blocking(STDIN_FILENO);
{
let job_group = job.group();

View File

@@ -91,7 +91,7 @@ fn process_input(streams: &mut IoStreams, continuous_mode: bool, verbose: bool)
let mut recent_chars = vec![];
streams.err.appendln("Press a key:\n");
let mut handoff = TtyHandoff::new();
let mut handoff = TtyHandoff::new(|| {});
handoff.enable_tty_protocols();
while (!first_char_seen || continuous_mode) && !check_exit_loop_maybe_warning(None) {
@@ -100,7 +100,7 @@ fn process_input(streams: &mut IoStreams, continuous_mode: bool, verbose: bool)
CharEvent::Readline(_) | CharEvent::Command(_) | CharEvent::Implicit(_) => continue,
CharEvent::QueryResponse(QueryResponseEvent::PrimaryDeviceAttribute) => {
if get_kitty_keyboard_capability() == Capability::Unknown {
set_kitty_keyboard_capability(Capability::NotSupported);
set_kitty_keyboard_capability(|| {}, Capability::NotSupported);
}
continue;
}

View File

@@ -16,6 +16,7 @@
use crate::input_common::InvalidPolicy;
use crate::nix::isatty;
use crate::reader::commandline_set_buffer;
use crate::reader::reader_save_screen_state;
use crate::reader::ReaderConfig;
use crate::reader::{reader_pop, reader_push, reader_readline};
use crate::tokenizer::Tokenizer;
@@ -244,7 +245,7 @@ fn read_interactive(
let mline = {
let _interactive = parser.push_scope(|s| s.is_interactive = true);
let mut scoped_handoff = TtyHandoff::new();
let mut scoped_handoff = TtyHandoff::new(reader_save_screen_state);
scoped_handoff.enable_tty_protocols();
reader_readline(parser, NonZeroUsize::try_from(nchars).ok())
};

View File

@@ -110,7 +110,7 @@ pub fn exec_job(parser: &Parser, job: &Job, block_io: IoChain) -> bool {
let deferred_process = get_deferred_process(job);
// We may want to transfer tty ownership to the pgroup leader.
let mut handoff = TtyHandoff::new();
let mut handoff = TtyHandoff::new(|| {});
// This loop loops over every process_t in the job, starting it as appropriate. This turns out
// to be rather complex, since a process_t can be one of many rather different things.

View File

@@ -9,7 +9,7 @@
self, alt, canonicalize_control_char, canonicalize_keyed_control_char, char_to_symbol, ctrl,
function_key, shift, Key, Modifiers, ViewportPosition,
};
use crate::reader::reader_test_and_clear_interrupted;
use crate::reader::{reader_save_screen_state, reader_test_and_clear_interrupted};
use crate::terminal::{Capability, SCROLL_FORWARD_SUPPORTED, SCROLL_FORWARD_TERMINFO_CODE};
use crate::threads::iothread_port;
use crate::tty_handoff::set_kitty_keyboard_capability;
@@ -1168,7 +1168,7 @@ fn parse_csi(&mut self, buffer: &mut Vec<u8>) -> Option<KeyEvent> {
reader,
"Received kitty progressive enhancement flags, marking as supported"
);
set_kitty_keyboard_capability(Capability::Supported);
set_kitty_keyboard_capability(reader_save_screen_state, Capability::Supported);
return None;
}

View File

@@ -716,7 +716,7 @@ fn read_i(parser: &Parser) {
// Set up tty protocols. These should be enabled while we're reading interactively,
// and disabled before we run fish script, wildcards, or completions. This is scoped.
// Note this may be disabled within the loop, e.g. when running fish script bound to keys.
let mut tty = TtyHandoff::new();
let mut tty = TtyHandoff::new(reader_save_screen_state);
while !check_exit_loop_maybe_warning(Some(&mut data)) {
RUN_COUNT.fetch_add(1, Ordering::Relaxed);
@@ -1484,6 +1484,10 @@ pub fn mouse_left_click(&mut self, cursor: ViewportPosition, click_position: Vie
}
}
pub fn reader_save_screen_state() {
current_data().map(|data| data.save_screen_state());
}
/// Given a command line and an autosuggestion, return the string that gets shown to the user.
/// Exposed for testing purposes only.
pub fn combine_command_and_autosuggestion(
@@ -2182,7 +2186,7 @@ impl<'a> Reader<'a> {
/// Read a command to execute, respecting input bindings.
/// Return the command, or none if we were asked to cancel (e.g. SIGHUP).
fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
let mut tty = TtyHandoff::new();
let mut tty = TtyHandoff::new(reader_save_screen_state);
self.rls = Some(ReadlineLoopState::new());
@@ -2323,7 +2327,7 @@ fn eval_bind_cmd(&mut self, cmd: &wstr) {
let last_statuses = self.parser.vars().get_last_statuses();
let prev_exec_external_count = self.parser.libdata().exec_external_count;
// Disable TTY protocols while we run a bind command, because it may call out.
let mut scoped_tty = TtyHandoff::new();
let mut scoped_tty = TtyHandoff::new(reader_save_screen_state);
let mut modified_tty = scoped_tty.disable_tty_protocols();
self.parser.eval(cmd, &IoChain::new());
@@ -2567,10 +2571,10 @@ fn handle_char_event(&mut self, injected_event: Option<CharEvent>) -> ControlFlo
return ControlFlow::Continue(());
}
if get_kitty_keyboard_capability() == Capability::Unknown {
set_kitty_keyboard_capability(Capability::NotSupported);
// We may have written to the tty, so save the screen state
// so we don't repaint.
self.screen.save_status();
set_kitty_keyboard_capability(
reader_save_screen_state,
Capability::NotSupported,
);
}
}
QueryResponseEvent::CursorPositionReport(cursor_pos) => {
@@ -2819,10 +2823,12 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) {
// Either the user hit tab only once, or we had no visible completion list.
// Disable tty protocols while we compute completions, so that control-C
// triggers SIGINT (suppressed by CSI-U).
let mut tty = TtyHandoff::new();
let mut tty = TtyHandoff::new(reader_save_screen_state);
tty.disable_tty_protocols();
self.compute_and_apply_completions(c);
tty.reclaim();
if tty.reclaim() {
self.save_screen_state();
}
}
}
rl::PagerToggleSearch => {
@@ -4614,7 +4620,7 @@ fn exec_prompt(&mut self, full_prompt: bool, final_prompt: bool) {
let _noninteractive = self.parser.push_scope(|s| s.is_interactive = false);
// Suppress TTY protocols in a scoped way so that e.g. control-C can cancel the prompt.
let mut scoped_tty = TtyHandoff::new();
let mut scoped_tty = TtyHandoff::new(reader_save_screen_state);
scoped_tty.disable_tty_protocols();
// Update the termsize now.

View File

@@ -72,10 +72,10 @@ pub fn get_kitty_keyboard_capability() -> Capability {
// Set CSI-U ("Kitty") support capability.
// This correctly handles the case where we think protocols are already enabled.
pub fn set_kitty_keyboard_capability(cap: Capability) {
pub fn set_kitty_keyboard_capability(on_write: fn(), cap: Capability) {
assert_is_main_thread();
// Disable and renable protocols around capabilities.
let mut tty = TtyHandoff::new();
let mut tty = TtyHandoff::new(on_write);
tty.disable_tty_protocols();
KITTY_KEYBOARD_SUPPORTED.store(cap as _, Ordering::Relaxed);
FLOG!(
@@ -252,7 +252,7 @@ pub fn initialize_tty_metadata() {
// Enable or disable TTY protocols by writing the appropriate commands to the tty.
// Return true if we emitted any bytes to the tty.
// Note this does NOT intialize the TTY protocls if not already initialized.
fn set_tty_protocols_active(enable: bool) -> bool {
fn set_tty_protocols_active(on_write: fn(), enable: bool) -> bool {
assert_is_main_thread();
// Have protocols at all? We require someone else to have initialized them.
let Some(protocols) = tty_protocols() else {
@@ -287,6 +287,7 @@ fn set_tty_protocols_active(enable: bool) -> bool {
ProtocolKind::Other => FLOG!(term_protocols, mode, "other extended keys"),
ProtocolKind::None => (),
};
(on_write)();
true
}
@@ -347,16 +348,19 @@ pub struct TtyHandoff {
tty_protocols_applied: bool,
// Whether reclaim was called, restoring the tty to its pre-scoped value.
reclaimed: bool,
// Called after writing to the TTY.
on_write: fn(),
}
impl TtyHandoff {
pub fn new() -> Self {
pub fn new(on_write: fn()) -> Self {
let protocols_active = get_tty_protocols_active();
TtyHandoff {
owner: None,
tty_protocols_initial: protocols_active,
tty_protocols_applied: protocols_active,
reclaimed: false,
on_write,
}
}
@@ -367,7 +371,7 @@ pub fn enable_tty_protocols(&mut self) -> bool {
return false; // Already enabled.
}
self.tty_protocols_applied = true;
set_tty_protocols_active(true)
set_tty_protocols_active(self.on_write, true)
}
/// Mark terminal modes as disabled.
@@ -377,7 +381,7 @@ pub fn disable_tty_protocols(&mut self) -> bool {
return false; // Already disabled.
};
self.tty_protocols_applied = false;
set_tty_protocols_active(false)
set_tty_protocols_active(self.on_write, false)
}
/// Transfer to the given job group, if it wants to own the terminal.