fix: adapt to a wider range of terminal cursor management through DECSET and DECRQM requests again

This commit is contained in:
sxyazi 2024-07-26 09:15:20 +08:00
parent c086f2f34d
commit f024ce03e7
No known key found for this signature in database
3 changed files with 48 additions and 52 deletions

View file

@ -130,7 +130,7 @@ impl Emulator {
RestorePosition RestorePosition
)?; )?;
let resp = futures::executor::block_on(Self::read_until_da1())?; let resp = futures::executor::block_on(Self::read_until_da1());
let names = [ let names = [
("kitty", Self::Kitty), ("kitty", Self::Kitty),
("Konsole", Self::Konsole), ("Konsole", Self::Konsole),
@ -189,31 +189,31 @@ impl Emulator {
result result
} }
pub async fn read_until_da1() -> Result<String> { pub async fn read_until_da1() -> String {
let mut buf: Vec<u8> = Vec::with_capacity(200);
let read = async { let read = async {
let mut stdin = BufReader::new(tokio::io::stdin()); let mut stdin = BufReader::new(tokio::io::stdin());
let mut buf: Vec<u8> = Vec::with_capacity(200);
loop { loop {
let mut c = [0; 1]; let mut c = [0; 1];
if stdin.read(&mut c).await? == 0 { if stdin.read(&mut c).await? == 0 {
bail!("unexpected EOF"); bail!("unexpected EOF");
} }
buf.push(c[0]); buf.push(c[0]);
if c[0] != b'c' { if c[0] != b'c' || !buf.contains(&b'\x1b') {
continue; continue;
} }
if buf.rsplitn(2, |&b| b == b'\xb1').next().is_some_and(|s| s.starts_with(b"\x1b[?")) { if buf.rsplitn(2, |&b| b == b'\x1b').next().is_some_and(|s| s.starts_with(b"[?")) {
break; break;
} }
} }
Ok(buf) Ok(())
}; };
let timeout = timeout(Duration::from_secs(10), read).await; match timeout(Duration::from_secs(10), read).await {
if let Err(ref e) = timeout { Err(e) => error!("read_until_da1 timed out: {buf:?}, error: {e:?}"),
error!("read_until_da1: {e:?}"); Ok(Err(e)) => error!("read_until_da1 failed: {buf:?}, error: {e:?}"),
Ok(Ok(())) => {}
} }
String::from_utf8_lossy(&buf).into_owned()
String::from_utf8(timeout??).map_err(Into::into)
} }
} }

View file

