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.
This commit is contained in:
Johannes Altmanninger
2025-04-25 22:24:06 +02:00
parent 788eddd0e8
commit 9b44a59b80
6 changed files with 110 additions and 111 deletions

View File

@@ -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<Option<TerminalQuery>> =
RefCell::new(Some(TerminalQuery::PrimaryDeviceAttribute(Queried::NotYet)));
initial_query(&blocking_query, streams.out, None);
if continuous_mode {
streams.err.append(L!("\n"));

View File

@@ -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<BlockingWait>> {
Reader::blocking_wait(self)
fn blocking_query(&self) -> RefMut<'_, Option<TerminalQuery>> {
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),
);
}
}

View File

@@ -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<CharEvent> {
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<CharEvent> {
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<CharEvent> {
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<u8>) -> Option<KeyEvent> {
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<u8>) -> Option<KeyEvent> {
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<BlockingWait>>;
fn is_blocked(&self) -> bool {
self.blocking_wait().is_some()
fn blocking_query(&self) -> RefMut<'_, Option<TerminalQuery>>;
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<BlockingWait>>) -> bool {
if wait_guard.is_none() {
return false;
}
*wait_guard = None;
true
pub(crate) fn stop_query(mut query: RefMut<'_, Option<TerminalQuery>>) -> bool {
query.take().is_some()
}
fn invalid_sequence(buffer: &[u8]) -> Option<KeyEvent> {
@@ -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<Option<BlockingWait>>,
blocking_query: RefCell<Option<TerminalQuery>>,
}
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<BlockingWait>> {
self.blocking_wait.borrow_mut()
fn blocking_query(&self) -> RefMut<'_, Option<TerminalQuery>> {
self.blocking_query.borrow_mut()
}
}

View File

@@ -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<Option<BlockingWait>>,
pub blocking_query: RefCell<Option<TerminalQuery>>,
}
impl Parser {
@@ -458,7 +458,9 @@ pub fn new(variables: Rc<EnvStack>, 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) {

View File

@@ -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<Option<TerminalQuery>>,
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<Pin<Box<ReaderData>>> {
struct ReaderDataStack(UnsafeCell<Vec<Pin<Box<ReaderData>>>>);
@@ -1498,16 +1524,16 @@ pub fn combine_command_and_autosuggestion(
}
impl<'a> Reader<'a> {
pub(crate) fn blocking_wait(&self) -> RefMut<'_, Option<BlockingWait>> {
self.parser.blocking_wait.borrow_mut()
pub(crate) fn blocking_query(&self) -> RefMut<'_, Option<TerminalQuery>> {
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<NonZeroUsize>) -> Option<WString> {
}
}
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<CharEvent>) -> 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,

View File

@@ -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<Option<BlockingWait>>,
blocking_query: RefCell<Option<TerminalQuery>>,
}
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<BlockingWait>> {
self.blocking_wait.borrow_mut()
fn blocking_query(&self) -> RefMut<'_, Option<TerminalQuery>> {
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