fix: adapt to a wider range of terminal cursor management through DECSET and DECRQM requests (#1346)

This commit is contained in:
三咲雅 · Misaki Masa 2024-07-25 19:43:11 +08:00 committed by sxyazi
parent 152b9ea75e
commit df1f094a28
No known key found for this signature in database
19 changed files with 112 additions and 63 deletions

53
Cargo.lock generated
View file

@ -303,9 +303,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.9"
version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462"
checksum = "8f6b81fb3c84f5563d509c59b5a48d935f689e993afa90fe39047f05adef9142"
dependencies = [
"clap_builder",
"clap_derive",
@ -313,9 +313,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.9"
version = "4.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942"
checksum = "5ca6706fd5224857d9ac5eb9355f6683563cc0541c7cd9d014043b57cbec78ac"
dependencies = [
"anstream",
"anstyle",
@ -325,9 +325,9 @@ dependencies = [
[[package]]
name = "clap_complete"
version = "4.5.8"
version = "4.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b4be9c4c4b1f30b78d8a750e0822b6a6102d97e62061c583a6c1dea2dfb33ae"
checksum = "faa2032320fd6f50d22af510d204b2994eef49600dfbd0e771a166213844e4cd"
dependencies = [
"clap",
]
@ -485,7 +485,7 @@ dependencies = [
"filedescriptor",
"futures-core",
"libc",
"mio",
"mio 0.8.11",
"parking_lot",
"signal-hook",
"signal-hook-mio",
@ -1269,6 +1269,18 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "mio"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
dependencies = [
"hermit-abi",
"libc",
"wasi",
"windows-sys 0.52.0",
]
[[package]]
name = "mlua"
version = "0.9.9"
@ -1344,7 +1356,7 @@ dependencies = [
"kqueue",
"libc",
"log",
"mio",
"mio 0.8.11",
"walkdir",
"windows-sys 0.48.0",
]
@ -1374,16 +1386,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.7"
@ -1876,7 +1878,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af"
dependencies = [
"libc",
"mio",
"mio 0.8.11",
"signal-hook",
]
@ -2139,28 +2141,27 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.38.1"
version = "1.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df"
checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"num_cpus",
"mio 1.0.1",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-macros"
version = "2.3.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",

View file

@ -10,12 +10,12 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
- 🚀 **Full Asynchronous Support**: All I/O operations are asynchronous, CPU tasks are spread across multiple threads, making the most of available resources.
- 💪 **Powerful Async Task Scheduling and Management**: Provides real-time progress updates, task cancellation, and internal task priority assignment.
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals.
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++ and Chafa, covering almost all terminals.
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer/preloader/fetcher; Just some pieces of Lua.
- 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence.
- 📦 **Package Manager**: Install plugins and themes with one command, keeping them always up to date, or pin them to a specific version.
- 🧰 Integration with fd, rg, fzf, zoxide
- 📦 **Package Manager**: Install plugins and themes with one command, keeping them up to date, or pin them to a specific version.
- 🧰 Integration with ripgrep, fd, fzf, zoxide
- 💫 Vim-like input/select/which/notify component, auto-completion for cd paths
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
- 🔄 Bulk Renaming, Visual Mode, File Chooser

View file

@ -1 +1 @@
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip"],"version":"0.2","language":"en","flagWords":[]}
{"flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp"," Überzug"," Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","singlefile","jpegopt","EXIF","rustfmt","mktemp","nanos","xclip","xsel","natord","Mintty","nixos","nixpkgs","SIGTSTP","SIGCONT","SIGCONT","mlua","nonstatic","userdata","metatable","natsort","backstack","luajit","Succ","Succ","cand","fileencoding","foldmethod","lightgreen","darkgray","lightred","lightyellow","lightcyan","nushell","msvc","aarch","linemode","sxyazi","rsplit","ZELLIJ","bitflags","bitflags","USERPROFILE","Neovim","vergen","gitcl","Renderable","preloaders","prec","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub","uzers","scopeguard","SPDLOG","globset","filetime","magick","magick","prefetcher","Prework","prefetchers","PREWORKERS","conds","translit","rxvt","Urxvt","realpath","realname","REPARSE","hardlink","hardlinking","nlink","nlink","linemodes","SIGSTOP","sevenzip","rsplitn","replacen","DECSET","DECRQM"],"language":"en","version":"0.2"}

View file

@ -25,7 +25,7 @@ imagesize = "0.13.0"
kamadak-exif = "0.5.5"
ratatui = "0.27.0"
scopeguard = "1.2.0"
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View file

@ -192,14 +192,17 @@ impl Emulator {
pub async fn read_until_da1() -> Result<String> {
let read = async {
let mut stdin = BufReader::new(tokio::io::stdin());
let mut buf = String::with_capacity(200);
let mut buf: Vec<u8> = Vec::with_capacity(200);
loop {
let mut c = [0; 1];
if stdin.read(&mut c).await? == 0 {
bail!("unexpected EOF");
}
buf.push(c[0] as char);
if c[0] == b'c' && buf.contains("\x1b[?") {
buf.push(c[0]);
if c[0] != b'c' {
continue;
}
if buf.rsplitn(2, |&b| b == b'\xb1').next().is_some_and(|s| s.starts_with(b"\x1b[?")) {
break;
}
}
@ -211,6 +214,6 @@ impl Emulator {
error!("read_until_da1: {e:?}");
}
timeout?
String::from_utf8(timeout??).map_err(Into::into)
}
}

View file

@ -15,12 +15,12 @@ yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
clap = { version = "4.5.9", features = [ "derive" ] }
clap = { version = "4.5.10", features = [ "derive" ] }
serde = { version = "1.0.204", features = [ "derive" ] }
[build-dependencies]
clap = { version = "4.5.9", features = [ "derive" ] }
clap_complete = "4.5.8"
clap = { version = "4.5.10", features = [ "derive" ] }
clap_complete = "4.5.9"
clap_complete_fig = "4.5.1"
clap_complete_nushell = "4.5.2"
vergen-gitcl = { version = "1.0.0", features = [ "build" ] }

View file

@ -15,17 +15,17 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
clap = { version = "4.5.9", features = [ "derive" ] }
clap = { version = "4.5.10", features = [ "derive" ] }
crossterm = "0.27.0"
md-5 = "0.10.6"
serde_json = "1.0.120"
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
toml_edit = "0.22.16"
[build-dependencies]
anyhow = "1.0.86"
clap = { version = "4.5.9", features = [ "derive" ] }
clap_complete = "4.5.8"
clap = { version = "4.5.10", features = [ "derive" ] }
clap_complete = "4.5.9"
clap_complete_fig = "4.5.1"
clap_complete_nushell = "4.5.2"
serde_json = "1.0.120"

View file

@ -5,7 +5,7 @@ use super::{Git, Package};
impl Package {
pub(super) async fn add(&mut self) -> Result<()> {
self.output("Upgrading package `{name}`")?;
self.header("Upgrading package `{name}`")?;
let path = self.local();
if !must_exists(&path).await {

View file

@ -11,7 +11,7 @@ impl Package {
let Some(name) = self.name().map(ToOwned::to_owned) else { bail!("Invalid package url") };
let from = self.local().join(&self.child);
self.output("Deploying package `{name}`")?;
self.header("Deploying package `{name}`")?;
self.is_flavor = maybe_exists(&from.join("flavor.toml")).await;
let to = if self.is_flavor {
Xdg::config_dir().join(format!("flavors/{name}"))

View file

@ -5,7 +5,7 @@ use super::{Git, Package};
impl Package {
pub(super) async fn install(&mut self) -> Result<()> {
self.output("Installing package `{name}`")?;
self.header("Installing package `{name}`")?;
let path = self.local();
if !must_exists(&path).await {

View file

@ -59,7 +59,7 @@ impl Package {
format!("https://github.com/{}.git", self.repo)
}
pub(super) fn output(&self, s: &str) -> Result<()> {
pub(super) fn header(&self, s: &str) -> Result<()> {
use crossterm::style::{Attribute, Print, SetAttributes};
crossterm::execute!(
@ -70,8 +70,7 @@ impl Package {
Print(" "),
Print(s.replacen("{name}", self.name().unwrap_or_default(), 1)),
Print(" "),
SetAttributes(Attribute::NoBold.into()),
SetAttributes(Attribute::NoReverse.into()),
SetAttributes(Attribute::Reset.into()),
Print("\n\n"),
)?;
Ok(())

View file

@ -31,7 +31,7 @@ regex = "1.10.5"
scopeguard = "1.2.0"
serde = "1.0.204"
shell-words = "1.1.0"
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"
unicode-width = "0.1.13"

View file

@ -22,7 +22,7 @@ mlua = { version = "0.9.9", features = [ "lua54" ] }
parking_lot = "0.12.3"
serde = { version = "1.0.204", features = [ "derive" ] }
serde_json = "1.0.120"
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"

View file

@ -32,7 +32,7 @@ mlua = { version = "0.9.9", features = [ "lua54" ] }
ratatui = "0.27.0"
scopeguard = "1.2.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"

View file

@ -1,12 +1,15 @@
use std::{io::{self, stderr, BufWriter, Stderr}, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, Ordering}};
use std::{io::{self, stderr, BufWriter, Stderr}, ops::{Deref, DerefMut}, sync::atomic::{AtomicBool, AtomicU8, Ordering}};
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 cursor::RestoreCursor;
use ratatui::{backend::CrosstermBackend, buffer::Buffer, layout::Rect, CompletedFrame, Frame, Terminal};
use yazi_adapter::Emulator;
use yazi_config::INPUT;
static CSI_U: AtomicBool = AtomicBool::new(false);
static BLINK: AtomicBool = AtomicBool::new(false);
static SHAPE: AtomicU8 = AtomicU8::new(0);
pub(super) struct Term {
inner: Terminal<CrosstermBackend<BufWriter<Stderr>>>,
@ -29,12 +32,25 @@ impl Term {
EnableBracketedPaste,
mouse::SetMouse(true),
SavePosition,
Print("\x1b[?u\x1b[c"),
Print("\x1b[?12$p"), // Request cursor blink status (DECSET)
Print("\x1bP$q q\x1b\\"), // Request cursor shape (DECRQM)
Print("\x1b[?u\x1b[c"), // Request keyboard enhancement flags (CSI u)
RestorePosition
)?;
let resp = futures::executor::block_on(Emulator::read_until_da1());
if resp.is_ok_and(|s| s.contains("\x1b[?0u")) {
if let Ok(s) = futures::executor::block_on(Emulator::read_until_da1()) {
CSI_U.store(s.contains("\x1b[?0u"), Ordering::Relaxed);
BLINK.store(s.contains("\x1b[?12;1$y"), Ordering::Relaxed);
SHAPE.store(
s.split_once("\x1bP1$r")
.and_then(|(_, s)| s.bytes().next())
.filter(|&b| matches!(b, b'0'..=b'6'))
.map_or(0, |b| b - b'0'),
Ordering::Relaxed,
);
}
if CSI_U.load(Ordering::Relaxed) {
queue!(
stderr(),
PushKeyboardEnhancementFlags(
@ -42,7 +58,6 @@ impl Term {
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
)
)?;
CSI_U.store(true, Ordering::Relaxed);
}
term.hide_cursor()?;
@ -61,7 +76,7 @@ impl Term {
mouse::SetMouse(false),
DisableBracketedPaste,
LeaveAlternateScreen,
crossterm::cursor::SetCursorStyle::DefaultUserShape
RestoreCursor,
)?;
self.show_cursor()?;
@ -79,8 +94,8 @@ impl Term {
mouse::SetMouse(false),
DisableBracketedPaste,
LeaveAlternateScreen,
crossterm::cursor::SetCursorStyle::DefaultUserShape,
crossterm::cursor::Show,
RestoreCursor,
crossterm::cursor::Show
)
.ok();
@ -191,3 +206,34 @@ mod mouse {
}
}
}
// --- Cursor shape
mod cursor {
use std::sync::atomic::Ordering;
use crossterm::cursor::SetCursorStyle;
use super::{BLINK, SHAPE};
pub struct RestoreCursor;
impl crossterm::Command for RestoreCursor {
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
let shape = SHAPE.load(Ordering::Relaxed).max(1);
let blink = BLINK.load(Ordering::Relaxed) ^ (shape & 1 == 0);
Ok(match (shape + 1) / 2 {
1 if blink => SetCursorStyle::BlinkingBlock.write_ansi(f)?,
1 if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?,
2 if blink => SetCursorStyle::BlinkingUnderScore.write_ansi(f)?,
2 if !blink => SetCursorStyle::SteadyUnderScore.write_ansi(f)?,
3 if blink => SetCursorStyle::BlinkingBar.write_ansi(f)?,
3 if !blink => SetCursorStyle::SteadyBar.write_ansi(f)?,
_ => tracing::error!("Terminal didn't respond to the cursor shape request: {shape}"),
})
}
#[cfg(windows)]
fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }
}
}

View file

@ -33,7 +33,7 @@ ratatui = "0.27.0"
shell-escape = "0.1.5"
shell-words = "1.1.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"
unicode-width = "0.1.13"

View file

@ -19,4 +19,4 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
mlua = { version = "0.9.9", features = [ "lua54" ] }
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }

View file

@ -21,7 +21,7 @@ async-priority-channel = "0.2.0"
futures = "0.3.30"
parking_lot = "0.12.3"
scopeguard = "1.2.0"
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View file

@ -21,7 +21,7 @@ ratatui = "0.27.0"
regex = "1.10.5"
serde = { version = "1.0.204", features = [ "derive" ] }
shell-words = "1.1.0"
tokio = { version = "1.38.1", features = [ "full" ] }
tokio = { version = "1.39.1", features = [ "full" ] }
[target."cfg(unix)".dependencies]
libc = "0.2.155"