diff --git a/src/input.rs b/src/input.rs index ce8117563..1f011e2b7 100644 --- a/src/input.rs +++ b/src/input.rs @@ -3,12 +3,14 @@ use crate::env::{Environment, CURSES_INITIALIZED}; use crate::event; use crate::flog::FLOG; +use crate::input_common::CursorPositionBlockingWait::MouseLeft; use crate::input_common::{ - CharEvent, CharInputStyle, ImplicitEvent, InputData, InputEventQueuer, ReadlineCmd, - WaitingForCursorPosition, R_END_INPUT_FUNCTIONS, + CharEvent, CharInputStyle, CursorPositionWait, ImplicitEvent, InputData, InputEventQueuer, + ReadlineCmd, R_END_INPUT_FUNCTIONS, }; use crate::key::ViewportPosition; use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers}; +use crate::output::Outputter; use crate::proc::job_reap; use crate::reader::{ reader_reading_interrupted, reader_reset_interrupted, reader_schedule_prompt_repaint, Reader, @@ -456,19 +458,30 @@ fn paste_commit(&mut self) { ))); } - fn is_waiting_for_cursor_position(&self) -> bool { - self.waiting_for_cursor_position.is_some() + fn cursor_position_wait(&self) -> &CursorPositionWait { + &self.cursor_position_wait } - fn cursor_position_wait_reason(&self) -> &Option { - &self.waiting_for_cursor_position + fn is_blocked_waiting_for_cursor_position(&self) -> bool { + matches!(self.cursor_position_wait, CursorPositionWait::Blocking(_)) + } + fn cursor_position_reporting_supported(&mut self) { + assert!(self.cursor_position_wait == CursorPositionWait::InitialFeatureProbe); + self.cursor_position_wait = CursorPositionWait::None; } fn stop_waiting_for_cursor_position(&mut self) -> bool { - self.waiting_for_cursor_position.take().is_some() + if !self.is_blocked_waiting_for_cursor_position() { + return false; + } + self.cursor_position_wait = CursorPositionWait::None; + true } fn on_mouse_left_click(&mut self, position: ViewportPosition) { FLOG!(reader, "Mouse left click", position); - self.request_cursor_position(WaitingForCursorPosition::MouseLeft(position)); + self.request_cursor_position( + &mut Outputter::stdoutput().borrow_mut(), + CursorPositionWait::Blocking(MouseLeft(position)), + ); } } diff --git a/src/input_common.rs b/src/input_common.rs index 71aa29293..293750e28 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -596,17 +596,25 @@ pub fn function_set_status(&mut self, status: bool) { } } -pub enum WaitingForCursorPosition { +#[derive(Eq, PartialEq)] +pub enum CursorPositionBlockingWait { MouseLeft(ViewportPosition), ScrollbackPush, } +#[derive(Eq, PartialEq)] +pub enum CursorPositionWait { + None, + InitialFeatureProbe, + Blocking(CursorPositionBlockingWait), +} + /// A trait which knows how to produce a stream of input events. /// Note this is conceptually a "base class" with override points. 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_waiting_for_cursor_position() { + if self.is_blocked_waiting_for_cursor_position() { match self.get_input_data().queue.front()? { CharEvent::Key(_) | CharEvent::Readline(_) | CharEvent::Command(_) => { return None; // No code execution while we're waiting for CPR. @@ -733,7 +741,7 @@ fn try_readch(&mut self, blocking: bool) -> Option { Some(seq.chars().skip(1).map(CharEvent::from_char)), ) }; - if self.is_waiting_for_cursor_position() { + if self.is_blocked_waiting_for_cursor_position() { FLOG!( reader, "Still waiting for cursor position report from terminal, deferring key event", @@ -994,15 +1002,17 @@ fn parse_csi(&mut self, buffer: &mut Vec) -> Option { if code != 0 || c != b'M' || modifiers.is_some() { return None; } - if self.is_waiting_for_cursor_position() { - // TODO: re-queue it I guess. - FLOG!( - reader, - "Received mouse left click while still waiting for Cursor Position Report" - ); - return None; + match self.cursor_position_wait() { + CursorPositionWait::None => self.on_mouse_left_click(position), + CursorPositionWait::InitialFeatureProbe => (), + CursorPositionWait::Blocking(_) => { + // TODO: re-queue it I guess. + FLOG!( + reader, + "Ignoring mouse left click received while still waiting for Cursor Position Report" + ); + } } - self.on_mouse_left_click(position); return None; } b't' => { @@ -1023,18 +1033,25 @@ fn parse_csi(&mut self, buffer: &mut Vec) -> Option { b'P' => masked_key(function_key(1), None), b'Q' => masked_key(function_key(2), None), b'R' => { - let wait_reason = self.cursor_position_wait_reason().as_ref()?; let y = usize::try_from(params[0][0] - 1).unwrap(); let x = usize::try_from(params[1][0] - 1).unwrap(); FLOG!(reader, "Received cursor position report y:", y, "x:", x); - let continuation = match wait_reason { - WaitingForCursorPosition::MouseLeft(click_position) => { + let blocking_wait = match self.cursor_position_wait() { + CursorPositionWait::None => return None, + CursorPositionWait::InitialFeatureProbe => { + self.cursor_position_reporting_supported(); + return None; + } + CursorPositionWait::Blocking(blocking_wait) => blocking_wait, + }; + let continuation = match blocking_wait { + CursorPositionBlockingWait::MouseLeft(click_position) => { ImplicitEvent::MouseLeftClickContinuation( ViewportPosition { x, y }, *click_position, ) } - WaitingForCursorPosition::ScrollbackPush => { + CursorPositionBlockingWait::ScrollbackPush => { ImplicitEvent::ScrollbackPushContinuation(y) } }; @@ -1393,11 +1410,12 @@ fn drop_leading_readline_events(&mut self) { } } - fn is_waiting_for_cursor_position(&self) -> bool { - false + fn cursor_position_wait(&self) -> &CursorPositionWait { + &CursorPositionWait::InitialFeatureProbe } - fn cursor_position_wait_reason(&self) -> &Option { - &None + fn cursor_position_reporting_supported(&mut self) {} + fn is_blocked_waiting_for_cursor_position(&self) -> bool { + false } fn stop_waiting_for_cursor_position(&mut self) -> bool { false diff --git a/src/reader.rs b/src/reader.rs index e7f903026..15f8c814d 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -78,9 +78,10 @@ }; use crate::input::init_input; use crate::input_common::terminal_protocols_disable_ifn; +use crate::input_common::CursorPositionBlockingWait; +use crate::input_common::CursorPositionWait; use crate::input_common::ImplicitEvent; use crate::input_common::InputEventQueuer; -use crate::input_common::WaitingForCursorPosition; use crate::input_common::IN_MIDNIGHT_COMMANDER_PRE_CSI_U; use crate::input_common::KITTY_PROGRESSIVE_ENHANCEMENTS_QUERY; use crate::input_common::{ @@ -505,7 +506,7 @@ pub struct ReaderData { /// The representation of the current screen contents. screen: Screen, - pub waiting_for_cursor_position: Option, + pub cursor_position_wait: CursorPositionWait, /// Data associated with input events. /// This is made public so that InputEventQueuer can be implemented on us. @@ -1158,7 +1159,7 @@ fn new(history: Arc, conf: ReaderConfig) -> Pin> { first_prompt: true, last_flash: Default::default(), screen: Screen::new(), - waiting_for_cursor_position: None, + cursor_position_wait: CursorPositionWait::None, input_data, queued_repaint: false, history, @@ -1376,11 +1377,12 @@ fn update_buff_pos(&mut self, elt: EditableLineTag, mut new_pos: Option) pub fn request_cursor_position( &mut self, - waiting_for_cursor_position: WaitingForCursorPosition, + out: &mut Outputter, + cursor_position_wait: CursorPositionWait, ) { - assert!(self.waiting_for_cursor_position.is_none()); - self.waiting_for_cursor_position = Some(waiting_for_cursor_position); - self.screen.write_bytes(b"\x1b[6n"); + assert!(self.cursor_position_wait == CursorPositionWait::None); + self.cursor_position_wait = cursor_position_wait; + let _ = out.write(b"\x1b[6n"); } pub fn mouse_left_click(&mut self, cursor: ViewportPosition, click_position: ViewportPosition) { @@ -2087,8 +2089,13 @@ fn readline(&mut self, nchars: Option) -> Option { static queried: RelaxedAtomicBool = RelaxedAtomicBool::new(false); if !queried.load() { queried.store(true); + let mut out = Outputter::stdoutput().borrow_mut(); + out.begin_buffering(); // Query for kitty keyboard protocol support. - let _ = write_loop(&STDOUT_FILENO, KITTY_PROGRESSIVE_ENHANCEMENTS_QUERY); + let _ = out.write(KITTY_PROGRESSIVE_ENHANCEMENTS_QUERY); + // Query for cursor position reporting support. + zelf.request_cursor_position(&mut out, CursorPositionWait::InitialFeatureProbe); + out.end_buffering(); } // HACK: Don't abandon line for the first prompt, because @@ -3618,31 +3625,22 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { el.end_edit_group(); } rl::ClearScreenAndRepaint => { - self.parser.libdata_mut().is_repaint = true; - let clear = screen_clear(); - if !clear.is_empty() { - // Clear the screen if we can. - // This is subtle: We first clear, draw the old prompt, - // and *then* reexecute the prompt and overdraw it. - // This removes the flicker, - // while keeping the prompt up-to-date. - Outputter::stdoutput().borrow_mut().write_wstr(&clear); - self.screen.reset_line(/*repaint_prompt=*/ true); - self.layout_and_repaint(L!("readline")); - } - self.exec_prompt(); - self.screen.reset_line(/*repaint_prompt=*/ true); - self.layout_and_repaint(L!("readline")); - self.force_exec_prompt_and_repaint = false; - self.parser.libdata_mut().is_repaint = false; + self.clear_screen_and_repaint(); } - rl::ScrollbackPush => { - if self.waiting_for_cursor_position.is_some() { + rl::ScrollbackPush => match self.cursor_position_wait() { + CursorPositionWait::None => self.request_cursor_position( + &mut Outputter::stdoutput().borrow_mut(), + CursorPositionWait::Blocking(CursorPositionBlockingWait::ScrollbackPush), + ), + CursorPositionWait::InitialFeatureProbe => self.clear_screen_and_repaint(), + CursorPositionWait::Blocking(_) => { // TODO: re-queue it I guess. - return; + FLOG!( + reader, + "Ignoring scrollback-push received while still waiting for Cursor Position Report" + ); } - self.request_cursor_position(WaitingForCursorPosition::ScrollbackPush); - } + }, rl::SelfInsert | rl::SelfInsertNotFirst | rl::FuncAnd | rl::FuncOr => { // This can be reached via `commandline -f and` etc // panic!("should have been handled by inputter_t::readch"); @@ -3650,6 +3648,26 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { } } + fn clear_screen_and_repaint(&mut self) { + self.parser.libdata_mut().is_repaint = true; + let clear = screen_clear(); + if !clear.is_empty() { + // Clear the screen if we can. + // This is subtle: We first clear, draw the old prompt, + // and *then* reexecute the prompt and overdraw it. + // This removes the flicker, + // while keeping the prompt up-to-date. + Outputter::stdoutput().borrow_mut().write_wstr(&clear); + self.screen.reset_line(/*repaint_prompt=*/ true); + self.layout_and_repaint(L!("readline")); + } + self.exec_prompt(); + self.screen.reset_line(/*repaint_prompt=*/ true); + self.layout_and_repaint(L!("readline")); + self.force_exec_prompt_and_repaint = false; + self.parser.libdata_mut().is_repaint = false; + } + fn backward_token(&mut self) -> Option { let (_elt, el) = self.active_edit_line(); let pos = el.position();