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