mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-21 14:51:03 +00:00
feat: drag and drop (#4005)
This commit is contained in:
parent
cf8b54179e
commit
7a51626505
52 changed files with 784 additions and 173 deletions
|
|
@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
|
||||
### Added
|
||||
|
||||
- Drag and drop ([#4005])
|
||||
- Bulk create ([#3793])
|
||||
- Image preview with Überzug++ on Niri ([#3990])
|
||||
|
||||
|
|
@ -1729,3 +1730,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/):
|
|||
[#3943]: https://github.com/sxyazi/yazi/pull/3943
|
||||
[#3989]: https://github.com/sxyazi/yazi/pull/3989
|
||||
[#3990]: https://github.com/sxyazi/yazi/pull/3990
|
||||
[#4005]: https://github.com/sxyazi/yazi/pull/4005
|
||||
|
|
|
|||
36
Cargo.lock
generated
36
Cargo.lock
generated
|
|
@ -29,9 +29,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66bd29a732b644c0431c6140f370d097879203d79b80c94a6747ba0872adaef8"
|
||||
checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"cpubits",
|
||||
|
|
@ -41,9 +41,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.11.0-rc.3"
|
||||
version = "0.11.0-rc.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e22c0c90bbe8d4f77c3ca9ddabe41a1f8382d6fc1f7cea89459d0f320371f972"
|
||||
checksum = "da8c919c118108f144adecad74b425b804ad075580d605d9b33c2d6d1c62a2f8"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
|
|
@ -716,9 +716,9 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|||
|
||||
[[package]]
|
||||
name = "compact_str"
|
||||
version = "0.9.0"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb1325a1cece981e8a296ab8f0f9b63ae357bd0784a9faaf548cc7b480707a"
|
||||
checksum = "9dfdd1c2274d9aa354115b09dc9a901d6c5576818cdf70d14cae2bdb47df00ab"
|
||||
dependencies = [
|
||||
"castaway",
|
||||
"cfg-if",
|
||||
|
|
@ -2156,9 +2156,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
|||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.16"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
||||
checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
|
@ -2201,9 +2201,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.29"
|
||||
version = "0.4.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
|
||||
checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5"
|
||||
|
||||
[[package]]
|
||||
name = "loop9"
|
||||
|
|
@ -2287,9 +2287,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
|
|||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
version = "2.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
||||
|
||||
[[package]]
|
||||
name = "memmem"
|
||||
|
|
@ -5685,6 +5685,7 @@ dependencies = [
|
|||
"yazi-shared",
|
||||
"yazi-shim",
|
||||
"yazi-term",
|
||||
"yazi-tty",
|
||||
"yazi-vfs",
|
||||
"yazi-widgets",
|
||||
]
|
||||
|
|
@ -5992,6 +5993,7 @@ dependencies = [
|
|||
"libc",
|
||||
"mlua",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"ratatui",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
|
|
@ -6135,8 +6137,10 @@ name = "yazi-shim"
|
|||
version = "26.5.6"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"base64",
|
||||
"hashbrown 0.17.1",
|
||||
"mlua",
|
||||
"percent-encoding",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"thiserror 2.0.18",
|
||||
|
|
@ -6264,18 +6268,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
|
||||
checksum = "bce33a6288fa3f072a8c2c7d0f2fdbb90e28298f0135c1f99b96c3db2efcc60b"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.48"
|
||||
version = "0.8.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
|
||||
checksum = "8fd425244944f4ab65ccff928e7323354c5a018c75838362fdce749dfad2ee1e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
|
|||
- 💫 Vim-like input/pick/confirm/which/notify component, auto-completion for cd paths
|
||||
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, code, directories, etc.)
|
||||
- 🔄 Bulk Rename/Create, Archive Extraction, Visual Mode, File Chooser, [Git Integration](https://github.com/yazi-rs/plugins/tree/main/git.yazi), [Mount Manager](https://github.com/yazi-rs/plugins/tree/main/mount.yazi)
|
||||
- 🎨 Theme System, Mouse Support, Trash Bin, Custom Layouts, CSI u, OSC 52
|
||||
- 🎨 Theme System, Mouse Support, Drag and Drop, Trash Bin, Custom Layouts, CSI u, OSC 52
|
||||
- ... and more!
|
||||
|
||||
https://github.com/sxyazi/yazi/assets/17523360/92ff23fa-0cd5-4f04-b387-894c12265cc7
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"flagWords":[],"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","mimetypes"],"language":"en","version":"0.2"}
|
||||
{"version":"0.2","language":"en","flagWords":[],"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","mimetypes","Mimelist"]}
|
||||
12
flake.lock
generated
12
flake.lock
generated
|
|
@ -20,11 +20,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1776329215,
|
||||
"narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=",
|
||||
"lastModified": 1779877693,
|
||||
"narHash": "sha256-NOF9NAREhxr50bbBfVcVOq+ArCMSoe8dP79Pk2uyARk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "b86751bc4085f48661017fa226dee99fab6c651b",
|
||||
"rev": "4100e830e085863741bc69b156ec4ccd53ab5be0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
@ -48,11 +48,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1776741231,
|
||||
"narHash": "sha256-k9G98qzn+7npROUaks8VqCFm7cFtEG8ulQLBBo5lItg=",
|
||||
"lastModified": 1779851998,
|
||||
"narHash": "sha256-UkkMh3bX9QW4Luqkm98nUaOqKWrU6i65mUnph3WeSSw=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "02061303f7c4c964f7b4584dabd9e985b4cd442b",
|
||||
"rev": "6cddd512fa2bf7231f098d3a2f92f6e4cff71e0a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,6 @@ rustPlatform.buildRustPackage (finalAttrs: {
|
|||
|
||||
cargoLock = {
|
||||
lockFile = "${src}/Cargo.lock";
|
||||
#outputHashes = {
|
||||
# "mlua-0.10.0" = "sha256-Xg6/jc+UP8tbJJ6x1sbAgt8ZHt051xEBBcjmikQqYlw=";
|
||||
#};
|
||||
};
|
||||
|
||||
env = {
|
||||
|
|
|
|||
45
yazi-actor/src/app/dnd.rs
Normal file
45
yazi-actor/src/app/dnd.rs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
use anyhow::Result;
|
||||
use mlua::{ObjectLike, Table};
|
||||
use tracing::error;
|
||||
use yazi_actor::lives::Lives;
|
||||
use yazi_binding::runtime_scope;
|
||||
use yazi_macro::succ;
|
||||
use yazi_parser::app::DndForm;
|
||||
use yazi_plugin::LUA;
|
||||
use yazi_shared::data::Data;
|
||||
|
||||
use crate::{Actor, Ctx};
|
||||
|
||||
pub struct Dnd;
|
||||
|
||||
impl Actor for Dnd {
|
||||
type Form = DndForm;
|
||||
|
||||
const NAME: &str = "dnd";
|
||||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
let event = yazi_binding::DndEvent::from(form.event);
|
||||
|
||||
let Some(size) = cx.term.as_ref().and_then(|t| t.size().ok()) else { succ!() };
|
||||
let area = yazi_binding::elements::Rect::from(size);
|
||||
|
||||
let result = Lives::scope(cx.core, move || {
|
||||
runtime_scope!(LUA, "root", {
|
||||
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
|
||||
|
||||
if event.is_drag() {
|
||||
root.call_method::<()>("drag", event)?;
|
||||
} else {
|
||||
root.call_method::<()>("drop", event)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
if let Err(ref e) = result {
|
||||
error!("{e}");
|
||||
}
|
||||
succ!(result?);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ yazi_macro::mod_flat!(
|
|||
accept_payload
|
||||
bootstrap
|
||||
deprecate
|
||||
dnd
|
||||
focus
|
||||
lua
|
||||
mouse
|
||||
|
|
|
|||
|
|
@ -25,25 +25,25 @@ impl Actor for Mouse {
|
|||
let area = yazi_binding::elements::Rect::from(size);
|
||||
|
||||
let result = Lives::scope(cx.core, move || {
|
||||
let root = runtime_scope!(LUA, "root", {
|
||||
LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)
|
||||
})?;
|
||||
runtime_scope!(LUA, "root", {
|
||||
let root = LUA.globals().raw_get::<Table>("Root")?.call_method::<Table>("new", area)?;
|
||||
|
||||
match event.kind {
|
||||
MouseEventKind::Down(_) => root.call_method("click", (event, false))?,
|
||||
MouseEventKind::Up(_) => root.call_method("click", (event, true))?,
|
||||
match event.kind {
|
||||
MouseEventKind::Down(_) => root.call_method("click", (event, false))?,
|
||||
MouseEventKind::Up(_) => root.call_method("click", (event, true))?,
|
||||
|
||||
MouseEventKind::ScrollDown => root.call_method("scroll", (event, 1))?,
|
||||
MouseEventKind::ScrollUp => root.call_method("scroll", (event, -1))?,
|
||||
MouseEventKind::ScrollDown => root.call_method("scroll", (event, 1))?,
|
||||
MouseEventKind::ScrollUp => root.call_method("scroll", (event, -1))?,
|
||||
|
||||
MouseEventKind::ScrollRight => root.call_method("touch", (event, 1))?,
|
||||
MouseEventKind::ScrollLeft => root.call_method("touch", (event, -1))?,
|
||||
MouseEventKind::ScrollRight => root.call_method("touch", (event, 1))?,
|
||||
MouseEventKind::ScrollLeft => root.call_method("touch", (event, -1))?,
|
||||
|
||||
MouseEventKind::Moved => root.call_method("move", event)?,
|
||||
MouseEventKind::Drag(_) => root.call_method("drag", event)?,
|
||||
}
|
||||
MouseEventKind::Moved => root.call_method("move", event)?,
|
||||
MouseEventKind::Drag(_) => root.call_method("drag", event)?,
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(())
|
||||
})
|
||||
});
|
||||
|
||||
if let Err(ref e) = result {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ impl Actor for Spawn {
|
|||
|
||||
fn act(cx: &mut Ctx, form: Self::Form) -> Result<Data> {
|
||||
succ!(match form.opt {
|
||||
TaskOpt::Cut(r#in) => cx.tasks.scheduler.file_cut(r#in),
|
||||
TaskOpt::Plugin(r#in) => cx.tasks.scheduler.plugin_entry(r#in),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{fmt::Write, io::Write as ioWrite, path::PathBuf};
|
||||
use std::{fmt::Write as _, io::Write as _, path::PathBuf};
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::{Engine, engine::{Config, general_purpose::STANDARD}};
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ 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" }
|
||||
yazi-vfs = { path = "../yazi-vfs", version = "26.5.6" }
|
||||
yazi-widgets = { path = "../yazi-widgets", version = "26.5.6" }
|
||||
|
||||
|
|
|
|||
55
yazi-binding/src/dnd.rs
Normal file
55
yazi-binding/src/dnd.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use std::{mem, ops::Deref};
|
||||
|
||||
use mlua::{UserData, UserDataFields, Value};
|
||||
use yazi_shim::strum::IntoStr;
|
||||
use yazi_term::event::{DndDropArrive, DndEvent as Inner};
|
||||
|
||||
use crate::{cached_field, cached_field_mut};
|
||||
|
||||
pub struct DndEvent {
|
||||
inner: Inner,
|
||||
|
||||
v_mimes: Option<Value>,
|
||||
v_data: Option<mlua::Result<Value>>,
|
||||
}
|
||||
|
||||
impl Deref for DndEvent {
|
||||
type Target = Inner;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl From<Inner> for DndEvent {
|
||||
fn from(inner: Inner) -> Self { Self { inner, v_mimes: None, v_data: None } }
|
||||
}
|
||||
|
||||
impl UserData for DndEvent {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("type", |_, me| Ok(me.inner.r#type()));
|
||||
|
||||
fields.add_field_method_get("x", |_, me| Ok(me.inner.x()));
|
||||
|
||||
fields.add_field_method_get("y", |_, me| Ok(me.inner.y()));
|
||||
|
||||
fields.add_field_method_get("idx", |_, me| Ok(me.inner.idx()));
|
||||
|
||||
fields.add_field_method_get("op", |_, me| Ok(me.inner.op().map(IntoStr::into_str)));
|
||||
|
||||
cached_field!(fields, mimes, |lua, me| {
|
||||
if let Some(mimes) = me.inner.mimes() {
|
||||
lua.create_sequence_from(mimes.iter())?.into_lua(lua)
|
||||
} else {
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
});
|
||||
|
||||
cached_field_mut!(fields, data, |lua, me| {
|
||||
match &mut me.inner {
|
||||
Inner::DropArrive(DndDropArrive { data, .. }) => {
|
||||
lua.create_external_string(mem::take(data))?.into_lua(lua)
|
||||
}
|
||||
_ => Ok(Value::Nil),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -2,4 +2,4 @@ mod macros;
|
|||
|
||||
yazi_macro::mod_pub!(config elements process theme);
|
||||
|
||||
yazi_macro::mod_flat!(access calculator cha chan chord_cow composer error fd file handle icon id image input iter layer mouse path permit range runtime scheme selector stage style url utils);
|
||||
yazi_macro::mod_flat!(access calculator cha chan chord_cow composer dnd error fd file handle icon id image input iter layer mouse path permit range runtime scheme selector stage style tty url utils);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ impl From<yazi_term::event::MouseEvent> for MouseEvent {
|
|||
|
||||
impl UserData for MouseEvent {
|
||||
fn add_fields<F: UserDataFields<Self>>(fields: &mut F) {
|
||||
fields.add_field("type", "legacy");
|
||||
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| {
|
||||
|
|
|
|||
74
yazi-binding/src/tty.rs
Normal file
74
yazi-binding/src/tty.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
use std::io::Write;
|
||||
|
||||
use mlua::{BorrowedBytes, ExternalError, IntoLuaMulti, Lua, MultiValue, Table, UserData, UserDataMethods};
|
||||
use yazi_shim::mlua::{ByteString, LuaTableExt};
|
||||
use yazi_term::sequence::{ConfirmDrag, ConfirmDrop, FinishDrop, PresentDrag, PresentDragIcon, StartDrag, StartDrop};
|
||||
use yazi_tty::TTY;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pub struct Tty;
|
||||
|
||||
impl Tty {
|
||||
fn queue(lua: &Lua, kind: &[u8], t: &Table) -> mlua::Result<MultiValue> {
|
||||
let mut w = TTY.writer();
|
||||
|
||||
let result = match kind {
|
||||
b"ConfirmDrag" => {
|
||||
let it = t.raw_get::<Table>("mimes")?.sequence_iter::<ByteString>(lua).flatten();
|
||||
write!(w, "{}", match &*t.raw_get::<BorrowedBytes>("type")? {
|
||||
b"copy" => ConfirmDrag::Copy(it),
|
||||
b"move" => ConfirmDrag::Move(it),
|
||||
b"either" => ConfirmDrag::Either(it),
|
||||
_ => return Err("invalid ConfirmDrag type".into_lua_err()),
|
||||
})
|
||||
}
|
||||
b"ConfirmDrop" => {
|
||||
let it = t.raw_get::<Table>("mimes")?.sequence_iter::<ByteString>(lua).flatten();
|
||||
write!(w, "{}", match &*t.raw_get::<BorrowedBytes>("type")? {
|
||||
b"reject" => ConfirmDrop::Reject,
|
||||
b"copy" => ConfirmDrop::Copy(it),
|
||||
b"move" => ConfirmDrop::Move(it),
|
||||
_ => return Err("invalid ConfirmDrop type".into_lua_err()),
|
||||
})
|
||||
}
|
||||
b"StartDrag" => write!(w, "{StartDrag}"),
|
||||
b"StartDrop" => write!(w, "{}", StartDrop(t.raw_get("idx")?)),
|
||||
b"PresentDrag" => {
|
||||
write!(w, "{}", PresentDrag(t.raw_get("idx")?, &t.raw_get::<BorrowedBytes>("data")?))
|
||||
}
|
||||
b"PresentDragIcon" => {
|
||||
write!(w, "{}", PresentDragIcon {
|
||||
format: t.raw_get("format")?,
|
||||
opacity: t.raw_get("opacity")?,
|
||||
width: t.raw_get("width")?,
|
||||
height: t.raw_get("height")?,
|
||||
data: &t.raw_get::<BorrowedBytes>("data")?,
|
||||
})
|
||||
}
|
||||
b"FinishDrop" => match &*t.raw_get::<BorrowedBytes>("type")? {
|
||||
b"copy" => write!(w, "{}", FinishDrop::Copy),
|
||||
b"move" => write!(w, "{}", FinishDrop::Move),
|
||||
_ => return Err("invalid FinishDrop type".into_lua_err()),
|
||||
},
|
||||
_ => return Err("invalid sequence kind".into_lua_err()),
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(()) => true.into_lua_multi(&lua),
|
||||
Err(e) => (false, Error::Io(e)).into_lua_multi(&lua),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserData for Tty {
|
||||
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
|
||||
methods
|
||||
.add_method("queue", |lua, _, (kind, t): (BorrowedBytes, Table)| Self::queue(lua, &kind, &t));
|
||||
|
||||
methods.add_method("flush", |lua, _, ()| match TTY.writer().flush() {
|
||||
Ok(()) => true.into_lua_multi(lua),
|
||||
Err(e) => (false, Error::Io(e)).into_lua_multi(lua),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use indexmap::IndexSet;
|
||||
use tracing::debug;
|
||||
use yazi_scheduler::file::FileInCut;
|
||||
use yazi_shared::url::{UrlBuf, UrlBufCov, UrlLike};
|
||||
|
||||
use super::Tasks;
|
||||
|
|
@ -17,7 +18,7 @@ impl Tasks {
|
|||
if force && *u == to {
|
||||
debug!("file_cut: same file, skip {to:?}");
|
||||
} else {
|
||||
self.scheduler.file_cut(u.0.clone(), to, force);
|
||||
self.scheduler.file_cut(FileInCut::new(u.0.clone(), to, force));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use yazi_scheduler::{TaskIn, plugin::PluginInEntry};
|
||||
use yazi_scheduler::{TaskIn, file::FileInCut, plugin::PluginInEntry};
|
||||
use yazi_shared::{Id, SStr};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum TaskOpt {
|
||||
Cut(FileInCut),
|
||||
|
||||
Plugin(PluginInEntry),
|
||||
}
|
||||
|
||||
|
|
@ -13,12 +15,16 @@ impl TaskIn for TaskOpt {
|
|||
|
||||
fn id(&self) -> Id {
|
||||
match self {
|
||||
Self::Cut(r#in) => r#in.id(),
|
||||
|
||||
Self::Plugin(r#in) => r#in.id(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_id(&mut self, id: Id) -> &mut Self {
|
||||
match self {
|
||||
Self::Cut(r#in) => _ = r#in.set_id(id),
|
||||
|
||||
Self::Plugin(r#in) => _ = r#in.set_id(id),
|
||||
}
|
||||
self
|
||||
|
|
@ -26,12 +32,16 @@ impl TaskIn for TaskOpt {
|
|||
|
||||
fn title(&self) -> Cow<'_, str> {
|
||||
match self {
|
||||
Self::Cut(r#in) => r#in.title(),
|
||||
|
||||
Self::Plugin(r#in) => r#in.title(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_title(&mut self, title: impl Into<SStr>) -> &mut Self {
|
||||
match self {
|
||||
Self::Cut(r#in) => _ = r#in.set_title(title),
|
||||
|
||||
Self::Plugin(r#in) => _ = r#in.set_title(title),
|
||||
}
|
||||
self
|
||||
|
|
|
|||
|
|
@ -4,10 +4,9 @@ use anyhow::Result;
|
|||
use tracing::warn;
|
||||
use yazi_actor::Ctx;
|
||||
use yazi_config::keymap::Key;
|
||||
use yazi_macro::{act, emit, writef};
|
||||
use yazi_macro::{act, emit};
|
||||
use yazi_shared::event::{ActionCow, Event, NEED_RENDER};
|
||||
use yazi_term::{event::{Event as TermEvent, KeyEvent, MouseEvent}, sequence::{ConfirmDrag, ConfirmDrop, FinishDrop, PresentDrag, PresentDragIcon, StartDrag, StartDrop}};
|
||||
use yazi_tty::TTY;
|
||||
use yazi_term::event::{DndEvent, Event as TermEvent, KeyEvent, MouseEvent};
|
||||
use yazi_widgets::input::InputMode;
|
||||
|
||||
use crate::{Executor, Router, app::App};
|
||||
|
|
@ -30,53 +29,7 @@ impl<'a> Dispatcher<'a> {
|
|||
Event::Term(TermEvent::FocusIn) => self.dispatch_focus(),
|
||||
Event::Term(TermEvent::FocusOut) => Ok(()),
|
||||
Event::Term(TermEvent::Paste(str)) => self.dispatch_paste(str),
|
||||
Event::Term(TermEvent::Dnd(dnd)) => {
|
||||
match dnd {
|
||||
yazi_term::event::DndEvent::DragOffer(event) => {
|
||||
tracing::debug!("DragOffer: {event:?}");
|
||||
// writef!(
|
||||
// TTY.writer(),
|
||||
// "{}{}{}{StartDrag}",
|
||||
// ConfirmDrag::Either(&["text/uri-list"]),
|
||||
// PresentDrag(0, b"file:///tmp/cspell.json\r\n"),
|
||||
// PresentDragIcon { format: 0, width: 6, height: 1, opacity: 0,
|
||||
// payload: b"drag" }, )
|
||||
// .ok();
|
||||
}
|
||||
yazi_term::event::DndEvent::DragAccept(event) => {
|
||||
tracing::debug!("DragAccept: {event:?}");
|
||||
}
|
||||
yazi_term::event::DndEvent::DragChange(event) => {
|
||||
tracing::debug!("DragChange: {event:?}");
|
||||
}
|
||||
yazi_term::event::DndEvent::DragLand => {
|
||||
tracing::debug!("DragLand");
|
||||
}
|
||||
yazi_term::event::DndEvent::DragEnd(event) => {
|
||||
tracing::debug!("DragEnd: {event:?}");
|
||||
}
|
||||
yazi_term::event::DndEvent::DragSend(event) => {
|
||||
tracing::debug!("DragSend: {event:?}");
|
||||
}
|
||||
|
||||
yazi_term::event::DndEvent::DropEnter(event) => {
|
||||
tracing::debug!("DropEnter: {event:?}");
|
||||
writef!(TTY.writer(), "{}", ConfirmDrop::Copy(&["text/uri-list"])).ok();
|
||||
}
|
||||
yazi_term::event::DndEvent::DropLeave => {
|
||||
tracing::debug!("DropLeave");
|
||||
}
|
||||
yazi_term::event::DndEvent::DropReady(event) => {
|
||||
tracing::debug!("DropReady: {event:?}");
|
||||
writef!(TTY.writer(), "{}", StartDrop(1)).ok();
|
||||
}
|
||||
yazi_term::event::DndEvent::DropData(event) => {
|
||||
tracing::debug!("DropData: {event:?}");
|
||||
writef!(TTY.writer(), "{}", FinishDrop::Copy).ok();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Event::Term(TermEvent::Dnd(dnd)) => self.dispatch_dnd(dnd),
|
||||
};
|
||||
|
||||
if let Err(e) = &result {
|
||||
|
|
@ -145,4 +98,9 @@ impl<'a> Dispatcher<'a> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_dnd(&mut self, dnd: DndEvent) -> Result<()> {
|
||||
let cx = &mut Ctx::active(&mut self.app.core, &mut self.app.term);
|
||||
act!(app:dnd, cx, dnd).map(|_| ())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
yazi-parser/src/app/dnd.rs
Normal file
19
yazi-parser/src/app/dnd.rs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
use mlua::{ExternalError, FromLua, IntoLua, Lua, Value};
|
||||
use yazi_term::event::DndEvent;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DndForm {
|
||||
pub event: DndEvent,
|
||||
}
|
||||
|
||||
impl From<DndEvent> for DndForm {
|
||||
fn from(event: DndEvent) -> Self { Self { event } }
|
||||
}
|
||||
|
||||
impl FromLua for DndForm {
|
||||
fn from_lua(_: Value, _: &Lua) -> mlua::Result<Self> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
||||
impl IntoLua for DndForm {
|
||||
fn into_lua(self, _: &Lua) -> mlua::Result<Value> { Err("unsupported".into_lua_err()) }
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(deprecate lua mouse plugin quit reflow title update_progress);
|
||||
yazi_macro::mod_flat!(deprecate dnd lua mouse plugin quit reflow title update_progress);
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ pub enum Spark<'a> {
|
|||
AppAcceptPayload(yazi_dds::Payload<'a>),
|
||||
AppBootstrap(crate::VoidForm),
|
||||
AppDeprecate(crate::app::DeprecateForm),
|
||||
AppDnd(crate::app::DndForm),
|
||||
AppFocus(crate::VoidForm),
|
||||
AppLua(crate::app::LuaForm),
|
||||
AppMouse(crate::app::MouseForm),
|
||||
|
|
@ -199,6 +200,7 @@ impl<'a> IntoLua for Spark<'a> {
|
|||
Self::AppAcceptPayload(b) => b.into_lua(lua),
|
||||
Self::AppBootstrap(b) => b.into_lua(lua),
|
||||
Self::AppDeprecate(b) => b.into_lua(lua),
|
||||
Self::AppDnd(b) => b.into_lua(lua),
|
||||
Self::AppFocus(b) => b.into_lua(lua),
|
||||
Self::AppLua(b) => b.into_lua(lua),
|
||||
Self::AppMouse(b) => b.into_lua(lua),
|
||||
|
|
@ -376,6 +378,7 @@ try_from_spark!(
|
|||
// App
|
||||
try_from_spark!(crate::ArrowForm, mgr:arrow, mgr:tab_swap);
|
||||
try_from_spark!(crate::app::DeprecateForm, app:deprecate);
|
||||
try_from_spark!(crate::app::DndForm, app:dnd);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -36,18 +36,19 @@ 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 }
|
||||
futures = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
twox-hash = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
ansi-to-tui = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
percent-encoding = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
tokio-stream = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
twox-hash = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[target."cfg(unix)".dependencies]
|
||||
libc = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
Current = {
|
||||
_id = "current",
|
||||
_dragging = false,
|
||||
_dropping = false,
|
||||
}
|
||||
|
||||
function Current:new(area, tab)
|
||||
|
|
@ -24,6 +26,14 @@ function Current:empty()
|
|||
}
|
||||
end
|
||||
|
||||
function Current:dropping()
|
||||
if Current._dropping then
|
||||
return Tip:new(self._area, "Drop to move here…"):redraw()
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
|
||||
function Current:reflow() return { self } end
|
||||
|
||||
function Current:redraw()
|
||||
|
|
@ -44,6 +54,7 @@ function Current:redraw()
|
|||
return {
|
||||
ui.List(left):area(self._area),
|
||||
ui.Text(right):area(self._area):align(ui.Align.RIGHT),
|
||||
table.unpack(self:dropping()),
|
||||
}
|
||||
end
|
||||
|
||||
|
|
@ -62,3 +73,31 @@ end
|
|||
function Current:scroll(event, step) ya.emit("arrow", { step }) end
|
||||
|
||||
function Current:touch(event, step) end
|
||||
|
||||
function Current:drag(event)
|
||||
if event.type == "offer" then
|
||||
Current._dragging = require("dnd").offer_uri_list()
|
||||
elseif event.type == "end" or event.type == "error" then
|
||||
Current._dragging = false
|
||||
end
|
||||
end
|
||||
|
||||
function Current:drop(event)
|
||||
if Current._dragging then
|
||||
return
|
||||
elseif event.type == "enter" then
|
||||
rt.tty:queue("ConfirmDrop", { type = "move", mimes = { "text/uri-list" } })
|
||||
elseif event.type == "ready" then
|
||||
rt.tty:queue("StartDrop", { idx = 1 })
|
||||
elseif event.type == "arrive" then
|
||||
rt.tty:queue("FinishDrop", { type = "move" })
|
||||
require("dnd").cut_uri_list(event.data)
|
||||
end
|
||||
rt.tty:flush()
|
||||
|
||||
local d = event.type == "enter"
|
||||
if Current._dropping ~= d then
|
||||
Current._dropping = d
|
||||
ui.render()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ function Rail:scroll(event, step) end
|
|||
function Rail:touch(event, step) end
|
||||
|
||||
function Rail:drag(event)
|
||||
if event.type ~= "legacy" then
|
||||
return
|
||||
end
|
||||
|
||||
local c, x, parent, current, preview = self._chunks, 0, 0, 0, 0
|
||||
if self._id == "rail-left" then
|
||||
x = math.min(event.x, c[2].right - 2)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
Root = {
|
||||
_id = "root",
|
||||
_dragging = nil,
|
||||
_dropping = nil,
|
||||
}
|
||||
|
||||
function Root:new(area)
|
||||
|
|
@ -50,7 +51,7 @@ end
|
|||
|
||||
-- Mouse events
|
||||
function Root:click(event, up)
|
||||
local c = Root._dragging or ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow())
|
||||
local c = ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow())
|
||||
Root._dragging = not up and c or nil
|
||||
|
||||
if tostring(cx.layer) == "mgr" then
|
||||
|
|
@ -84,3 +85,16 @@ function Root:drag(event)
|
|||
local c = Root._dragging
|
||||
return c and c.drag and c:drag(event)
|
||||
end
|
||||
|
||||
function Root:drop(event)
|
||||
local d = Root._dropping
|
||||
local c = event.x and ya.child_at(ui.Rect { x = event.x, y = event.y }, self:reflow()) or d
|
||||
if d and d.drop and d._id ~= c._id then
|
||||
d:drop { type = "leave" }
|
||||
end
|
||||
|
||||
Root._dropping = c
|
||||
if tostring(cx.layer) == "mgr" then
|
||||
return c and c.drop and c:drop(event)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
29
yazi-plugin/preset/components/tip.lua
Normal file
29
yazi-plugin/preset/components/tip.lua
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
Tip = {
|
||||
_id = "tip",
|
||||
}
|
||||
|
||||
function Tip:new(area, text)
|
||||
local me = setmetatable({ _area = area, _text = text }, { __index = self })
|
||||
me:layout()
|
||||
return me
|
||||
end
|
||||
|
||||
function Tip:layout()
|
||||
self._chunks = ui.Layout()
|
||||
:direction(ui.Layout.VERTICAL)
|
||||
:constraints({
|
||||
ui.Constraint.Fill(1),
|
||||
ui.Constraint.Length(1),
|
||||
ui.Constraint.Fill(1),
|
||||
})
|
||||
:split(self._area)
|
||||
end
|
||||
|
||||
function Tip:reflow() return {} end
|
||||
|
||||
function Tip:redraw()
|
||||
return {
|
||||
ui.Clear(self._chunks[2]),
|
||||
ui.Text(self._text):area(self._chunks[2]):align(ui.Align.CENTER):fg("black"):bg("yellow"):bold(),
|
||||
}
|
||||
end
|
||||
45
yazi-plugin/preset/plugins/dnd.lua
Normal file
45
yazi-plugin/preset/plugins/dnd.lua
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
local M = {}
|
||||
|
||||
function M.selected_uri_list()
|
||||
local paths = {}
|
||||
for _, u in pairs(cx.active.selected) do
|
||||
paths[#paths + 1] = "file://" .. ya.percent_encode(tostring(u.path))
|
||||
end
|
||||
if #paths == 0 and cx.active.current.hovered then
|
||||
paths[1] = "file://" .. ya.percent_encode(tostring(cx.active.current.hovered.path))
|
||||
end
|
||||
return paths
|
||||
end
|
||||
|
||||
function M.cut_uri_list(list)
|
||||
for line in list:gmatch("[^\r\n]+") do
|
||||
if line:sub(1, 7) ~= "file://" then
|
||||
goto continue
|
||||
end
|
||||
|
||||
local from = Url(ya.percent_decode(line:sub(8)))
|
||||
if from.name then
|
||||
local to = cx.active.current.cwd:join(from.name)
|
||||
ya.async(function() ya.task("cut", { from = from, to = to }):spawn() end)
|
||||
end
|
||||
|
||||
::continue::
|
||||
end
|
||||
end
|
||||
|
||||
function M.offer_uri_list()
|
||||
local list = M.selected_uri_list()
|
||||
if #list == 0 then
|
||||
return false
|
||||
end
|
||||
|
||||
local icon = string.format("%d selected file(s)", #list)
|
||||
rt.tty:queue("ConfirmDrag", { type = "either", mimes = { "text/uri-list" } })
|
||||
rt.tty:queue("PresentDrag", { idx = 0, data = table.concat(list, "\r\n") })
|
||||
rt.tty:queue("PresentDragIcon", { format = 0, opacity = 0, width = 6, height = 4, data = icon })
|
||||
rt.tty:queue("StartDrag", {})
|
||||
rt.tty:flush()
|
||||
return true
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
use mlua::{IntoLua, Lua, LuaSerdeExt, Value};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Url, config::{OpenRules, Opener}, elements::Wrap};
|
||||
use yazi_binding::{Composer, ComposerGet, ComposerSet, SER_OPT, Tty, Url, config::{OpenRules, Opener}, elements::Wrap};
|
||||
use yazi_boot::ARGS;
|
||||
use yazi_config::YAZI;
|
||||
|
||||
|
|
@ -7,6 +7,7 @@ pub fn compose() -> Composer<ComposerGet, ComposerSet> {
|
|||
fn get(lua: &Lua, key: &[u8]) -> mlua::Result<Value> {
|
||||
match key {
|
||||
b"args" => args().into_lua(lua)?,
|
||||
b"tty" => Tty.into_lua(lua)?,
|
||||
b"term" => super::term().into_lua(lua)?,
|
||||
b"mgr" => mgr().into_lua(lua)?,
|
||||
b"open" => open().into_lua(lua)?,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ fn stage_1(lua: &Lua) -> Result<()> {
|
|||
lua.load(preset!("components/tab")).set_name("tab.lua").exec()?;
|
||||
lua.load(preset!("components/tabs")).set_name("tabs.lua").exec()?;
|
||||
lua.load(preset!("components/tasks")).set_name("tasks.lua").exec()?;
|
||||
lua.load(preset!("components/tip")).set_name("tip.lua").exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ impl Utils {
|
|||
pub(super) fn task(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, (kind, value): (mlua::String, Value)| {
|
||||
Ok(TaskOpt(match &*kind.as_bytes() {
|
||||
b"cut" => tasks::TaskOpt::Cut(<_>::from_lua(value, lua)?),
|
||||
|
||||
b"plugin" => tasks::TaskOpt::Plugin(<_>::from_lua(value, lua)?),
|
||||
|
||||
_ => Err(format!("unsupported spawn kind: {}", kind.display()).into_lua_err())?,
|
||||
}))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use std::borrow::Cow;
|
||||
|
||||
use mlua::{Function, Lua};
|
||||
use twox_hash::XxHash3_128;
|
||||
use yazi_shim::RFC_3986;
|
||||
use yazi_widgets::CLIPBOARD;
|
||||
|
||||
use super::Utils;
|
||||
|
|
@ -33,4 +36,24 @@ impl Utils {
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn percent_decode(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, s: mlua::String| {
|
||||
let b = s.as_bytes();
|
||||
match percent_encoding::percent_decode(&b).into() {
|
||||
Cow::Borrowed(_) => Ok(s),
|
||||
Cow::Owned(b) => lua.create_external_string(b),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn percent_encode(lua: &Lua) -> mlua::Result<Function> {
|
||||
lua.create_function(|lua, s: mlua::String| {
|
||||
let b = s.as_bytes();
|
||||
match percent_encoding::percent_encode(&b, RFC_3986).into() {
|
||||
Cow::Borrowed(_) => Ok(s),
|
||||
Cow::Owned(s) => lua.create_external_string(s),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,8 @@ pub fn compose(
|
|||
b"hash" => Utils::hash(lua)?,
|
||||
b"quote" => Utils::quote(lua)?,
|
||||
b"clipboard" => Utils::clipboard(lua)?,
|
||||
b"percent_encode" => Utils::percent_encode(lua)?,
|
||||
b"percent_decode" => Utils::percent_decode(lua)?,
|
||||
|
||||
// Time
|
||||
b"time" => Utils::time(lua)?,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ impl Default for Loader {
|
|||
("archive".to_owned(), preset!("plugins/archive").into()),
|
||||
("code".to_owned(), preset!("plugins/code").into()),
|
||||
("dds".to_owned(), preset!("plugins/dds").into()),
|
||||
("dnd".to_owned(), preset!("plugins/dnd").into()),
|
||||
("empty".to_owned(), preset!("plugins/empty").into()),
|
||||
("extract".to_owned(), preset!("plugins/extract").into()),
|
||||
("file".to_owned(), preset!("plugins/file").into()),
|
||||
|
|
@ -71,6 +72,7 @@ impl Default for Loader {
|
|||
("tab".to_owned(), [][..].into()),
|
||||
("tabs".to_owned(), [][..].into()),
|
||||
("tasks".to_owned(), [][..].into()),
|
||||
("tip".to_owned(), [][..].into()),
|
||||
// Reserved
|
||||
("inline".to_owned(), [][..].into()),
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use std::{borrow::Cow, mem, path::PathBuf};
|
||||
|
||||
use mlua::{ExternalError, FromLua, Lua, Value};
|
||||
use tokio::sync::mpsc;
|
||||
use yazi_fs::cha::Cha;
|
||||
use yazi_shared::{Id, url::{UrlBuf, UrlLike}};
|
||||
|
|
@ -172,7 +173,7 @@ impl FileInCopy {
|
|||
|
||||
// --- Cut
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FileInCut {
|
||||
pub struct FileInCut {
|
||||
pub(crate) id: Id,
|
||||
pub(crate) from: UrlBuf,
|
||||
pub(crate) to: UrlBuf,
|
||||
|
|
@ -203,6 +204,19 @@ impl Drop for FileInCut {
|
|||
}
|
||||
|
||||
impl FileInCut {
|
||||
pub fn new(from: UrlBuf, to: UrlBuf, force: bool) -> Self {
|
||||
Self {
|
||||
follow: !from.scheme().covariant(to.scheme()),
|
||||
id: Id::ZERO,
|
||||
from,
|
||||
to,
|
||||
force,
|
||||
cha: None,
|
||||
retry: 0,
|
||||
drop: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn into_link(mut self) -> FileInLink {
|
||||
FileInLink {
|
||||
id: self.id,
|
||||
|
|
@ -222,6 +236,20 @@ impl FileInCut {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromLua for FileInCut {
|
||||
fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> {
|
||||
let Value::Table(t) = value else {
|
||||
return Err("constructing FileInCut from non-table value".into_lua_err());
|
||||
};
|
||||
|
||||
Ok(Self::new(
|
||||
t.raw_get::<yazi_binding::Url>("from")?.into(),
|
||||
t.raw_get::<yazi_binding::Url>("to")?.into(),
|
||||
t.raw_get("force")?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Link
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct FileInLink {
|
||||
|
|
|
|||
|
|
@ -72,9 +72,9 @@ impl TaskIn for PluginInEntry {
|
|||
}
|
||||
}
|
||||
|
||||
impl PluginInEntry {
|
||||
pub(crate) fn into_job(self) -> EntryJob {
|
||||
EntryJob { id: self.id, args: self.args, plugin: self.plugin }
|
||||
impl From<PluginInEntry> for EntryJob {
|
||||
fn from(value: PluginInEntry) -> Self {
|
||||
Self { id: value.id, args: value.args, plugin: value.plugin }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ impl Plugin {
|
|||
pub(crate) async fn entry(&self, task: PluginInEntry) -> Result<(), PluginOutEntry> {
|
||||
let id = task.id;
|
||||
|
||||
RUNNER.entry(task.into_job()).await?;
|
||||
RUNNER.entry(task.into()).await?;
|
||||
Ok(self.ops.out(id, PluginOutEntry::Succ))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,17 +65,16 @@ impl Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_cut(&self, from: UrlBuf, to: UrlBuf, force: bool) {
|
||||
let follow = !from.scheme().covariant(to.scheme());
|
||||
let mut r#in =
|
||||
FileInCut { id: Id::ZERO, from, to, force, cha: None, follow, retry: 0, drop: None };
|
||||
pub fn file_cut(&self, mut r#in: FileInCut) -> Id {
|
||||
let id = self.add(&mut r#in, |t| t.id);
|
||||
|
||||
self.add(&mut r#in, |_| ());
|
||||
if r#in.to.try_starts_with(&r#in.from).unwrap_or(false) && !r#in.to.covariant(&r#in.from) {
|
||||
self.ops.out(r#in.id, FileOutCut::Fail("Cannot cut directory into itself".to_owned()));
|
||||
} else {
|
||||
self.file.submit(r#in, LOW);
|
||||
}
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
pub fn file_copy(&self, from: UrlBuf, to: UrlBuf, force: bool, follow: bool) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ dyn-clone = { workspace = true }
|
|||
foldhash = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
memchr = "2.8.0"
|
||||
memchr = "2.8.1"
|
||||
ordered-float = { workspace = true }
|
||||
parking_lot = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -20,15 +20,17 @@ vendored-lua = [ "mlua/vendored" ]
|
|||
yazi-macro = { path = "../yazi-macro", version = "26.5.6" }
|
||||
|
||||
# External dependencies
|
||||
arc-swap = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
twox-hash = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
base64 = { workspace = true }
|
||||
hashbrown = { workspace = true }
|
||||
mlua = { workspace = true }
|
||||
percent-encoding = { workspace = true }
|
||||
ratatui = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
twox-hash = { workspace = true }
|
||||
unicode-width = { workspace = true }
|
||||
|
||||
[dependencies.unicode-segmentation]
|
||||
version = "1.13.2"
|
||||
|
|
|
|||
8
yazi-shim/src/base64.rs
Normal file
8
yazi-shim/src/base64.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
use base64::{alphabet::STANDARD, engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}};
|
||||
|
||||
pub const BASE64_SANE: GeneralPurpose = GeneralPurpose::new(
|
||||
&STANDARD,
|
||||
GeneralPurposeConfig::new()
|
||||
.with_encode_padding(false)
|
||||
.with_decode_padding_mode(DecodePaddingMode::Indifferent),
|
||||
);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
yazi_macro::mod_pub!(arc_swap cell mlua path ratatui serde strum toml vec);
|
||||
|
||||
yazi_macro::mod_flat!(twox utf8);
|
||||
yazi_macro::mod_flat!(base64 percent_encoding twox utf8);
|
||||
|
||||
#[cfg(windows)]
|
||||
yazi_macro::mod_flat!(win32);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(traits);
|
||||
yazi_macro::mod_flat!(sequence string traits);
|
||||
|
|
|
|||
49
yazi-shim/src/mlua/sequence.rs
Normal file
49
yazi-shim/src/mlua/sequence.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use std::marker::PhantomData;
|
||||
|
||||
use mlua::{FromLua, Integer, IntoLua, Lua, Table, ffi};
|
||||
|
||||
// mlua's `TableSequence<'_, V>` holds `table: &'a Table` and cannot be Clone.
|
||||
// By owning both `Lua` and `Table` (both are reference-counted; Clone = two
|
||||
// atomic increments) we get a Clone-able sequence iterator with zero heap
|
||||
// allocation.
|
||||
//
|
||||
// `next()` acquires the Lua lock once per element (same as `raw_get`), but uses
|
||||
// `lua_rawgeti` to skip the separate integer key push that `raw_get` requires.
|
||||
// Holding the lock across all elements (as `TableSequence` does) is not
|
||||
// possible here because `LuaGuard` is `pub(crate)` and cannot be stored in
|
||||
// Clone types outside mlua.
|
||||
#[derive(Clone)]
|
||||
pub struct SequenceIter<V> {
|
||||
pub(super) lua: Lua,
|
||||
pub(super) table: Table,
|
||||
pub(super) index: Integer,
|
||||
pub(super) _phantom: PhantomData<V>,
|
||||
}
|
||||
|
||||
impl<V: FromLua> Iterator for SequenceIter<V> {
|
||||
type Item = mlua::Result<V>;
|
||||
|
||||
fn next(&mut self) -> Option<mlua::Result<V>> {
|
||||
self.index += 1;
|
||||
self.lua.exec_raw_lua(|lua| {
|
||||
unsafe {
|
||||
let state = lua.state();
|
||||
if ffi::lua_checkstack(state, 2) == 0 {
|
||||
return Some(Err(mlua::Error::StackError));
|
||||
}
|
||||
|
||||
if let Err(e) = <&Table as IntoLua>::push_into_stack(&self.table, lua) {
|
||||
return Some(Err(e));
|
||||
};
|
||||
|
||||
let result = match ffi::lua_rawgeti(state, -1, self.index) {
|
||||
ffi::LUA_TNIL => None,
|
||||
_ => Some(<V as FromLua>::from_stack(-1, lua)),
|
||||
};
|
||||
|
||||
ffi::lua_pop(state, 2); // table + value
|
||||
result
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
26
yazi-shim/src/mlua/string.rs
Normal file
26
yazi-shim/src/mlua/string.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use std::{fmt, ops::Deref};
|
||||
|
||||
use mlua::{FromLua, IntoLua, Lua, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ByteString(mlua::String);
|
||||
|
||||
impl Deref for ByteString {
|
||||
type Target = mlua::String;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl fmt::Display for ByteString {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.display().fmt(f) }
|
||||
}
|
||||
|
||||
impl FromLua for ByteString {
|
||||
fn from_lua(value: Value, lua: &Lua) -> mlua::Result<Self> {
|
||||
Ok(ByteString(mlua::String::from_lua(value, lua)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua for ByteString {
|
||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { self.0.into_lua(lua) }
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
use mlua::{ExternalError, Table, Value};
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::toml::DeserializeOverWith;
|
||||
use mlua::{ExternalError, FromLua, Lua, Table, Value};
|
||||
|
||||
use crate::{mlua::SequenceIter, toml::DeserializeOverWith};
|
||||
|
||||
// --- DeserializeOverLua
|
||||
pub trait DeserializeOverLua: DeserializeOverWith {
|
||||
fn deserialize_over_lua(self, table: &Table) -> mlua::Result<Self> {
|
||||
let de = mlua::serde::Deserializer::new(Value::Table(table.clone()));
|
||||
|
|
@ -10,3 +13,19 @@ pub trait DeserializeOverLua: DeserializeOverWith {
|
|||
}
|
||||
|
||||
impl<T: DeserializeOverWith> DeserializeOverLua for T {}
|
||||
|
||||
// --- IntoLua
|
||||
pub trait LuaTableExt {
|
||||
fn sequence_iter<V: FromLua>(&self, lua: &Lua) -> SequenceIter<V>;
|
||||
}
|
||||
|
||||
impl LuaTableExt for Table {
|
||||
fn sequence_iter<V: FromLua>(&self, lua: &Lua) -> SequenceIter<V> {
|
||||
SequenceIter {
|
||||
lua: lua.clone(),
|
||||
table: self.clone(),
|
||||
index: 0,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
20
yazi-shim/src/percent_encoding.rs
Normal file
20
yazi-shim/src/percent_encoding.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
use percent_encoding::{AsciiSet, CONTROLS};
|
||||
|
||||
// RFC 3986 path component: encode everything that is not a safe path character.
|
||||
// Safe chars: unreserved (A-Za-z0-9 -._~) + sub-delims (!$&'()*+,;=) + : @ /
|
||||
pub const RFC_3986: &AsciiSet = &CONTROLS
|
||||
.add(b' ')
|
||||
.add(b'"')
|
||||
.add(b'#')
|
||||
.add(b'%')
|
||||
.add(b'<')
|
||||
.add(b'>')
|
||||
.add(b'?')
|
||||
.add(b'[')
|
||||
.add(b'\\')
|
||||
.add(b']')
|
||||
.add(b'^')
|
||||
.add(b'`')
|
||||
.add(b'{')
|
||||
.add(b'|')
|
||||
.add(b'}');
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
use std::str::SplitWhitespace;
|
||||
|
||||
use strum::FromRepr;
|
||||
use base64::Engine;
|
||||
use strum::{FromRepr, IntoStaticStr};
|
||||
use yazi_shim::BASE64_SANE;
|
||||
|
||||
use crate::parser::StateOsc72;
|
||||
|
||||
|
|
@ -13,12 +15,14 @@ pub enum DndEvent {
|
|||
DragLand,
|
||||
DragEnd(DndDragEnd),
|
||||
DragSend(DndDragSend),
|
||||
DragError(DndDragError),
|
||||
|
||||
// Drop
|
||||
DropEnter(DndDropEnter),
|
||||
DropLeave,
|
||||
DropReady(DndDropReady),
|
||||
DropData(DndDropData),
|
||||
DropArrive(DndDropArrive),
|
||||
DropError(DndDropError),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
|
@ -47,6 +51,13 @@ pub struct DndDragSend {
|
|||
pub idx: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DndDragError {
|
||||
pub idx: u8,
|
||||
pub name: String,
|
||||
pub desc: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DndDropEnter {
|
||||
pub x: u32,
|
||||
|
|
@ -64,12 +75,96 @@ pub struct DndDropReady {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DndDropData {
|
||||
pub idx: u8,
|
||||
pub payload: Vec<u8>,
|
||||
pub struct DndDropArrive {
|
||||
pub idx: u8,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub struct DndDropError {
|
||||
pub idx: u8,
|
||||
pub name: String,
|
||||
pub desc: String,
|
||||
}
|
||||
|
||||
impl DndEvent {
|
||||
pub fn r#type(&self) -> &'static str {
|
||||
match self {
|
||||
Self::DragOffer(_) => "offer",
|
||||
Self::DragAccept(_) => "accept",
|
||||
Self::DragChange(_) => "change",
|
||||
Self::DragLand => "land",
|
||||
Self::DragEnd(_) => "end",
|
||||
Self::DragSend(_) => "send",
|
||||
Self::DragError(_) => "error",
|
||||
|
||||
Self::DropEnter(_) => "enter",
|
||||
Self::DropLeave => "leave",
|
||||
Self::DropReady(_) => "ready",
|
||||
Self::DropArrive(_) => "arrive",
|
||||
Self::DropError(_) => "error",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn x(&self) -> Option<u32> {
|
||||
match self {
|
||||
Self::DragOffer(e) => Some(e.x),
|
||||
Self::DropEnter(e) => Some(e.x),
|
||||
Self::DropReady(e) => Some(e.x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn y(&self) -> Option<u32> {
|
||||
match self {
|
||||
Self::DragOffer(e) => Some(e.y),
|
||||
Self::DropEnter(e) => Some(e.y),
|
||||
Self::DropReady(e) => Some(e.y),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idx(&self) -> Option<u8> {
|
||||
match self {
|
||||
Self::DragAccept(e) => Some(e.idx),
|
||||
Self::DragSend(e) => Some(e.idx),
|
||||
Self::DragError(e) => Some(e.idx),
|
||||
Self::DropArrive(e) => Some(e.idx),
|
||||
Self::DropError(e) => Some(e.idx),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn op(&self) -> Option<DndOp> {
|
||||
match self {
|
||||
Self::DragChange(e) => Some(e.op),
|
||||
Self::DropEnter(e) => Some(e.op),
|
||||
Self::DropReady(e) => Some(e.op),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mimes(&self) -> Option<&DndMimeList> {
|
||||
match self {
|
||||
Self::DropEnter(e) => Some(&e.mimes),
|
||||
Self::DropReady(e) => Some(&e.mimes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_drag(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::DragOffer(_)
|
||||
| Self::DragAccept(_)
|
||||
| Self::DragChange(_)
|
||||
| Self::DragLand
|
||||
| Self::DragEnd(_)
|
||||
| Self::DragSend(_)
|
||||
| Self::DragError(_)
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn from_state(s: StateOsc72) -> Option<Self> {
|
||||
Some(match s.r#type.unwrap_or_default() {
|
||||
// Drag
|
||||
|
|
@ -79,6 +174,10 @@ impl DndEvent {
|
|||
b'e' if s.x? == 3 => Self::DragLand,
|
||||
b'e' if s.x? == 4 => Self::DragEnd(DndDragEnd { canceled: s.y? != 0 }),
|
||||
b'e' if s.x? == 5 => Self::DragSend(DndDragSend { idx: s.y?.try_into().ok()? }),
|
||||
b'E' => {
|
||||
let (name, desc) = parse_error(s.payload)?;
|
||||
Self::DragError(DndDragError { idx: s.y?.try_into().ok()?, name, desc })
|
||||
}
|
||||
|
||||
// Drop
|
||||
b'm' if s.x == Some(-1) && s.y == Some(-1) => Self::DropLeave,
|
||||
|
|
@ -94,7 +193,14 @@ impl DndEvent {
|
|||
op: DndOp::from_repr(s.op?)?,
|
||||
mimes: DndMimeList::new(s.payload)?,
|
||||
}),
|
||||
b'r' => Self::DropData(DndDropData { idx: s.x?.try_into().ok()?, payload: s.payload }),
|
||||
b'r' => Self::DropArrive(DndDropArrive {
|
||||
idx: s.x?.try_into().ok()?,
|
||||
data: BASE64_SANE.decode(&s.payload).ok()?,
|
||||
}),
|
||||
b'R' => {
|
||||
let (name, desc) = parse_error(s.payload)?;
|
||||
Self::DropError(DndDropError { idx: s.x?.try_into().ok()?, name, desc })
|
||||
}
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
|
|
@ -102,7 +208,7 @@ impl DndEvent {
|
|||
}
|
||||
|
||||
// --- Operation
|
||||
#[derive(Clone, Copy, Debug, Eq, FromRepr, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, FromRepr, IntoStaticStr, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum DndOp {
|
||||
Copy = 1,
|
||||
|
|
@ -119,3 +225,13 @@ impl DndMimeList {
|
|||
|
||||
pub fn iter(&self) -> SplitWhitespace<'_> { self.0.split_whitespace() }
|
||||
}
|
||||
|
||||
// --- Error payload parsing
|
||||
fn parse_error(payload: Vec<u8>) -> Option<(String, String)> {
|
||||
let s = String::from_utf8(payload).ok()?;
|
||||
Some(match s.split_once(':') {
|
||||
Some((name, desc)) => (name.to_owned(), desc.to_owned()),
|
||||
None if s.is_empty() => return None,
|
||||
None => (s, String::new()),
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
use std::{fmt::{self, Display}, str};
|
||||
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use base64::Engine;
|
||||
use yazi_shim::BASE64_SANE;
|
||||
|
||||
use super::traits::Mimelist;
|
||||
|
||||
/// Enable drag support: `OSC 72 ; t=o:x=1 ; machine id ST`
|
||||
pub struct EnableDrag<'a>(pub &'a str);
|
||||
|
|
@ -12,11 +15,11 @@ impl Display for EnableDrag<'_> {
|
|||
}
|
||||
|
||||
/// Enable drop support: `OSC 72 ; t=a ; MIME list ST`
|
||||
pub struct EnableDrop<'a>(pub &'a [&'a str]);
|
||||
pub struct EnableDrop<M>(pub M);
|
||||
|
||||
impl Display for EnableDrop<'_> {
|
||||
impl<M: Mimelist> Display for EnableDrop<M> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "\x1b]72;t=a;{}\x1b\\", ListDndMimes(self.0))
|
||||
write!(f, "\x1b]72;t=a;{}\x1b\\", ListDndMimes(self.0.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -35,35 +38,35 @@ impl Display for DisableDrop {
|
|||
}
|
||||
|
||||
/// Confirm drag: `OSC 72 ; t=o:o=operation ST`
|
||||
pub enum ConfirmDrag<'a> {
|
||||
Copy(&'a [&'a str]),
|
||||
Move(&'a [&'a str]),
|
||||
Either(&'a [&'a str]),
|
||||
pub enum ConfirmDrag<M> {
|
||||
Copy(M),
|
||||
Move(M),
|
||||
Either(M),
|
||||
}
|
||||
|
||||
impl Display for ConfirmDrag<'_> {
|
||||
impl<M: Mimelist> Display for ConfirmDrag<M> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Copy(mimes) => write!(f, "\x1b]72;t=o:o=1;{}\x1b\\", ListDndMimes(mimes)),
|
||||
Self::Move(mimes) => write!(f, "\x1b]72;t=o:o=2;{}\x1b\\", ListDndMimes(mimes)),
|
||||
Self::Either(mimes) => write!(f, "\x1b]72;t=o:o=3;{}\x1b\\", ListDndMimes(mimes)),
|
||||
Self::Copy(mimes) => write!(f, "\x1b]72;t=o:o=1;{}\x1b\\", ListDndMimes(mimes.clone())),
|
||||
Self::Move(mimes) => write!(f, "\x1b]72;t=o:o=2;{}\x1b\\", ListDndMimes(mimes.clone())),
|
||||
Self::Either(mimes) => write!(f, "\x1b]72;t=o:o=3;{}\x1b\\", ListDndMimes(mimes.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Confirm dropped data: `OSC 72 ; t=m:o=O ; MIME list ST`
|
||||
pub enum ConfirmDrop<'a> {
|
||||
pub enum ConfirmDrop<M> {
|
||||
Reject,
|
||||
Copy(&'a [&'a str]),
|
||||
Move(&'a [&'a str]),
|
||||
Copy(M),
|
||||
Move(M),
|
||||
}
|
||||
|
||||
impl Display for ConfirmDrop<'_> {
|
||||
impl<M: Mimelist> Display for ConfirmDrop<M> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Reject => write!(f, "\x1b]72;t=m:o=0\x1b\\"),
|
||||
Self::Copy(mimes) => write!(f, "\x1b]72;t=m:o=1;{}\x1b\\", ListDndMimes(mimes)),
|
||||
Self::Move(mimes) => write!(f, "\x1b]72;t=m:o=2;{}\x1b\\", ListDndMimes(mimes)),
|
||||
Self::Copy(mimes) => write!(f, "\x1b]72;t=m:o=1;{}\x1b\\", ListDndMimes(mimes.clone())),
|
||||
Self::Move(mimes) => write!(f, "\x1b]72;t=m:o=2;{}\x1b\\", ListDndMimes(mimes.clone())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -89,7 +92,7 @@ pub struct PresentDrag<'a>(pub u8, pub &'a [u8]);
|
|||
|
||||
impl Display for PresentDrag<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let b64 = general_purpose::STANDARD_NO_PAD.encode(self.1).into_bytes();
|
||||
let b64 = BASE64_SANE.encode(self.1).into_bytes();
|
||||
let chunks = b64.len().div_ceil(4096);
|
||||
|
||||
for (i, chunk) in b64.chunks(4096).enumerate() {
|
||||
|
|
@ -112,12 +115,12 @@ pub struct PresentDragIcon<'a> {
|
|||
pub opacity: u16,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub payload: &'a [u8],
|
||||
pub data: &'a [u8],
|
||||
}
|
||||
|
||||
impl Display for PresentDragIcon<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let b64 = general_purpose::STANDARD_NO_PAD.encode(self.payload).into_bytes();
|
||||
let b64 = BASE64_SANE.encode(self.data).into_bytes();
|
||||
let chunks = b64.len().div_ceil(4096);
|
||||
|
||||
for (i, chunk) in b64.chunks(4096).enumerate() {
|
||||
|
|
@ -155,11 +158,11 @@ impl Display for FinishDrop {
|
|||
}
|
||||
|
||||
/// Write MIME types separated by spaces.
|
||||
struct ListDndMimes<'a>(&'a [&'a str]);
|
||||
struct ListDndMimes<M>(pub M);
|
||||
|
||||
impl Display for ListDndMimes<'_> {
|
||||
impl<M: Mimelist> Display for ListDndMimes<M> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (i, &m) in self.0.iter().enumerate() {
|
||||
for (i, m) in self.0.clone().into_iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
yazi_macro::mod_flat!(clipboard csi_u cursor dnd erase mode query r#if restore_background set_background style sync);
|
||||
yazi_macro::mod_flat!(clipboard csi_u cursor dnd erase mode query r#if restore_background set_background style sync traits);
|
||||
|
|
|
|||
11
yazi-term/src/sequence/traits.rs
Normal file
11
yazi-term/src/sequence/traits.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
/// Types that can be iterated to produce a list of MIME types.
|
||||
pub(super) trait Mimelist: IntoIterator<Item: Display> + Clone {}
|
||||
|
||||
impl<T> Mimelist for T
|
||||
where
|
||||
T: IntoIterator + Clone,
|
||||
T::Item: Display,
|
||||
{
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{fmt::{self, Debug}, io::{self, BufWriter, Write}, ops::Deref};
|
||||
use std::{fmt::{self, Debug}, io::{self, BufWriter}, ops::Deref};
|
||||
|
||||
use parking_lot::Mutex;
|
||||
|
||||
|
|
@ -23,12 +23,6 @@ impl io::Write for TtyWriter<'_> {
|
|||
fn flush(&mut self) -> io::Result<()> { self.0.lock().flush() }
|
||||
}
|
||||
|
||||
impl fmt::Write for TtyWriter<'_> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.0.lock().write_all(s.as_bytes()).map_err(|_| fmt::Error)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl std::os::fd::AsFd for TtyWriter<'_> {
|
||||
fn as_fd(&self) -> std::os::fd::BorrowedFd<'_> {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue