From eaa837effab8a6f39b82592f571ab8aebfd81d96 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Thu, 24 Jul 2025 19:46:51 +0200 Subject: [PATCH] Refresh TTY timestamps again in most cases See commit 081c3282b78 (Refresh TTY timestamps also in some rare cases, 2025-01-15) and others. Fixes d27f5a52934 (Adopt TtyHandoff in remaining places, 2025-06-21) Fixes #11671 --- src/builtins/fg.rs | 4 ++-- src/builtins/fish_key_reader.rs | 4 ++-- src/builtins/read.rs | 3 ++- src/exec.rs | 2 +- src/input_common.rs | 4 ++-- src/reader.rs | 26 ++++++++++++++++---------- src/tty_handoff.rs | 16 ++++++++++------ 7 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/builtins/fg.rs b/src/builtins/fg.rs index 1e6854ddd..593e4a3fc 100644 --- a/src/builtins/fg.rs +++ b/src/builtins/fg.rs @@ -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(); diff --git a/src/builtins/fish_key_reader.rs b/src/builtins/fish_key_reader.rs index d70d2a554..e9f9b8840 100644 --- a/src/builtins/fish_key_reader.rs +++ b/src/builtins/fish_key_reader.rs @@ -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; } diff --git a/src/builtins/read.rs b/src/builtins/read.rs index 80fea9eae..fe7cb9b6c 100644 --- a/src/builtins/read.rs +++ b/src/builtins/read.rs @@ -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()) }; diff --git a/src/exec.rs b/src/exec.rs index ff42aa3f7..40dea914e 100644 --- a/src/exec.rs +++ b/src/exec.rs @@ -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. diff --git a/src/input_common.rs b/src/input_common.rs index 280652715..e06b0eaba 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -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) -> Option { 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; } diff --git a/src/reader.rs b/src/reader.rs index 86e77967c..d6be8b5d7 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -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) -> Option { - 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) -> 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. diff --git a/src/tty_handoff.rs b/src/tty_handoff.rs index ada5f20d5..d3b3529c1 100644 --- a/src/tty_handoff.rs +++ b/src/tty_handoff.rs @@ -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.