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"
|
chrono = "0.4.44"
|
||||||
clap = { version = "4.6.1", features = [ "derive" ] }
|
clap = { version = "4.6.1", features = [ "derive" ] }
|
||||||
core-foundation-sys = "0.8.7"
|
core-foundation-sys = "0.8.7"
|
||||||
crossterm = { version = "0.29.0", features = [ "event-stream" ] }
|
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
dyn-clone = "1.0.20"
|
dyn-clone = "1.0.20"
|
||||||
either = { version = "1.15.0" }
|
either = { version = "1.16.0" }
|
||||||
foldhash = "0.2.0"
|
foldhash = "0.2.0"
|
||||||
futures = "0.3.32"
|
futures = "0.3.32"
|
||||||
globset = "0.4.18"
|
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" ] }
|
indexmap = { version = "2.14.0", features = [ "serde" ] }
|
||||||
libc = "0.2.186"
|
libc = "0.2.186"
|
||||||
lru = "0.18.0"
|
lru = "0.18.0"
|
||||||
|
|
@ -61,13 +60,13 @@ parking_lot = "0.12.5"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
percent-encoding = "2.3.2"
|
percent-encoding = "2.3.2"
|
||||||
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
|
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"
|
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"
|
scopeguard = "1.2.0"
|
||||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||||
serde_json = "1.0.149"
|
serde_json = "1.0.149"
|
||||||
serde_with = "3.19.0"
|
serde_with = "3.20.0"
|
||||||
strum = { version = "0.28.0", features = [ "derive" ] }
|
strum = { version = "0.28.0", features = [ "derive" ] }
|
||||||
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
syntect = { version = "5.3.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||||
thiserror = "2.0.18"
|
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-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
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-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
|
|
@ -55,6 +55,3 @@ tracing = { workspace = true }
|
||||||
|
|
||||||
[target."cfg(unix)".dependencies]
|
[target."cfg(unix)".dependencies]
|
||||||
libc = { workspace = true }
|
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 anyhow::Result;
|
||||||
use crossterm::event::MouseEventKind;
|
|
||||||
use mlua::{ObjectLike, Table};
|
use mlua::{ObjectLike, Table};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use yazi_actor::lives::Lives;
|
use yazi_actor::lives::Lives;
|
||||||
|
|
@ -8,6 +7,7 @@ use yazi_macro::succ;
|
||||||
use yazi_parser::app::MouseForm;
|
use yazi_parser::app::MouseForm;
|
||||||
use yazi_plugin::LUA;
|
use yazi_plugin::LUA;
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::data::Data;
|
||||||
|
use yazi_term::event::MouseEventKind;
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
|
use std::process;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use yazi_boot::ARGS;
|
use yazi_boot::ARGS;
|
||||||
use yazi_fs::provider::{Provider, local::Local};
|
use yazi_fs::provider::{Provider, local::Local};
|
||||||
use yazi_parser::app::QuitForm;
|
use yazi_parser::app::QuitForm;
|
||||||
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
|
use yazi_shared::{data::Data, strand::{StrandBuf, StrandLike, ToStrand}};
|
||||||
use yazi_term::Term;
|
use yazi_tui::Raterm;
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
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 anyhow::Result;
|
||||||
use yazi_macro::{act, render, succ};
|
use yazi_macro::{act, render, succ};
|
||||||
use yazi_parser::app::ResumeForm;
|
use yazi_parser::app::ReflowForm;
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::data::Data;
|
||||||
use yazi_term::Term;
|
use yazi_tui::Raterm;
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
||||||
pub struct Resume;
|
pub struct Resume;
|
||||||
|
|
||||||
impl Actor for Resume {
|
impl Actor for Resume {
|
||||||
type Form = ResumeForm;
|
type Form = ReflowForm;
|
||||||
|
|
||||||
const NAME: &str = "resume";
|
const NAME: &str = "resume";
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
cx.active_mut().preview.reset();
|
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.
|
// 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.
|
// We need to trigger a resize, and render the UI based on the resized area.
|
||||||
act!(app:resize, cx, form.reflow)?;
|
act!(app:resize, cx, form.reflow)?;
|
||||||
|
|
||||||
form.tx.send((true, form.replier))?;
|
|
||||||
|
|
||||||
act!(app:title, cx).ok();
|
act!(app:title, cx).ok();
|
||||||
succ!(render!());
|
succ!(render!());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use yazi_macro::succ;
|
use yazi_macro::succ;
|
||||||
use yazi_parser::app::StopForm;
|
use yazi_parser::VoidForm;
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::data::Data;
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
@ -8,20 +8,15 @@ use crate::{Actor, Ctx};
|
||||||
pub struct Stop;
|
pub struct Stop;
|
||||||
|
|
||||||
impl Actor for Stop {
|
impl Actor for Stop {
|
||||||
type Form = StopForm;
|
type Form = VoidForm;
|
||||||
|
|
||||||
const NAME: &str = "stop";
|
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();
|
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;
|
*cx.term = None;
|
||||||
|
|
||||||
form.tx.send((false, form.replier))?;
|
|
||||||
|
|
||||||
succ!();
|
succ!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{execute, terminal::SetTitle};
|
|
||||||
use yazi_actor::Ctx;
|
use yazi_actor::Ctx;
|
||||||
use yazi_macro::succ;
|
use yazi_macro::{succ, writef};
|
||||||
use yazi_parser::{app::TitleForm, spark::SparkKind};
|
use yazi_parser::{app::TitleForm, spark::SparkKind};
|
||||||
use yazi_shared::{Source, data::Data};
|
use yazi_shared::{Source, data::Data};
|
||||||
use yazi_term::TermState;
|
use yazi_term::sequence::SetTitle;
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
|
use yazi_tui::RatermState;
|
||||||
|
|
||||||
use crate::Actor;
|
use crate::Actor;
|
||||||
|
|
||||||
|
|
@ -18,9 +18,9 @@ impl Actor for Title {
|
||||||
|
|
||||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
let s = form.value.unwrap_or_else(|| format!("Yazi: {}", cx.tab().name()).into());
|
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!()
|
succ!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,11 @@ use anyhow::{Result, anyhow};
|
||||||
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}};
|
use yazi_core::{Core, mgr::Tabs, tab::{Folder, Tab}};
|
||||||
use yazi_fs::File;
|
use yazi_fs::File;
|
||||||
use yazi_shared::{Id, Source, event::Action, url::UrlBuf};
|
use yazi_shared::{Id, Source, event::Action, url::UrlBuf};
|
||||||
use yazi_term::Term;
|
use yazi_tui::Raterm;
|
||||||
|
|
||||||
pub struct Ctx<'a> {
|
pub struct Ctx<'a> {
|
||||||
pub core: &'a mut Core,
|
pub core: &'a mut Core,
|
||||||
pub term: &'a mut Option<Term>,
|
pub term: &'a mut Option<Raterm>,
|
||||||
pub tab: usize,
|
pub tab: usize,
|
||||||
pub level: usize,
|
pub level: usize,
|
||||||
pub source: Source,
|
pub source: Source,
|
||||||
|
|
@ -27,7 +27,7 @@ impl DerefMut for Ctx<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Ctx<'a> {
|
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") {
|
let tab = if let Ok(id) = action.get::<Id>("tab") {
|
||||||
core
|
core
|
||||||
.mgr
|
.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;
|
let tab = core.mgr.tabs.cursor;
|
||||||
Self {
|
Self {
|
||||||
core,
|
core,
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ use scopeguard::defer;
|
||||||
use yazi_binding::Permit;
|
use yazi_binding::Permit;
|
||||||
use yazi_config::{YAZI, opener::OpenerRule};
|
use yazi_config::{YAZI, opener::OpenerRule};
|
||||||
use yazi_fs::{File, FilesOp, Splatter, provider::{Provider, local::Local}};
|
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_parser::VoidForm;
|
||||||
use yazi_proxy::TasksProxy;
|
use yazi_proxy::TasksProxy;
|
||||||
use yazi_scheduler::{AppProxy, NotifyProxy};
|
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_shim::path::CROSS_SEPARATOR;
|
||||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
use yazi_vfs::{VfsFile, provider};
|
use yazi_vfs::{VfsFile, provider};
|
||||||
use yazi_watcher::WATCHER;
|
use yazi_watcher::WATCHER;
|
||||||
|
|
@ -61,7 +61,7 @@ impl Actor for BulkCreate {
|
||||||
|
|
||||||
impl BulkCreate {
|
impl BulkCreate {
|
||||||
async fn r#do(cwd: UrlBuf, todo: Vec<Entry<'_>>) -> Result<()> {
|
async fn r#do(cwd: UrlBuf, todo: Vec<Entry<'_>>) -> Result<()> {
|
||||||
terminal_clear(TTY.writer())?;
|
writef!(TTY.writer(), "{EraseScreen}\n")?;
|
||||||
if todo.is_empty() {
|
if todo.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else if !Self::ask_continue(&todo, None)? {
|
} else if !Self::ask_continue(&todo, None)? {
|
||||||
|
|
@ -134,7 +134,7 @@ impl BulkCreate {
|
||||||
|
|
||||||
async fn output_failed(failed: Vec<(Entry<'_>, anyhow::Error)>) -> Result<()> {
|
async fn output_failed(failed: Vec<(Entry<'_>, anyhow::Error)>) -> Result<()> {
|
||||||
let mut stdout = TTY.lockout();
|
let mut stdout = TTY.lockout();
|
||||||
terminal_clear(&mut *stdout)?;
|
writeln!(stdout, "{EraseScreen}")?;
|
||||||
|
|
||||||
writeln!(stdout, "Failed to create:")?;
|
writeln!(stdout, "Failed to create:")?;
|
||||||
for (entry, err) in failed {
|
for (entry, err) in failed {
|
||||||
|
|
@ -161,7 +161,7 @@ impl<'a> Entry<'a> {
|
||||||
None => (s, false),
|
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 std::{hash::Hash, io::{Read, Write}, ops::Deref, path::Path, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{Result, anyhow};
|
use anyhow::{Result, anyhow};
|
||||||
use crossterm::{execute, style::Print};
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
@ -9,12 +8,12 @@ use yazi_binding::Permit;
|
||||||
use yazi_config::{YAZI, opener::OpenerRule};
|
use yazi_config::{YAZI, opener::OpenerRule};
|
||||||
use yazi_dds::Pubsub;
|
use yazi_dds::Pubsub;
|
||||||
use yazi_fs::{File, FilesOp, Splatter, max_common_root, path::skip_url, provider::{FileBuilder, Provider, local::{Gate, Local}}};
|
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_parser::VoidForm;
|
||||||
use yazi_proxy::TasksProxy;
|
use yazi_proxy::TasksProxy;
|
||||||
use yazi_scheduler::{AppProxy, NotifyProxy};
|
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_shared::{data::Data, path::PathDyn, strand::{AsStrand, AsStrandJoin, Strand, StrandBuf, StrandLike}, url::{AsUrl, UrlBuf, UrlCow, UrlLike}};
|
||||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
use yazi_vfs::{VfsFile, maybe_exists, provider};
|
||||||
use yazi_watcher::WATCHER;
|
use yazi_watcher::WATCHER;
|
||||||
|
|
@ -100,11 +99,10 @@ impl BulkRename {
|
||||||
selected: Vec<UrlBuf>,
|
selected: Vec<UrlBuf>,
|
||||||
decision: Option<bool>,
|
decision: Option<bool>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
terminal_clear(TTY.writer())?;
|
writef!(TTY.writer(), "{EraseScreen}\n")?;
|
||||||
if old.len() != new.len() {
|
if old.len() != new.len() {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
let s = format!("Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len());
|
writef!(TTY.writer(), "Number of new and old file names mismatch (New: {}, Old: {}).\nPress <Enter> to exit...", new.len(), old.len())?;
|
||||||
execute!(TTY.writer(), Print(s))?;
|
|
||||||
|
|
||||||
TTY.reader().read_exact(&mut [0])?;
|
TTY.reader().read_exact(&mut [0])?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -186,7 +184,7 @@ impl BulkRename {
|
||||||
|
|
||||||
async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {
|
async fn output_failed(failed: Vec<(Tuple, Tuple, anyhow::Error)>) -> Result<()> {
|
||||||
let mut stdout = TTY.lockout();
|
let mut stdout = TTY.lockout();
|
||||||
terminal_clear(&mut *stdout)?;
|
writeln!(stdout, "{EraseScreen}")?;
|
||||||
|
|
||||||
writeln!(stdout, "Failed to rename:")?;
|
writeln!(stdout, "Failed to rename:")?;
|
||||||
for (old, new, err) in failed {
|
for (old, new, err) in failed {
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,6 @@ impl Actor for Close {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hook(cx: &Ctx, _form: &Self::Form) -> Option<SparkKind> {
|
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> {
|
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 anyhow::Result;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use yazi_core::notify::Notify;
|
use yazi_core::notify::Notify;
|
||||||
use yazi_emulator::Dimension;
|
|
||||||
use yazi_macro::{render, render_partial, succ};
|
use yazi_macro::{render, render_partial, succ};
|
||||||
use yazi_parser::notify::TickForm;
|
use yazi_parser::notify::TickForm;
|
||||||
use yazi_proxy::NotifyProxy;
|
use yazi_proxy::NotifyProxy;
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::data::Data;
|
||||||
|
use yazi_term::{Dimension, TERM};
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
||||||
|
|
@ -21,8 +21,8 @@ impl Actor for Tick {
|
||||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||||
cx.notify.ticker.take().map(|h| h.abort());
|
cx.notify.ticker.take().map(|h| h.abort());
|
||||||
|
|
||||||
let Dimension { rows, columns, .. } = Dimension::available();
|
let Dimension { rows, cols, .. } = TERM.dimension();
|
||||||
let area = Notify::available(Rect { x: 0, y: 0, width: columns, height: rows });
|
let area = Notify::available(Rect { x: 0, y: 0, width: cols, height: rows });
|
||||||
|
|
||||||
let limit = cx.notify.limit(area);
|
let limit = cx.notify.limit(area);
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{execute, terminal::{disable_raw_mode, enable_raw_mode}};
|
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
||||||
use yazi_binding::Permit;
|
use yazi_binding::Permit;
|
||||||
use yazi_macro::succ;
|
use yazi_term::TERM;
|
||||||
|
use yazi_macro::{succ, writef};
|
||||||
use yazi_parser::VoidForm;
|
use yazi_parser::VoidForm;
|
||||||
use yazi_scheduler::AppProxy;
|
use yazi_scheduler::AppProxy;
|
||||||
use yazi_shared::{data::Data, terminal_clear};
|
use yazi_shared::data::Data;
|
||||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
@ -39,24 +39,27 @@ impl Actor for Inspect {
|
||||||
task.logs.clone()
|
task.logs.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Stop the app and clear the terminal
|
||||||
AppProxy::stop().await;
|
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().write_all(buffered.as_bytes()).ok();
|
||||||
TTY.writer().flush().ok();
|
TTY.writer().flush().ok();
|
||||||
|
|
||||||
defer! { disable_raw_mode().ok(); }
|
defer! { TERM.enter_cooked_mode().ok(); }
|
||||||
enable_raw_mode().ok();
|
TERM.enter_raw_mode().ok();
|
||||||
|
|
||||||
let mut stdin = stdin(); // TODO: stdin
|
let mut stdin = stdin(); // TODO: stdin
|
||||||
let mut answer = 0;
|
let mut answer = 0;
|
||||||
loop {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
Some(line) = rx.recv() => {
|
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)) => {
|
_ = time::sleep(time::Duration::from_millis(500)) => {
|
||||||
if !ongoing.lock().exists(id) {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,18 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", 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-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
ansi-to-tui = { workspace = true }
|
ansi-to-tui = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
base64 = { 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" ] }
|
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"
|
moxcms = "0.8.1"
|
||||||
palette = { version = "0.7.6", default-features = false }
|
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 }
|
ratatui = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { 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 ansi_to_tui::IntoText;
|
||||||
use anyhow::{Result, anyhow, bail};
|
use anyhow::{Result, anyhow, bail};
|
||||||
use crossterm::{cursor::MoveTo, queue};
|
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use yazi_emulator::Emulator;
|
use yazi_emulator::Emulator;
|
||||||
|
use yazi_term::sequence::MoveTo;
|
||||||
|
|
||||||
use crate::Adapter;
|
use crate::Adapter;
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ impl Chafa {
|
||||||
Emulator::move_lock((max.x, max.y), |w| {
|
Emulator::move_lock((max.x, max.y), |w| {
|
||||||
for (i, line) in lines.into_iter().enumerate() {
|
for (i, line) in lines.into_iter().enumerate() {
|
||||||
w.write_all(line)?;
|
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)
|
Ok(area)
|
||||||
})
|
})
|
||||||
|
|
@ -72,7 +72,7 @@ impl Chafa {
|
||||||
let s = " ".repeat(area.width as usize);
|
let s = " ".repeat(area.width as usize);
|
||||||
Emulator::move_lock((0, 0), |w| {
|
Emulator::move_lock((0, 0), |w| {
|
||||||
for y in area.top()..area.bottom() {
|
for y in area.top()..area.bottom() {
|
||||||
queue!(w, MoveTo(area.x, y))?;
|
write!(w, "{}", MoveTo(area.x, y))?;
|
||||||
write!(w, "{s}")?;
|
write!(w, "{s}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@ use std::{fmt::Write, io::Write as ioWrite, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
|
use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
|
||||||
use crossterm::{cursor::MoveTo, queue};
|
|
||||||
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
|
use image::{DynamicImage, ExtendedColorType, ImageEncoder, codecs::{jpeg::JpegEncoder, png::PngEncoder}};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use yazi_config::YAZI;
|
use yazi_config::YAZI;
|
||||||
use yazi_emulator::{CLOSE, Emulator, START};
|
use yazi_emulator::{CLOSE, Emulator, START};
|
||||||
|
use yazi_term::sequence::MoveTo;
|
||||||
|
|
||||||
use crate::{Image, adapter::Adapter};
|
use crate::{Image, adapter::Adapter};
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ impl Iip {
|
||||||
let s = " ".repeat(area.width as usize);
|
let s = " ".repeat(area.width as usize);
|
||||||
Emulator::move_lock((0, 0), |w| {
|
Emulator::move_lock((0, 0), |w| {
|
||||||
for y in area.top()..area.bottom() {
|
for y in area.top()..area.bottom() {
|
||||||
queue!(w, MoveTo(area.x, y))?;
|
write!(w, "{}", MoveTo(area.x, y))?;
|
||||||
write!(w, "{s}")?;
|
write!(w, "{s}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use base64::{Engine, engine::general_purpose};
|
use base64::{Engine, engine::general_purpose};
|
||||||
use crossterm::{cursor::MoveTo, queue};
|
|
||||||
use image::DynamicImage;
|
use image::DynamicImage;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::{layout::Rect, style::Color};
|
||||||
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
|
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
|
||||||
use yazi_shim::cell::SyncCell;
|
use yazi_shim::cell::SyncCell;
|
||||||
|
use yazi_term::sequence::{MoveTo, SetFg};
|
||||||
|
|
||||||
use crate::{adapter::Adapter, image::Image};
|
use crate::{adapter::Adapter, image::Image};
|
||||||
|
|
||||||
|
|
@ -334,7 +334,7 @@ impl Kgp {
|
||||||
let s = " ".repeat(area.width as usize);
|
let s = " ".repeat(area.width as usize);
|
||||||
Emulator::move_lock((0, 0), |w| {
|
Emulator::move_lock((0, 0), |w| {
|
||||||
for y in area.top()..area.bottom() {
|
for y in area.top()..area.bottom() {
|
||||||
queue!(w, MoveTo(area.x, y))?;
|
write!(w, "{}", MoveTo(area.x, y))?;
|
||||||
write!(w, "{s}")?;
|
write!(w, "{s}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,10 +385,10 @@ impl Kgp {
|
||||||
|
|
||||||
let id = Self::image_id();
|
let id = Self::image_id();
|
||||||
let (r, g, b) = ((id >> 16) & 0xff, (id >> 8) & 0xff, id & 0xff);
|
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 {
|
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 {
|
for x in 0..area.width {
|
||||||
write!(buf, "\u{10EEEE}")?;
|
write!(buf, "\u{10EEEE}")?;
|
||||||
write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;
|
write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use std::{io::Write, path::PathBuf};
|
use std::{io::Write, path::PathBuf};
|
||||||
|
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use crossterm::{cursor::MoveTo, queue};
|
|
||||||
use image::{DynamicImage, GenericImageView, RgbImage};
|
use image::{DynamicImage, GenericImageView, RgbImage};
|
||||||
use palette::{Srgb, cast::ComponentsAs};
|
use palette::{Srgb, cast::ComponentsAs};
|
||||||
use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};
|
use quantette::{PaletteSize, color_map::IndexedColorMap, wu::{BinnerU8x3, WuU8x3}};
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
|
use yazi_emulator::{CLOSE, ESCAPE, Emulator, START};
|
||||||
|
use yazi_term::sequence::MoveTo;
|
||||||
|
|
||||||
use crate::{Image, adapter::Adapter};
|
use crate::{Image, adapter::Adapter};
|
||||||
|
|
||||||
|
|
@ -35,7 +35,7 @@ impl Sixel {
|
||||||
let s = " ".repeat(area.width as usize);
|
let s = " ".repeat(area.width as usize);
|
||||||
Emulator::move_lock((0, 0), |w| {
|
Emulator::move_lock((0, 0), |w| {
|
||||||
for y in area.top()..area.bottom() {
|
for y in area.top()..area.bottom() {
|
||||||
queue!(w, MoveTo(area.x, y))?;
|
write!(w, "{}", MoveTo(area.x, y))?;
|
||||||
write!(w, "{s}")?;
|
write!(w, "{s}")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,13 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", 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-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
ansi-to-tui = { workspace = true }
|
ansi-to-tui = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
|
|
@ -45,8 +45,5 @@ unicode-width = { workspace = true }
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
|
||||||
|
|
||||||
[target."cfg(windows)".dependencies]
|
[target."cfg(windows)".dependencies]
|
||||||
windows-sys = { version = "0.61.2", features = [ "Win32_Foundation", "Win32_System_JobObjects" ] }
|
windows-sys = { version = "0.61.2", features = [ "Win32_Foundation", "Win32_System_JobObjects" ] }
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use crossterm::event::MouseButton;
|
|
||||||
use mlua::{UserData, UserDataFields};
|
use mlua::{UserData, UserDataFields};
|
||||||
|
use yazi_term::event::{MouseButton, MouseEventKind};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct MouseEvent(crossterm::event::MouseEvent);
|
pub struct MouseEvent(yazi_term::event::MouseEvent);
|
||||||
|
|
||||||
impl Deref for MouseEvent {
|
impl Deref for MouseEvent {
|
||||||
type Target = crossterm::event::MouseEvent;
|
type Target = yazi_term::event::MouseEvent;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target { &self.0 }
|
fn deref(&self) -> &Self::Target { &self.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<crossterm::event::MouseEvent> for MouseEvent {
|
impl From<yazi_term::event::MouseEvent> for MouseEvent {
|
||||||
fn from(event: crossterm::event::MouseEvent) -> Self { Self(event) }
|
fn from(event: yazi_term::event::MouseEvent) -> Self { Self(event) }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserData for MouseEvent {
|
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("x", |_, me| Ok(me.column));
|
||||||
fields.add_field_method_get("y", |_, me| Ok(me.row));
|
fields.add_field_method_get("y", |_, me| Ok(me.row));
|
||||||
fields.add_field_method_get("is_left", |_, me| {
|
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))
|
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| {
|
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))
|
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| {
|
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))
|
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-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", 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-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
|
|
@ -33,7 +34,7 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
clap_complete = "4.6.3"
|
clap_complete = "4.6.5"
|
||||||
clap_complete_fig = "4.5.2"
|
clap_complete_fig = "4.5.2"
|
||||||
clap_complete_nushell = "4.6.0"
|
clap_complete_nushell = "4.6.0"
|
||||||
vergen-gitcl = { version = "9.1.0", features = [ "build", "rustc" ] }
|
vergen-gitcl = { version = "9.1.0", features = [ "build", "rustc" ] }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use yazi_config::{THEME, YAZI};
|
||||||
use yazi_emulator::Mux;
|
use yazi_emulator::Mux;
|
||||||
use yazi_fs::Xdg;
|
use yazi_fs::Xdg;
|
||||||
use yazi_shared::timestamp_us;
|
use yazi_shared::timestamp_us;
|
||||||
|
use yazi_term::TERM;
|
||||||
|
|
||||||
use super::Actions;
|
use super::Actions;
|
||||||
|
|
||||||
|
|
@ -39,7 +40,7 @@ impl Actions {
|
||||||
|
|
||||||
writeln!(s, "\nAdapter")?;
|
writeln!(s, "\nAdapter")?;
|
||||||
writeln!(s, " Adapter.matches : {:?}", yazi_adapter::ADAPTOR)?;
|
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, "\nDesktop")?;
|
||||||
writeln!(s, " XDG_SESSION_TYPE : {:?}", env::var_os("XDG_SESSION_TYPE"))?;
|
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-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", 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
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
|
@ -37,16 +36,13 @@ yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
# External build dependencies
|
# External build dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
clap_complete = "4.6.3"
|
clap_complete = "4.6.5"
|
||||||
clap_complete_fig = "4.5.2"
|
clap_complete_fig = "4.5.2"
|
||||||
clap_complete_nushell = "4.6.0"
|
clap_complete_nushell = "4.6.0"
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
vergen-gitcl = { version = "9.1.0", features = [ "build" ] }
|
vergen-gitcl = { version = "9.1.0", features = [ "build" ] }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "ya"
|
name = "ya"
|
||||||
path = "src/main.rs"
|
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 anyhow::{Result, bail};
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
|
@ -48,20 +48,19 @@ impl Dependency {
|
||||||
pub(super) fn header(&self, s: &str) -> Result<()> {
|
pub(super) fn header(&self, s: &str) -> Result<()> {
|
||||||
use std::io::IsTerminal;
|
use std::io::IsTerminal;
|
||||||
|
|
||||||
use crossterm::style::{Attribute, Print, SetAttributes};
|
use yazi_macro::writef;
|
||||||
use yazi_shim::crossterm::If;
|
use yazi_term::sequence::{If, SetSgr};
|
||||||
|
|
||||||
let ansi = env::var_os("YA_FORCE_ANSI").is_some_and(|v| v == "1") || io::stdout().is_terminal();
|
let ansi = env::var_os("YA_FORCE_ANSI").is_some_and(|v| v == "1") || io::stdout().is_terminal();
|
||||||
crossterm::execute!(
|
writef!(
|
||||||
BufWriter::new(io::stdout()),
|
io::stdout(),
|
||||||
Print("\n"),
|
"\n{}{}{} {} {}{}\n\n",
|
||||||
If(ansi, SetAttributes(Attribute::Reverse.into())),
|
If(ansi, SetSgr::Reverse),
|
||||||
If(ansi, SetAttributes(Attribute::Bold.into())),
|
If(ansi, SetSgr::Bold),
|
||||||
Print(" "),
|
" ",
|
||||||
Print(s.replacen("{name}", &self.name, 1)),
|
s.replacen("{name}", &self.name, 1),
|
||||||
Print(" "),
|
" ",
|
||||||
If(ansi, SetAttributes(Attribute::Reset.into())),
|
If(ansi, SetSgr::Reset),
|
||||||
Print("\n\n"),
|
|
||||||
)?;
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ yazi-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", 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-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
arc-swap = { workspace = true }
|
arc-swap = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
globset = { workspace = true }
|
globset = { workspace = true }
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
|
|
@ -36,6 +36,3 @@ strum = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tracing = { 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 std::{fmt::{Display, Write}, str::FromStr};
|
||||||
|
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use yazi_term::event::{KeyCode, KeyEvent, Modifiers};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct Key {
|
pub struct Key {
|
||||||
|
|
@ -30,29 +30,12 @@ impl Default for Key {
|
||||||
|
|
||||||
impl From<KeyEvent> for Key {
|
impl From<KeyEvent> for Key {
|
||||||
fn from(value: KeyEvent) -> Self {
|
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 {
|
Self {
|
||||||
code: value.code,
|
code: value.code,
|
||||||
shift,
|
shift: value.modifiers.contains(Modifiers::SHIFT),
|
||||||
ctrl: value.modifiers.contains(KeyModifiers::CONTROL),
|
ctrl: value.modifiers.contains(Modifiers::CONTROL),
|
||||||
alt: value.modifiers.contains(KeyModifiers::ALT),
|
alt: value.modifiers.contains(Modifiers::ALT),
|
||||||
super_: value.modifiers.contains(KeyModifiers::SUPER),
|
super_: value.modifiers.contains(Modifiers::SUPER),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,29 +75,28 @@ impl FromStr for Key {
|
||||||
"pageup" => key.code = KeyCode::PageUp,
|
"pageup" => key.code = KeyCode::PageUp,
|
||||||
"pagedown" => key.code = KeyCode::PageDown,
|
"pagedown" => key.code = KeyCode::PageDown,
|
||||||
"tab" => key.code = KeyCode::Tab,
|
"tab" => key.code = KeyCode::Tab,
|
||||||
"backtab" => key.code = KeyCode::BackTab,
|
|
||||||
"delete" => key.code = KeyCode::Delete,
|
"delete" => key.code = KeyCode::Delete,
|
||||||
"insert" => key.code = KeyCode::Insert,
|
"insert" => key.code = KeyCode::Insert,
|
||||||
"f1" => key.code = KeyCode::F(1),
|
"f1" => key.code = KeyCode::Fn(1),
|
||||||
"f2" => key.code = KeyCode::F(2),
|
"f2" => key.code = KeyCode::Fn(2),
|
||||||
"f3" => key.code = KeyCode::F(3),
|
"f3" => key.code = KeyCode::Fn(3),
|
||||||
"f4" => key.code = KeyCode::F(4),
|
"f4" => key.code = KeyCode::Fn(4),
|
||||||
"f5" => key.code = KeyCode::F(5),
|
"f5" => key.code = KeyCode::Fn(5),
|
||||||
"f6" => key.code = KeyCode::F(6),
|
"f6" => key.code = KeyCode::Fn(6),
|
||||||
"f7" => key.code = KeyCode::F(7),
|
"f7" => key.code = KeyCode::Fn(7),
|
||||||
"f8" => key.code = KeyCode::F(8),
|
"f8" => key.code = KeyCode::Fn(8),
|
||||||
"f9" => key.code = KeyCode::F(9),
|
"f9" => key.code = KeyCode::Fn(9),
|
||||||
"f10" => key.code = KeyCode::F(10),
|
"f10" => key.code = KeyCode::Fn(10),
|
||||||
"f11" => key.code = KeyCode::F(11),
|
"f11" => key.code = KeyCode::Fn(11),
|
||||||
"f12" => key.code = KeyCode::F(12),
|
"f12" => key.code = KeyCode::Fn(12),
|
||||||
"f13" => key.code = KeyCode::F(13),
|
"f13" => key.code = KeyCode::Fn(13),
|
||||||
"f14" => key.code = KeyCode::F(14),
|
"f14" => key.code = KeyCode::Fn(14),
|
||||||
"f15" => key.code = KeyCode::F(15),
|
"f15" => key.code = KeyCode::Fn(15),
|
||||||
"f16" => key.code = KeyCode::F(16),
|
"f16" => key.code = KeyCode::Fn(16),
|
||||||
"f17" => key.code = KeyCode::F(17),
|
"f17" => key.code = KeyCode::Fn(17),
|
||||||
"f18" => key.code = KeyCode::F(18),
|
"f18" => key.code = KeyCode::Fn(18),
|
||||||
"f19" => key.code = KeyCode::F(19),
|
"f19" => key.code = KeyCode::Fn(19),
|
||||||
"esc" => key.code = KeyCode::Esc,
|
"esc" => key.code = KeyCode::Escape,
|
||||||
|
|
||||||
_ => match next {
|
_ => match next {
|
||||||
s if it.peek().is_none() => {
|
s if it.peek().is_none() => {
|
||||||
|
|
@ -166,29 +148,28 @@ impl Display for Key {
|
||||||
KeyCode::PageUp => "PageUp",
|
KeyCode::PageUp => "PageUp",
|
||||||
KeyCode::PageDown => "PageDown",
|
KeyCode::PageDown => "PageDown",
|
||||||
KeyCode::Tab => "Tab",
|
KeyCode::Tab => "Tab",
|
||||||
KeyCode::BackTab => "BackTab",
|
|
||||||
KeyCode::Delete => "Delete",
|
KeyCode::Delete => "Delete",
|
||||||
KeyCode::Insert => "Insert",
|
KeyCode::Insert => "Insert",
|
||||||
KeyCode::F(1) => "F1",
|
KeyCode::Fn(1) => "F1",
|
||||||
KeyCode::F(2) => "F2",
|
KeyCode::Fn(2) => "F2",
|
||||||
KeyCode::F(3) => "F3",
|
KeyCode::Fn(3) => "F3",
|
||||||
KeyCode::F(4) => "F4",
|
KeyCode::Fn(4) => "F4",
|
||||||
KeyCode::F(5) => "F5",
|
KeyCode::Fn(5) => "F5",
|
||||||
KeyCode::F(6) => "F6",
|
KeyCode::Fn(6) => "F6",
|
||||||
KeyCode::F(7) => "F7",
|
KeyCode::Fn(7) => "F7",
|
||||||
KeyCode::F(8) => "F8",
|
KeyCode::Fn(8) => "F8",
|
||||||
KeyCode::F(9) => "F9",
|
KeyCode::Fn(9) => "F9",
|
||||||
KeyCode::F(10) => "F10",
|
KeyCode::Fn(10) => "F10",
|
||||||
KeyCode::F(11) => "F11",
|
KeyCode::Fn(11) => "F11",
|
||||||
KeyCode::F(12) => "F12",
|
KeyCode::Fn(12) => "F12",
|
||||||
KeyCode::F(13) => "F13",
|
KeyCode::Fn(13) => "F13",
|
||||||
KeyCode::F(14) => "F14",
|
KeyCode::Fn(14) => "F14",
|
||||||
KeyCode::F(15) => "F15",
|
KeyCode::Fn(15) => "F15",
|
||||||
KeyCode::F(16) => "F16",
|
KeyCode::Fn(16) => "F16",
|
||||||
KeyCode::F(17) => "F17",
|
KeyCode::Fn(17) => "F17",
|
||||||
KeyCode::F(18) => "F18",
|
KeyCode::Fn(18) => "F18",
|
||||||
KeyCode::F(19) => "F19",
|
KeyCode::Fn(19) => "F19",
|
||||||
KeyCode::Esc => "Esc",
|
KeyCode::Escape => "Esc",
|
||||||
|
|
||||||
KeyCode::Char(' ') => "Space",
|
KeyCode::Char(' ') => "Space",
|
||||||
KeyCode::Char(c) => {
|
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 std::io::{Read, Write};
|
||||||
|
|
||||||
use yazi_shim::{cell::{RoCell, SyncCell}, toml::{DeserializeOver, DeserializeOverWith}};
|
use yazi_shim::{cell::{RoCell, SyncCell}, toml::{DeserializeOver, DeserializeOverWith}};
|
||||||
|
use yazi_term::sequence::SetSgr;
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
|
|
||||||
pub static YAZI: RoCell<yazi::Yazi> = RoCell::new();
|
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}")?;
|
writeln!(stdout, "\nCaused by:\n{src}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
use crossterm::style::{Attribute, Print, SetAttributes};
|
writeln!(
|
||||||
crossterm::execute!(
|
|
||||||
stdout,
|
stdout,
|
||||||
SetAttributes(Attribute::Reverse.into()),
|
"{}{}Press <Enter> to continue with preset settings...{}",
|
||||||
SetAttributes(Attribute::Bold.into()),
|
SetSgr::Reverse,
|
||||||
Print("Press <Enter> to continue with preset settings..."),
|
SetSgr::Bold,
|
||||||
SetAttributes(Attribute::Reset.into()),
|
SetSgr::Reset
|
||||||
Print("\n"),
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
TTY.reader().read_exact(&mut [0])?;
|
TTY.reader().read_exact(&mut [0])?;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use anyhow::{Result, bail};
|
use anyhow::{Result, bail};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use crossterm::event::MouseEventKind;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_term::event::MouseEventKind;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
|
#[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 {
|
impl From<MouseEventKind> for MouseEvents {
|
||||||
fn from(value: crossterm::event::MouseEventKind) -> Self {
|
fn from(value: MouseEventKind) -> Self {
|
||||||
match value {
|
match value {
|
||||||
MouseEventKind::Down(_) | MouseEventKind::Up(_) => Self::CLICK,
|
MouseEventKind::Down(_) | MouseEventKind::Up(_) => Self::CLICK,
|
||||||
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => Self::SCROLL,
|
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => Self::SCROLL,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crossterm::terminal::WindowSize;
|
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
|
use yazi_term::Dimension;
|
||||||
|
|
||||||
use super::{Offset, Origin};
|
use super::{Offset, Origin};
|
||||||
|
|
||||||
|
|
@ -12,15 +12,15 @@ pub struct Position {
|
||||||
impl Position {
|
impl Position {
|
||||||
pub const fn new(origin: Origin, offset: Offset) -> Self { Self { origin, offset } }
|
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::*;
|
use Origin::*;
|
||||||
let Offset { x, y, width, height } = self.offset;
|
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 {
|
let new_x = match self.origin {
|
||||||
TopLeft | BottomLeft => x.clamp(0, max_x as i16) as u16,
|
TopLeft | BottomLeft => x.clamp(0, max_x as i16) as u16,
|
||||||
TopCenter | BottomCenter | Center => {
|
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),
|
TopRight | BottomRight => max_x.saturating_add_signed(x).clamp(0, max_x),
|
||||||
Hovered => unreachable!(),
|
Hovered => unreachable!(),
|
||||||
|
|
@ -37,18 +37,18 @@ impl Position {
|
||||||
Rect {
|
Rect {
|
||||||
x: new_x,
|
x: new_x,
|
||||||
y: new_y,
|
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)),
|
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 Offset { x, y, width, height } = offset;
|
||||||
|
|
||||||
let above =
|
let above =
|
||||||
base.y.saturating_add(base.height).saturating_add(height).saturating_add_signed(y) > rows;
|
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 {
|
let new_y = if above {
|
||||||
base.y.saturating_sub(height.saturating_sub(y.unsigned_abs()))
|
base.y.saturating_sub(height.saturating_sub(y.unsigned_abs()))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -58,7 +58,7 @@ impl Position {
|
||||||
Rect {
|
Rect {
|
||||||
x: new_x,
|
x: new_x,
|
||||||
y: new_y,
|
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)),
|
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-binding = { path = "../yazi-binding", version = "26.5.6" }
|
||||||
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
||||||
yazi-dds = { path = "../yazi-dds", 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-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-runner = { path = "../yazi-runner", version = "26.5.6" }
|
yazi-runner = { path = "../yazi-runner", version = "26.5.6" }
|
||||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
|
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", 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-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
dyn-clone = { workspace = true }
|
dyn-clone = { workspace = true }
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
|
|
@ -51,6 +50,3 @@ tokio-util = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
unicode-width = { workspace = true }
|
unicode-width = { workspace = true }
|
||||||
yazi-prebuilt = "0.1.0"
|
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) }
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("PluginCallback").finish_non_exhaustive()
|
f.debug_struct("PluginCallback").finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crossterm::cursor::SetCursorStyle;
|
|
||||||
use ratatui::layout::{Position, Rect};
|
use ratatui::layout::{Position, Rect};
|
||||||
use yazi_shared::Layer;
|
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};
|
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 {
|
if self.input.visible {
|
||||||
let Rect { x, y, .. } = self.mgr.area(self.input.position);
|
let Rect { x, y, .. } = self.mgr.area(self.input.position);
|
||||||
return Some((
|
return Some((
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::{cursor::SetCursorStyle, event::KeyCode};
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
|
use yazi_config::{KEYMAP, YAZI, keymap::{Chord, Key}};
|
||||||
use yazi_emulator::Dimension;
|
|
||||||
use yazi_macro::{act, render, render_and};
|
use yazi_macro::{act, render, render_and};
|
||||||
use yazi_shared::Layer;
|
use yazi_shared::Layer;
|
||||||
|
use yazi_term::{CursorStyle, TERM, event::KeyCode};
|
||||||
use yazi_widgets::Scrollable;
|
use yazi_widgets::Scrollable;
|
||||||
|
|
||||||
use crate::help::HELP_MARGIN;
|
use crate::help::HELP_MARGIN;
|
||||||
|
|
@ -27,7 +26,7 @@ impl Help {
|
||||||
pub fn r#type(&mut self, key: &Key) -> Result<bool> {
|
pub fn r#type(&mut self, key: &Key) -> Result<bool> {
|
||||||
let Some(input) = &mut self.in_filter else { return Ok(false) };
|
let Some(input) = &mut self.in_filter else { return Ok(false) };
|
||||||
match key {
|
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;
|
self.in_filter = None;
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
|
|
@ -85,26 +84,22 @@ impl Help {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if let Some(kw) = self.keyword() {
|
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
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
|
pub fn rel_cursor(&self) -> usize { self.cursor - self.offset }
|
||||||
|
|
||||||
pub fn cursor_shape(&self) -> SetCursorStyle {
|
pub fn cursor_shape(&self) -> CursorStyle {
|
||||||
if YAZI.input.cursor_blink {
|
if YAZI.input.cursor_blink { CursorStyle::BlinkingBlock } else { CursorStyle::SteadyBlock }
|
||||||
SetCursorStyle::BlinkingBlock
|
|
||||||
} else {
|
|
||||||
SetCursorStyle::SteadyBlock
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scrollable for Help {
|
impl Scrollable for Help {
|
||||||
fn total(&self) -> usize { self.bindings.len() }
|
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 }
|
fn cursor_mut(&mut self) -> &mut usize { &mut self.cursor }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ use std::iter;
|
||||||
|
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use yazi_config::popup::{Origin, Position};
|
use yazi_config::popup::{Origin, Position};
|
||||||
use yazi_emulator::Dimension;
|
|
||||||
use yazi_fs::Splatable;
|
use yazi_fs::Splatable;
|
||||||
use yazi_shared::url::{AsUrl, Url, UrlBuf};
|
use yazi_shared::url::{AsUrl, Url, UrlBuf};
|
||||||
|
use yazi_term::TERM;
|
||||||
use yazi_watcher::Watcher;
|
use yazi_watcher::Watcher;
|
||||||
|
|
||||||
use super::{Batcher, Mimetype, Tabs, Yanked};
|
use super::{Batcher, Mimetype, Tabs, Yanked};
|
||||||
|
|
@ -35,7 +35,7 @@ impl Mgr {
|
||||||
if pos.origin == Origin::Hovered {
|
if pos.origin == Origin::Hovered {
|
||||||
self.active().hovered_rect_based(pos)
|
self.active().hovered_rect_based(pos)
|
||||||
} else {
|
} else {
|
||||||
pos.rect(Dimension::available().into())
|
pos.rect(TERM.dimension())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ use anyhow::Result;
|
||||||
use ratatui::layout::Rect;
|
use ratatui::layout::Rect;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use yazi_config::{LAYOUT, popup::{Origin, Position}};
|
use yazi_config::{LAYOUT, popup::{Origin, Position}};
|
||||||
use yazi_emulator::Dimension;
|
|
||||||
use yazi_fs::File;
|
use yazi_fs::File;
|
||||||
use yazi_shared::{Id, Ids, url::{UrlBuf, UrlLike}};
|
use yazi_shared::{Id, Ids, url::{UrlBuf, UrlLike}};
|
||||||
|
use yazi_term::TERM;
|
||||||
|
|
||||||
use super::{Backstack, Finder, Folder, History, Mode, Preference, Preview};
|
use super::{Backstack, Finder, Folder, History, Mode, Preference, Preview};
|
||||||
use crate::{spot::Spot, tab::Selected};
|
use crate::{spot::Spot, tab::Selected};
|
||||||
|
|
@ -90,11 +90,11 @@ impl Tab {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn hovered_rect_based(&self, pos: Position) -> Rect {
|
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() {
|
if let Some(r) = self.hovered_rect() {
|
||||||
Position::sticky(ws, r, pos.offset)
|
Position::sticky(dim, r, pos.offset)
|
||||||
} else {
|
} 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 std::{sync::Arc, time::Duration};
|
||||||
|
|
||||||
use tokio::{task::JoinHandle, time::sleep};
|
use tokio::{task::JoinHandle, time::sleep};
|
||||||
use yazi_emulator::Dimension;
|
|
||||||
use yazi_scheduler::{Scheduler, TaskSnap, TaskSummary};
|
use yazi_scheduler::{Scheduler, TaskSnap, TaskSummary};
|
||||||
|
use yazi_term::TERM;
|
||||||
|
|
||||||
use super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT};
|
use super::{TASKS_BORDER, TASKS_PADDING, TASKS_PERCENT};
|
||||||
use crate::AppProxy;
|
use crate::AppProxy;
|
||||||
|
|
@ -52,8 +52,8 @@ impl Tasks {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn limit() -> usize {
|
pub fn limit() -> usize {
|
||||||
((Dimension::available().rows * TASKS_PERCENT / 100)
|
((TERM.dimension().rows * TASKS_PERCENT / 100).saturating_sub(TASKS_BORDER + TASKS_PADDING)
|
||||||
.saturating_sub(TASKS_BORDER + TASKS_PADDING) as usize)
|
as usize)
|
||||||
/ 3
|
/ 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ impl IntoLua for EmberCd<'_> {
|
||||||
lua
|
lua
|
||||||
.create_table_from([
|
.create_table_from([
|
||||||
("tab", self.tab.get().into_lua(lua)?),
|
("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)
|
.into_lua(lua)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,15 +16,13 @@ workspace = true
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", 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-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
|
ratatui = { workspace = true }
|
||||||
scopeguard = { workspace = true }
|
scopeguard = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
|
||||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
|
||||||
|
|
|
||||||
|
|
@ -1,59 +1,16 @@
|
||||||
use std::mem;
|
use yazi_term::TERM;
|
||||||
|
|
||||||
use crossterm::terminal::WindowSize;
|
|
||||||
|
|
||||||
use crate::EMULATOR;
|
use crate::EMULATOR;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
pub struct Dimension {
|
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 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl 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)> {
|
pub fn cell_size() -> Option<(f64, f64)> {
|
||||||
let emu = &*EMULATOR;
|
let emu = &*EMULATOR;
|
||||||
Some(if emu.force_16t {
|
Some(if emu.force_16t {
|
||||||
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
|
(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
|
r
|
||||||
} else if emu.csi_16t.0 != 0 && emu.csi_16t.1 != 0 {
|
} else if emu.csi_16t.0 != 0 && emu.csi_16t.1 != 0 {
|
||||||
(emu.csi_16t.0 as f64, emu.csi_16t.1 as f64)
|
(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 anyhow::Result;
|
||||||
use crossterm::{cursor::{RestorePosition, SavePosition}, execute, style::Print, terminal::{disable_raw_mode, enable_raw_mode}};
|
|
||||||
use either::Either;
|
use either::Either;
|
||||||
|
use ratatui::style::Color;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use tokio::time::sleep;
|
use tokio::time::sleep;
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
|
use yazi_macro::writef;
|
||||||
use yazi_shim::cell::RoCell;
|
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 yazi_tty::{Handle, TTY};
|
||||||
|
|
||||||
use crate::{Brand, Dimension, Mux, TMUX, Unknown};
|
use crate::{Brand, Mux, TMUX, Unknown};
|
||||||
|
|
||||||
pub static EMULATOR: RoCell<Emulator> = RoCell::new();
|
pub static EMULATOR: RoCell<Emulator> = RoCell::new();
|
||||||
|
|
||||||
|
|
@ -36,25 +38,18 @@ impl Default for Emulator {
|
||||||
|
|
||||||
impl Emulator {
|
impl Emulator {
|
||||||
pub fn detect() -> Result<Self> {
|
pub fn detect() -> Result<Self> {
|
||||||
defer! { disable_raw_mode().ok(); }
|
defer! { TERM.enter_cooked_mode().ok(); }
|
||||||
enable_raw_mode()?;
|
TERM.enter_raw_mode()?;
|
||||||
|
|
||||||
let resort = Brand::from_env();
|
let resort = Brand::from_env();
|
||||||
let kgp_seq = if resort.is_none() {
|
writef!(
|
||||||
Mux::csi("\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\")
|
|
||||||
} else {
|
|
||||||
"".into()
|
|
||||||
};
|
|
||||||
|
|
||||||
execute!(
|
|
||||||
TTY.writer(),
|
TTY.writer(),
|
||||||
SavePosition,
|
"{SaveCursorPos}{}{}{}{}{}{RestoreCursorPos}",
|
||||||
Print(kgp_seq), // Detect KGP
|
If(resort.is_none(), Mux::wrap(KittyGraphicsQuery)),
|
||||||
Print(Mux::csi("\x1b[>q")), // Request terminal version
|
Mux::wrap(RequestXtVersion),
|
||||||
Print("\x1b[16t"), // Request cell size
|
RequestCellPixelSize,
|
||||||
Print("\x1b]11;?\x07"), // Request background color
|
RequestBgColor,
|
||||||
Print(Mux::csi("\x1b[0c")), // Request device attributes
|
Mux::wrap(RequestDA1),
|
||||||
RestorePosition
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let resp = Self::read_until_da1();
|
let resp = Self::read_until_da1();
|
||||||
|
|
@ -83,28 +78,26 @@ impl Emulator {
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut BufWriter<Handle>) -> Result<T>,
|
F: FnOnce(&mut BufWriter<Handle>) -> Result<T>,
|
||||||
{
|
{
|
||||||
use std::{io::Write, thread, time::Duration};
|
use std::{thread, time::Duration};
|
||||||
|
|
||||||
use crossterm::{cursor::{Hide, MoveTo, Show}, queue};
|
|
||||||
|
|
||||||
let mut w = TTY.lockout();
|
let mut w = TTY.lockout();
|
||||||
|
|
||||||
// I really don't want to add this,
|
// I really don't want to add this,
|
||||||
// But tmux and ConPTY sometimes cause the cursor position to get out of sync.
|
// But tmux and ConPTY sometimes cause the cursor position to get out of sync.
|
||||||
if TMUX.get() || cfg!(windows) {
|
if TMUX.get() || cfg!(windows) {
|
||||||
execute!(w, SavePosition, MoveTo(x, y), Show)?;
|
writef!(w, "{SaveCursorPos}{}{ShowCursor}", MoveTo(x, y))?;
|
||||||
execute!(w, MoveTo(x, y), Show)?;
|
writef!(w, "{}{ShowCursor}", MoveTo(x, y))?;
|
||||||
execute!(w, MoveTo(x, y), Show)?;
|
writef!(w, "{}{ShowCursor}", MoveTo(x, y))?;
|
||||||
thread::sleep(Duration::from_millis(1));
|
thread::sleep(Duration::from_millis(1));
|
||||||
} else {
|
} else {
|
||||||
queue!(w, SavePosition, MoveTo(x, y))?;
|
write!(w, "{SaveCursorPos}{}", MoveTo(x, y))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = cb(&mut w);
|
let result = cb(&mut w);
|
||||||
if TMUX.get() || cfg!(windows) {
|
if TMUX.get() || cfg!(windows) {
|
||||||
queue!(w, Hide, RestorePosition)?;
|
write!(w, "{HideCursor}{RestoreCursorPos}")?;
|
||||||
} else {
|
} else {
|
||||||
queue!(w, RestorePosition)?;
|
write!(w, "{RestoreCursorPos}")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
w.flush()?;
|
w.flush()?;
|
||||||
|
|
@ -148,21 +141,13 @@ impl Emulator {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn error_to_user() {
|
async fn error_to_user() {
|
||||||
use crossterm::style::{Attribute, Color, Print, ResetColor, SetAttributes, SetForegroundColor};
|
|
||||||
|
|
||||||
sleep(Duration::from_millis(400)).await;
|
sleep(Duration::from_millis(400)).await;
|
||||||
_ = crossterm::execute!(
|
_ = writef!(
|
||||||
std::io::stderr(),
|
io::stderr(),
|
||||||
SetForegroundColor(Color::Red),
|
"{}{}\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",
|
||||||
SetAttributes(Attribute::Bold.into()),
|
SetFg(Color::Red),
|
||||||
Print("\r\nTerminal response timeout: "),
|
SetSgr::Bold,
|
||||||
ResetColor,
|
SetSgr::Reset,
|
||||||
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"
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -206,8 +191,6 @@ impl Emulator {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dimension::available()
|
TERM.dimension().ratio().is_none_or(|(rw, rh)| rw.floor() as u16 != w || rh.floor() as u16 != h)
|
||||||
.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 anyhow::Result;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use yazi_macro::time;
|
use yazi_macro::{time, writef};
|
||||||
use yazi_shim::cell::SyncCell;
|
use yazi_shim::cell::SyncCell;
|
||||||
|
use yazi_term::sequence::RequestDeviceStatus;
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
|
|
||||||
use crate::Emulator;
|
use crate::Emulator;
|
||||||
|
|
@ -16,15 +17,24 @@ pub static CLOSE: SyncCell<&'static str> = SyncCell::new("");
|
||||||
pub struct Mux;
|
pub struct Mux;
|
||||||
|
|
||||||
impl Mux {
|
impl Mux {
|
||||||
pub fn csi(s: &str) -> Cow<'_, str> {
|
pub fn wrap<T: Display>(s: T) -> impl Display {
|
||||||
if TMUX.get() {
|
struct Wrapper<T>(T);
|
||||||
Cow::Owned(format!(
|
|
||||||
"{START}{}{CLOSE}",
|
impl<T: Display> Display for Wrapper<T> {
|
||||||
s.trim_start_matches('\x1b').replace('\x1b', ESCAPE.get()),
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
))
|
if !TMUX.get() {
|
||||||
} else {
|
return self.0.fmt(f);
|
||||||
Cow::Borrowed(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{START}{}{CLOSE}",
|
||||||
|
self.0.to_string().trim_start_matches('\x1b').replace('\x1b', ESCAPE.get())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Wrapper(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tmux_passthrough() {
|
pub fn tmux_passthrough() {
|
||||||
|
|
@ -56,7 +66,7 @@ impl Mux {
|
||||||
|
|
||||||
pub fn tmux_drain() -> Result<()> {
|
pub fn tmux_drain() -> Result<()> {
|
||||||
if TMUX.get() {
|
if TMUX.get() {
|
||||||
crossterm::execute!(TTY.writer(), crossterm::style::Print(Self::csi("\x1b[5n")))?;
|
writef!(TTY.writer(), "{}", Self::wrap(RequestDeviceStatus))?;
|
||||||
_ = Emulator::read_until_dsr();
|
_ = Emulator::read_until_dsr();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ yazi-boot = { path = "../yazi-boot", version = "26.5.6" }
|
||||||
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
yazi-config = { path = "../yazi-config", version = "26.5.6" }
|
||||||
yazi-core = { path = "../yazi-core", version = "26.5.6" }
|
yazi-core = { path = "../yazi-core", version = "26.5.6" }
|
||||||
yazi-dds = { path = "../yazi-dds", 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-fs = { path = "../yazi-fs", version = "26.5.6" }
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-parser = { path = "../yazi-parser", 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-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||||
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
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-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||||
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
yazi-watcher = { path = "../yazi-watcher", version = "26.5.6" }
|
||||||
yazi-widgets = { path = "../yazi-widgets", 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
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
better-panic = "0.3.0"
|
better-panic = "0.3.0"
|
||||||
crossterm = { workspace = true }
|
|
||||||
fdlimit = "0.3.0"
|
fdlimit = "0.3.0"
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
|
|
@ -60,9 +59,6 @@ tracing-subscriber = { version = "0.3.23", features = [ "env-filter" ] }
|
||||||
libc = { workspace = true }
|
libc = { workspace = true }
|
||||||
signal-hook-tokio = { version = "0.4.0", features = [ "futures-v0_3" ] }
|
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]
|
[target.'cfg(all(not(target_os = "macos"), not(target_os = "windows")))'.dependencies]
|
||||||
tikv-jemallocator = "0.6.1"
|
tikv-jemallocator = "0.6.1"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,13 @@ use yazi_actor::Ctx;
|
||||||
use yazi_core::Core;
|
use yazi_core::Core;
|
||||||
use yazi_macro::act;
|
use yazi_macro::act;
|
||||||
use yazi_shared::{data::Data, event::{Event, NEED_RENDER}};
|
use yazi_shared::{data::Data, event::{Event, NEED_RENDER}};
|
||||||
use yazi_term::Term;
|
use yazi_tui::Raterm;
|
||||||
|
|
||||||
use crate::{Dispatcher, Signals};
|
use crate::{Dispatcher, Signals};
|
||||||
|
|
||||||
pub(crate) struct App {
|
pub(crate) struct App {
|
||||||
pub(crate) core: Core,
|
pub(crate) core: Core,
|
||||||
pub(crate) term: Option<Term>,
|
pub(crate) term: Option<Raterm>,
|
||||||
pub(crate) signals: Signals,
|
|
||||||
|
|
||||||
need_render: u8,
|
need_render: u8,
|
||||||
pub(crate) last_render: Instant,
|
pub(crate) last_render: Instant,
|
||||||
|
|
@ -21,11 +20,10 @@ pub(crate) struct App {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
fn make(term: Term, signals: Signals) -> Result<Self> {
|
fn make(term: Raterm) -> Result<Self> {
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
core: Core::make(),
|
core: Core::make(),
|
||||||
term: Some(term),
|
term: Some(term),
|
||||||
signals,
|
|
||||||
|
|
||||||
need_render: 0,
|
need_render: 0,
|
||||||
last_render: Instant::now(),
|
last_render: Instant::now(),
|
||||||
|
|
@ -34,12 +32,13 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) async fn serve() -> Result<()> {
|
pub(crate) async fn serve() -> Result<()> {
|
||||||
let term = Term::start()?;
|
let term = Raterm::start()?;
|
||||||
let (mut rx, signals) = (Event::take(), Signals::start()?);
|
Signals::start()?;
|
||||||
|
|
||||||
let mut app = Self::make(term, signals)?;
|
let mut app = Self::make(term)?;
|
||||||
app.bootstrap()?;
|
app.bootstrap()?;
|
||||||
|
|
||||||
|
let mut rx = Event::take();
|
||||||
loop {
|
loop {
|
||||||
if let Some(t) = app.next_render.take() {
|
if let Some(t) = app.next_render.take() {
|
||||||
select! {
|
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 anyhow::Result;
|
||||||
use crossterm::{cursor::{MoveTo, SetCursorStyle, Show}, execute, queue, terminal::{BeginSynchronizedUpdate, EndSynchronizedUpdate}};
|
use ratatui::{CompletedFrame, backend::Backend, buffer::Buffer, layout::Position};
|
||||||
use ratatui::{CompletedFrame, backend::{Backend, CrosstermBackend}, buffer::Buffer, layout::Position};
|
|
||||||
use yazi_actor::{Ctx, lives::Lives};
|
use yazi_actor::{Ctx, lives::Lives};
|
||||||
use yazi_binding::runtime_scope;
|
use yazi_binding::runtime_scope;
|
||||||
use yazi_config::LAYOUT;
|
use yazi_config::LAYOUT;
|
||||||
use yazi_macro::{act, succ};
|
use yazi_macro::{act, succ, writef};
|
||||||
use yazi_plugin::LUA;
|
use yazi_plugin::LUA;
|
||||||
use yazi_shared::{data::Data, event::NEED_RENDER};
|
use yazi_shared::{data::Data, event::NEED_RENDER};
|
||||||
|
use yazi_term::{CursorStyle, sequence::{BeginSyncUpdate, EndSyncUpdate, MoveTo, SetCursorStyle, ShowCursor}};
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
|
use yazi_tui::RatermBackend;
|
||||||
use yazi_widgets::COLLISION;
|
use yazi_widgets::COLLISION;
|
||||||
|
|
||||||
use crate::{app::App, root::Root};
|
use crate::{app::App, root::Root};
|
||||||
|
|
@ -90,10 +91,10 @@ impl App {
|
||||||
}
|
}
|
||||||
|
|
||||||
let patches = frame.buffer.diff(&new);
|
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);
|
static COUNT: AtomicU8 = AtomicU8::new(0);
|
||||||
if push && COUNT.fetch_add(1, Ordering::Relaxed) != 0 {
|
if push && COUNT.fetch_add(1, Ordering::Relaxed) != 0 {
|
||||||
return;
|
return;
|
||||||
|
|
@ -101,12 +102,18 @@ impl App {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = if push {
|
if push {
|
||||||
queue!(TTY.writer(), BeginSynchronizedUpdate)
|
write!(TTY.writer(), "{BeginSyncUpdate}").ok();
|
||||||
} else if let Some((Position { x, y }, shape)) = cursor {
|
} 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 {
|
} 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 ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
|
||||||
use yazi_config::{THEME, popup::{Offset, Position}};
|
use yazi_config::{THEME, popup::{Offset, Position}};
|
||||||
use yazi_core::Core;
|
use yazi_core::Core;
|
||||||
use yazi_emulator::Dimension;
|
|
||||||
use yazi_shared::strand::StrandLike;
|
use yazi_shared::strand::StrandLike;
|
||||||
|
use yazi_term::TERM;
|
||||||
|
|
||||||
pub(crate) struct Cmp<'a> {
|
pub(crate) struct Cmp<'a> {
|
||||||
core: &'a Core,
|
core: &'a Core,
|
||||||
|
|
@ -38,7 +38,7 @@ impl Widget for Cmp<'_> {
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let input_area = self.core.mgr.area(self.core.input.position);
|
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,
|
x: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
width: input_area.width.saturating_sub(2),
|
width: input_area.width.saturating_sub(2),
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::event::{KeyEvent, MouseEvent};
|
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use yazi_actor::Ctx;
|
use yazi_actor::Ctx;
|
||||||
use yazi_config::keymap::Key;
|
use yazi_config::keymap::Key;
|
||||||
use yazi_macro::{act, emit};
|
use yazi_macro::{act, emit};
|
||||||
use yazi_shared::event::{ActionCow, Event, NEED_RENDER};
|
use yazi_shared::event::{ActionCow, Event, NEED_RENDER};
|
||||||
|
use yazi_term::event::{Event as TermEvent, KeyEvent, MouseEvent};
|
||||||
use yazi_widgets::input::InputMode;
|
use yazi_widgets::input::InputMode;
|
||||||
|
|
||||||
use crate::{Executor, Router, app::App};
|
use crate::{Executor, Router, app::App};
|
||||||
|
|
@ -23,11 +23,12 @@ impl<'a> Dispatcher<'a> {
|
||||||
Event::Call(action) => Ok(self.dispatch_call(action)),
|
Event::Call(action) => Ok(self.dispatch_call(action)),
|
||||||
Event::Seq(actions) => Ok(self.dispatch_seq(actions)),
|
Event::Seq(actions) => Ok(self.dispatch_seq(actions)),
|
||||||
Event::Render(partial) => self.dispatch_render(partial),
|
Event::Render(partial) => self.dispatch_render(partial),
|
||||||
Event::Key(key) => self.dispatch_key(key),
|
Event::Term(TermEvent::Key(key)) => self.dispatch_key(key),
|
||||||
Event::Mouse(mouse) => self.dispatch_mouse(mouse),
|
Event::Term(TermEvent::Mouse(mouse)) => self.dispatch_mouse(mouse),
|
||||||
Event::Resize => self.dispatch_resize(),
|
Event::Term(TermEvent::Resize(_)) => self.dispatch_resize(),
|
||||||
Event::Focus => self.dispatch_focus(),
|
Event::Term(TermEvent::FocusIn) => self.dispatch_focus(),
|
||||||
Event::Paste(str) => self.dispatch_paste(str),
|
Event::Term(TermEvent::FocusOut) => Ok(()),
|
||||||
|
Event::Term(TermEvent::Paste(str)) => self.dispatch_paste(str),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = &result {
|
if let Err(e) = &result {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::Result;
|
||||||
use yazi_actor::Ctx;
|
use yazi_actor::Ctx;
|
||||||
use yazi_macro::{act, succ};
|
use yazi_macro::{act, succ};
|
||||||
use yazi_shared::{Layer, data::Data, event::ActionCow};
|
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 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)?;
|
let cx = &mut Ctx::new(&action, &mut self.app.core, &mut self.app.term)?;
|
||||||
|
|
||||||
macro_rules! on {
|
macro_rules! on {
|
||||||
|
|
@ -51,19 +51,12 @@ impl<'a> Executor<'a> {
|
||||||
on!(lua);
|
on!(lua);
|
||||||
on!(deprecate);
|
on!(deprecate);
|
||||||
on!(theme);
|
on!(theme);
|
||||||
|
on!(stop);
|
||||||
on!(quit);
|
on!(quit);
|
||||||
|
|
||||||
match &*action.name {
|
match &*action.name {
|
||||||
"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
|
"resize" => act!(app:resize, cx, crate::Root::reflow as fn(_) -> _),
|
||||||
"resume" => act!(app:resume, cx, yazi_parser::app::ResumeForm {
|
"resume" => act!(app:resume, cx, crate::Root::reflow as fn(_) -> _),
|
||||||
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")?,
|
|
||||||
}),
|
|
||||||
_ => succ!(),
|
_ => succ!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,14 @@
|
||||||
use std::fs::File;
|
use std::{fs::File, io};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
|
use ratatui::style::Color;
|
||||||
use tracing_appender::non_blocking::WorkerGuard;
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
use yazi_fs::Xdg;
|
use yazi_fs::Xdg;
|
||||||
|
use yazi_macro::writef;
|
||||||
use yazi_shared::LOG_LEVEL;
|
use yazi_shared::LOG_LEVEL;
|
||||||
use yazi_shim::cell::RoCell;
|
use yazi_shim::cell::RoCell;
|
||||||
|
use yazi_term::sequence::{SetFg, SetSgr};
|
||||||
|
|
||||||
static _GUARD: RoCell<WorkerGuard> = RoCell::new();
|
static _GUARD: RoCell<WorkerGuard> = RoCell::new();
|
||||||
|
|
||||||
|
|
@ -36,11 +38,11 @@ impl Logs {
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
_GUARD.init(guard);
|
_GUARD.init(guard);
|
||||||
Ok(crossterm::execute!(
|
Ok(writef!(
|
||||||
std::io::stderr(),
|
io::stderr(),
|
||||||
SetForegroundColor(Color::Yellow),
|
"{}Running with log level `{level}`, logs are written to {log_path:?}\n{}",
|
||||||
Print(format!("Running with log level `{level}`, logs are written to {log_path:?}\n")),
|
SetFg(Color::Yellow),
|
||||||
ResetColor
|
SetSgr::Reset,
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ async fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
yazi_tty::init();
|
yazi_tty::init();
|
||||||
|
|
||||||
yazi_term::init();
|
yazi_term::init()?;
|
||||||
|
|
||||||
yazi_fs::init();
|
yazi_fs::init();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use yazi_term::Term;
|
use yazi_tui::Raterm;
|
||||||
|
|
||||||
pub(super) struct Panic;
|
pub(super) struct Panic;
|
||||||
|
|
||||||
|
|
@ -8,10 +8,8 @@ impl Panic {
|
||||||
|
|
||||||
let hook = std::panic::take_hook();
|
let hook = std::panic::take_hook();
|
||||||
std::panic::set_hook(Box::new(move |info| {
|
std::panic::set_hook(Box::new(move |info| {
|
||||||
Term::goodbye(|| {
|
Raterm::stop();
|
||||||
hook(info);
|
hook(info);
|
||||||
1
|
|
||||||
});
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,39 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::event::{Event as CrosstermEvent, EventStream, KeyEvent, KeyEventKind};
|
use tokio_stream::StreamExt;
|
||||||
use futures::StreamExt;
|
|
||||||
use tokio::{select, sync::mpsc};
|
|
||||||
use yazi_config::YAZI;
|
|
||||||
use yazi_shared::event::{Event, Replier};
|
|
||||||
|
|
||||||
pub(super) struct Signals {
|
pub(super) struct Signals;
|
||||||
pub(super) tx: mpsc::UnboundedSender<(bool, Replier)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Signals {
|
impl Signals {
|
||||||
pub(super) fn start() -> Result<Self> {
|
pub(super) fn start() -> Result<()> {
|
||||||
let (tx, rx) = mpsc::unbounded_channel();
|
#[cfg(unix)]
|
||||||
Self::spawn(rx)?;
|
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)]
|
#[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 libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGSTOP, SIGTERM, SIGTSTP};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use yazi_term::YIELD_TO_SUBPROCESS;
|
use yazi_term::YIELD_TO_SUBPROCESS;
|
||||||
|
|
@ -30,16 +45,14 @@ impl Signals {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
SIGTSTP => {
|
SIGTSTP => {
|
||||||
tokio::spawn(async move {
|
|
||||||
yazi_scheduler::AppProxy::stop().await;
|
yazi_scheduler::AppProxy::stop().await;
|
||||||
if unsafe { libc::kill(0, SIGSTOP) } != 0 {
|
if unsafe { libc::kill(0, SIGSTOP) } != 0 {
|
||||||
error!("Failed to stop the process:\n{}", std::io::Error::last_os_error());
|
error!("Failed to stop the process:\n{}", std::io::Error::last_os_error());
|
||||||
yazi_proxy::AppProxy::quit(Default::default());
|
yazi_proxy::AppProxy::quit(Default::default());
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
SIGCONT if YIELD_TO_SUBPROCESS.try_acquire().is_ok() => {
|
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)]
|
#[cfg(windows)]
|
||||||
#[inline]
|
async fn handle(_: ()) -> bool { unreachable!() }
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,12 @@ macro_rules! errln {
|
||||||
writeln!(std::io::stderr(), $($tt)*)
|
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-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
|
yazi-scheduler = { path = "../yazi-scheduler", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", 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-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
paste = { workspace = true }
|
paste = { workspace = true }
|
||||||
|
|
@ -41,6 +41,3 @@ serde = { workspace = true }
|
||||||
serde_with = { workspace = true }
|
serde_with = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
tokio = { 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 mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||||
|
use yazi_term::event::MouseEvent;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MouseForm {
|
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),
|
AppQuit(crate::app::QuitForm),
|
||||||
AppReflow(crate::app::ReflowForm),
|
AppReflow(crate::app::ReflowForm),
|
||||||
AppResize(crate::app::ReflowForm),
|
AppResize(crate::app::ReflowForm),
|
||||||
AppResume(crate::app::ResumeForm),
|
AppResume(crate::app::ReflowForm),
|
||||||
AppStop(crate::app::StopForm),
|
AppStop(crate::VoidForm),
|
||||||
AppTheme(crate::VoidForm),
|
AppTheme(crate::VoidForm),
|
||||||
AppTitle(crate::app::TitleForm),
|
AppTitle(crate::app::TitleForm),
|
||||||
AppUpdateProgress(crate::app::UpdateProgressForm),
|
AppUpdateProgress(crate::app::UpdateProgressForm),
|
||||||
|
|
@ -352,6 +352,7 @@ try_from_spark!(
|
||||||
crate::VoidForm,
|
crate::VoidForm,
|
||||||
app:bootstrap,
|
app:bootstrap,
|
||||||
app:focus,
|
app:focus,
|
||||||
|
app:stop,
|
||||||
app:theme,
|
app:theme,
|
||||||
mgr:back,
|
mgr:back,
|
||||||
mgr:bulk_rename,
|
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::MouseForm, app:mouse);
|
||||||
try_from_spark!(crate::app::PluginForm, app:plugin, app:plugin_do);
|
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::QuitForm, app:quit, mgr:quit);
|
||||||
try_from_spark!(crate::app::ReflowForm, app:reflow, app:resize);
|
try_from_spark!(crate::app::ReflowForm, app:reflow, app:resize, app:resume);
|
||||||
try_from_spark!(crate::app::ResumeForm, app:resume);
|
|
||||||
try_from_spark!(crate::app::StopForm, app:stop);
|
|
||||||
try_from_spark!(crate::app::TitleForm, app:title);
|
try_from_spark!(crate::app::TitleForm, app:title);
|
||||||
try_from_spark!(crate::app::UpdateProgressForm, app:update_progress);
|
try_from_spark!(crate::app::UpdateProgressForm, app:update_progress);
|
||||||
try_from_spark!(crate::cmp::CloseForm, cmp:close);
|
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-runner = { path = "../yazi-runner", version = "26.5.6" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
yazi-shared = { path = "../yazi-shared", version = "26.5.6" }
|
||||||
yazi-term = { path = "../yazi-term", 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" }
|
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||||
|
yazi-term = { path = "../yazi-term", version = "26.5.6" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
dyn-clone = { workspace = true }
|
dyn-clone = { workspace = true }
|
||||||
foldhash = { workspace = true }
|
foldhash = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
|
|
@ -41,6 +41,3 @@ uzers = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
windows-sys = { version = "0.61.2", features = [ "Win32_UI_Shell" ] }
|
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;
|
use dyn_clone::DynClone;
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ impl Clone for Box<dyn DataAny> {
|
||||||
fn clone(&self) -> Self { dyn_clone::clone_box(&**self) }
|
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 {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("DataAny").finish_non_exhaustive()
|
f.debug_struct("DataAny").finish_non_exhaustive()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use crossterm::event::{KeyEvent, MouseEvent};
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use yazi_shim::cell::RoCell;
|
use yazi_shim::cell::RoCell;
|
||||||
|
|
||||||
|
|
@ -11,12 +10,8 @@ static RX: RoCell<mpsc::UnboundedReceiver<Event>> = RoCell::new();
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Call(ActionCow),
|
Call(ActionCow),
|
||||||
Seq(Vec<ActionCow>),
|
Seq(Vec<ActionCow>),
|
||||||
|
Term(yazi_term::event::Event),
|
||||||
Render(bool),
|
Render(bool),
|
||||||
Key(KeyEvent),
|
|
||||||
Mouse(MouseEvent),
|
|
||||||
Resize,
|
|
||||||
Focus,
|
|
||||||
Paste(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Event {
|
impl Event {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
yazi_macro::mod_pub!(data event loc path pool scheme shell strand translit url wtf8);
|
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() {
|
pub fn init() {
|
||||||
LOCAL_SET.with(tokio::task::LocalSet::new);
|
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::Regular(_), U::Regular(_)) => Ok(prefix),
|
||||||
(U::Search { .. }, U::Search { .. }) => Ok(prefix),
|
(U::Search { .. }, U::Search { .. }) => Ok(prefix),
|
||||||
(U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => {
|
(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, .. }) => {
|
(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
|
// Both are local files
|
||||||
|
|
@ -380,16 +380,16 @@ impl<'a> Url<'a> {
|
||||||
|
|
||||||
// Only the entry of archives is a local file
|
// Only the entry of archives is a local file
|
||||||
(U::Regular(_), U::Archive { .. }) => {
|
(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 { .. }) => {
|
(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(_)) => {
|
(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 { .. }) => {
|
(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
|
// Independent virtual file space
|
||||||
|
|
@ -414,10 +414,10 @@ impl<'a> Url<'a> {
|
||||||
(U::Regular(_), U::Regular(_)) => Ok(suffix),
|
(U::Regular(_), U::Regular(_)) => Ok(suffix),
|
||||||
(U::Search { .. }, U::Search { .. }) => Ok(suffix),
|
(U::Search { .. }, U::Search { .. }) => Ok(suffix),
|
||||||
(U::Archive { domain: a, .. }, U::Archive { domain: b, .. }) => {
|
(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, .. }) => {
|
(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
|
// Both are local files
|
||||||
|
|
@ -426,16 +426,16 @@ impl<'a> Url<'a> {
|
||||||
|
|
||||||
// Only the entry of archives is a local file
|
// Only the entry of archives is a local file
|
||||||
(U::Regular(_), U::Archive { .. }) => {
|
(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 { .. }) => {
|
(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(_)) => {
|
(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 { .. }) => {
|
(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
|
// 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
|
# External dependencies
|
||||||
arc-swap = { workspace = true }
|
arc-swap = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
|
||||||
hashbrown = { workspace = true }
|
hashbrown = { workspace = true }
|
||||||
mlua = { workspace = true }
|
mlua = { workspace = true }
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
|
|
@ -33,6 +32,3 @@ unicode-width = { workspace = true }
|
||||||
|
|
||||||
[dependencies.unicode-segmentation]
|
[dependencies.unicode-segmentation]
|
||||||
version = "1.13.2"
|
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]
|
[package]
|
||||||
name = "yazi-term"
|
name = "yazi-term"
|
||||||
description = "Yazi terminal extensions"
|
description = "Cross-platform virtual terminal"
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
license.workspace = true
|
license.workspace = true
|
||||||
|
|
@ -13,17 +13,23 @@ rust-version.workspace = true
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[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-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||||
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
crossterm = { workspace = true }
|
base64 = { workspace = true }
|
||||||
|
bitflags = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
|
parking_lot = { workspace = true }
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
[target.'cfg(target_os = "macos")'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
crossterm = { workspace = true, features = [ "use-dev-tty", "libc" ] }
|
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
|
# 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.
|
A fork of [Termina](https://github.com/helix-editor/termina) used by Yazi.
|
||||||
|
|
||||||
[source]: https://github.com/sxyazi/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