@ -14,22 +14,18 @@ pub struct Plugin {
} }
impl Plugin { impl Plugin {
pub fn fetchers( pub fn fetchers<'a>(
&self, &'a self,
path: &Path, path: &'a Path,
mime: Option<&str>, mime: Option<&'a str>,
f: impl Fn(&str) -> bool + Copy, factor: impl Fn(&str) -> bool + Copy,
) -> Vec<&Fetcher> { ) -> impl Iterator<Item = &'a Fetcher> {
let is_dir = mime == Some(MIME_DIR); let is_dir = mime == Some(MIME_DIR);
self self.fetchers.iter().filter(move |&f| {
.fetchers f.if_.as_ref().and_then(|c| c.eval(factor)) != Some(false)
.iter() && (f.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
.filter(|&p| { || f.name.as_ref().is_some_and(|p| p.match_path(path, is_dir)))
p.if_.as_ref().and_then(|c| c.eval(f)) != Some(false) })
&& (p.mime.as_ref().zip(mime).map_or(false, |(p, m)| p.match_mime(m))
|| p.name.as_ref().is_some_and(|p| p.match_path(path, is_dir)))
})
.collect()
} }
pub fn preloaders(&self, path: &Path, mime: Option<&str>) -> Vec<&Preloader> { pub fn preloaders(&self, path: &Path, mime: Option<&str>) -> Vec<&Preloader> {

View file

@ -1,7 +1,7 @@
use std::{io::{self, stderr, BufWriter, Stderr}, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}}; use std::{io::{self, stderr, BufWriter, Stderr}, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}};
use anyhow::Result; use anyhow::Result;
use crossterm::{cursor::{RestorePosition, SavePosition}, event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle}}; use crossterm::{event::{DisableBracketedPaste, EnableBracketedPaste, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags}, execute, queue, style::Print, terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen, SetTitle}};
use cursor::RestoreCursor; use cursor::RestoreCursor;
use ratatui::{backend::CrosstermBackend, buffer::Buffer, layout::Rect, CompletedFrame, Frame, Terminal}; use ratatui::{backend::CrosstermBackend, buffer::Buffer, layout::Rect, CompletedFrame, Frame, Terminal};
use yazi_adapter::Emulator; use yazi_adapter::Emulator;
@ -28,27 +28,24 @@ impl Term {
enable_raw_mode()?; enable_raw_mode()?;
execute!( execute!(
BufWriter::new(stderr()), BufWriter::new(stderr()),
EnterAlternateScreen,
EnableBracketedPaste,
mouse::SetMouse(true),
SavePosition,
Print("\x1b[?12$p"), // Request cursor blink status (DECSET) Print("\x1b[?12$p"), // Request cursor blink status (DECSET)
Print("\x1bP$q q\x1b\\"), // Request cursor shape (DECRQM) Print("\x1bP$q q\x1b\\"), // Request cursor shape (DECRQM)
Print("\x1b[?u\x1b[c"), // Request keyboard enhancement flags (CSI u) Print("\x1b[?u\x1b[c"), // Request keyboard enhancement flags (CSI u)
RestorePosition EnterAlternateScreen,
EnableBracketedPaste,
mouse::SetMouse(true),
)?; )?;
if let Ok(s) = futures::executor::block_on(Emulator::read_until_da1()) { let da = futures::executor::block_on(Emulator::read_until_da1());
CSI_U.store(s.contains("\x1b[?0u"), Ordering::Relaxed); CSI_U.store(da.contains("\x1b[?0u"), Ordering::Relaxed);
BLINK.store(s.contains("\x1b[?12;1$y"), Ordering::Relaxed); BLINK.store(da.contains("\x1b[?12;1$y"), Ordering::Relaxed);
SHAPE.store( SHAPE.store(
s.split_once("\x1bP1$r") da.split_once("\x1bP1$r")
.and_then(|(_, s)| s.bytes().next()) .and_then(|(_, s)| s.bytes().next())
.filter(|&b| matches!(b, b'0'..=b'6')) .filter(|&b| matches!(b, b'0'..=b'6'))
.map_or(0, |b| b - b'0'), .map_or(u8::MAX, |b| b - b'0'),
Ordering::Relaxed, Ordering::Relaxed,
); );
}
if CSI_U.load(Ordering::Relaxed) { if CSI_U.load(Ordering::Relaxed) {
queue!( queue!(
@ -74,9 +71,9 @@ impl Term {
execute!( execute!(
stderr(), stderr(),
mouse::SetMouse(false), mouse::SetMouse(false),
RestoreCursor,
DisableBracketedPaste, DisableBracketedPaste,
LeaveAlternateScreen, LeaveAlternateScreen,
RestoreCursor,
)?; )?;
self.show_cursor()?; self.show_cursor()?;
@ -90,11 +87,11 @@ impl Term {
execute!( execute!(
stderr(), stderr(),
SetTitle(""),
mouse::SetMouse(false), mouse::SetMouse(false),
RestoreCursor,
SetTitle(""),
DisableBracketedPaste, DisableBracketedPaste,
LeaveAlternateScreen, LeaveAlternateScreen,
RestoreCursor,
crossterm::cursor::Show crossterm::cursor::Show
) )
.ok(); .ok();
@ -219,17 +216,20 @@ mod cursor {
impl crossterm::Command for RestoreCursor { impl crossterm::Command for RestoreCursor {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result { fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
let shape = SHAPE.load(Ordering::Relaxed).max(1); let (shape, shape_blink) = match SHAPE.load(Ordering::Relaxed) {
let blink = BLINK.load(Ordering::Relaxed) ^ (shape & 1 == 0); u8::MAX => (0, false),
n => ((n.max(1) + 1) / 2, n.max(1) & 1 == 1),
};
Ok(match (shape + 1) / 2 { let blink = BLINK.load(Ordering::Relaxed) ^ shape_blink;
1 if blink => SetCursorStyle::BlinkingBlock.write_ansi(f)?, Ok(match shape {
1 if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?,
2 if blink => SetCursorStyle::BlinkingUnderScore.write_ansi(f)?, 2 if blink => SetCursorStyle::BlinkingUnderScore.write_ansi(f)?,
2 if !blink => SetCursorStyle::SteadyUnderScore.write_ansi(f)?, 2 if !blink => SetCursorStyle::SteadyUnderScore.write_ansi(f)?,
3 if blink => SetCursorStyle::BlinkingBar.write_ansi(f)?, 3 if blink => SetCursorStyle::BlinkingBar.write_ansi(f)?,
3 if !blink => SetCursorStyle::SteadyBar.write_ansi(f)?, 3 if !blink => SetCursorStyle::SteadyBar.write_ansi(f)?,
_ => tracing::error!("Terminal didn't respond to the cursor shape request: {shape}"), _ if blink => SetCursorStyle::DefaultUserShape.write_ansi(f)?,
_ if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?,
_ => unreachable!(),
}) })
} }