mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-25 19:25:06 +00:00
scrollback-push to fall back to clear-screen if missing CPR feature
The new ctrl-l implementation relies on Cursor Position Reporting (CPR)
This may not work on exotic terminals that don't support CSI 6n yet
As a workaround, probe for this feature by sending a CSI 6n (CPR)
on startup. Until the terminal responds, have scrollback-push fall
back to clear-screen.
The theoretical problem here is that we might handle scrollback-push
before we have handled the response to our feature probe. That seems
fairly unlikely; also e49dde87cc
has the same characteristics.
This could query a capability instead (via XTGETTCAP or otherwise)
but I haven't found one; and this seems at least as reliable.
While at it, change the naming a bit.
See #11003
This commit is contained in:
parent
dda4371679
commit
75832b3c5d
3 changed files with 106 additions and 57 deletions
29
src/input.rs
29
src/input.rs
|
@ -3,12 +3,14 @@ use crate::curses;
|
|||
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 @@ impl<'a> InputEventQueuer for Reader<'a> {
|
|||
)));
|
||||
}
|
||||
|
||||
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<WaitingForCursorPosition> {
|
||||
&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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -596,17 +596,25 @@ impl InputData {
|
|||
}
|
||||
}
|
||||
|
||||
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<CharEvent> {
|
||||
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 @@ pub trait InputEventQueuer {
|
|||
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 @@ pub trait InputEventQueuer {
|
|||
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 @@ pub trait InputEventQueuer {
|
|||
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 @@ pub trait InputEventQueuer {
|
|||
}
|
||||
}
|
||||
|
||||
fn is_waiting_for_cursor_position(&self) -> bool {
|
||||
false
|
||||
fn cursor_position_wait(&self) -> &CursorPositionWait {
|
||||
&CursorPositionWait::InitialFeatureProbe
|
||||
}
|
||||
fn cursor_position_wait_reason(&self) -> &Option<WaitingForCursorPosition> {
|
||||
&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
|
||||
|
|
|
@ -78,9 +78,10 @@ use crate::history::{
|
|||
};
|
||||
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<WaitingForCursorPosition>,
|
||||
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 @@ impl ReaderData {
|
|||
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 @@ impl ReaderData {
|
|||
|
||||
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 @@ impl<'a> Reader<'a> {
|
|||
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 @@ impl<'a> Reader<'a> {
|
|||
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 @@ impl<'a> Reader<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
let (_elt, el) = self.active_edit_line();
|
||||
let pos = el.position();
|
||||
|
|
Loading…
Reference in a new issue