mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat!: new virtual terminal backend (#3989)
This commit is contained in:
parent
3f5cc47a48
commit
c037855c4f
148 changed files with 3434 additions and 1596 deletions
|
|
@ -16,6 +16,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
|
|
||||||
- Bulk create ([#3793])
|
- Bulk create ([#3793])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Rename `<BackTab>` to `<S-Tab>` ([#3989])
|
||||||
|
- Remove Legacy Console Mode on Windows ([#3989])
|
||||||
|
|
||||||
## [v26.5.6]
|
## [v26.5.6]
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
@ -1721,3 +1726,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
||||||
[#3906]: https://github.com/sxyazi/yazi/pull/3906
|
[#3906]: https://github.com/sxyazi/yazi/pull/3906
|
||||||
[#3934]: https://github.com/sxyazi/yazi/pull/3934
|
[#3934]: https://github.com/sxyazi/yazi/pull/3934
|
||||||
[#3943]: https://github.com/sxyazi/yazi/pull/3943
|
[#3943]: https://github.com/sxyazi/yazi/pull/3943
|
||||||
|
[#3989]: https://github.com/sxyazi/yazi/pull/3989
|
||||||
|
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ impl Cha {
|
||||||
where
|
where
|
||||||
U: AsUrl,
|
U: AsUrl,
|
||||||
{
|
{
|
||||||
|
#[allow(unused_mut)]
|
||||||
let mut kind = ChaKind::DUMMY;
|
let mut kind = ChaKind::DUMMY;
|
||||||
let mode = r#type.map(ChaMode::from_bare).unwrap_or_default();
|
let mode = r#type.map(ChaMode::from_bare).unwrap_or_default();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,9 @@ impl TryFrom<Attrs> for std::fs::FileTimes {
|
||||||
impl TryFrom<Attrs> for std::fs::Permissions {
|
impl TryFrom<Attrs> for std::fs::Permissions {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn try_from(value: Attrs) -> Result<Self, Self::Error> {
|
fn try_from(_value: Attrs) -> Result<Self, Self::Error> {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
if let Some(mode) = value.mode {
|
if let Some(mode) = _value.mode {
|
||||||
return Ok(mode.into());
|
return Ok(mode.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,13 @@ 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 _;
|
||||||
|
let dst = &mut $dst;
|
||||||
|
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-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||||
yazi-emulator = { path = "../yazi-emulator", version = "26.5.6" }
|
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
||||||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
yazi-tty = { path = "../yazi-tty", version = "26.5.9" }
|
||||||
yazi-shim = { path = "../yazi-shim", version = "26.5.6" }
|
|
||||||
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 }
|
||||||
ratatui = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
tokio = { workspace = true }
|
futures = { workspace = true }
|
||||||
|
parking_lot = { workspace = true }
|
||||||
|
ratatui = { workspace = true }
|
||||||
|
thiserror = { 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 waker);
|
||||||
|
|
||||||
|
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)?.checked_sub(1).ok_or(ParseError::Invalid)?;
|
||||||
|
let row = parse_next::<u16>(&mut it)?.checked_sub(1).ok_or(ParseError::Invalid)?;
|
||||||
|
|
||||||
|
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].checked_sub(33).ok_or(ParseError::Invalid)?);
|
||||||
|
let row = u16::from(seq[5].checked_sub(33).ok_or(ParseError::Invalid)?);
|
||||||
|
|
||||||
|
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);
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue