mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: new virtual terminal backend
This commit is contained in:
parent
3f5cc47a48
commit
6a001a431e
143 changed files with 3436 additions and 1594 deletions
697
Cargo.lock
generated
697
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
|
@ -43,14 +43,13 @@ bitflags = { version = "2.11.1", features = [ "serde" ] }
|
|||
chrono = "0.4.44"
|
||||
clap = { version = "4.6.1", features = [ "derive" ] }
|
||||
core-foundation-sys = "0.8.7"
|
||||
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
|
||||
dirs = "6.0.0"
|
||||
dyn-clone = "1.0.20"
|
||||
either = { version = "1.15.0" }
|
||||
either = { version = "1.16.0" }
|
||||
foldhash = "0.2.0"
|
||||
futures = "0.3.32"
|
||||
globset = "0.4.18"
|
||||
hashbrown = { version = "0.17.0", features = [ "serde" ] }
|
||||
hashbrown = { version = "0.17.1", features = [ "serde" ] }
|
||||
indexmap = { version = "2.14.0", features = [ "serde" ] }
|
||||
libc = "0.2.186"
|
||||
lru = "0.18.0"
|
||||
|
|
@ -61,13 +60,13 @@ parking_lot = "0.12.5"
|
|||
paste = "1.0.15"
|
||||
percent-encoding = "2.3.2"
|
||||
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
|
||||
ratatui = { version = "0.30.0", features = [ "serde", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||
ratatui = { version = "0.30.0", default-features = false, features = [ "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||
regex = "1.12.3"
|
||||
russh = { version = "0.60.2", default-features = false, features = [ "ring", "rsa" ] }
|
||||
russh = { version = "0.61.0", default-features = false, features = [ "ring", "rsa" ] }
|
||||
scopeguard = "1.2.0"
|
||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||
serde_json = "1.0.149"
|
||||
serde_with = "3.19.0"
|
||||
serde_with = "3.20.0"
|
||||
strum = { version = "0.28.0", features = [ "derive" ] }
|
||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||
thiserror = "2.0.18"
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","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","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","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","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","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize","TOCTOU","fellback","watchee","Textlike","sstr","pointee"],"version":"0.2","language":"en","flagWords":[]}
|
||||
{"language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","peekable","ratatui","syntect","pbpaste","pbcopy","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","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","Konsole","Überzug","pkgs","pdftoppm","poppler","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","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","repeek","cwds","tcsi","Hyprland","Wayfire","SWAYSOCK","btime","nsec","codegen","gethostname","fchmod","fdfind","Rustc","rustc","ffprobe","vframes","luma","obase","outln","errln","tmtheme","twox","cfgs","fstype","objc","rdev","runloop","exfat","rclone","DECRQSS","DECSCUSR","libvterm","Uninit","lockin","rposition","resvg","foldhash","tilded","futs","chdir","hashbrown","JEMALLOC","RUSTFLAGS","RDONLY","GETPATH","fcntl","casefold","inodes","Splatable","casefied","thiserror","memchr","memmem","russh","deadpool","keepalive","nodelay","publickey","deadpool","initing","treelize","TOCTOU","fellback","watchee","Textlike","sstr","pointee","writef","wakeup","nonblocking","sigwinch","timespec","termios","tcgetattr","tcsetattr","tcgetwinsize","rustix","codepoint","codepoints","Raterm"],"version":"0.2","flagWords":[]}
|
||||
|
|
@ -34,13 +34,13 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
|||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
yazi-tui = { path = "../yazi-tui", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
either = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
|
|
@ -55,6 +55,3 @@ tracing = { workspace = true }
|
|||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use crossterm::event::MouseEventKind;
|
||||
use mlua::{ObjectLike, Table};
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
|
|
@ -8,6 +7,7 @@ use yazi_macro::succ;
|
|||
use yazi_parser::app::MouseForm;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_term::event::MouseEventKind;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
use std::process;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_fs::provider::{Provider, local::Local};
|
||||
use yazi_parser::app::QuitForm;
|
||||
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
|
||||
use yazi_term::Term;
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -27,7 +29,8 @@ impl Actor for Quit {
|
|||
);
|
||||
});
|
||||
|
||||
Term::goodbye(|| opt.code);
|
||||
Raterm::stop();
|
||||
process::exit(opt.code);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::{act, render, succ};
|
||||
use yazi_parser::app::ResumeForm;
|
||||
use yazi_parser::app::ReflowForm;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_term::Term;
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Resume;
|
||||
|
||||
impl Actor for Resume {
|
||||
type Form = ResumeForm;
|
||||
type Form = ReflowForm;
|
||||
|
||||
const NAME: &str = "resume";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
cx.active_mut().preview.reset();
|
||||
*cx.term = Some(Term::start()?);
|
||||
|
||||
drop(cx.term.take());
|
||||
*cx.term = Some(Raterm::start()?);
|
||||
|
||||
// While the app resumes, it's possible that the terminal size has changed.
|
||||
// We need to trigger a resize, and render the UI based on the resized area.
|
||||
act!(app:resize, cx, form.reflow)?;
|
||||
|
||||
form.tx.send((true, form.replier))?;
|
||||
|
||||
act!(app:title, cx).ok();
|
||||
succ!(render!());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use anyhow::Result;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::StopForm;
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -8,20 +8,15 @@ use crate::{Actor, Ctx};
|
|||
pub struct Stop;
|
||||
|
||||
impl Actor for Stop {
|
||||
type Form = StopForm;
|
||||
type Form = VoidForm;
|
||||
|
||||
const NAME: &str = "stop";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
fn act(cx: &mut Ctx, _: Self::Form) -> Result<Data> {
|
||||
cx.active_mut().preview.reset_image();
|
||||
|
||||
// We need to destroy the `term` first before stopping the `signals`
|
||||
// to prevent any signal from triggering the term to render again
|
||||
// while the app is being suspended.
|
||||
*cx.term = None;
|
||||
|
||||
form.tx.send((false, form.replier))?;
|
||||
|
||||
succ!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
use anyhow::Result;
|
||||
use crossterm::{execute, terminal::SetTitle};
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::succ;
|
||||
use yazi_macro::{succ, writef};
|
||||
use yazi_parser::{app::TitleForm, spark::SparkKind};
|
||||
use yazi_shared::{Source, data::Data};
|
||||
use yazi_term::TermState;
|
||||
use yazi_term::sequence::SetTitle;
|
||||
use yazi_tty::TTY;
|
||||
use yazi_tui::RatermState;
|
||||
|
||||
use crate::Actor;
|
||||
|
||||
|
|
@ -18,9 +18,9 @@ impl Actor for Title {
|
|||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let s = form.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
|
||||
execute!(TTY.writer(), SetTitle(&s))?;
|
||||
writef!(TTY.writer(), "{}", SetTitle(&s))?;
|
||||
|
||||
yazi_term::STATE.set(TermState { title: !s.is_empty(), ..yazi_term::STATE.get() });
|
||||
yazi_tui::STATE.set(RatermState { title: !s.is_empty(), ..yazi_tui::STATE.get() });
|
||||
succ!()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ use anyhow::{Result, anyhow};
|
|||
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}};
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, Source, event::Action, url::UrlBuf};
|
||||
use yazi_term::Term;
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
pub core: &'a mut Core,
|
||||
pub term: &'a mut Option<Term>,
|
||||
pub term: &'a mut Option<Raterm>,
|
||||
pub tab: usize,
|
||||
pub level: usize,
|
||||
pub source: Source,
|
||||
|
|
@ -27,7 +27,7 @@ impl DerefMut for Ctx<'_> {
|
|||
}
|
||||
|
||||
impl<'a> Ctx<'a> {
|
||||
pub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Term>) -> Result<Self> {
|
||||
pub fn new(action: &Action, core: &'a mut Core, term: &'a mut Option<Raterm>) -> Result<Self> {
|
||||
let tab = if let Ok(id) = action.get::<Id>("tab") {
|
||||
core
|
||||
.mgr
|
||||
|
|
@ -63,7 +63,7 @@ impl<'a> Ctx<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn active(core: &'a mut Core, term: &'a mut Option<Term>) -> Self {
|
||||
pub fn active(core: &'a mut Core, term: &'a mut Option<Raterm>) -> Self {
|
||||
let tab = core.mgr.tabs.cursor;
|
||||
Self {
|
||||
core,
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ use scopeguard::defer;
|
|||
use yazi_binding::Permit;
|
||||
use yazi_config::{YAZI, opener::OpenerRule};
|
||||
use yazi_fs::{File, FilesOp, Splatter, provider::{Provider, local::Local}};
|
||||
use yazi_macro::succ;
|
||||
use yazi_macro::{succ, writef};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::TasksProxy;
|
||||
use yazi_scheduler::{AppProxy, NotifyProxy};
|
||||
use yazi_shared::{data::Data, strand::Strand, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shared::{data::Data, strand::Strand, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_shim::path::CROSS_SEPARATOR;
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
||||
use yazi_tty::TTY;
|
||||
use yazi_vfs::{VfsFile, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
|
@ -61,7 +61,7 @@ impl Actor for BulkCreate {
|
|||
|
||||
impl BulkCreate {
|
||||
async fn r#do(cwd: UrlBuf, todo: Vec<Entry<'_>>) -> Result<()> {
|
||||
terminal_clear(TTY.writer())?;
|
||||
writef!(TTY.writer(), "{EraseScreen}\n")?;
|
||||
if todo.is_empty() {
|
||||
return Ok(());
|
||||
} else if !Self::ask_continue(&todo, None)? {
|
||||
|
|
@ -134,7 +134,7 @@ impl BulkCreate {
|
|||
|
||||
async fn output_failed(failed: Vec<(Entry<'_>, anyhow::Error)>) -> Result<()> {
|
||||
let mut stdout = TTY.lockout();
|
||||
terminal_clear(&mut *stdout)?;
|
||||
writeln!(stdout, "{EraseScreen}")?;
|
||||
|
||||
writeln!(stdout, "Failed to create:")?;
|
||||
for (entry, err) in failed {
|
||||
|
|
@ -161,7 +161,7 @@ impl<'a> Entry<'a> {
|
|||
None => (s, false),
|
||||
};
|
||||
|
||||
Some(Self { path: path.into(), is_dir }).filter(|_| !path.is_empty())
|
||||
(!path.is_empty()).then_some(Self { path: path.into(), is_dir })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
use std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use crossterm::{execute, style::Print};
|
||||
use hashbrown::HashMap;
|
||||
use scopeguard::defer;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
|
@ -9,12 +8,12 @@ use yazi_binding::Permit;
|
|||
use yazi_config::{YAZI, opener::OpenerRule};
|
||||
use yazi_dds::Pubsub;
|
||||
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
||||
use yazi_macro::{err, succ};
|
||||
use yazi_macro::{err, succ, writef};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_proxy::TasksProxy;
|
||||
use yazi_scheduler::{AppProxy, NotifyProxy};
|
||||
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, terminal_clear, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
||||
use yazi_tty::TTY;
|
||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||
use yazi_watcher::WATCHER;
|
||||
|
|
@ -100,11 +99,10 @@ impl BulkRename {
|
|||
selected: Vec<UrlBuf>,
|
||||
decision: Option<bool>,
|
||||
) -> Result<()> {
|
||||
terminal_clear(TTY.writer())?;
|
||||
writef!(TTY.writer(), "{EraseScreen}\n")?;
|
||||
if old.len() != new.len() {
|
||||
#[rustfmt::skip]
|
||||
let s = format!("Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len());
|
||||
execute!(TTY.writer(), Print(s))?;
|
||||
writef!(TTY.writer(), "Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len())?;
|
||||
|
||||
TTY.reader().read_exact(&mut [0])?;
|
||||
return Ok(());
|
||||
|
|
@ -186,7 +184,7 @@ impl BulkRename {
|
|||
|
||||
async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {
|
||||
let mut stdout = TTY.lockout();
|
||||
terminal_clear(&mut *stdout)?;
|
||||
writeln!(stdout, "{EraseScreen}")?;
|
||||
|
||||
writeln!(stdout, "Failed to rename:")?;
|
||||
for (old, new, err) in failed {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,6 @@ impl Actor for Close {
|
|||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
Some(SparkKind::KeyClose).filter(|_| cx.source().is_key())
|
||||
cx.source().is_key().then_some(SparkKind::KeyClose)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ impl Actor for Quit {
|
|||
}
|
||||
|
||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
||||
Some(SparkKind::KeyQuit).filter(|_| cx.source().is_key())
|
||||
cx.source().is_key().then_some(SparkKind::KeyQuit)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ use std::time::Duration;
|
|||
use anyhow::Result;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_core::notify::Notify;
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_macro::{render, render_partial, succ};
|
||||
use yazi_parser::notify::TickForm;
|
||||
use yazi_proxy::NotifyProxy;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_term::{Dimension, TERM};
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
|
|
@ -21,8 +21,8 @@ impl Actor for Tick {
|
|||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
cx.notify.ticker.take().map(|h| h.abort());
|
||||
|
||||
let Dimension { rows, columns, .. } = Dimension::available();
|
||||
let area = Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });
|
||||
let Dimension { rows, cols, .. } = TERM.dimension();
|
||||
let area = Notify::available(Rect { x: 0, y: 0, width: cols, height: rows });
|
||||
|
||||
let limit = cx.notify.limit(area);
|
||||
if limit == 0 {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use std::io::Write;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
use scopeguard::defer;
|
||||
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
||||
use yazi_binding::Permit;
|
||||
use yazi_macro::succ;
|
||||
use yazi_term::TERM;
|
||||
use yazi_macro::{succ, writef};
|
||||
use yazi_parser::VoidForm;
|
||||
use yazi_scheduler::AppProxy;
|
||||
use yazi_shared::{data::Data, terminal_clear};
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
use yazi_shared::data::Data;
|
||||
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
||||
use yazi_tty::TTY;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
|
@ -39,24 +39,27 @@ impl Actor for Inspect {
|
|||
task.logs.clone()
|
||||
};
|
||||
|
||||
// Stop the app and clear the terminal
|
||||
AppProxy::stop().await;
|
||||
terminal_clear(TTY.writer()).ok();
|
||||
writeln!(TTY.writer(), "{EraseScreen}").ok();
|
||||
|
||||
// Print the buffered logs
|
||||
TTY.writer().write_all(buffered.as_bytes()).ok();
|
||||
TTY.writer().flush().ok();
|
||||
|
||||
defer! { disable_raw_mode().ok(); }
|
||||
enable_raw_mode().ok();
|
||||
defer! { TERM.enter_cooked_mode().ok(); }
|
||||
TERM.enter_raw_mode().ok();
|
||||
|
||||
let mut stdin = stdin(); // TODO: stdin
|
||||
let mut answer = 0;
|
||||
loop {
|
||||
select! {
|
||||
Some(line) = rx.recv() => {
|
||||
execute!(TTY.writer(), crossterm::style::Print(line), crossterm::style::Print("\r\n")).ok();
|
||||
writef!(TTY.writer(), "{line}\r\n").ok();
|
||||
}
|
||||
_ = time::sleep(time::Duration::from_millis(500)) => {
|
||||
if !ongoing.lock().exists(id) {
|
||||
execute!(TTY.writer(), crossterm::style::Print("Task finished, press `q` to quit\r\n")).ok();
|
||||
writef!(TTY.writer(), "Task finished, press `q` to quit\r\n").ok();
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,21 +19,18 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
|||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
image = { version = "0.25.10", default-features = false, features = [ "avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp" ] }
|
||||
moxcms = "0.8.1"
|
||||
palette = { version = "0.7.6", default-features = false }
|
||||
quantette = { version = "0.5.1", default-features = false }
|
||||
quantette = { version = "0.6.0", default-features = false }
|
||||
ratatui = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -2,10 +2,10 @@ use std::{io::Write, path::PathBuf, process::Stdio};
|
|||
|
||||
use ansi_to_tui::IntoText;
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
use crossterm::{cursor::MoveTo, queue};
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::process::Command;
|
||||
use yazi_emulator::Emulator;
|
||||
use yazi_term::sequence::MoveTo;
|
||||
|
||||
use crate::Adapter;
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ impl Chafa {
|
|||
Emulator::move_lock((max.x, max.y), |w| {
|
||||
for (i, line) in lines.into_iter().enumerate() {
|
||||
w.write_all(line)?;
|
||||
queue!(w, MoveTo(max.x, max.y + i as u16 + 1))?;
|
||||
write!(w, "{}", MoveTo(max.x, max.y + i as u16 + 1))?;
|
||||
}
|
||||
Ok(area)
|
||||
})
|
||||
|
|
@ -72,7 +72,7 @@ impl Chafa {
|
|||
let s = " ".repeat(area.width as usize);
|
||||
Emulator::move_lock((0, 0), |w| {
|
||||
for y in area.top()..area.bottom() {
|
||||
queue!(w, MoveTo(area.x, y))?;
|
||||
write!(w, "{}", MoveTo(area.x, y))?;
|
||||
write!(w, "{s}")?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ use std::{fmt::Write, io::Write as ioWrite, path::PathBuf};
|
|||
|
||||
use anyhow::Result;
|
||||
use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
|
||||
use crossterm::{cursor::MoveTo, queue};
|
||||
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::YAZI;
|
||||
use yazi_emulator::{CLOSE, Emulator, START};
|
||||
use yazi_term::sequence::MoveTo;
|
||||
|
||||
use crate::{Image, adapter::Adapter};
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ impl Iip {
|
|||
let s = " ".repeat(area.width as usize);
|
||||
Emulator::move_lock((0, 0), |w| {
|
||||
for y in area.top()..area.bottom() {
|
||||
queue!(w, MoveTo(area.x, y))?;
|
||||
write!(w, "{}", MoveTo(area.x, y))?;
|
||||
write!(w, "{s}")?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ use std::{io::Write, path::PathBuf};
|
|||
|
||||
use anyhow::Result;
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use crossterm::{cursor::MoveTo, queue};
|
||||
use image::DynamicImage;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::{layout::Rect, style::Color};
|
||||
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
|
||||
use yazi_shim::cell::SyncCell;
|
||||
use yazi_term::sequence::{MoveTo, SetFg};
|
||||
|
||||
use crate::{adapter::Adapter, image::Image};
|
||||
|
||||
|
|
@ -334,7 +334,7 @@ impl Kgp {
|
|||
let s = " ".repeat(area.width as usize);
|
||||
Emulator::move_lock((0, 0), |w| {
|
||||
for y in area.top()..area.bottom() {
|
||||
queue!(w, MoveTo(area.x, y))?;
|
||||
write!(w, "{}", MoveTo(area.x, y))?;
|
||||
write!(w, "{s}")?;
|
||||
}
|
||||
|
||||
|
|
@ -385,10 +385,10 @@ impl Kgp {
|
|||
|
||||
let id = Self::image_id();
|
||||
let (r, g, b) = ((id >> 16) & 0xff, (id >> 8) & 0xff, id & 0xff);
|
||||
write!(buf, "\x1b[38;2;{r};{g};{b}m")?;
|
||||
write!(buf, "{}", SetFg(Color::Rgb(r as u8, g as u8, b as u8)))?;
|
||||
|
||||
for y in 0..area.height {
|
||||
write!(buf, "\x1b[{};{}H", area.y + y + 1, area.x + 1)?;
|
||||
write!(buf, "{}", MoveTo(area.x, area.y + y))?;
|
||||
for x in 0..area.width {
|
||||
write!(buf, "\u{10EEEE}")?;
|
||||
write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{io::Write, path::PathBuf};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use crossterm::{cursor::MoveTo, queue};
|
||||
use image::{DynamicImage, GenericImageView, RgbImage};
|
||||
use palette::{Srgb, cast::ComponentsAs};
|
||||
use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
|
||||
use yazi_term::sequence::MoveTo;
|
||||
|
||||
use crate::{Image, adapter::Adapter};
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ impl Sixel {
|
|||
let s = " ".repeat(area.width as usize);
|
||||
Emulator::move_lock((0, 0), |w| {
|
||||
for y in area.top()..area.bottom() {
|
||||
queue!(w, MoveTo(area.x, y))?;
|
||||
write!(w, "{}", MoveTo(area.x, y))?;
|
||||
write!(w, "{s}")?;
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -24,13 +24,13 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
|||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
|
|
@ -45,8 +45,5 @@ unicode-width = { workspace = true }
|
|||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
||||
[target."cfg(windows)".dependencies]
|
||||
windows-sys = { version = "0.61.2", features = [ "Win32_Foundation", "Win32_System_JobObjects" ] }
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use crossterm::event::MouseButton;
|
||||
use mlua::{UserData, UserDataFields};
|
||||
use yazi_term::event::{MouseButton, MouseEventKind};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MouseEvent(crossterm::event::MouseEvent);
|
||||
pub struct MouseEvent(yazi_term::event::MouseEvent);
|
||||
|
||||
impl Deref for MouseEvent {
|
||||
type Target = crossterm::event::MouseEvent;
|
||||
type Target = yazi_term::event::MouseEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl From<crossterm::event::MouseEvent> for MouseEvent {
|
||||
fn from(event: crossterm::event::MouseEvent) -> Self { Self(event) }
|
||||
impl From<yazi_term::event::MouseEvent> for MouseEvent {
|
||||
fn from(event: yazi_term::event::MouseEvent) -> Self { Self(event) }
|
||||
}
|
||||
|
||||
impl UserData for MouseEvent {
|
||||
|
|
@ -21,15 +21,15 @@ impl UserData for MouseEvent {
|
|||
fields.add_field_method_get("x", |_, me| Ok(me.column));
|
||||
fields.add_field_method_get("y", |_, me| Ok(me.row));
|
||||
fields.add_field_method_get("is_left", |_, me| {
|
||||
use crossterm::event::MouseEventKind as K;
|
||||
use MouseEventKind as K;
|
||||
Ok(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Left))
|
||||
});
|
||||
fields.add_field_method_get("is_right", |_, me| {
|
||||
use crossterm::event::MouseEventKind as K;
|
||||
use MouseEventKind as K;
|
||||
Ok(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Right))
|
||||
});
|
||||
fields.add_field_method_get("is_middle", |_, me| {
|
||||
use crossterm::event::MouseEventKind as K;
|
||||
use MouseEventKind as K;
|
||||
Ok(matches!(me.kind, K::Down(b) | K::Up(b) | K::Drag(b) if b == MouseButton::Middle))
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
|||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
|
|
@ -33,7 +34,7 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
|||
|
||||
# External dependencies
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.6.3"
|
||||
clap_complete = "4.6.5"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.6.0"
|
||||
vergen-gitcl = { version = "9.1.0", features = [ "build", "rustc" ] }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use yazi_config::{THEME, YAZI};
|
|||
use yazi_emulator::Mux;
|
||||
use yazi_fs::Xdg;
|
||||
use yazi_shared::timestamp_us;
|
||||
use yazi_term::TERM;
|
||||
|
||||
use super::Actions;
|
||||
|
||||
|
|
@ -39,7 +40,7 @@ impl Actions {
|
|||
|
||||
writeln!(s, "\nAdapter")?;
|
||||
writeln!(s, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?;
|
||||
writeln!(s, " Dimension.available: {:?}", yazi_emulator::Dimension::available())?;
|
||||
writeln!(s, " Dimension.available: {:?}", TERM.dimension())?;
|
||||
|
||||
writeln!(s, "\nDesktop")?;
|
||||
writeln!(s, " XDG_SESSION_TYPE : {:?}", env::var_os("XDG_SESSION_TYPE"))?;
|
||||
|
|
|
|||
|
|
@ -18,12 +18,11 @@ yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
|
|||
yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
|
|
@ -37,16 +36,13 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
|||
# External build dependencies
|
||||
anyhow = { workspace = true }
|
||||
clap = { workspace = true }
|
||||
clap_complete = "4.6.3"
|
||||
clap_complete = "4.6.5"
|
||||
clap_complete_fig = "4.5.2"
|
||||
clap_complete_nushell = "4.6.0"
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
vergen-gitcl = { version = "9.1.0", features = [ "build" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
||||
[[bin]]
|
||||
name = "ya"
|
||||
path = "src/main.rs"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{env, io::{self, BufWriter}, path::{Path, PathBuf}, str::FromStr};
|
||||
use std::{env, io, path::{Path, PathBuf}, str::FromStr};
|
||||
|
||||
use anyhow::{Result, bail};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
|
@ -48,20 +48,19 @@ impl Dependency {
|
|||
pub(super) fn header(&self, s: &str) -> Result<()> {
|
||||
use std::io::IsTerminal;
|
||||
|
||||
use crossterm::style::{Attribute, Print, SetAttributes};
|
||||
use yazi_shim::crossterm::If;
|
||||
use yazi_macro::writef;
|
||||
use yazi_term::sequence::{If, SetSgr};
|
||||
|
||||
let ansi = env::var_os("YA_FORCE_ANSI").is_some_and(|v| v == "1") || io::stdout().is_terminal();
|
||||
crossterm::execute!(
|
||||
BufWriter::new(io::stdout()),
|
||||
Print("\n"),
|
||||
If(ansi, SetAttributes(Attribute::Reverse.into())),
|
||||
If(ansi, SetAttributes(Attribute::Bold.into())),
|
||||
Print(" "),
|
||||
Print(s.replacen("{name}", &self.name, 1)),
|
||||
Print(" "),
|
||||
If(ansi, SetAttributes(Attribute::Reset.into())),
|
||||
Print("\n\n"),
|
||||
writef!(
|
||||
io::stdout(),
|
||||
"\n{}{}{} {} {}{}\n\n",
|
||||
If(ansi, SetSgr::Reverse),
|
||||
If(ansi, SetSgr::Bold),
|
||||
" ",
|
||||
s.replacen("{name}", &self.name, 1),
|
||||
" ",
|
||||
If(ansi, SetSgr::Reset),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,13 +18,13 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
|||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
globset = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
|
|
@ -36,6 +36,3 @@ strum = { workspace = true }
|
|||
tokio = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::{fmt::{Display, Write}, str::FromStr};
|
||||
|
||||
use anyhow::bail;
|
||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||
use yazi_term::event::{KeyCode, KeyEvent, Modifiers};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct Key {
|
||||
|
|
@ -30,29 +30,12 @@ impl Default for Key {
|
|||
|
||||
impl From<KeyEvent> for Key {
|
||||
fn from(value: KeyEvent) -> Self {
|
||||
// For alphabet:
|
||||
// Unix : <S-a> => Char("A") + SHIFT
|
||||
// Windows : <S-a> => Char("A") + SHIFT
|
||||
//
|
||||
// For non-alphabet:
|
||||
// Unix : <S-`> => Char("~") + NULL
|
||||
// Windows : <S-`> => Char("~") + SHIFT
|
||||
//
|
||||
// So we detect `Char("~") + SHIFT`, and change it to `Char("~") + NULL`
|
||||
// for consistent behavior between OSs.
|
||||
|
||||
let shift = match (value.code, value.modifiers) {
|
||||
(KeyCode::Char(c), _) => c.is_ascii_uppercase(),
|
||||
(KeyCode::BackTab, _) => false,
|
||||
(_, m) => m.contains(KeyModifiers::SHIFT),
|
||||
};
|
||||
|
||||
Self {
|
||||
code: value.code,
|
||||
shift,
|
||||
ctrl: value.modifiers.contains(KeyModifiers::CONTROL),
|
||||
alt: value.modifiers.contains(KeyModifiers::ALT),
|
||||
super_: value.modifiers.contains(KeyModifiers::SUPER),
|
||||
code: value.code,
|
||||
shift: value.modifiers.contains(Modifiers::SHIFT),
|
||||
ctrl: value.modifiers.contains(Modifiers::CONTROL),
|
||||
alt: value.modifiers.contains(Modifiers::ALT),
|
||||
super_: value.modifiers.contains(Modifiers::SUPER),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -92,29 +75,28 @@ impl FromStr for Key {
|
|||
"pageup" => key.code = KeyCode::PageUp,
|
||||
"pagedown" => key.code = KeyCode::PageDown,
|
||||
"tab" => key.code = KeyCode::Tab,
|
||||
"backtab" => key.code = KeyCode::BackTab,
|
||||
"delete" => key.code = KeyCode::Delete,
|
||||
"insert" => key.code = KeyCode::Insert,
|
||||
"f1" => key.code = KeyCode::F(1),
|
||||
"f2" => key.code = KeyCode::F(2),
|
||||
"f3" => key.code = KeyCode::F(3),
|
||||
"f4" => key.code = KeyCode::F(4),
|
||||
"f5" => key.code = KeyCode::F(5),
|
||||
"f6" => key.code = KeyCode::F(6),
|
||||
"f7" => key.code = KeyCode::F(7),
|
||||
"f8" => key.code = KeyCode::F(8),
|
||||
"f9" => key.code = KeyCode::F(9),
|
||||
"f10" => key.code = KeyCode::F(10),
|
||||
"f11" => key.code = KeyCode::F(11),
|
||||
"f12" => key.code = KeyCode::F(12),
|
||||
"f13" => key.code = KeyCode::F(13),
|
||||
"f14" => key.code = KeyCode::F(14),
|
||||
"f15" => key.code = KeyCode::F(15),
|
||||
"f16" => key.code = KeyCode::F(16),
|
||||
"f17" => key.code = KeyCode::F(17),
|
||||
"f18" => key.code = KeyCode::F(18),
|
||||
"f19" => key.code = KeyCode::F(19),
|
||||
"esc" => key.code = KeyCode::Esc,
|
||||
"f1" => key.code = KeyCode::Fn(1),
|
||||
"f2" => key.code = KeyCode::Fn(2),
|
||||
"f3" => key.code = KeyCode::Fn(3),
|
||||
"f4" => key.code = KeyCode::Fn(4),
|
||||
"f5" => key.code = KeyCode::Fn(5),
|
||||
"f6" => key.code = KeyCode::Fn(6),
|
||||
"f7" => key.code = KeyCode::Fn(7),
|
||||
"f8" => key.code = KeyCode::Fn(8),
|
||||
"f9" => key.code = KeyCode::Fn(9),
|
||||
"f10" => key.code = KeyCode::Fn(10),
|
||||
"f11" => key.code = KeyCode::Fn(11),
|
||||
"f12" => key.code = KeyCode::Fn(12),
|
||||
"f13" => key.code = KeyCode::Fn(13),
|
||||
"f14" => key.code = KeyCode::Fn(14),
|
||||
"f15" => key.code = KeyCode::Fn(15),
|
||||
"f16" => key.code = KeyCode::Fn(16),
|
||||
"f17" => key.code = KeyCode::Fn(17),
|
||||
"f18" => key.code = KeyCode::Fn(18),
|
||||
"f19" => key.code = KeyCode::Fn(19),
|
||||
"esc" => key.code = KeyCode::Escape,
|
||||
|
||||
_ => match next {
|
||||
s if it.peek().is_none() => {
|
||||
|
|
@ -166,29 +148,28 @@ impl Display for Key {
|
|||
KeyCode::PageUp => "PageUp",
|
||||
KeyCode::PageDown => "PageDown",
|
||||
KeyCode::Tab => "Tab",
|
||||
KeyCode::BackTab => "BackTab",
|
||||
KeyCode::Delete => "Delete",
|
||||
KeyCode::Insert => "Insert",
|
||||
KeyCode::F(1) => "F1",
|
||||
KeyCode::F(2) => "F2",
|
||||
KeyCode::F(3) => "F3",
|
||||
KeyCode::F(4) => "F4",
|
||||
KeyCode::F(5) => "F5",
|
||||
KeyCode::F(6) => "F6",
|
||||
KeyCode::F(7) => "F7",
|
||||
KeyCode::F(8) => "F8",
|
||||
KeyCode::F(9) => "F9",
|
||||
KeyCode::F(10) => "F10",
|
||||
KeyCode::F(11) => "F11",
|
||||
KeyCode::F(12) => "F12",
|
||||
KeyCode::F(13) => "F13",
|
||||
KeyCode::F(14) => "F14",
|
||||
KeyCode::F(15) => "F15",
|
||||
KeyCode::F(16) => "F16",
|
||||
KeyCode::F(17) => "F17",
|
||||
KeyCode::F(18) => "F18",
|
||||
KeyCode::F(19) => "F19",
|
||||
KeyCode::Esc => "Esc",
|
||||
KeyCode::Fn(1) => "F1",
|
||||
KeyCode::Fn(2) => "F2",
|
||||
KeyCode::Fn(3) => "F3",
|
||||
KeyCode::Fn(4) => "F4",
|
||||
KeyCode::Fn(5) => "F5",
|
||||
KeyCode::Fn(6) => "F6",
|
||||
KeyCode::Fn(7) => "F7",
|
||||
KeyCode::Fn(8) => "F8",
|
||||
KeyCode::Fn(9) => "F9",
|
||||
KeyCode::Fn(10) => "F10",
|
||||
KeyCode::Fn(11) => "F11",
|
||||
KeyCode::Fn(12) => "F12",
|
||||
KeyCode::Fn(13) => "F13",
|
||||
KeyCode::Fn(14) => "F14",
|
||||
KeyCode::Fn(15) => "F15",
|
||||
KeyCode::Fn(16) => "F16",
|
||||
KeyCode::Fn(17) => "F17",
|
||||
KeyCode::Fn(18) => "F18",
|
||||
KeyCode::Fn(19) => "F19",
|
||||
KeyCode::Escape => "Esc",
|
||||
|
||||
KeyCode::Char(' ') => "Space",
|
||||
KeyCode::Char(c) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ yazi_macro::mod_flat!(icon layout mixing pattern platform preset priority select
|
|||
use std::io::{Read, Write};
|
||||
|
||||
use yazi_shim::{cell::{RoCell, SyncCell}, toml::{DeserializeOver, DeserializeOverWith}};
|
||||
use yazi_term::sequence::SetSgr;
|
||||
use yazi_tty::TTY;
|
||||
|
||||
pub static YAZI: RoCell<yazi::Yazi> = RoCell::new();
|
||||
|
|
@ -74,14 +75,12 @@ fn wait_for_key(e: anyhow::Error) -> anyhow::Result<()> {
|
|||
writeln!(stdout, "\nCaused by:\n{src}")?;
|
||||
}
|
||||
|
||||
use crossterm::style::{Attribute, Print, SetAttributes};
|
||||
crossterm::execute!(
|
||||
writeln!(
|
||||
stdout,
|
||||
SetAttributes(Attribute::Reverse.into()),
|
||||
SetAttributes(Attribute::Bold.into()),
|
||||
Print("Press <Enter> to continue with preset settings..."),
|
||||
SetAttributes(Attribute::Reset.into()),
|
||||
Print("\n"),
|
||||
"{}{}Press <Enter> to continue with preset settings...{}",
|
||||
SetSgr::Reverse,
|
||||
SetSgr::Bold,
|
||||
SetSgr::Reset
|
||||
)?;
|
||||
|
||||
TTY.reader().read_exact(&mut [0])?;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use anyhow::{Result, bail};
|
||||
use bitflags::bitflags;
|
||||
use crossterm::event::MouseEventKind;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use yazi_term::event::MouseEventKind;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
||||
|
|
@ -45,8 +45,8 @@ impl From<MouseEvents> for Vec<String> {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<crossterm::event::MouseEventKind> for MouseEvents {
|
||||
fn from(value: crossterm::event::MouseEventKind) -> Self {
|
||||
impl From<MouseEventKind> for MouseEvents {
|
||||
fn from(value: MouseEventKind) -> Self {
|
||||
match value {
|
||||
MouseEventKind::Down(_) | MouseEventKind::Up(_) => Self::CLICK,
|
||||
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => Self::SCROLL,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crossterm::terminal::WindowSize;
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_term::Dimension;
|
||||
|
||||
use super::{Offset, Origin};
|
||||
|
||||
|
|
@ -12,15 +12,15 @@ pub struct Position {
|
|||
impl Position {
|
||||
pub const fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
|
||||
|
||||
pub fn rect(&self, WindowSize { columns, rows, .. }: WindowSize) -> Rect {
|
||||
pub fn rect(&self, Dimension { cols, rows, .. }: Dimension) -> Rect {
|
||||
use Origin::*;
|
||||
let Offset { x, y, width, height } = self.offset;
|
||||
|
||||
let max_x = columns.saturating_sub(width);
|
||||
let max_x = cols.saturating_sub(width);
|
||||
let new_x = match self.origin {
|
||||
TopLeft | BottomLeft => x.clamp(0, max_x as i16) as u16,
|
||||
TopCenter | BottomCenter | Center => {
|
||||
(columns / 2).saturating_sub(width / 2).saturating_add_signed(x).clamp(0, max_x)
|
||||
(cols / 2).saturating_sub(width / 2).saturating_add_signed(x).clamp(0, max_x)
|
||||
}
|
||||
TopRight | BottomRight => max_x.saturating_add_signed(x).clamp(0, max_x),
|
||||
Hovered => unreachable!(),
|
||||
|
|
@ -37,18 +37,18 @@ impl Position {
|
|||
Rect {
|
||||
x: new_x,
|
||||
y: new_y,
|
||||
width: width.min(columns.saturating_sub(new_x)),
|
||||
width: width.min(cols.saturating_sub(new_x)),
|
||||
height: height.min(rows.saturating_sub(new_y)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sticky(WindowSize { columns, rows, .. }: WindowSize, base: Rect, offset: Offset) -> Rect {
|
||||
pub fn sticky(Dimension { cols, rows, .. }: Dimension, base: Rect, offset: Offset) -> Rect {
|
||||
let Offset { x, y, width, height } = offset;
|
||||
|
||||
let above =
|
||||
base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows;
|
||||
|
||||
let new_x = base.x.saturating_add_signed(x).clamp(0, columns.saturating_sub(width));
|
||||
let new_x = base.x.saturating_add_signed(x).clamp(0, cols.saturating_sub(width));
|
||||
let new_y = if above {
|
||||
base.y.saturating_sub(height.saturating_sub(y.unsigned_abs()))
|
||||
} else {
|
||||
|
|
@ -58,7 +58,7 @@ impl Position {
|
|||
Rect {
|
||||
x: new_x,
|
||||
y: new_y,
|
||||
width: width.min(columns.saturating_sub(new_x)),
|
||||
width: width.min(cols.saturating_sub(new_x)),
|
||||
height: height.min(rows.saturating_sub(new_y)),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,20 +21,19 @@ yazi-adapter = { path = "../yazi-adapter", version = "26.5.6" }
|
|||
yazi-binding = { path = "../yazi-binding", version = "26.5.6" }
|
||||
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
|
||||
yazi-emulator = { path = "../yazi-emulator", version = "26.5.6" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-runner = { path = "../yazi-runner", version = "26.5.6" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
dyn-clone = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
indexmap = { workspace = true }
|
||||
|
|
@ -51,6 +50,3 @@ tokio-util = { workspace = true }
|
|||
tracing = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
yazi-prebuilt = "0.1.0"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ impl Clone for Box<dyn PluginCallback> {
|
|||
fn clone(&self) -> Self { dyn_clone::clone_box(&**self) }
|
||||
}
|
||||
|
||||
impl fmt::Debug for dyn PluginCallback {
|
||||
impl Debug for dyn PluginCallback {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PluginCallback").finish_non_exhaustive()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use crossterm::cursor::SetCursorStyle;
|
||||
use ratatui::layout::{Position, Rect};
|
||||
use yazi_shared::Layer;
|
||||
use yazi_term::CursorStyle;
|
||||
|
||||
use crate::{cmp::Cmp, confirm::Confirm, help::Help, input::Input, mgr::Mgr, notify::Notify, pick::Pick, tab::{Folder, Tab}, tasks::Tasks, which::Which};
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl Core {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn cursor(&self) -> Option<(Position, SetCursorStyle)> {
|
||||
pub fn cursor(&self) -> Option<(Position, CursorStyle)> {
|
||||
if self.input.visible {
|
||||
let Rect { x, y, .. } = self.mgr.area(self.input.position);
|
||||
return Some((
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
use anyhow::Result;
|
||||
use crossterm::{cursor::SetCursorStyle, event::KeyCode};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_macro::{act, render, render_and};
|
||||
use yazi_shared::Layer;
|
||||
use yazi_term::{CursorStyle, TERM, event::KeyCode};
|
||||
use yazi_widgets::Scrollable;
|
||||
|
||||
use crate::help::HELP_MARGIN;
|
||||
|
|
@ -27,7 +26,7 @@ impl Help {
|
|||
pub fn r#type(&mut self, key: &Key) -> Result<bool> {
|
||||
let Some(input) = &mut self.in_filter else { return Ok(false) };
|
||||
match key {
|
||||
Key { code: KeyCode::Esc, shift: false, ctrl: false, alt: false, super_: false } => {
|
||||
Key { code: KeyCode::Escape, shift: false, ctrl: false, alt: false, super_: false } => {
|
||||
self.in_filter = None;
|
||||
render!();
|
||||
}
|
||||
|
|
@ -85,26 +84,22 @@ impl Help {
|
|||
return None;
|
||||
}
|
||||
if let Some(kw) = self.keyword() {
|
||||
return Some((kw.width() as u16, Dimension::available().rows));
|
||||
return Some((kw.width() as u16, TERM.dimension().rows));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
|
||||
|
||||
pub fn cursor_shape(&self) -> SetCursorStyle {
|
||||
if YAZI.input.cursor_blink {
|
||||
SetCursorStyle::BlinkingBlock
|
||||
} else {
|
||||
SetCursorStyle::SteadyBlock
|
||||
}
|
||||
pub fn cursor_shape(&self) -> CursorStyle {
|
||||
if YAZI.input.cursor_blink { CursorStyle::BlinkingBlock } else { CursorStyle::SteadyBlock }
|
||||
}
|
||||
}
|
||||
|
||||
impl Scrollable for Help {
|
||||
fn total(&self) -> usize { self.bindings.len() }
|
||||
|
||||
fn limit(&self) -> usize { Dimension::available().rows.saturating_sub(HELP_MARGIN) as usize }
|
||||
fn limit(&self) -> usize { TERM.dimension().rows.saturating_sub(HELP_MARGIN) as usize }
|
||||
|
||||
fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ use std::iter;
|
|||
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_config::popup::{Origin, Position};
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_fs::Splatable;
|
||||
use yazi_shared::url::{AsUrl, Url, UrlBuf};
|
||||
use yazi_term::TERM;
|
||||
use yazi_watcher::Watcher;
|
||||
|
||||
use super::{Batcher, Mimetype, Tabs, Yanked};
|
||||
|
|
@ -35,7 +35,7 @@ impl Mgr {
|
|||
if pos.origin == Origin::Hovered {
|
||||
self.active().hovered_rect_based(pos)
|
||||
} else {
|
||||
pos.rect(Dimension::available().into())
|
||||
pos.rect(TERM.dimension())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ use anyhow::Result;
|
|||
use ratatui::layout::Rect;
|
||||
use tokio::task::JoinHandle;
|
||||
use yazi_config::{LAYOUT, popup::{Origin, Position}};
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_fs::File;
|
||||
use yazi_shared::{Id, Ids, url::{UrlBuf, UrlLike}};
|
||||
use yazi_term::TERM;
|
||||
|
||||
use super::{Backstack, Finder, Folder, History, Mode, Preference, Preview};
|
||||
use crate::{spot::Spot, tab::Selected};
|
||||
|
|
@ -90,11 +90,11 @@ impl Tab {
|
|||
}
|
||||
|
||||
pub fn hovered_rect_based(&self, pos: Position) -> Rect {
|
||||
let ws = Dimension::available().into();
|
||||
let dim = TERM.dimension();
|
||||
if let Some(r) = self.hovered_rect() {
|
||||
Position::sticky(ws, r, pos.offset)
|
||||
Position::sticky(dim, r, pos.offset)
|
||||
} else {
|
||||
Position::new(Origin::TopCenter, pos.offset).rect(ws)
|
||||
Position::new(Origin::TopCenter, pos.offset).rect(dim)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use tokio::{task::JoinHandle, time::sleep};
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_scheduler::{Scheduler, TaskSnap, TaskSummary};
|
||||
use yazi_term::TERM;
|
||||
|
||||
use super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT};
|
||||
use crate::AppProxy;
|
||||
|
|
@ -52,8 +52,8 @@ impl Tasks {
|
|||
}
|
||||
|
||||
pub fn limit() -> usize {
|
||||
((Dimension::available().rows * TASKS_PERCENT / 100)
|
||||
.saturating_sub(TASKS_BORDER + TASKS_PADDING) as usize)
|
||||
((TERM.dimension().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_BORDER + TASKS_PADDING)
|
||||
as usize)
|
||||
/ 3
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ impl IntoLua for EmberCd<'_> {
|
|||
lua
|
||||
.create_table_from([
|
||||
("tab", self.tab.get().into_lua(lua)?),
|
||||
("url", Some(self.url).filter(|_| !self.dummy).map(yazi_binding::Url::new).into_lua(lua)?),
|
||||
("url", (!self.dummy).then_some(self.url).map(yazi_binding::Url::new).into_lua(lua)?),
|
||||
])?
|
||||
.into_lua(lua)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,15 +16,13 @@ workspace = true
|
|||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
either = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
anyhow = { workspace = true }
|
||||
either = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
scopeguard = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,59 +1,16 @@
|
|||
use std::mem;
|
||||
|
||||
use crossterm::terminal::WindowSize;
|
||||
use yazi_term::TERM;
|
||||
|
||||
use crate::EMULATOR;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Dimension {
|
||||
pub rows: u16,
|
||||
pub columns: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
impl From<WindowSize> for Dimension {
|
||||
fn from(s: WindowSize) -> Self {
|
||||
Self { rows: s.rows, columns: s.columns, width: s.width, height: s.height }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Dimension> for WindowSize {
|
||||
fn from(d: Dimension) -> Self {
|
||||
Self { rows: d.rows, columns: d.columns, width: d.width, height: d.height }
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Dimension;
|
||||
|
||||
impl Dimension {
|
||||
pub fn available() -> Self {
|
||||
let mut size = WindowSize { rows: 0, columns: 0, width: 0, height: 0 };
|
||||
if let Ok(s) = crossterm::terminal::window_size() {
|
||||
_ = mem::replace(&mut size, s);
|
||||
}
|
||||
|
||||
if (size.columns == 0 || size.rows == 0)
|
||||
&& let Ok((cols, rows)) = crossterm::terminal::size()
|
||||
{
|
||||
size.columns = cols;
|
||||
size.rows = rows;
|
||||
}
|
||||
|
||||
size.into()
|
||||
}
|
||||
|
||||
pub fn ratio(self) -> Option<(f64, f64)> {
|
||||
if self.rows == 0 || self.columns == 0 || self.width == 0 || self.height == 0 {
|
||||
None
|
||||
} else {
|
||||
Some((self.width as f64 / self.columns as f64, self.height as f64 / self.rows as f64))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cell_size() -> Option<(f64, f64)> {
|
||||
let emu = &*EMULATOR;
|
||||
Some(if emu.force_16t {
|
||||
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
|
||||
} else if let Some(r) = Self::available().ratio() {
|
||||
} else if let Some(r) = TERM.dimension().ratio() {
|
||||
r
|
||||
} else if emu.csi_16t.0 != 0 && emu.csi_16t.1 != 0 {
|
||||
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
use std::{io::BufWriter, time::Duration};
|
||||
use std::{io::{self, BufWriter, Write}, time::Duration};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
|
||||
use either::Either;
|
||||
use ratatui::style::Color;
|
||||
use scopeguard::defer;
|
||||
use tokio::time::sleep;
|
||||
use tracing::{debug, error, warn};
|
||||
use yazi_macro::writef;
|
||||
use yazi_shim::cell::RoCell;
|
||||
use yazi_term::{TERM, sequence::{HideCursor, If, KittyGraphicsQuery, MoveTo, RequestBgColor, RequestCellPixelSize, RequestDA1, RequestXtVersion, RestoreCursorPos, SaveCursorPos, SetFg, SetSgr, ShowCursor}};
|
||||
use yazi_tty::{Handle, TTY};
|
||||
|
||||
use crate::{Brand, Dimension, Mux, TMUX, Unknown};
|
||||
use crate::{Brand, Mux, TMUX, Unknown};
|
||||
|
||||
pub static EMULATOR: RoCell<Emulator> = RoCell::new();
|
||||
|
||||
|
|
@ -36,25 +38,18 @@ impl Default for Emulator {
|
|||
|
||||
impl Emulator {
|
||||
pub fn detect() -> Result<Self> {
|
||||
defer! { disable_raw_mode().ok(); }
|
||||
enable_raw_mode()?;
|
||||
defer! { TERM.enter_cooked_mode().ok(); }
|
||||
TERM.enter_raw_mode()?;
|
||||
|
||||
let resort = Brand::from_env();
|
||||
let kgp_seq = if resort.is_none() {
|
||||
Mux::csi("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\")
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
|
||||
execute!(
|
||||
writef!(
|
||||
TTY.writer(),
|
||||
SavePosition,
|
||||
Print(kgp_seq), // Detect KGP
|
||||
Print(Mux::csi("\x1b[>q")), // Request terminal version
|
||||
Print("\x1b[16t"), // Request cell size
|
||||
Print("\x1b]11;?\x07"), // Request background color
|
||||
Print(Mux::csi("\x1b[0c")), // Request device attributes
|
||||
RestorePosition
|
||||
"{SaveCursorPos}{}{}{}{}{}{RestoreCursorPos}",
|
||||
If(resort.is_none(), Mux::wrap(KittyGraphicsQuery)),
|
||||
Mux::wrap(RequestXtVersion),
|
||||
RequestCellPixelSize,
|
||||
RequestBgColor,
|
||||
Mux::wrap(RequestDA1),
|
||||
)?;
|
||||
|
||||
let resp = Self::read_until_da1();
|
||||
|
|
@ -83,28 +78,26 @@ impl Emulator {
|
|||
where
|
||||
F: FnOnce(&mut BufWriter<Handle>) -> Result<T>,
|
||||
{
|
||||
use std::{io::Write, thread, time::Duration};
|
||||
|
||||
use crossterm::{cursor::{Hide, MoveTo, Show}, queue};
|
||||
use std::{thread, time::Duration};
|
||||
|
||||
let mut w = TTY.lockout();
|
||||
|
||||
// I really don't want to add this,
|
||||
// But tmux and ConPTY sometimes cause the cursor position to get out of sync.
|
||||
if TMUX.get() || cfg!(windows) {
|
||||
execute!(w, SavePosition, MoveTo(x, y), Show)?;
|
||||
execute!(w, MoveTo(x, y), Show)?;
|
||||
execute!(w, MoveTo(x, y), Show)?;
|
||||
writef!(w, "{SaveCursorPos}{}{ShowCursor}", MoveTo(x, y))?;
|
||||
writef!(w, "{}{ShowCursor}", MoveTo(x, y))?;
|
||||
writef!(w, "{}{ShowCursor}", MoveTo(x, y))?;
|
||||
thread::sleep(Duration::from_millis(1));
|
||||
} else {
|
||||
queue!(w, SavePosition, MoveTo(x, y))?;
|
||||
write!(w, "{SaveCursorPos}{}", MoveTo(x, y))?;
|
||||
}
|
||||
|
||||
let result = cb(&mut w);
|
||||
if TMUX.get() || cfg!(windows) {
|
||||
queue!(w, Hide, RestorePosition)?;
|
||||
write!(w, "{HideCursor}{RestoreCursorPos}")?;
|
||||
} else {
|
||||
queue!(w, RestorePosition)?;
|
||||
write!(w, "{RestoreCursorPos}")?;
|
||||
}
|
||||
|
||||
w.flush()?;
|
||||
|
|
@ -148,21 +141,13 @@ impl Emulator {
|
|||
}
|
||||
|
||||
async fn error_to_user() {
|
||||
use crossterm::style::{Attribute, Color, Print, ResetColor, SetAttributes, SetForegroundColor};
|
||||
|
||||
sleep(Duration::from_millis(400)).await;
|
||||
_ = crossterm::execute!(
|
||||
std::io::stderr(),
|
||||
SetForegroundColor(Color::Red),
|
||||
SetAttributes(Attribute::Bold.into()),
|
||||
Print("\r\nTerminal response timeout: "),
|
||||
ResetColor,
|
||||
SetAttributes(Attribute::Reset.into()),
|
||||
//
|
||||
Print("The request sent by Yazi didn't receive a correct response.\r\n"),
|
||||
Print(
|
||||
"Please check your terminal environment as per: https://yazi-rs.github.io/docs/faq#trt\r\n"
|
||||
),
|
||||
_ = writef!(
|
||||
io::stderr(),
|
||||
"{}{}\r\nTerminal response timeout: {}The request sent by Yazi didn't receive a correct response.\r\nPlease check your terminal environment as per: https://yazi-rs.github.io/docs/faq#trt\r\n",
|
||||
SetFg(Color::Red),
|
||||
SetSgr::Bold,
|
||||
SetSgr::Reset,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -206,8 +191,6 @@ impl Emulator {
|
|||
return false;
|
||||
}
|
||||
|
||||
Dimension::available()
|
||||
.ratio()
|
||||
.is_none_or(|(rw, rh)| rw.floor() as u16 != w || rh.floor() as u16 != h)
|
||||
TERM.dimension().ratio().is_none_or(|(rw, rh)| rw.floor() as u16 != w || rh.floor() as u16 != h)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display};
|
||||
|
||||
use anyhow::Result;
|
||||
use tracing::error;
|
||||
use yazi_macro::time;
|
||||
use yazi_macro::{time, writef};
|
||||
use yazi_shim::cell::SyncCell;
|
||||
use yazi_term::sequence::RequestDeviceStatus;
|
||||
use yazi_tty::TTY;
|
||||
|
||||
use crate::Emulator;
|
||||
|
|
@ -16,15 +17,24 @@ pub static CLOSE: SyncCell<&'static str> = SyncCell::new("");
|
|||
pub struct Mux;
|
||||
|
||||
impl Mux {
|
||||
pub fn csi(s: &str) -> Cow<'_, str> {
|
||||
if TMUX.get() {
|
||||
Cow::Owned(format!(
|
||||
"{START}{}{CLOSE}",
|
||||
s.trim_start_matches('\x1b').replace('\x1b', ESCAPE.get()),
|
||||
))
|
||||
} else {
|
||||
Cow::Borrowed(s)
|
||||
pub fn wrap<T: Display>(s: T) -> impl Display {
|
||||
struct Wrapper<T>(T);
|
||||
|
||||
impl<T: Display> Display for Wrapper<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if !TMUX.get() {
|
||||
return self.0.fmt(f);
|
||||
}
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{START}{}{CLOSE}",
|
||||
self.0.to_string().trim_start_matches('\x1b').replace('\x1b', ESCAPE.get())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Wrapper(s)
|
||||
}
|
||||
|
||||
pub fn tmux_passthrough() {
|
||||
|
|
@ -56,7 +66,7 @@ impl Mux {
|
|||
|
||||
pub fn tmux_drain() -> Result<()> {
|
||||
if TMUX.get() {
|
||||
crossterm::execute!(TTY.writer(), crossterm::style::Print(Self::csi("\x1b[5n")))?;
|
||||
writef!(TTY.writer(), "{}", Self::wrap(RequestDeviceStatus))?;
|
||||
_ = Emulator::read_until_dsr();
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ yazi-boot = { path = "../yazi-boot", version = "26.5.6" }
|
|||
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
||||
yazi-core = { path = "../yazi-core", version = "26.5.6" }
|
||||
yazi-dds = { path = "../yazi-dds", version = "26.5.6" }
|
||||
yazi-emulator = { path = "../yazi-emulator", version = "26.5.6" }
|
||||
yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-parser = { path = "../yazi-parser", version = "26.5.6" }
|
||||
|
|
@ -36,6 +35,7 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
|||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
yazi-tui = { path = "../yazi-tui", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
|
@ -43,7 +43,6 @@ yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
|||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
better-panic = "0.3.0"
|
||||
crossterm = { workspace = true }
|
||||
fdlimit = "0.3.0"
|
||||
futures = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
|
|
@ -60,9 +59,6 @@ tracing-subscriber = { version = "0.3.23", features = [ "env-filter" ] }
|
|||
libc = { workspace = true }
|
||||
signal-hook-tokio = { version = "0.4.0", features = [ "futures-v0_3" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
||||
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]
|
||||
tikv-jemallocator = "0.6.1"
|
||||
|
||||
|
|
|
|||
|
|
@ -6,14 +6,13 @@ use yazi_actor::Ctx;
|
|||
use yazi_core::Core;
|
||||
use yazi_macro::act;
|
||||
use yazi_shared::{data::Data, event::{Event, NEED_RENDER}};
|
||||
use yazi_term::Term;
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
use crate::{Dispatcher, Signals};
|
||||
|
||||
pub(crate) struct App {
|
||||
pub(crate) core: Core,
|
||||
pub(crate) term: Option<Term>,
|
||||
pub(crate) signals: Signals,
|
||||
pub(crate) core: Core,
|
||||
pub(crate) term: Option<Raterm>,
|
||||
|
||||
need_render: u8,
|
||||
pub(crate) last_render: Instant,
|
||||
|
|
@ -21,11 +20,10 @@ pub(crate) struct App {
|
|||
}
|
||||
|
||||
impl App {
|
||||
fn make(term: Term, signals: Signals) -> Result<Self> {
|
||||
fn make(term: Raterm) -> Result<Self> {
|
||||
Ok(Self {
|
||||
core: Core::make(),
|
||||
term: Some(term),
|
||||
signals,
|
||||
|
||||
need_render: 0,
|
||||
last_render: Instant::now(),
|
||||
|
|
@ -34,12 +32,13 @@ impl App {
|
|||
}
|
||||
|
||||
pub(crate) async fn serve() -> Result<()> {
|
||||
let term = Term::start()?;
|
||||
let (mut rx, signals) = (Event::take(), Signals::start()?);
|
||||
let term = Raterm::start()?;
|
||||
Signals::start()?;
|
||||
|
||||
let mut app = Self::make(term, signals)?;
|
||||
let mut app = Self::make(term)?;
|
||||
app.bootstrap()?;
|
||||
|
||||
let mut rx = Event::take();
|
||||
loop {
|
||||
if let Some(t) = app.next_render.take() {
|
||||
select! {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
use std::{sync::atomic::{AtomicU8, Ordering}, time::Instant};
|
||||
use std::{io::Write, sync::atomic::{AtomicU8, Ordering}, time::Instant};
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
|
||||
use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer, layout::Position};
|
||||
use ratatui::{CompletedFrame, backend::Backend, buffer::Buffer, layout::Position};
|
||||
use yazi_actor::{Ctx, lives::Lives};
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_config::LAYOUT;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_macro::{act, succ, writef};
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::{data::Data, event::NEED_RENDER};
|
||||
use yazi_term::{CursorStyle, sequence::{BeginSyncUpdate, EndSyncUpdate, MoveTo, SetCursorStyle, ShowCursor}};
|
||||
use yazi_tty::TTY;
|
||||
use yazi_tui::RatermBackend;
|
||||
use yazi_widgets::COLLISION;
|
||||
|
||||
use crate::{app::App, root::Root};
|
||||
|
|
@ -90,10 +91,10 @@ impl App {
|
|||
}
|
||||
|
||||
let patches = frame.buffer.diff(&new);
|
||||
CrosstermBackend::new(&mut *TTY.lockout()).draw(patches.into_iter()).ok();
|
||||
RatermBackend::new(&mut *TTY.lockout()).draw(patches.into_iter()).ok();
|
||||
}
|
||||
|
||||
fn routine(push: bool, cursor: Option<(Position, SetCursorStyle)>) {
|
||||
fn routine(push: bool, cursor: Option<(Position, CursorStyle)>) {
|
||||
static COUNT: AtomicU8 = AtomicU8::new(0);
|
||||
if push && COUNT.fetch_add(1, Ordering::Relaxed) != 0 {
|
||||
return;
|
||||
|
|
@ -101,12 +102,18 @@ impl App {
|
|||
return;
|
||||
}
|
||||
|
||||
_ = if push {
|
||||
queue!(TTY.writer(), BeginSynchronizedUpdate)
|
||||
if push {
|
||||
write!(TTY.writer(), "{BeginSyncUpdate}").ok();
|
||||
} else if let Some((Position { x, y }, shape)) = cursor {
|
||||
execute!(TTY.writer(), shape, MoveTo(x, y), Show, EndSynchronizedUpdate)
|
||||
writef!(
|
||||
TTY.writer(),
|
||||
"{}{}{ShowCursor}{EndSyncUpdate}",
|
||||
SetCursorStyle(shape as u8),
|
||||
MoveTo(x, y),
|
||||
)
|
||||
.ok();
|
||||
} else {
|
||||
execute!(TTY.writer(), EndSynchronizedUpdate)
|
||||
writef!(TTY.writer(), "{EndSyncUpdate}").ok();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ use std::path::MAIN_SEPARATOR_STR;
|
|||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
|
||||
use yazi_config::{THEME, popup::{Offset, Position}};
|
||||
use yazi_core::Core;
|
||||
use yazi_emulator::Dimension;
|
||||
use yazi_shared::strand::StrandLike;
|
||||
use yazi_term::TERM;
|
||||
|
||||
pub(crate) struct Cmp<'a> {
|
||||
core: &'a Core,
|
||||
|
|
@ -38,7 +38,7 @@ impl Widget for Cmp<'_> {
|
|||
.collect();
|
||||
|
||||
let input_area = self.core.mgr.area(self.core.input.position);
|
||||
let mut area = Position::sticky(Dimension::available().into(), input_area, Offset {
|
||||
let mut area = Position::sticky(TERM.dimension(), input_area, Offset {
|
||||
x: 1,
|
||||
y: 0,
|
||||
width: input_area.width.saturating_sub(2),
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use anyhow::Result;
|
||||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tracing::warn;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_config::keymap::Key;
|
||||
use yazi_macro::{act, emit};
|
||||
use yazi_shared::event::{ActionCow, Event, NEED_RENDER};
|
||||
use yazi_term::event::{Event as TermEvent, KeyEvent, MouseEvent};
|
||||
use yazi_widgets::input::InputMode;
|
||||
|
||||
use crate::{Executor, Router, app::App};
|
||||
|
|
@ -23,11 +23,12 @@ impl<'a> Dispatcher<'a> {
|
|||
Event::Call(action) => Ok(self.dispatch_call(action)),
|
||||
Event::Seq(actions) => Ok(self.dispatch_seq(actions)),
|
||||
Event::Render(partial) => self.dispatch_render(partial),
|
||||
Event::Key(key) => self.dispatch_key(key),
|
||||
Event::Mouse(mouse) => self.dispatch_mouse(mouse),
|
||||
Event::Resize => self.dispatch_resize(),
|
||||
Event::Focus => self.dispatch_focus(),
|
||||
Event::Paste(str) => self.dispatch_paste(str),
|
||||
Event::Term(TermEvent::Key(key)) => self.dispatch_key(key),
|
||||
Event::Term(TermEvent::Mouse(mouse)) => self.dispatch_mouse(mouse),
|
||||
Event::Term(TermEvent::Resize(_)) => self.dispatch_resize(),
|
||||
Event::Term(TermEvent::FocusIn) => self.dispatch_focus(),
|
||||
Event::Term(TermEvent::FocusOut) => Ok(()),
|
||||
Event::Term(TermEvent::Paste(str)) => self.dispatch_paste(str),
|
||||
};
|
||||
|
||||
if let Err(e) = &result {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::{Context, Result};
|
||||
use anyhow::Result;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_macro::{act, succ};
|
||||
use yazi_shared::{Layer, data::Data, event::ActionCow};
|
||||
|
|
@ -33,7 +33,7 @@ impl<'a> Executor<'a> {
|
|||
|
||||
fn null(&mut self, _action: ActionCow) -> Result<Data> { succ!() }
|
||||
|
||||
fn app(&mut self, mut action: ActionCow) -> Result<Data> {
|
||||
fn app(&mut self, action: ActionCow) -> Result<Data> {
|
||||
let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;
|
||||
|
||||
macro_rules! on {
|
||||
|
|
@ -51,19 +51,12 @@ impl<'a> Executor<'a> {
|
|||
on!(lua);
|
||||
on!(deprecate);
|
||||
on!(theme);
|
||||
on!(stop);
|
||||
on!(quit);
|
||||
|
||||
match &*action.name {
|
||||
"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
|
||||
"resume" => act!(app:resume, cx, yazi_parser::app::ResumeForm {
|
||||
tx: self.app.signals.tx.clone(),
|
||||
reflow: crate::Root::reflow,
|
||||
replier: action.take_replier().context("Missing replier in ResumeForm")?,
|
||||
}),
|
||||
"stop" => act!(app:stop, cx, yazi_parser::app::StopForm {
|
||||
tx: self.app.signals.tx.clone(),
|
||||
replier: action.take_replier().context("Missing replier in StopForm")?,
|
||||
}),
|
||||
"resume" => act!(app:resume, cx, crate::Root::reflow as fn(_) -> _),
|
||||
_ => succ!(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
use std::fs::File;
|
||||
use std::{fs::File, io};
|
||||
|
||||
use anyhow::Context;
|
||||
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
|
||||
use ratatui::style::Color;
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
use yazi_fs::Xdg;
|
||||
use yazi_macro::writef;
|
||||
use yazi_shared::LOG_LEVEL;
|
||||
use yazi_shim::cell::RoCell;
|
||||
use yazi_term::sequence::{SetFg, SetSgr};
|
||||
|
||||
static _GUARD: RoCell<WorkerGuard> = RoCell::new();
|
||||
|
||||
|
|
@ -36,11 +38,11 @@ impl Logs {
|
|||
.init();
|
||||
|
||||
_GUARD.init(guard);
|
||||
Ok(crossterm::execute!(
|
||||
std::io::stderr(),
|
||||
SetForegroundColor(Color::Yellow),
|
||||
Print(format!("Running with log level `{level}`, logs are written to {log_path:?}\n")),
|
||||
ResetColor
|
||||
Ok(writef!(
|
||||
io::stderr(),
|
||||
"{}Running with log level `{level}`, logs are written to {log_path:?}\n{}",
|
||||
SetFg(Color::Yellow),
|
||||
SetSgr::Reset,
|
||||
)?)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
yazi_tty::init();
|
||||
|
||||
yazi_term::init();
|
||||
yazi_term::init()?;
|
||||
|
||||
yazi_fs::init();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use yazi_term::Term;
|
||||
use yazi_tui::Raterm;
|
||||
|
||||
pub(super) struct Panic;
|
||||
|
||||
|
|
@ -8,10 +8,8 @@ impl Panic {
|
|||
|
||||
let hook = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(move |info| {
|
||||
Term::goodbye(|| {
|
||||
hook(info);
|
||||
1
|
||||
});
|
||||
Raterm::stop();
|
||||
hook(info);
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,39 @@
|
|||
use anyhow::Result;
|
||||
use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind};
|
||||
use futures::StreamExt;
|
||||
use tokio::{select, sync::mpsc};
|
||||
use yazi_config::YAZI;
|
||||
use yazi_shared::event::{Event, Replier};
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
pub(super) struct Signals {
|
||||
pub(super) tx: mpsc::UnboundedSender<(bool, Replier)>,
|
||||
}
|
||||
pub(super) struct Signals;
|
||||
|
||||
impl Signals {
|
||||
pub(super) fn start() -> Result<Self> {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
Self::spawn(rx)?;
|
||||
pub(super) fn start() -> Result<()> {
|
||||
#[cfg(unix)]
|
||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP};
|
||||
|
||||
Ok(Self { tx })
|
||||
#[cfg(unix)]
|
||||
let mut stream = signal_hook_tokio::Signals::new([
|
||||
// Interrupt signals (Ctrl-C, Ctrl-\)
|
||||
SIGINT, SIGQUIT, //
|
||||
// Hangup signal (Terminal closed)
|
||||
SIGHUP, //
|
||||
// Termination signal (kill)
|
||||
SIGTERM, //
|
||||
// Job control signals (Ctrl-Z, fg/bg)
|
||||
SIGTSTP, SIGCONT,
|
||||
])?;
|
||||
#[cfg(windows)]
|
||||
let mut stream = tokio_stream::empty();
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(n) = stream.next().await {
|
||||
if !Self::handle(n).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn handle_sys(n: libc::c_int) -> bool {
|
||||
async fn handle(n: libc::c_int) -> bool {
|
||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGSTOP, SIGTERM, SIGTSTP};
|
||||
use tracing::error;
|
||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||
|
|
@ -30,16 +45,14 @@ impl Signals {
|
|||
return false;
|
||||
}
|
||||
SIGTSTP => {
|
||||
tokio::spawn(async move {
|
||||
yazi_scheduler::AppProxy::stop().await;
|
||||
if unsafe { libc::kill(0, SIGSTOP) } != 0 {
|
||||
error!("Failed to stop the process:\n{}", std::io::Error::last_os_error());
|
||||
yazi_proxy::AppProxy::quit(Default::default());
|
||||
}
|
||||
});
|
||||
yazi_scheduler::AppProxy::stop().await;
|
||||
if unsafe { libc::kill(0, SIGSTOP) } != 0 {
|
||||
error!("Failed to stop the process:\n{}", std::io::Error::last_os_error());
|
||||
yazi_proxy::AppProxy::quit(Default::default());
|
||||
}
|
||||
}
|
||||
SIGCONT if YIELD_TO_SUBPROCESS.try_acquire().is_ok() => {
|
||||
tokio::spawn(yazi_scheduler::AppProxy::resume());
|
||||
yazi_scheduler::AppProxy::resume().await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
|
@ -47,69 +60,5 @@ impl Signals {
|
|||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[inline]
|
||||
fn handle_sys(_: ()) -> bool { unreachable!() }
|
||||
|
||||
fn handle_term(event: CrosstermEvent) {
|
||||
match event {
|
||||
CrosstermEvent::Key(key @ KeyEvent { kind: KeyEventKind::Press, .. }) => {
|
||||
Event::Key(key).emit()
|
||||
}
|
||||
CrosstermEvent::Mouse(mouse) if YAZI.mgr.mouse_events.get().contains(mouse.kind.into()) => {
|
||||
Event::Mouse(mouse).emit()
|
||||
}
|
||||
CrosstermEvent::Resize(..) => Event::Resize.emit(),
|
||||
CrosstermEvent::FocusGained => Event::Focus.emit(),
|
||||
CrosstermEvent::Paste(str) => Event::Paste(str).emit(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn spawn(mut rx: mpsc::UnboundedReceiver<(bool, Replier)>) -> Result<()> {
|
||||
#[cfg(unix)]
|
||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM, SIGTSTP};
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut sys = signal_hook_tokio::Signals::new([
|
||||
// Interrupt signals (Ctrl-C, Ctrl-\)
|
||||
SIGINT, SIGQUIT, //
|
||||
// Hangup signal (Terminal closed)
|
||||
SIGHUP, //
|
||||
// Termination signal (kill)
|
||||
SIGTERM, //
|
||||
// Job control signals (Ctrl-Z, fg/bg)
|
||||
SIGTSTP, SIGCONT,
|
||||
])?;
|
||||
#[cfg(windows)]
|
||||
let mut sys = tokio_stream::empty();
|
||||
|
||||
let mut term = Some(EventStream::new());
|
||||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Some(t) = &mut term {
|
||||
select! {
|
||||
biased;
|
||||
Some((state, replier)) = rx.recv() => {
|
||||
term = term.filter(|_| state);
|
||||
replier.send(Ok(().into())).ok();
|
||||
},
|
||||
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
|
||||
Some(Ok(e)) = t.next() => Self::handle_term(e)
|
||||
}
|
||||
} else {
|
||||
select! {
|
||||
biased;
|
||||
Some((state, replier)) = rx.recv() => {
|
||||
term = state.then(EventStream::new);
|
||||
replier.send(Ok(().into())).ok();
|
||||
},
|
||||
Some(n) = sys.next() => if !Self::handle_sys(n) { return },
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
async fn handle(_: ()) -> bool { unreachable!() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,3 +13,12 @@ macro_rules! errln {
|
|||
writeln!(std::io::stderr(), $($tt)*)
|
||||
}}
|
||||
}
|
||||
|
||||
/// Like [`write!`] but immediately flushes the writer afterwards.
|
||||
#[macro_export]
|
||||
macro_rules! writef {
|
||||
($dst:expr, $($arg:tt)*) => {{
|
||||
use std::io::Write as _;
|
||||
write!($dst, $($arg)*).and_then(|_| ($dst).flush())
|
||||
}};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,13 +26,13 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
|||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
|
|
@ -41,6 +41,3 @@ serde = { workspace = true }
|
|||
serde_with = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(deprecate lua mouse plugin quit reflow resume stop title update_progress);
|
||||
yazi_macro::mod_flat!(deprecate lua mouse plugin quit reflow title update_progress);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use crossterm::event::MouseEvent;
|
||||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_term::event::MouseEvent;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MouseForm {
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Table, Value};
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shared::event::Replier;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResumeForm {
|
||||
pub tx: mpsc::UnboundedSender<(bool, Replier)>,
|
||||
pub reflow: fn(Rect) -> mlua::Result<Table>,
|
||||
pub replier: Replier,
|
||||
}
|
||||
|
||||
impl FromLua for ResumeForm {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for ResumeForm {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shared::event::Replier;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StopForm {
|
||||
pub tx: mpsc::UnboundedSender<(bool, Replier)>,
|
||||
pub replier: Replier,
|
||||
}
|
||||
|
||||
impl FromLua for StopForm {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for StopForm {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
@ -19,8 +19,8 @@ pub enum Spark<'a> {
|
|||
AppQuit(crate::app::QuitForm),
|
||||
AppReflow(crate::app::ReflowForm),
|
||||
AppResize(crate::app::ReflowForm),
|
||||
AppResume(crate::app::ResumeForm),
|
||||
AppStop(crate::app::StopForm),
|
||||
AppResume(crate::app::ReflowForm),
|
||||
AppStop(crate::VoidForm),
|
||||
AppTheme(crate::VoidForm),
|
||||
AppTitle(crate::app::TitleForm),
|
||||
AppUpdateProgress(crate::app::UpdateProgressForm),
|
||||
|
|
@ -352,6 +352,7 @@ try_from_spark!(
|
|||
crate::VoidForm,
|
||||
app:bootstrap,
|
||||
app:focus,
|
||||
app:stop,
|
||||
app:theme,
|
||||
mgr:back,
|
||||
mgr:bulk_rename,
|
||||
|
|
@ -379,9 +380,7 @@ try_from_spark!(crate::app::LuaForm, app:lua);
|
|||
try_from_spark!(crate::app::MouseForm, app:mouse);
|
||||
try_from_spark!(crate::app::PluginForm, app:plugin, app:plugin_do);
|
||||
try_from_spark!(crate::app::QuitForm, app:quit, mgr:quit);
|
||||
try_from_spark!(crate::app::ReflowForm, app:reflow, app:resize);
|
||||
try_from_spark!(crate::app::ResumeForm, app:resume);
|
||||
try_from_spark!(crate::app::StopForm, app:stop);
|
||||
try_from_spark!(crate::app::ReflowForm, app:reflow, app:resize, app:resume);
|
||||
try_from_spark!(crate::app::TitleForm, app:title);
|
||||
try_from_spark!(crate::app::UpdateProgressForm, app:update_progress);
|
||||
try_from_spark!(crate::cmp::CloseForm, cmp:close);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
|||
yazi-runner = { path = "../yazi-runner", version = "26.5.6" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
yazi-tui = { path = "../yazi-tui", version = "26.5.6" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ workspace = true
|
|||
[dependencies]
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
dyn-clone = { workspace = true }
|
||||
foldhash = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
|
@ -41,6 +41,3 @@ uzers = { workspace = true }
|
|||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.61.2", features = [ "Win32_UI_Shell" ] }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{any::Any, fmt};
|
||||
use std::{any::Any, fmt::{self, Debug}};
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ impl Clone for Box<dyn DataAny> {
|
|||
fn clone(&self) -> Self { dyn_clone::clone_box(&**self) }
|
||||
}
|
||||
|
||||
impl fmt::Debug for dyn DataAny {
|
||||
impl Debug for dyn DataAny {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("DataAny").finish_non_exhaustive()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use crossterm::event::{KeyEvent, MouseEvent};
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_shim::cell::RoCell;
|
||||
|
||||
|
|
@ -11,12 +10,8 @@ static RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new();
|
|||
pub enum Event {
|
||||
Call(ActionCow),
|
||||
Seq(Vec<ActionCow>),
|
||||
Term(yazi_term::event::Event),
|
||||
Render(bool),
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Resize,
|
||||
Focus,
|
||||
Paste(String),
|
||||
}
|
||||
|
||||
impl Event {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
yazi_macro::mod_pub!(data event loc path pool scheme shell strand translit url wtf8);
|
||||
|
||||
yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id kebab_cased_string last_value layer localset natsort non_empty_string os predictor snake_cased_string source terminal tests throttle time utf8);
|
||||
yazi_macro::mod_flat!(alias bytes chars completion_token condition debounce env id kebab_cased_string last_value layer localset natsort non_empty_string os predictor snake_cased_string source tests throttle time);
|
||||
|
||||
pub fn init() {
|
||||
LOCAL_SET.with(tokio::task::LocalSet::new);
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
use std::io::Write;
|
||||
|
||||
use crossterm::execute;
|
||||
|
||||
#[inline]
|
||||
pub fn terminal_clear(mut w: impl Write) -> std::io::Result<()> {
|
||||
execute!(
|
||||
w,
|
||||
crossterm::terminal::Clear(crossterm::terminal::ClearType::All),
|
||||
crossterm::style::Print("\n")
|
||||
)
|
||||
}
|
||||
|
|
@ -368,10 +368,10 @@ impl<'a> Url<'a> {
|
|||
(U::Regular(_), U::Regular(_)) => Ok(prefix),
|
||||
(U::Search { .. }, U::Search { .. }) => Ok(prefix),
|
||||
(U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => {
|
||||
Some(prefix).filter(|_| a == b).ok_or(Exotic)
|
||||
(a == b).then_some(prefix).ok_or(Exotic)
|
||||
}
|
||||
(U::Sftp { domain: a, .. }, U::Sftp { domain: b, .. }) => {
|
||||
Some(prefix).filter(|_| a == b).ok_or(Exotic)
|
||||
(a == b).then_some(prefix).ok_or(Exotic)
|
||||
}
|
||||
|
||||
// Both are local files
|
||||
|
|
@ -380,16 +380,16 @@ impl<'a> Url<'a> {
|
|||
|
||||
// Only the entry of archives is a local file
|
||||
(U::Regular(_), U::Archive { .. }) => {
|
||||
Some(prefix).filter(|_| base.uri().is_empty()).ok_or(NotPrefix)
|
||||
base.uri().is_empty().then_some(prefix).ok_or(NotPrefix)
|
||||
}
|
||||
(U::Search { .. }, U::Archive { .. }) => {
|
||||
Some(prefix).filter(|_| base.uri().is_empty()).ok_or(NotPrefix)
|
||||
base.uri().is_empty().then_some(prefix).ok_or(NotPrefix)
|
||||
}
|
||||
(U::Archive { .. }, U::Regular(_)) => {
|
||||
Some(prefix).filter(|_| self.uri().is_empty()).ok_or(NotPrefix)
|
||||
self.uri().is_empty().then_some(prefix).ok_or(NotPrefix)
|
||||
}
|
||||
(U::Archive { .. }, U::Search { .. }) => {
|
||||
Some(prefix).filter(|_| self.uri().is_empty()).ok_or(NotPrefix)
|
||||
self.uri().is_empty().then_some(prefix).ok_or(NotPrefix)
|
||||
}
|
||||
|
||||
// Independent virtual file space
|
||||
|
|
@ -414,10 +414,10 @@ impl<'a> Url<'a> {
|
|||
(U::Regular(_), U::Regular(_)) => Ok(suffix),
|
||||
(U::Search { .. }, U::Search { .. }) => Ok(suffix),
|
||||
(U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => {
|
||||
Some(suffix).filter(|_| a == b).ok_or(Exotic)
|
||||
(a == b).then_some(suffix).ok_or(Exotic)
|
||||
}
|
||||
(U::Sftp { domain: a, .. }, U::Sftp { domain: b, .. }) => {
|
||||
Some(suffix).filter(|_| a == b).ok_or(Exotic)
|
||||
(a == b).then_some(suffix).ok_or(Exotic)
|
||||
}
|
||||
|
||||
// Both are local files
|
||||
|
|
@ -426,16 +426,16 @@ impl<'a> Url<'a> {
|
|||
|
||||
// Only the entry of archives is a local file
|
||||
(U::Regular(_), U::Archive { .. }) => {
|
||||
Some(suffix).filter(|_| other.uri().is_empty()).ok_or(NotSuffix)
|
||||
other.uri().is_empty().then_some(suffix).ok_or(NotSuffix)
|
||||
}
|
||||
(U::Search { .. }, U::Archive { .. }) => {
|
||||
Some(suffix).filter(|_| other.uri().is_empty()).ok_or(NotSuffix)
|
||||
other.uri().is_empty().then_some(suffix).ok_or(NotSuffix)
|
||||
}
|
||||
(U::Archive { .. }, U::Regular(_)) => {
|
||||
Some(suffix).filter(|_| self.uri().is_empty()).ok_or(NotSuffix)
|
||||
self.uri().is_empty().then_some(suffix).ok_or(NotSuffix)
|
||||
}
|
||||
(U::Archive { .. }, U::Search { .. }) => {
|
||||
Some(suffix).filter(|_| self.uri().is_empty()).ok_or(NotSuffix)
|
||||
self.uri().is_empty().then_some(suffix).ok_or(NotSuffix)
|
||||
}
|
||||
|
||||
// Independent virtual file space
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
// https://tools.ietf.org/html/rfc3629
|
||||
const UTF8_CHAR_WIDTH: &[u8; 256] = &[
|
||||
// 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
|
||||
0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E
|
||||
4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
|
||||
];
|
||||
|
||||
/// Given a first byte, determines how many bytes are in this UTF-8 character.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn utf8_char_width(b: u8) -> usize { UTF8_CHAR_WIDTH[b as usize] as usize }
|
||||
|
||||
/// Finds the closest `x` not exceeding `index` where [`is_char_boundary(x)`] is
|
||||
/// `true`.
|
||||
///
|
||||
/// This method can help you truncate a string so that it's still valid UTF-8,
|
||||
/// but doesn't exceed a given number of bytes. Note that this is done purely at
|
||||
/// the character level and can still visually split graphemes, even though the
|
||||
/// underlying characters aren't split. For example, the emoji 🧑🔬 (scientist)
|
||||
/// could be split so that the string only includes 🧑 (person) instead.
|
||||
#[inline]
|
||||
pub fn floor_char_boundary(s: &str, index: usize) -> usize {
|
||||
if index >= s.len() {
|
||||
s.len()
|
||||
} else {
|
||||
let lower_bound = index.saturating_sub(3);
|
||||
let new_index =
|
||||
s.as_bytes()[lower_bound..=index].iter().rposition(|&b| is_utf8_char_boundary(b));
|
||||
|
||||
// SAFETY: we know that the character boundary will be within four bytes
|
||||
unsafe { lower_bound + new_index.unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
const fn is_utf8_char_boundary(b: u8) -> bool {
|
||||
// This is bit magic equivalent to: b < 128 || b >= 192
|
||||
(b as i8) >= -0x40
|
||||
}
|
||||
|
|
@ -21,7 +21,6 @@ yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
|||
|
||||
# External dependencies
|
||||
arc-swap = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
|
|
@ -33,6 +32,3 @@ unicode-width = { workspace = true }
|
|||
|
||||
[dependencies.unicode-segmentation]
|
||||
version = "1.13.2"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
pub struct If<T: crossterm::Command>(pub bool, pub T);
|
||||
|
||||
impl<T: crossterm::Command> crossterm::Command for If<T> {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
if self.0 { self.1.write_ansi(f) } else { Ok(()) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> {
|
||||
if self.0 { self.1.execute_winapi() } else { Ok(()) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn is_ansi_code_supported(&self) -> bool { self.1.is_ansi_code_supported() }
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
yazi_macro::mod_flat!(r#if restore_background restore_cursor set_background);
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
pub struct RestoreBackground;
|
||||
|
||||
impl crossterm::Command for RestoreBackground {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
write!(f, "\x1b]111\x1b\\")
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
use crossterm::cursor::SetCursorStyle;
|
||||
|
||||
pub struct RestoreCursor {
|
||||
pub shape: u8,
|
||||
pub blink: bool,
|
||||
}
|
||||
|
||||
impl crossterm::Command for RestoreCursor {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
let (shape, shape_blink) = match self.shape {
|
||||
u8::MAX => (0, None),
|
||||
n => (n.max(1).div_ceil(2), Some(n.max(1) & 1 == 1)),
|
||||
};
|
||||
|
||||
let blink = shape_blink.unwrap_or(self.blink);
|
||||
Ok(match shape {
|
||||
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)?,
|
||||
_ if blink => SetCursorStyle::DefaultUserShape.write_ansi(f)?,
|
||||
_ if !blink => SetCursorStyle::SteadyBlock.write_ansi(f)?,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
pub struct SetBackground<'a>(pub &'a str);
|
||||
|
||||
impl crossterm::Command for SetBackground<'_> {
|
||||
fn write_ansi(&self, f: &mut impl std::fmt::Write) -> std::fmt::Result {
|
||||
if self.0.is_empty() { Ok(()) } else { write!(f, "\x1b]11;{}\x1b\\", self.0) }
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn execute_winapi(&self) -> std::io::Result<()> { Ok(()) }
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
yazi_macro::mod_pub!(arc_swap cell crossterm mlua path ratatui serde strum toml vec);
|
||||
yazi_macro::mod_pub!(arc_swap cell mlua path ratatui serde strum toml vec);
|
||||
|
||||
yazi_macro::mod_flat!(twox);
|
||||
yazi_macro::mod_flat!(twox utf8);
|
||||
|
||||
#[cfg(windows)]
|
||||
yazi_macro::mod_flat!(win32);
|
||||
|
|
|
|||
25
yazi-shim/src/utf8.rs
Normal file
25
yazi-shim/src/utf8.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// https://tools.ietf.org/html/rfc3629
|
||||
const UTF8_CHAR_WIDTH: &[u8; 256] = &[
|
||||
// 1 2 3 4 5 6 7 8 9 A B C D E F
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B
|
||||
0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E
|
||||
4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F
|
||||
];
|
||||
|
||||
/// Given a first byte, determines how many bytes are in this UTF-8 character.
|
||||
#[must_use]
|
||||
#[inline]
|
||||
pub const fn utf8_char_width(b: u8) -> u8 { UTF8_CHAR_WIDTH[b as usize] }
|
||||
13
yazi-shim/src/win32.rs
Normal file
13
yazi-shim/src/win32.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use std::io;
|
||||
|
||||
/// Maps a Win32 BOOL return value to `io::Result<()>`; 0 becomes
|
||||
/// `Err(last_os_error())`.
|
||||
pub fn bool_ok(r: i32) -> io::Result<()> {
|
||||
if r == 0 { Err(io::Error::last_os_error()) } else { Ok(()) }
|
||||
}
|
||||
|
||||
/// Maps a Win32 non-zero-success return value to `io::Result<u32>`; 0 becomes
|
||||
/// `Err(last_os_error())`.
|
||||
pub fn nz_ok(r: u32) -> io::Result<u32> {
|
||||
if r == 0 { Err(io::Error::last_os_error()) } else { Ok(r) }
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-term"
|
||||
description = "Yazi terminal extensions"
|
||||
description = "Cross-platform virtual terminal"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
|
@ -13,17 +13,23 @@ rust-version.workspace = true
|
|||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
||||
yazi-emulator = { path = "../yazi-emulator", version = "26.5.6" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = { workspace = true }
|
||||
crossterm = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
bitflags = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
signal-hook = "0.4.4"
|
||||
rustix = { version = "1.1.4" , default-features = false, features = [ "std", "stdio", "termios", "event" ] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.61.2", features = [ "Win32_Globalization", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Threading" ] }
|
||||
|
|
|
|||
22
yazi-term/LICENSE
Normal file
22
yazi-term/LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 - Michael Davis
|
||||
Copyright (c) 2026 - sxyazi
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
# yazi-term
|
||||
|
||||
This crate is part of [Yazi][source], and it is not supposed to be used outside, as there are no guarantees about the stability of its API.
|
||||
|
||||
[source]: https://github.com/sxyazi/yazi
|
||||
A fork of [Termina](https://github.com/helix-editor/termina) used by Yazi.
|
||||
|
|
|
|||
41
yazi-term/src/dimension.rs
Normal file
41
yazi-term/src/dimension.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use std::io;
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Dimension {
|
||||
pub cols: u16,
|
||||
pub rows: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl From<rustix::termios::Winsize> for Dimension {
|
||||
fn from(size: rustix::termios::Winsize) -> Self {
|
||||
Self {
|
||||
cols: size.ws_col,
|
||||
rows: size.ws_row,
|
||||
width: size.ws_xpixel,
|
||||
height: size.ws_ypixel,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Dimension {
|
||||
pub fn checked(self) -> io::Result<Self> {
|
||||
if self.cols == 0 || self.rows == 0 {
|
||||
Err(io::Error::other(
|
||||
"failed to get terminal dimension with both ioctl and $LINES/$COLUMNS variables",
|
||||
))
|
||||
} else {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ratio(self) -> Option<(f64, f64)> {
|
||||
if self.rows == 0 || self.cols == 0 || self.width == 0 || self.height == 0 {
|
||||
None
|
||||
} else {
|
||||
Some((self.width as f64 / self.cols as f64, self.height as f64 / self.rows as f64))
|
||||
}
|
||||
}
|
||||
}
|
||||
21
yazi-term/src/error.rs
Normal file
21
yazi-term/src/error.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use std::str;
|
||||
|
||||
pub type Result<T, E = ParseError> = std::result::Result<T, E>;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ParseError {
|
||||
/// Malformed or unrecognized byte sequence.
|
||||
#[error("invalid or unrecognized byte sequence")]
|
||||
Invalid,
|
||||
/// Recognised sequence that should not be emitted as an event.
|
||||
#[error("recognised sequence that should be ignored")]
|
||||
Ignored,
|
||||
/// The sequence is not yet complete; more bytes are needed before it can be
|
||||
/// parsed.
|
||||
#[error("incomplete sequence; more bytes are needed")]
|
||||
Incomplete,
|
||||
}
|
||||
|
||||
impl From<str::Utf8Error> for ParseError {
|
||||
fn from(_: str::Utf8Error) -> Self { Self::Invalid }
|
||||
}
|
||||
11
yazi-term/src/event/event.rs
Normal file
11
yazi-term/src/event/event.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use crate::{Dimension, event::{KeyEvent, MouseEvent}};
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Event {
|
||||
Key(KeyEvent),
|
||||
Mouse(MouseEvent),
|
||||
Resize(Dimension),
|
||||
FocusIn,
|
||||
FocusOut,
|
||||
Paste(String),
|
||||
}
|
||||
268
yazi-term/src/event/keyboard.rs
Normal file
268
yazi-term/src/event/keyboard.rs
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
use bitflags::bitflags;
|
||||
|
||||
use crate::{ParseError, Result, bail, event::Modifiers};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct KeyEvent {
|
||||
pub code: KeyCode,
|
||||
pub kind: KeyEventKind,
|
||||
pub modifiers: Modifiers,
|
||||
pub state: KeyEventState,
|
||||
}
|
||||
|
||||
impl KeyEvent {
|
||||
pub const fn new(code: KeyCode, modifiers: Modifiers) -> Self {
|
||||
Self { code, kind: KeyEventKind::Press, modifiers, state: KeyEventState::empty() }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<KeyCode> for KeyEvent {
|
||||
fn from(code: KeyCode) -> Self {
|
||||
Self {
|
||||
code,
|
||||
kind: KeyEventKind::Press,
|
||||
modifiers: Modifiers::empty(),
|
||||
state: KeyEventState::empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Kind
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub enum KeyEventKind {
|
||||
#[default]
|
||||
Press,
|
||||
Release,
|
||||
Repeat,
|
||||
}
|
||||
|
||||
impl KeyEventKind {
|
||||
pub(crate) fn from_vt_code(code: u8) -> Self {
|
||||
match code {
|
||||
2 => Self::Repeat,
|
||||
3 => Self::Release,
|
||||
_ => Self::Press,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- State
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct KeyEventState: u8 {
|
||||
const KEYPAD = 1;
|
||||
const CAPS_LOCK = 2;
|
||||
const NUM_LOCK = 4;
|
||||
}
|
||||
}
|
||||
|
||||
impl KeyEventState {
|
||||
pub(crate) fn from_vt_mask(mask: u8) -> Self {
|
||||
let m = mask.saturating_sub(1);
|
||||
let mut state = Self::empty();
|
||||
if m & 64 != 0 {
|
||||
state |= Self::CAPS_LOCK;
|
||||
}
|
||||
if m & 128 != 0 {
|
||||
state |= Self::NUM_LOCK;
|
||||
}
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
// --- Code
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum KeyCode {
|
||||
Char(char),
|
||||
Enter,
|
||||
Backspace,
|
||||
Tab,
|
||||
Escape,
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
Home,
|
||||
End,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Insert,
|
||||
Delete,
|
||||
KeypadBegin,
|
||||
CapsLock,
|
||||
ScrollLock,
|
||||
NumLock,
|
||||
PrintScreen,
|
||||
Pause,
|
||||
Menu,
|
||||
Null,
|
||||
Fn(u8),
|
||||
Modifier(ModifierKeyCode),
|
||||
Media(MediaKeyCode),
|
||||
}
|
||||
|
||||
impl KeyCode {
|
||||
pub(crate) fn from_xterm_modifier(r#final: u8) -> Result<Self> {
|
||||
Ok(match r#final {
|
||||
b'A' => KeyCode::Up,
|
||||
b'B' => KeyCode::Down,
|
||||
b'C' => KeyCode::Right,
|
||||
b'D' => KeyCode::Left,
|
||||
b'F' => KeyCode::End,
|
||||
b'H' => KeyCode::Home,
|
||||
b'P' => KeyCode::Fn(1),
|
||||
b'Q' => KeyCode::Fn(2),
|
||||
b'R' => KeyCode::Fn(3),
|
||||
b'S' => KeyCode::Fn(4),
|
||||
_ => bail!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn from_codepoint(codepoint: u32) -> Result<(Self, KeyEventState)> {
|
||||
if let Ok(pair) = Self::from_kitty_codepoint(codepoint) {
|
||||
return Ok(pair);
|
||||
}
|
||||
|
||||
let code = match char::from_u32(codepoint).ok_or(ParseError::Invalid)? {
|
||||
'\x1B' => Self::Escape,
|
||||
'\r' => Self::Enter,
|
||||
'\t' => Self::Tab,
|
||||
'\x7F' => Self::Backspace,
|
||||
c => Self::Char(c),
|
||||
};
|
||||
Ok((code, KeyEventState::empty()))
|
||||
}
|
||||
|
||||
fn from_kitty_codepoint(codepoint: u32) -> Result<(Self, KeyEventState)> {
|
||||
// Keypad keys
|
||||
if (57399..=57427).contains(&codepoint) {
|
||||
let code = match codepoint {
|
||||
57399 => Self::Char('0'),
|
||||
57400 => Self::Char('1'),
|
||||
57401 => Self::Char('2'),
|
||||
57402 => Self::Char('3'),
|
||||
57403 => Self::Char('4'),
|
||||
57404 => Self::Char('5'),
|
||||
57405 => Self::Char('6'),
|
||||
57406 => Self::Char('7'),
|
||||
57407 => Self::Char('8'),
|
||||
57408 => Self::Char('9'),
|
||||
57409 => Self::Char('.'),
|
||||
57410 => Self::Char('/'),
|
||||
57411 => Self::Char('*'),
|
||||
57412 => Self::Char('-'),
|
||||
57413 => Self::Char('+'),
|
||||
57414 => Self::Enter,
|
||||
57415 => Self::Char('='),
|
||||
57416 => Self::Char(','),
|
||||
57417 => Self::Left,
|
||||
57418 => Self::Right,
|
||||
57419 => Self::Up,
|
||||
57420 => Self::Down,
|
||||
57421 => Self::PageUp,
|
||||
57422 => Self::PageDown,
|
||||
57423 => Self::Home,
|
||||
57424 => Self::End,
|
||||
57425 => Self::Insert,
|
||||
57426 => Self::Delete,
|
||||
57427 => Self::KeypadBegin,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
return Ok((code, KeyEventState::KEYPAD));
|
||||
}
|
||||
|
||||
// F13–F35 keys
|
||||
if (57376..=57398).contains(&codepoint) {
|
||||
return Ok((Self::Fn((codepoint - 57363) as u8), KeyEventState::empty()));
|
||||
}
|
||||
|
||||
let code = match codepoint {
|
||||
57358 => Self::CapsLock,
|
||||
57359 => Self::ScrollLock,
|
||||
57360 => Self::NumLock,
|
||||
57361 => Self::PrintScreen,
|
||||
57362 => Self::Pause,
|
||||
57363 => Self::Menu,
|
||||
57428 => Self::Media(MediaKeyCode::Play),
|
||||
57429 => Self::Media(MediaKeyCode::Pause),
|
||||
57430 => Self::Media(MediaKeyCode::PlayPause),
|
||||
57431 => Self::Media(MediaKeyCode::Reverse),
|
||||
57432 => Self::Media(MediaKeyCode::Stop),
|
||||
57433 => Self::Media(MediaKeyCode::FastForward),
|
||||
57434 => Self::Media(MediaKeyCode::Rewind),
|
||||
57435 => Self::Media(MediaKeyCode::NextTrack),
|
||||
57436 => Self::Media(MediaKeyCode::PreviousTrack),
|
||||
57437 => Self::Media(MediaKeyCode::Record),
|
||||
57438 => Self::Media(MediaKeyCode::LowerVolume),
|
||||
57439 => Self::Media(MediaKeyCode::RaiseVolume),
|
||||
57440 => Self::Media(MediaKeyCode::MuteVolume),
|
||||
57441 => Self::Modifier(ModifierKeyCode::LeftShift),
|
||||
57442 => Self::Modifier(ModifierKeyCode::LeftControl),
|
||||
57443 => Self::Modifier(ModifierKeyCode::LeftAlt),
|
||||
57444 => Self::Modifier(ModifierKeyCode::LeftSuper),
|
||||
57445 => Self::Modifier(ModifierKeyCode::LeftHyper),
|
||||
57446 => Self::Modifier(ModifierKeyCode::LeftMeta),
|
||||
57447 => Self::Modifier(ModifierKeyCode::RightShift),
|
||||
57448 => Self::Modifier(ModifierKeyCode::RightControl),
|
||||
57449 => Self::Modifier(ModifierKeyCode::RightAlt),
|
||||
57450 => Self::Modifier(ModifierKeyCode::RightSuper),
|
||||
57451 => Self::Modifier(ModifierKeyCode::RightHyper),
|
||||
57452 => Self::Modifier(ModifierKeyCode::RightMeta),
|
||||
57453 => Self::Modifier(ModifierKeyCode::IsoLevel3Shift),
|
||||
57454 => Self::Modifier(ModifierKeyCode::IsoLevel5Shift),
|
||||
_ => bail!(),
|
||||
};
|
||||
Ok((code, KeyEventState::empty()))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Modifier key
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum ModifierKeyCode {
|
||||
LeftShift,
|
||||
LeftControl,
|
||||
LeftAlt,
|
||||
LeftSuper, // Command/Windows/Super key
|
||||
LeftHyper,
|
||||
LeftMeta,
|
||||
RightShift,
|
||||
RightControl,
|
||||
RightAlt,
|
||||
RightSuper, // Command/Windows/Super key
|
||||
RightHyper,
|
||||
RightMeta,
|
||||
IsoLevel3Shift,
|
||||
IsoLevel5Shift,
|
||||
}
|
||||
|
||||
impl ModifierKeyCode {
|
||||
pub(crate) fn to_modifier(self) -> Option<Modifiers> {
|
||||
match self {
|
||||
Self::LeftShift | Self::RightShift => Some(Modifiers::SHIFT),
|
||||
Self::LeftControl | Self::RightControl => Some(Modifiers::CONTROL),
|
||||
Self::LeftAlt | Self::RightAlt => Some(Modifiers::ALT),
|
||||
Self::LeftSuper | Self::RightSuper => Some(Modifiers::SUPER),
|
||||
Self::LeftHyper | Self::RightHyper => Some(Modifiers::HYPER),
|
||||
Self::LeftMeta | Self::RightMeta => Some(Modifiers::META),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Media key
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub enum MediaKeyCode {
|
||||
Play,
|
||||
Pause,
|
||||
PlayPause,
|
||||
Reverse,
|
||||
Stop,
|
||||
FastForward,
|
||||
Rewind,
|
||||
NextTrack,
|
||||
PreviousTrack,
|
||||
Record,
|
||||
LowerVolume,
|
||||
RaiseVolume,
|
||||
MuteVolume,
|
||||
}
|
||||
1
yazi-term/src/event/mod.rs
Normal file
1
yazi-term/src/event/mod.rs
Normal file
|
|
@ -0,0 +1 @@
|
|||
yazi_macro::mod_flat!(event keyboard modifiers mouse);
|
||||
21
yazi-term/src/event/modifiers.rs
Normal file
21
yazi-term/src/event/modifiers.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use bitflags::bitflags;
|
||||
|
||||
bitflags! {
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Modifiers: u8 {
|
||||
const SHIFT = 1;
|
||||
const ALT = 2;
|
||||
const CONTROL = 4;
|
||||
const SUPER = 8;
|
||||
const HYPER = 16;
|
||||
const META = 32;
|
||||
}
|
||||
}
|
||||
|
||||
impl Modifiers {
|
||||
pub(crate) fn from_vt_mask(mask: u8) -> Self { Self::from_bits_truncate(mask.saturating_sub(1)) }
|
||||
|
||||
pub(crate) fn for_char(c: char) -> Self {
|
||||
if c.is_uppercase() { Self::SHIFT } else { Self::empty() }
|
||||
}
|
||||
}
|
||||
76
yazi-term/src/event/mouse.rs
Normal file
76
yazi-term/src/event/mouse.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use crate::{Result, bail, event::Modifiers};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MouseEvent {
|
||||
pub kind: MouseEventKind,
|
||||
pub column: u16,
|
||||
pub row: u16,
|
||||
pub modifiers: Modifiers,
|
||||
}
|
||||
|
||||
// --- Kind
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MouseEventKind {
|
||||
Down(MouseButton),
|
||||
Up(MouseButton),
|
||||
Drag(MouseButton),
|
||||
Moved,
|
||||
ScrollDown,
|
||||
ScrollUp,
|
||||
ScrollLeft,
|
||||
ScrollRight,
|
||||
}
|
||||
|
||||
impl MouseEventKind {
|
||||
/// Cb is the byte of a mouse input that contains the button being used, the
|
||||
/// key modifiers being held and whether the mouse is dragging or not.
|
||||
///
|
||||
/// Bit layout of cb, from low to high:
|
||||
/// - button number (bits 0–1)
|
||||
/// - shift (bit 2)
|
||||
/// - meta/alt (bit 3)
|
||||
/// - control (bit 4)
|
||||
/// - mouse is dragging (bit 5)
|
||||
/// - button number (bits 6–7)
|
||||
pub(crate) fn from_cb(cb: u8) -> Result<(Self, Modifiers)> {
|
||||
let button = (cb & 0b0000_0011) | ((cb & 0b1100_0000) >> 4);
|
||||
let dragging = cb & 0b0010_0000 == 0b0010_0000;
|
||||
|
||||
let kind = match (button, dragging) {
|
||||
(0, false) => Self::Down(MouseButton::Left),
|
||||
(1, false) => Self::Down(MouseButton::Middle),
|
||||
(2, false) => Self::Down(MouseButton::Right),
|
||||
(0, true) => Self::Drag(MouseButton::Left),
|
||||
(1, true) => Self::Drag(MouseButton::Middle),
|
||||
(2, true) => Self::Drag(MouseButton::Right),
|
||||
(3, false) => Self::Up(MouseButton::Left),
|
||||
(3, true) | (4, true) | (5, true) => Self::Moved,
|
||||
(4, false) => Self::ScrollUp,
|
||||
(5, false) => Self::ScrollDown,
|
||||
(6, false) => Self::ScrollLeft,
|
||||
(7, false) => Self::ScrollRight,
|
||||
_ => bail!(),
|
||||
};
|
||||
|
||||
let mut modifiers = Modifiers::empty();
|
||||
if cb & 0b0000_0100 == 0b0000_0100 {
|
||||
modifiers |= Modifiers::SHIFT;
|
||||
}
|
||||
if cb & 0b0000_1000 == 0b0000_1000 {
|
||||
modifiers |= Modifiers::ALT;
|
||||
}
|
||||
if cb & 0b0001_0000 == 0b0001_0000 {
|
||||
modifiers |= Modifiers::CONTROL;
|
||||
}
|
||||
|
||||
Ok((kind, modifiers))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Button
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MouseButton {
|
||||
Left,
|
||||
Right,
|
||||
Middle,
|
||||
}
|
||||
|
|
@ -1,3 +1,13 @@
|
|||
yazi_macro::mod_flat!(option semaphore state term);
|
||||
mod macros;
|
||||
|
||||
pub fn init() { YIELD_TO_SUBPROCESS.init(tokio::sync::Semaphore::new(1)); }
|
||||
yazi_macro::mod_flat!(dimension error semaphore style term timeout);
|
||||
|
||||
yazi_macro::mod_pub!(event parser restorer sequence source stream terminal);
|
||||
|
||||
pub fn init() -> anyhow::Result<()> {
|
||||
YIELD_TO_SUBPROCESS.init(tokio::sync::Semaphore::new(1));
|
||||
|
||||
TERM.init(terminal::Terminal::new(&yazi_tty::TTY)?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
6
yazi-term/src/macros.rs
Normal file
6
yazi-term/src/macros.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
#[macro_export]
|
||||
macro_rules! bail {
|
||||
() => {
|
||||
return Err($crate::ParseError::Invalid)
|
||||
};
|
||||
}
|
||||
279
yazi-term/src/parser/csi.rs
Normal file
279
yazi-term/src/parser/csi.rs
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
use std::str::{self, FromStr};
|
||||
|
||||
use super::parser::Parser;
|
||||
use crate::{ParseError, Result, bail, event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, Modifiers, MouseEvent, MouseEventKind}};
|
||||
|
||||
impl Parser {
|
||||
pub(super) fn parse_csi(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B["));
|
||||
|
||||
if seq.len() == 2 {
|
||||
return Err(ParseError::Incomplete);
|
||||
}
|
||||
|
||||
let first = seq[2];
|
||||
let second = seq.get(3);
|
||||
let last = seq[seq.len() - 1];
|
||||
|
||||
Ok(match first {
|
||||
b'[' => match second {
|
||||
None => return Err(ParseError::Incomplete),
|
||||
Some(b @ b'A'..=b'E') => Event::Key(KeyCode::Fn(1 + b - b'A').into()),
|
||||
Some(_) => bail!(),
|
||||
},
|
||||
b'D' => Event::Key(KeyCode::Left.into()),
|
||||
b'C' => Event::Key(KeyCode::Right.into()),
|
||||
b'A' => Event::Key(KeyCode::Up.into()),
|
||||
b'B' => Event::Key(KeyCode::Down.into()),
|
||||
b'H' => Event::Key(KeyCode::Home.into()),
|
||||
b'F' => Event::Key(KeyCode::End.into()),
|
||||
b'Z' => Event::Key(KeyEvent::new(KeyCode::Tab, Modifiers::SHIFT)),
|
||||
b'M' => return self.parse_csi_normal_mouse(),
|
||||
b'<' => return self.parse_csi_sgr_mouse(),
|
||||
b'I' => Event::FocusIn,
|
||||
b'O' => Event::FocusOut,
|
||||
b';' => return self.parse_csi_modifier_key(),
|
||||
// P, Q, and S for compatibility with Kitty keyboard protocol,
|
||||
// as the 1 in 'CSI 1 P' etc. must be omitted if there are no
|
||||
// modifiers pressed:
|
||||
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-functional-keys
|
||||
b'P' => Event::Key(KeyCode::Fn(1).into()),
|
||||
b'Q' => Event::Key(KeyCode::Fn(2).into()),
|
||||
b'S' => Event::Key(KeyCode::Fn(4).into()),
|
||||
b'?' => match last {
|
||||
b'u' | b'c' | b'n' | b'y' => return Err(ParseError::Ignored),
|
||||
_ => bail!(),
|
||||
},
|
||||
b'>' => match seq[seq.len() - 2..] {
|
||||
[b' ', b'q'] => return Err(ParseError::Ignored),
|
||||
_ => bail!(),
|
||||
},
|
||||
b'0'..=b'9' if !(64..=126).contains(&last) => return Err(ParseError::Incomplete),
|
||||
b'0'..=b'9' => match last {
|
||||
b'M' => return self.parse_csi_rxvt_mouse(),
|
||||
b'~' => return self.parse_csi_special_key(),
|
||||
b'u' => return self.parse_csi_u_key(),
|
||||
b'R' => return Err(ParseError::Ignored),
|
||||
_ if self.seq.contains(&b';') => return self.parse_csi_modifier_key(),
|
||||
_ => return self.parse_csi_modifier_legacy_key(),
|
||||
},
|
||||
_ => bail!(),
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn parse_csi_u_key(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B[")); // CSI
|
||||
debug_assert!(seq.ends_with(b"u"));
|
||||
|
||||
let s = str::from_utf8(&seq[2..seq.len() - 1])?;
|
||||
let mut it = s.split(';');
|
||||
|
||||
// In `CSI u`, this is parsed as:
|
||||
//
|
||||
// CSI codepoint ; modifiers u
|
||||
// codepoint: ASCII Dec value
|
||||
//
|
||||
// The Kitty Keyboard Protocol extends this with optional components that can be
|
||||
// enabled progressively. The full sequence is parsed as:
|
||||
//
|
||||
// CSI unicode-key-code:alternate-key-codes ; modifiers:event-type ;
|
||||
// text-as-codepoints u
|
||||
let mut codepoints = it.next().ok_or(ParseError::Invalid)?.split(':');
|
||||
|
||||
let (mut code, state_from_keycode) = KeyCode::from_codepoint(parse_next(&mut codepoints)?)?;
|
||||
let (mut modifiers, kind, state_from_modifiers) = parse_mks(&mut it).unwrap_or_default();
|
||||
|
||||
if let KeyCode::Modifier(c) = code
|
||||
&& let Some(m) = c.to_modifier()
|
||||
{
|
||||
modifiers |= m;
|
||||
}
|
||||
|
||||
// When the "report alternate keys" flag is enabled in the Kitty Keyboard
|
||||
// Protocol and the terminal sends a keyboard event containing shift, the
|
||||
// sequence will contain an additional codepoint separated by a ':' character
|
||||
// which contains the shifted character according to the keyboard layout.
|
||||
if modifiers.contains(Modifiers::SHIFT)
|
||||
&& let Ok(Some(shifted)) = parse_next(&mut codepoints).map(char::from_u32)
|
||||
{
|
||||
code = KeyCode::Char(shifted);
|
||||
modifiers.remove(Modifiers::SHIFT);
|
||||
}
|
||||
|
||||
Ok(Event::Key(KeyEvent {
|
||||
code,
|
||||
modifiers,
|
||||
kind,
|
||||
state: state_from_keycode | state_from_modifiers,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Parses `CSI [1;] modifier[:kind] final` — sequences that carry a
|
||||
/// semicolon, e.g. `\x1B[;2A` (Shift+Up, leading 1 omitted) or `\x1B[1;2A`
|
||||
/// (Shift+Up).
|
||||
pub(super) fn parse_csi_modifier_key(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B[")); // CSI
|
||||
|
||||
let s = str::from_utf8(&seq[2..seq.len() - 1])?;
|
||||
let mut it = s.split(';');
|
||||
it.next(); // skip leading "1" or empty string
|
||||
|
||||
let (modifiers, kind, _) = parse_mks(&mut it).unwrap_or_default();
|
||||
let code = KeyCode::from_xterm_modifier(seq[seq.len() - 1])?;
|
||||
Ok(Event::Key(KeyEvent { code, modifiers, kind, state: KeyEventState::empty() }))
|
||||
}
|
||||
|
||||
/// Parses legacy `CSI modifier final` - no semicolon, modifier digit
|
||||
/// immediately before the final byte, e.g. `\x1B[2A` = Shift+Up.
|
||||
pub(super) fn parse_csi_modifier_legacy_key(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B[")); // CSI
|
||||
|
||||
let modifier = seq[seq.len() - 2];
|
||||
if !modifier.is_ascii_digit() {
|
||||
bail!();
|
||||
}
|
||||
|
||||
Ok(Event::Key(KeyEvent {
|
||||
code: KeyCode::from_xterm_modifier(seq[seq.len() - 1])?,
|
||||
modifiers: Modifiers::from_vt_mask(modifier - b'0'),
|
||||
kind: KeyEventKind::Press,
|
||||
state: KeyEventState::empty(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn parse_csi_special_key(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B[")); // CSI
|
||||
debug_assert!(seq.ends_with(b"~"));
|
||||
|
||||
let s = str::from_utf8(&seq[2..seq.len() - 1])?;
|
||||
let mut it = s.split(';');
|
||||
|
||||
// This CSI sequence can be a list of semicolon-separated numbers.
|
||||
let first: u8 = parse_next(&mut it)?;
|
||||
|
||||
let (modifiers, kind, state) = parse_mks(&mut it).unwrap_or_default();
|
||||
|
||||
let code = match first {
|
||||
1 | 7 => KeyCode::Home,
|
||||
2 => KeyCode::Insert,
|
||||
3 => KeyCode::Delete,
|
||||
4 | 8 => KeyCode::End,
|
||||
5 => KeyCode::PageUp,
|
||||
6 => KeyCode::PageDown,
|
||||
v @ 11..=15 => KeyCode::Fn(v - 10),
|
||||
v @ 17..=21 => KeyCode::Fn(v - 11),
|
||||
v @ 23..=26 => KeyCode::Fn(v - 12),
|
||||
v @ 28..=29 => KeyCode::Fn(v - 15),
|
||||
v @ 31..=34 => KeyCode::Fn(v - 17),
|
||||
_ => bail!(),
|
||||
};
|
||||
|
||||
let event = Event::Key(KeyEvent { code, modifiers, kind, state });
|
||||
|
||||
Ok(event)
|
||||
}
|
||||
|
||||
// Parse rxvt mouse: CSI Cb ; Cx ; Cy ; M
|
||||
pub(super) fn parse_csi_rxvt_mouse(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B[")); // CSI
|
||||
debug_assert!(seq.ends_with(b"M"));
|
||||
|
||||
let s = str::from_utf8(&seq[2..seq.len() - 1])?;
|
||||
let mut it = s.split(';');
|
||||
|
||||
let cb = parse_next::<u8>(&mut it)?.checked_sub(32).ok_or(ParseError::Invalid)?;
|
||||
let (kind, modifiers) = MouseEventKind::from_cb(cb)?;
|
||||
|
||||
let column = parse_next::<u16>(&mut it)? - 1;
|
||||
let row = parse_next::<u16>(&mut it)? - 1;
|
||||
|
||||
Ok(Event::Mouse(MouseEvent { kind, column, row, modifiers }))
|
||||
}
|
||||
|
||||
// Parse normal mouse: CSI M CB Cx Cy (6 characters only).
|
||||
pub(super) fn parse_csi_normal_mouse(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B[M")); // CSI M
|
||||
|
||||
if seq.len() < 6 {
|
||||
return Err(ParseError::Incomplete);
|
||||
}
|
||||
|
||||
let cb = seq[3].checked_sub(32).ok_or(ParseError::Invalid)?;
|
||||
let (kind, modifiers) = MouseEventKind::from_cb(cb)?;
|
||||
|
||||
// See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
|
||||
// Mouse positions are encoded as (value + 32), but the upper left
|
||||
// character position on the terminal is denoted as 1,1.
|
||||
// So, we need to subtract 32 + 1 (33) to keep it synced with the cursor.
|
||||
let column = u16::from(seq[4].saturating_sub(33));
|
||||
let row = u16::from(seq[5].saturating_sub(33));
|
||||
|
||||
Ok(Event::Mouse(MouseEvent { kind, column, row, modifiers }))
|
||||
}
|
||||
|
||||
// Parse SGR mouse: CSI < Cb ; Cx ; Cy (;) (M or m)
|
||||
pub(super) fn parse_csi_sgr_mouse(&self) -> Result<Event> {
|
||||
let seq = &self.seq;
|
||||
debug_assert!(seq.starts_with(b"\x1B[<")); // CSI <
|
||||
|
||||
if !seq.ends_with(b"m") && !seq.ends_with(b"M") {
|
||||
return Err(ParseError::Ignored);
|
||||
}
|
||||
|
||||
let s = str::from_utf8(&seq[3..seq.len() - 1])?;
|
||||
let mut it = s.split(';');
|
||||
|
||||
let cb = parse_next(&mut it)?;
|
||||
let (mut kind, modifiers) = MouseEventKind::from_cb(cb)?;
|
||||
|
||||
// See http://www.xfree86.org/current/ctlseqs.html#Mouse%20Tracking
|
||||
// The upper left character position on the terminal is denoted as 1,1.
|
||||
// Subtract 1 to keep it synced with cursor
|
||||
let column = parse_next::<u16>(&mut it)?.checked_sub(1).ok_or(ParseError::Invalid)?;
|
||||
let row = parse_next::<u16>(&mut it)?.checked_sub(1).ok_or(ParseError::Invalid)?;
|
||||
|
||||
// When button 3 in Cb is used to represent mouse release, you can't tell which
|
||||
// button was released. SGR mode solves this by having the sequence end with a
|
||||
// lowercase m if it's a button release and an uppercase M if it's a button
|
||||
// press.
|
||||
//
|
||||
// We've already checked that the last character is a lowercase or uppercase M
|
||||
// at the start of this function, so we just need one if.
|
||||
if seq.ends_with(b"m")
|
||||
&& let MouseEventKind::Down(button) = kind
|
||||
{
|
||||
kind = MouseEventKind::Up(button);
|
||||
}
|
||||
|
||||
Ok(Event::Mouse(MouseEvent { kind, column, row, modifiers }))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_next<'a, T>(iter: &mut impl Iterator<Item = &'a str>) -> Result<T>
|
||||
where
|
||||
T: FromStr,
|
||||
{
|
||||
iter.next().ok_or(ParseError::Invalid)?.parse::<T>().map_err(|_| ParseError::Invalid)
|
||||
}
|
||||
|
||||
fn parse_mks<'a, I>(mut it: I) -> Option<(Modifiers, KeyEventKind, KeyEventState)>
|
||||
where
|
||||
I: Iterator<Item = &'a str>,
|
||||
{
|
||||
let mut it = it.next()?.split(':');
|
||||
let mask: u8 = it.next()?.parse().ok()?;
|
||||
let code: u8 = it.next().and_then(|s| s.parse().ok()).unwrap_or(1);
|
||||
|
||||
Some((
|
||||
Modifiers::from_vt_mask(mask),
|
||||
KeyEventKind::from_vt_code(code),
|
||||
KeyEventState::from_vt_mask(mask),
|
||||
))
|
||||
}
|
||||
24
yazi-term/src/parser/ground.rs
Normal file
24
yazi-term/src/parser/ground.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use crate::{event::{KeyCode, KeyEvent, Modifiers}, parser::Parser};
|
||||
|
||||
impl Parser {
|
||||
/// Maps a ground-state byte to a [`KeyEvent`], returning [`None`] for ESC
|
||||
/// (`\x1B`), multi-byte UTF-8 lead bytes, and continuation / invalid bytes.
|
||||
pub(super) fn parse_ground_key(b: u8) -> Option<KeyEvent> {
|
||||
match b {
|
||||
b'\r' => Some(KeyCode::Enter.into()),
|
||||
b'\t' => Some(KeyCode::Tab.into()),
|
||||
b'\x7F' => Some(KeyCode::Backspace.into()),
|
||||
b'\0' => Some(KeyEvent::new(KeyCode::Char(' '), Modifiers::CONTROL)),
|
||||
c @ b'\x01'..=b'\x1A' => {
|
||||
Some(KeyEvent::new(KeyCode::Char((c - 0x1 + b'a') as char), Modifiers::CONTROL))
|
||||
}
|
||||
c @ b'\x1C'..=b'\x1F' => {
|
||||
Some(KeyEvent::new(KeyCode::Char((c - 0x1c + b'4') as char), Modifiers::CONTROL))
|
||||
}
|
||||
b'\x20'..=b'\x7E' => {
|
||||
Some(KeyEvent::new(KeyCode::Char(b as char), Modifiers::for_char(b as char)))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
4
yazi-term/src/parser/mod.rs
Normal file
4
yazi-term/src/parser/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
yazi_macro::mod_flat!(csi ground parser state);
|
||||
|
||||
#[cfg(windows)]
|
||||
yazi_macro::mod_flat!(windows);
|
||||
260
yazi-term/src/parser/parser.rs
Normal file
260
yazi-term/src/parser/parser.rs
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
use std::{collections::VecDeque, num::NonZeroU8, str};
|
||||
|
||||
use yazi_shim::utf8_char_width;
|
||||
|
||||
use super::state::State;
|
||||
use crate::event::{Event, KeyCode, KeyEvent, Modifiers};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Parser {
|
||||
state: State,
|
||||
pub(super) seq: Vec<u8>,
|
||||
pub(crate) events: VecDeque<Event>,
|
||||
}
|
||||
|
||||
impl Default for Parser {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
state: State::Ground,
|
||||
seq: Vec::with_capacity(64),
|
||||
events: VecDeque::with_capacity(32),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn parse(&mut self, bytes: &[u8]) {
|
||||
for &b in bytes {
|
||||
self.step(b);
|
||||
}
|
||||
}
|
||||
|
||||
fn step(&mut self, b: u8) {
|
||||
match self.state {
|
||||
State::Ground => self.on_ground(b),
|
||||
State::Esc => self.on_esc(b),
|
||||
State::EscO => self.on_esco(b),
|
||||
State::Csi => self.on_csi(b),
|
||||
State::NormalMouse => self.on_normal_mouse(b),
|
||||
State::BracketedPaste => self.on_bracketed_paste(b),
|
||||
State::Osc | State::OscSt => self.on_osc(b),
|
||||
State::Dcs | State::DcsSt => self.on_dcs(b),
|
||||
State::Utf8(n) => self.on_utf8(b, n),
|
||||
State::AltUtf8(n) => self.on_alt_utf8(b, n),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve any pending ambiguous state.
|
||||
///
|
||||
/// Call this when no more input bytes are immediately available. If the
|
||||
/// parser is waiting in the [`State::Esc`] state (a lone `\x1B` has
|
||||
/// been seen but no follow-up bytes arrived), this emits a bare
|
||||
/// [`KeyCode::Escape`] event and resets to [`State::Ground`].
|
||||
pub fn flush(&mut self) {
|
||||
if self.state == State::Esc {
|
||||
self.emit_key(KeyCode::Escape);
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.state = State::Ground;
|
||||
self.seq.clear();
|
||||
}
|
||||
|
||||
pub(crate) fn emit(&mut self, event: impl Into<Event>) { self.events.push_back(event.into()); }
|
||||
|
||||
fn emit_key(&mut self, event: impl Into<KeyEvent>) { self.emit(Event::Key(event.into())); }
|
||||
|
||||
pub fn pop(&mut self) -> Option<Event> { self.events.pop_front() }
|
||||
|
||||
fn on_ground(&mut self, b: u8) {
|
||||
match b {
|
||||
b'\x1B' => {
|
||||
self.seq.clear();
|
||||
self.seq.push(b'\x1B');
|
||||
self.state = State::Esc;
|
||||
}
|
||||
_ if let Some(key) = Self::parse_ground_key(b) => {
|
||||
self.emit_key(key);
|
||||
}
|
||||
_ if let w @ 2..=4 = utf8_char_width(b) => {
|
||||
self.seq.clear();
|
||||
self.seq.push(b);
|
||||
self.state = State::Utf8(NonZeroU8::new(w - 1).unwrap());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_esc(&mut self, b: u8) {
|
||||
self.seq.push(b);
|
||||
match b {
|
||||
b'[' => self.state = State::Csi,
|
||||
b']' => self.state = State::Osc,
|
||||
b'P' => self.state = State::Dcs,
|
||||
b'O' => self.state = State::EscO,
|
||||
// ESC ESC: emit Escape for the first byte, the second is consumed.
|
||||
b'\x1B' => {
|
||||
self.emit_key(KeyCode::Escape);
|
||||
self.reset();
|
||||
}
|
||||
// Alt + <key>
|
||||
_ if let Some(mut key) = Self::parse_ground_key(b) => {
|
||||
key.modifiers |= Modifiers::ALT;
|
||||
self.emit_key(key);
|
||||
self.reset();
|
||||
}
|
||||
// seq = [ESC, b], collect w-1 more continuation bytes.
|
||||
_ if let w @ 2..=4 = utf8_char_width(b) => {
|
||||
self.state = State::AltUtf8(NonZeroU8::new(w - 1).unwrap());
|
||||
}
|
||||
// invalid byte — discard
|
||||
_ => self.reset(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_esco(&mut self, b: u8) {
|
||||
self.seq.push(b);
|
||||
let event = match b {
|
||||
b'A' => Event::Key(KeyCode::Up.into()),
|
||||
b'B' => Event::Key(KeyCode::Down.into()),
|
||||
b'C' => Event::Key(KeyCode::Right.into()),
|
||||
b'D' => Event::Key(KeyCode::Left.into()),
|
||||
b'F' => Event::Key(KeyCode::End.into()),
|
||||
b'H' => Event::Key(KeyCode::Home.into()),
|
||||
v @ b'P'..=b'S' => Event::Key(KeyCode::Fn(1 + v - b'P').into()),
|
||||
_ => {
|
||||
self.reset();
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.emit(event);
|
||||
self.reset();
|
||||
}
|
||||
|
||||
fn on_csi(&mut self, b: u8) {
|
||||
self.seq.push(b);
|
||||
|
||||
// Bytes below 0x40 are parameter or intermediate bytes; keep accumulating.
|
||||
if !(0x40..=0x7e).contains(&b) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The Linux console sends `\x1B[[x` for F1–F5. The inner `[` (0x5B) is
|
||||
// technically in the final-byte range but must not trigger early dispatch.
|
||||
if b == b'[' && self.seq.len() == 3 {
|
||||
return;
|
||||
}
|
||||
|
||||
// `\x1B[M` (no params) = X10 normal-mouse encoding; 3 raw bytes follow.
|
||||
if b == b'M' && self.seq.len() == 3 {
|
||||
self.state = State::NormalMouse;
|
||||
return;
|
||||
}
|
||||
|
||||
// `\x1B[200~` = start of bracketed-paste mode.
|
||||
if self.seq == b"\x1B[200~" {
|
||||
self.state = State::BracketedPaste;
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(e) = self.parse_csi() {
|
||||
self.emit(e);
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
|
||||
fn on_normal_mouse(&mut self, b: u8) {
|
||||
self.seq.push(b);
|
||||
|
||||
// Need `\x1B[M` + 3 raw bytes = 6 bytes total.
|
||||
if self.seq.len() < 6 {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(e) = self.parse_csi_normal_mouse() {
|
||||
self.emit(e);
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
|
||||
fn on_bracketed_paste(&mut self, b: u8) {
|
||||
self.seq.push(b);
|
||||
|
||||
if self.seq.ends_with(b"\x1b[201~") {
|
||||
// seq = b"\x1B[200~" + paste_content + b"\x1B[201~"
|
||||
let paste = String::from_utf8_lossy(&self.seq[6..self.seq.len() - 6]).into_owned();
|
||||
self.emit(Event::Paste(paste));
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_osc(&mut self, b: u8) {
|
||||
self.seq.push(b);
|
||||
|
||||
match (self.state, b) {
|
||||
(State::Osc, b'\x07') => self.reset(), // BEL — OSC complete (discard)
|
||||
(State::Osc, b'\x1B') => self.state = State::OscSt,
|
||||
(State::Osc, _) => {} // keep accumulating
|
||||
(State::OscSt, b'\\') => self.reset(), // ST (`\x1B\\`) — OSC complete (discard)
|
||||
(State::OscSt, b'\x1B') => {} // another ESC — stay in OscSt
|
||||
(State::OscSt, _) => self.state = State::Osc, // not ST — resume OSC
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_dcs(&mut self, b: u8) {
|
||||
self.seq.push(b);
|
||||
|
||||
match (self.state, b) {
|
||||
(State::Dcs, b'\x1B') => self.state = State::DcsSt,
|
||||
(State::Dcs, _) => {}
|
||||
(State::DcsSt, b'\\') => self.reset(), // ST — DCS complete (discard)
|
||||
(State::DcsSt, b'\x1B') => {} // another ESC — stay in DcsSt
|
||||
(State::DcsSt, _) => self.state = State::Dcs, // not ST — resume DCS
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn on_utf8(&mut self, b: u8, remaining: NonZeroU8) {
|
||||
if b & 0xc0 != 0x80 {
|
||||
self.reset();
|
||||
self.on_ground(b);
|
||||
return; // Not a continuation byte — abandon the sequence and retry from ground.
|
||||
}
|
||||
|
||||
self.seq.push(b);
|
||||
if remaining.get() != 1 {
|
||||
return self.state = State::Utf8(NonZeroU8::new(remaining.get() - 1).unwrap());
|
||||
}
|
||||
|
||||
if let Ok(s) = str::from_utf8(&self.seq)
|
||||
&& let Some(c) = s.chars().next()
|
||||
{
|
||||
self.emit_key(KeyEvent::new(KeyCode::Char(c), Modifiers::for_char(c)));
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
|
||||
fn on_alt_utf8(&mut self, b: u8, remaining: NonZeroU8) {
|
||||
if b & 0xc0 != 0x80 {
|
||||
self.reset();
|
||||
self.on_ground(b);
|
||||
return; // Not a continuation byte — abandon Alt+UTF-8 and retry from ground.
|
||||
}
|
||||
|
||||
self.seq.push(b);
|
||||
if remaining.get() != 1 {
|
||||
return self.state = State::AltUtf8(NonZeroU8::new(remaining.get() - 1).unwrap());
|
||||
}
|
||||
|
||||
// seq = [ESC, …utf8_bytes…]
|
||||
if let Ok(s) = str::from_utf8(&self.seq[1..])
|
||||
&& let Some(c) = s.chars().next()
|
||||
{
|
||||
self.emit_key(KeyEvent::new(KeyCode::Char(c), Modifiers::ALT | Modifiers::for_char(c)));
|
||||
}
|
||||
self.reset();
|
||||
}
|
||||
}
|
||||
29
yazi-term/src/parser/state.rs
Normal file
29
yazi-term/src/parser/state.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use std::num::NonZeroU8;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub(crate) enum State {
|
||||
/// Normal ground state.
|
||||
Ground,
|
||||
/// Saw `\x1B`, waiting for the next byte.
|
||||
Esc,
|
||||
/// Saw `\x1B O`, waiting for the SS3 key byte.
|
||||
EscO,
|
||||
/// Inside a CSI sequence (`\x1B[` … final-byte in 0x40–0x7E).
|
||||
Csi,
|
||||
/// Saw `\x1B[M`, collecting 3 raw mouse bytes (CB Cx Cy).
|
||||
NormalMouse,
|
||||
/// Inside bracketed paste (`\x1B[200~` … `\x1B[201~`).
|
||||
BracketedPaste,
|
||||
/// Inside an OSC sequence (`\x1B]` … BEL or ST).
|
||||
Osc,
|
||||
/// Inside OSC, just saw `\x1B` (potential start of ST = `\x1B\\`).
|
||||
OscSt,
|
||||
/// Inside a DCS sequence (`\x1BP` … ST).
|
||||
Dcs,
|
||||
/// Inside DCS, just saw `\x1B` (potential start of ST).
|
||||
DcsSt,
|
||||
/// Mid-UTF-8 character in ground: `n` continuation bytes still needed.
|
||||
Utf8(NonZeroU8),
|
||||
/// `\x1B` + mid-UTF-8 character: `n` continuation bytes still needed.
|
||||
AltUtf8(NonZeroU8),
|
||||
}
|
||||
36
yazi-term/src/parser/windows.rs
Normal file
36
yazi-term/src/parser/windows.rs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
use windows_sys::Win32::System::Console;
|
||||
|
||||
use crate::{Dimension, event::Event, parser::Parser};
|
||||
|
||||
impl Parser {
|
||||
pub(crate) fn parse_input_records(&mut self, records: &[Console::INPUT_RECORD]) {
|
||||
for record in records {
|
||||
match record.EventType as u32 {
|
||||
Console::KEY_EVENT => {
|
||||
let event = unsafe { record.Event.KeyEvent };
|
||||
let ch = unsafe { event.uChar.AsciiChar } as u8;
|
||||
|
||||
if event.bKeyDown != 0 && ch != 0 {
|
||||
self.parse(&[ch]);
|
||||
}
|
||||
}
|
||||
Console::WINDOW_BUFFER_SIZE_EVENT => {
|
||||
let event = unsafe { record.Event.WindowBufferSizeEvent };
|
||||
if event.dwSize.X <= 0 || event.dwSize.Y <= 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.events.push_back(Event::Resize(Dimension {
|
||||
rows: event.dwSize.Y as u16,
|
||||
cols: event.dwSize.X as u16,
|
||||
width: 0,
|
||||
height: 0,
|
||||
}));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
5
yazi-term/src/restorer/mod.rs
Normal file
5
yazi-term/src/restorer/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#[cfg(unix)]
|
||||
yazi_macro::mod_flat!(unix);
|
||||
|
||||
#[cfg(windows)]
|
||||
yazi_macro::mod_flat!(windows);
|
||||
20
yazi-term/src/restorer/unix.rs
Normal file
20
yazi-term/src/restorer/unix.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use std::io::{self, Write};
|
||||
|
||||
use rustix::termios::{self, OptionalActions, Termios};
|
||||
use yazi_tty::Tty;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Restorer {
|
||||
pub(crate) termios: Termios,
|
||||
}
|
||||
|
||||
impl Restorer {
|
||||
pub(crate) fn new(tty: &Tty) -> io::Result<Self> {
|
||||
Ok(Self { termios: rustix::termios::tcgetattr(tty.writer())? })
|
||||
}
|
||||
|
||||
pub fn restore(&self, tty: &Tty) {
|
||||
tty.writer().flush().ok();
|
||||
termios::tcsetattr(tty.writer(), OptionalActions::Now, &self.termios).ok();
|
||||
}
|
||||
}
|
||||
38
yazi-term/src/restorer/windows.rs
Normal file
38
yazi-term/src/restorer/windows.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use std::{io::{self, Write}, os::windows::io::AsRawHandle};
|
||||
|
||||
use windows_sys::Win32::System::Console::{CONSOLE_MODE, GetConsoleCP, GetConsoleMode, GetConsoleOutputCP, SetConsoleCP, SetConsoleMode, SetConsoleOutputCP};
|
||||
use yazi_shim::{bool_ok, nz_ok};
|
||||
use yazi_tty::Tty;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Restorer {
|
||||
pub(crate) input_mode: CONSOLE_MODE,
|
||||
pub(crate) output_mode: CONSOLE_MODE,
|
||||
pub(crate) input_cp: u32,
|
||||
pub(crate) output_cp: u32,
|
||||
}
|
||||
|
||||
impl Restorer {
|
||||
pub(crate) fn new(tty: &Tty) -> io::Result<Self> {
|
||||
let mut input_mode = 0;
|
||||
bool_ok(unsafe { GetConsoleMode(tty.reader().as_raw_handle(), &mut input_mode) })?;
|
||||
|
||||
let mut output_mode = 0;
|
||||
bool_ok(unsafe { GetConsoleMode(tty.writer().as_raw_handle(), &mut output_mode) })?;
|
||||
|
||||
let input_cp = nz_ok(unsafe { GetConsoleCP() })?;
|
||||
let output_cp = nz_ok(unsafe { GetConsoleOutputCP() })?;
|
||||
|
||||
Ok(Self { input_mode, output_mode, input_cp, output_cp })
|
||||
}
|
||||
|
||||
pub fn restore(&self, tty: &Tty) {
|
||||
tty.writer().flush().ok();
|
||||
unsafe {
|
||||
SetConsoleCP(self.input_cp);
|
||||
SetConsoleMode(tty.reader().as_raw_handle(), self.input_mode);
|
||||
SetConsoleOutputCP(self.output_cp);
|
||||
SetConsoleMode(tty.writer().as_raw_handle(), self.output_mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
yazi-term/src/sequence/clipboard.rs
Normal file
20
yazi-term/src/sequence/clipboard.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use std::fmt::{self, Display};
|
||||
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
|
||||
/// Set clipboard content via OSC 52
|
||||
pub struct SetClipboard {
|
||||
content: String,
|
||||
}
|
||||
|
||||
impl SetClipboard {
|
||||
pub fn new(content: impl AsRef<[u8]>) -> Self {
|
||||
Self { content: general_purpose::STANDARD.encode(content) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SetClipboard {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\x1b]52;c;{}\x1b\\", self.content)
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue