From 9b44a59b80373b530d548bc44cd36487400423e0 Mon Sep 17 00:00:00 2001 From: Johannes Altmanninger Date: Fri, 25 Apr 2025 22:24:06 +0200 Subject: [PATCH] Rename terminal-query-state data structure While at it, extract a function for initialization. This looks pretty ugly but it will get better with the next commit. --- src/builtins/fish_key_reader.rs | 20 ++---- src/input.rs | 10 +-- src/input_common.rs | 63 +++++++++--------- src/parser.rs | 8 ++- src/reader.rs | 110 +++++++++++++++++--------------- src/tests/input.rs | 10 +-- 6 files changed, 110 insertions(+), 111 deletions(-) diff --git a/src/builtins/fish_key_reader.rs b/src/builtins/fish_key_reader.rs index ffc0f251e..0e6252569 100644 --- a/src/builtins/fish_key_reader.rs +++ b/src/builtins/fish_key_reader.rs @@ -7,7 +7,7 @@ //! //! Type "exit" or "quit" to terminate the program. -use std::{ops::ControlFlow, os::unix::prelude::OsStrExt, sync::atomic::Ordering}; +use std::{cell::RefCell, ops::ControlFlow, os::unix::prelude::OsStrExt, sync::atomic::Ordering}; use libc::{STDIN_FILENO, TCSANOW, VEOF, VINTR}; @@ -19,20 +19,16 @@ env::env_init, input_common::{ terminal_protocol_hacks, terminal_protocols_enable_ifn, CharEvent, ImplicitEvent, - InputEventQueue, InputEventQueuer, KeyEvent, + InputEventQueue, InputEventQueuer, KeyEvent, Queried, TerminalQuery, }, key::{char_to_symbol, Key, Modifiers}, nix::isatty, panic::panic_handler, print_help::print_help, proc::set_interactive_session, - reader::{check_exit_loop_maybe_warning, reader_init}, + reader::{check_exit_loop_maybe_warning, initial_query, reader_init}, signal::signal_set_handlers, - terminal::{ - Capability, Output, - TerminalCommand::{QueryKittyKeyboardProgressiveEnhancements, QueryPrimaryDeviceAttribute}, - KITTY_KEYBOARD_SUPPORTED, - }, + terminal::{Capability, KITTY_KEYBOARD_SUPPORTED}, threads, topic_monitor::topic_monitor_init, wchar::prelude::*, @@ -155,11 +151,9 @@ fn setup_and_process_keys( // in fish-proper this is done once a command is run. unsafe { libc::tcsetattr(0, TCSANOW, &*shell_modes()) }; terminal_protocol_hacks(); - - streams - .out - .write_command(QueryKittyKeyboardProgressiveEnhancements); - streams.out.write_command(QueryPrimaryDeviceAttribute); + let blocking_query: RefCell> = + RefCell::new(Some(TerminalQuery::PrimaryDeviceAttribute(Queried::NotYet))); + initial_query(&blocking_query, streams.out, None); if continuous_mode { streams.err.append(L!("\n")); diff --git a/src/input.rs b/src/input.rs index 5ad6260a4..ec727c630 100644 --- a/src/input.rs +++ b/src/input.rs @@ -7,8 +7,8 @@ use crate::future::IsSomeAnd; use crate::global_safety::RelaxedAtomicBool; use crate::input_common::{ - BlockingWait, CharEvent, CharInputStyle, CursorPositionWait, ImplicitEvent, InputData, - InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS, + CharEvent, CharInputStyle, CursorPositionQuery, ImplicitEvent, InputData, InputEventQueuer, + ReadlineCmd, TerminalQuery, R_END_INPUT_FUNCTIONS, }; use crate::key::ViewportPosition; use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers}; @@ -451,15 +451,15 @@ fn paste_commit(&mut self) { ))); } - fn blocking_wait(&self) -> RefMut<'_, Option> { - Reader::blocking_wait(self) + fn blocking_query(&self) -> RefMut<'_, Option> { + Reader::blocking_query(self) } fn on_mouse_left_click(&mut self, position: ViewportPosition) { FLOG!(reader, "Mouse left click", position); self.request_cursor_position( &mut Outputter::stdoutput().borrow_mut(), - CursorPositionWait::MouseLeft(position), + CursorPositionQuery::MouseLeft(position), ); } } diff --git a/src/input_common.rs b/src/input_common.rs index 4e644eb6e..d1b28677e 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -755,7 +755,7 @@ pub fn function_set_status(&mut self, status: bool) { } #[derive(Eq, PartialEq)] -pub enum CursorPositionWait { +pub enum CursorPositionQuery { MouseLeft(ViewportPosition), ScrollbackPush, } @@ -767,9 +767,9 @@ pub enum Queried { } #[derive(Eq, PartialEq)] -pub enum BlockingWait { - Startup(Queried), - CursorPosition(CursorPositionWait), +pub enum TerminalQuery { + PrimaryDeviceAttribute(Queried), + CursorPositionReport(CursorPositionQuery), } /// A trait which knows how to produce a stream of input events. @@ -777,7 +777,7 @@ pub enum BlockingWait { pub trait InputEventQueuer { /// Return the next event in the queue, or none if the queue is empty. fn try_pop(&mut self) -> Option { - if self.is_blocked() { + if self.is_blocked_querying() { match self.get_input_data().queue.front()? { CharEvent::Key(_) | CharEvent::Readline(_) | CharEvent::Command(_) => { return None; // No code execution while blocked. @@ -909,7 +909,7 @@ fn try_readch(&mut self, blocking: bool) -> Option { Some(seq.chars().skip(1).map(CharEvent::from_char)), ) }; - if self.is_blocked() { + if self.is_blocked_querying() { FLOG!( reader, "Still blocked on response from terminal, deferring key event", @@ -928,7 +928,7 @@ fn try_readch(&mut self, blocking: bool) -> Option { reader, "Received interrupt key, giving up waiting for response from terminal" ); - let ok = unblock_input(self.blocking_wait()); + let ok = stop_query(self.blocking_query()); assert!(ok); } continue; @@ -1131,15 +1131,15 @@ fn parse_csi(&mut self, buffer: &mut Vec) -> Option { if code != 0 || c != b'M' || modifiers.is_some() { return None; } - let wait_guard = self.blocking_wait(); - let Some(wait) = &*wait_guard else { - drop(wait_guard); + let query = self.blocking_query(); + let Some(query) = &*query else { + drop(query); self.on_mouse_left_click(position); return None; }; - match wait { - BlockingWait::Startup(_) => {} - BlockingWait::CursorPosition(_) => { + match query { + TerminalQuery::PrimaryDeviceAttribute(_) => {} + TerminalQuery::CursorPositionReport(_) => { // TODO: re-queue it I guess. FLOG!( reader, @@ -1180,22 +1180,23 @@ fn parse_csi(&mut self, buffer: &mut Vec) -> Option { return invalid_sequence(buffer); }; FLOG!(reader, "Received cursor position report y:", y, "x:", x); - let wait_guard = self.blocking_wait(); - let Some(BlockingWait::CursorPosition(wait)) = &*wait_guard else { + let query = self.blocking_query(); + use TerminalQuery::CursorPositionReport; + let Some(CursorPositionReport(cursor_pos_query)) = &*query else { return None; }; - let continuation = match wait { - CursorPositionWait::MouseLeft(click_position) => { + let continuation = match cursor_pos_query { + CursorPositionQuery::MouseLeft(click_position) => { ImplicitEvent::MouseLeftClickContinuation( ViewportPosition { x, y }, *click_position, ) } - CursorPositionWait::ScrollbackPush => { + CursorPositionQuery::ScrollbackPush => { ImplicitEvent::ScrollbackPushContinuation(y) } }; - drop(wait_guard); + drop(query); self.push_front(CharEvent::Implicit(continuation)); return None; } @@ -1628,9 +1629,9 @@ fn drop_leading_readline_events(&mut self) { } } - fn blocking_wait(&self) -> RefMut<'_, Option>; - fn is_blocked(&self) -> bool { - self.blocking_wait().is_some() + fn blocking_query(&self) -> RefMut<'_, Option>; + fn is_blocked_querying(&self) -> bool { + self.blocking_query().is_some() } fn on_mouse_left_click(&mut self, _position: ViewportPosition) {} @@ -1646,7 +1647,7 @@ fn enqueue_interrupt_key(&mut self) { let vintr = shell_modes().c_cc[libc::VINTR]; if vintr != 0 { let interrupt_evt = CharEvent::from_key(KeyEvent::from_single_byte(vintr)); - if unblock_input(self.blocking_wait()) { + if stop_query(self.blocking_query()) { FLOG!( reader, "Received interrupt, giving up on waiting for terminal response" @@ -1750,12 +1751,8 @@ pub(crate) fn decode_input_byte( invalid(out_seq, || FLOG!(reader, "Illegal codepoint")) } -pub(crate) fn unblock_input(mut wait_guard: RefMut<'_, Option>) -> bool { - if wait_guard.is_none() { - return false; - } - *wait_guard = None; - true +pub(crate) fn stop_query(mut query: RefMut<'_, Option>) -> bool { + query.take().is_some() } fn invalid_sequence(buffer: &[u8]) -> Option { @@ -1784,14 +1781,14 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { /// A simple, concrete implementation of InputEventQueuer. pub struct InputEventQueue { data: InputData, - blocking_wait: RefCell>, + blocking_query: RefCell>, } impl InputEventQueue { pub fn new(in_fd: RawFd) -> Self { Self { data: InputData::new(in_fd), - blocking_wait: RefCell::new(None), + blocking_query: RefCell::new(None), } } } @@ -1810,8 +1807,8 @@ fn select_interrupted(&mut self) { self.enqueue_interrupt_key(); } } - fn blocking_wait(&self) -> RefMut<'_, Option> { - self.blocking_wait.borrow_mut() + fn blocking_query(&self) -> RefMut<'_, Option> { + self.blocking_query.borrow_mut() } } diff --git a/src/parser.rs b/src/parser.rs index 9acc3ed9e..60617e59c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -14,7 +14,7 @@ }; use crate::fds::{open_dir, BEST_O_SEARCH}; use crate::global_safety::RelaxedAtomicBool; -use crate::input_common::{terminal_protocols_disable_ifn, BlockingWait, Queried}; +use crate::input_common::{terminal_protocols_disable_ifn, Queried, TerminalQuery}; use crate::io::IoChain; use crate::job_group::MaybeJobId; use crate::operation_context::{OperationContext, EXPANSION_LIMIT_DEFAULT}; @@ -440,7 +440,7 @@ pub struct Parser { /// Global event blocks. pub global_event_blocks: AtomicU64, - pub blocking_wait: RefCell>, + pub blocking_query: RefCell>, } impl Parser { @@ -458,7 +458,9 @@ pub fn new(variables: Rc, cancel_behavior: CancelBehavior) -> Parser { cancel_behavior, profile_items: RefCell::default(), global_event_blocks: AtomicU64::new(0), - blocking_wait: RefCell::new(Some(BlockingWait::Startup(Queried::NotYet))), + blocking_query: RefCell::new(Some(TerminalQuery::PrimaryDeviceAttribute( + Queried::NotYet, + ))), }; match open_dir(CStr::from_bytes_with_nul(b".\0").unwrap(), BEST_O_SEARCH) { diff --git a/src/reader.rs b/src/reader.rs index d7f028456..b18df2ef7 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -23,6 +23,7 @@ #[cfg(not(target_has_atomic = "64"))] use portable_atomic::AtomicU64; use std::borrow::Cow; +use std::cell::RefCell; use std::cell::RefMut; use std::cell::UnsafeCell; use std::cmp; @@ -79,12 +80,12 @@ SearchType, }; use crate::input::init_input; +use crate::input_common::stop_query; use crate::input_common::terminal_protocols_disable_ifn; -use crate::input_common::unblock_input; -use crate::input_common::BlockingWait; -use crate::input_common::CursorPositionWait; +use crate::input_common::CursorPositionQuery; use crate::input_common::ImplicitEvent; use crate::input_common::Queried; +use crate::input_common::TerminalQuery; use crate::input_common::IN_DVTM; use crate::input_common::IN_MIDNIGHT_COMMANDER; use crate::input_common::{ @@ -236,6 +237,31 @@ fn redirect_tty_after_sighup() { } } +pub(crate) fn initial_query( + blocking_query: &RefCell>, + out: &mut impl Output, + vars: Option<&dyn Environment>, +) { + if *blocking_query.borrow() != Some(TerminalQuery::PrimaryDeviceAttribute(Queried::NotYet)) { + return; + } + *blocking_query.borrow_mut() = { + let query = if is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load() { + None + } else { + // Query for kitty keyboard protocol support. + out.write_command(QueryKittyKeyboardProgressiveEnhancements); + out.write_command(QueryXtversion); + if let Some(vars) = vars { + query_capabilities_via_dcs(out.by_ref(), vars); + } + out.write_command(QueryPrimaryDeviceAttribute); + Some(TerminalQuery::PrimaryDeviceAttribute(Queried::Yes)) + }; + query + }; +} + /// The stack of current interactive reading contexts. fn reader_data_stack() -> &'static mut Vec>> { struct ReaderDataStack(UnsafeCell>>>); @@ -1498,16 +1524,16 @@ pub fn combine_command_and_autosuggestion( } impl<'a> Reader<'a> { - pub(crate) fn blocking_wait(&self) -> RefMut<'_, Option> { - self.parser.blocking_wait.borrow_mut() + pub(crate) fn blocking_query(&self) -> RefMut<'_, Option> { + self.parser.blocking_query.borrow_mut() } - pub fn request_cursor_position(&mut self, out: &mut Outputter, wait: CursorPositionWait) { - let mut wait_guard = self.blocking_wait(); - assert!(wait_guard.is_none()); - *wait_guard = Some(BlockingWait::CursorPosition(wait)); + pub fn request_cursor_position(&mut self, out: &mut Outputter, q: CursorPositionQuery) { + let mut query = self.blocking_query(); + assert!(query.is_none()); + *query = Some(TerminalQuery::CursorPositionReport(q)); out.write_command(QueryCursorPosition); - drop(wait_guard); + drop(query); self.save_screen_state(); } @@ -2179,21 +2205,11 @@ fn readline(&mut self, nchars: Option) -> Option { } } - if *self.blocking_wait() == Some(BlockingWait::Startup(Queried::NotYet)) { - if is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load() { - *self.blocking_wait() = None; - } else { - *self.blocking_wait() = Some(BlockingWait::Startup(Queried::Yes)); - let mut out = Outputter::stdoutput().borrow_mut(); - out.begin_buffering(); - // Query for kitty keyboard protocol support. - out.write_command(QueryKittyKeyboardProgressiveEnhancements); - out.write_command(QueryXtversion); - query_capabilities_via_dcs(out.by_ref(), self.parser.vars()); - out.write_command(QueryPrimaryDeviceAttribute); - out.end_buffering(); - } - } + initial_query( + &self.parser.blocking_query, + &mut BufferedOutputter::new(Outputter::stdoutput()), + Some(self.parser.vars()), + ); // HACK: Don't abandon line for the first prompt, because // if we're started with the terminal it might not have settled, @@ -2511,35 +2527,25 @@ fn handle_char_event(&mut self, injected_event: Option) -> ControlFlo self.save_screen_state(); } ImplicitEvent::PrimaryDeviceAttribute => { - let wait_guard = self.blocking_wait(); - let Some(wait) = &*wait_guard else { + let query = self.blocking_query(); + if !matches!(*query, Some(TerminalQuery::PrimaryDeviceAttribute(_))) { // Rogue reply. return ControlFlow::Continue(()); - }; - let BlockingWait::Startup(stage) = wait else { - // Rogue reply. - return ControlFlow::Continue(()); - }; - match stage { - Queried::NotYet => panic!(), - Queried::Yes => { - if KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed) - == Capability::Unknown as _ - { - KITTY_KEYBOARD_SUPPORTED - .store(Capability::NotSupported as _, Ordering::Release); - } - } } - unblock_input(wait_guard); + if KITTY_KEYBOARD_SUPPORTED.load(Ordering::Relaxed) == Capability::Unknown as _ + { + KITTY_KEYBOARD_SUPPORTED + .store(Capability::NotSupported as _, Ordering::Release); + } + stop_query(query); } ImplicitEvent::MouseLeftClickContinuation(cursor, click_position) => { self.mouse_left_click(cursor, click_position); - unblock_input(self.blocking_wait()); + stop_query(self.blocking_query()); } ImplicitEvent::ScrollbackPushContinuation(cursor_y) => { self.screen.push_to_scrollback(cursor_y); - unblock_input(self.blocking_wait()); + stop_query(self.blocking_query()); } }, } @@ -3798,18 +3804,18 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { if !SCROLL_FORWARD_SUPPORTED.load() { return; } - let wait_guard = self.blocking_wait(); - let Some(wait) = &*wait_guard else { - drop(wait_guard); + let query = self.blocking_query(); + let Some(query) = &*query else { + drop(query); self.request_cursor_position( &mut Outputter::stdoutput().borrow_mut(), - CursorPositionWait::ScrollbackPush, + CursorPositionQuery::ScrollbackPush, ); return; }; - match wait { - BlockingWait::Startup(_) => panic!(), - BlockingWait::CursorPosition(_) => { + match query { + TerminalQuery::PrimaryDeviceAttribute(_) => panic!(), + TerminalQuery::CursorPositionReport(_) => { // TODO: re-queue it I guess. FLOG!( reader, diff --git a/src/tests/input.rs b/src/tests/input.rs index e2287b616..89b55cd6b 100644 --- a/src/tests/input.rs +++ b/src/tests/input.rs @@ -1,6 +1,6 @@ use crate::env::EnvStack; use crate::input::{EventQueuePeeker, InputMappingSet, KeyNameStyle, DEFAULT_BIND_MODE}; -use crate::input_common::{BlockingWait, CharEvent, InputData, InputEventQueuer, KeyEvent}; +use crate::input_common::{CharEvent, InputData, InputEventQueuer, KeyEvent, TerminalQuery}; use crate::key::Key; use crate::wchar::prelude::*; use std::cell::{RefCell, RefMut}; @@ -8,7 +8,7 @@ struct TestInputEventQueuer { input_data: InputData, - blocking_wait: RefCell>, + blocking_query: RefCell>, } impl InputEventQueuer for TestInputEventQueuer { @@ -18,8 +18,8 @@ fn get_input_data(&self) -> &InputData { fn get_input_data_mut(&mut self) -> &mut InputData { &mut self.input_data } - fn blocking_wait(&self) -> RefMut<'_, Option> { - self.blocking_wait.borrow_mut() + fn blocking_query(&self) -> RefMut<'_, Option> { + self.blocking_query.borrow_mut() } } @@ -28,7 +28,7 @@ fn test_input() { let vars = Rc::new(EnvStack::new()); let mut input = TestInputEventQueuer { input_data: InputData::new(i32::MAX), // value doesn't matter since we don't read from it - blocking_wait: RefCell::new(None), + blocking_query: RefCell::new(None), }; // Ensure sequences are order independent. Here we add two bindings where the first is a prefix // of the second, and then emit the second key list. The second binding should be invoked, not