mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: terminal drag and drop protocol (#3999)
This commit is contained in:
parent
f7fea2b19a
commit
cf8b54179e
17 changed files with 474 additions and 43 deletions
37
Cargo.lock
generated
37
Cargo.lock
generated
|
|
@ -239,9 +239,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "av-scenechange"
|
name = "av-scenechange"
|
||||||
|
|
@ -489,9 +489,9 @@ checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.20.2"
|
version = "3.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "by_address"
|
name = "by_address"
|
||||||
|
|
@ -2047,9 +2047,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.98"
|
version = "0.3.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08"
|
checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|
@ -3590,9 +3590,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "russh"
|
name = "russh"
|
||||||
version = "0.61.0"
|
version = "0.61.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fba341d1fce5e094392f088499c5808d2a60d3a658f58fe480c26ac77b1c7dd3"
|
checksum = "f67013f080c226e5a34db1c71f2567f44d95a6300005bb6cd4e2c8fe3c326d1b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"bitflags 2.11.1",
|
"bitflags 2.11.1",
|
||||||
|
|
@ -4934,9 +4934,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790"
|
checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -4947,9 +4947,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.71"
|
version = "0.4.72"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8"
|
checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
@ -4957,9 +4957,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578"
|
checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
|
@ -4967,9 +4967,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2"
|
checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
|
@ -4980,9 +4980,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.121"
|
version = "0.2.122"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441"
|
checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -6159,6 +6159,7 @@ dependencies = [
|
||||||
"ratatui",
|
"ratatui",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"signal-hook 0.4.4",
|
"signal-hook 0.4.4",
|
||||||
|
"strum 0.28.0",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"tokio",
|
"tokio",
|
||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ percent-encoding = "2.3.2"
|
||||||
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
|
rand = { version = "0.10.1", default-features = false, features = [ "std", "sys_rng" ] }
|
||||||
ratatui = { version = "0.30.0", default-features = false, features = [ "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
ratatui = { version = "0.30.0", default-features = false, features = [ "layout-cache", "serde", "underline-color", "unstable-rendered-line-info", "unstable-widget-ref" ] }
|
||||||
regex = "1.12.3"
|
regex = "1.12.3"
|
||||||
russh = { version = "0.61.0", default-features = false, features = [ "ring", "rsa" ] }
|
russh = { version = "0.61.1", default-features = false, features = [ "ring", "rsa" ] }
|
||||||
scopeguard = "1.2.0"
|
scopeguard = "1.2.0"
|
||||||
serde = { version = "1.0.228", features = [ "derive" ] }
|
serde = { version = "1.0.228", features = [ "derive" ] }
|
||||||
serde_json = "1.0.150"
|
serde_json = "1.0.150"
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"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":[]}
|
{"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"}
|
||||||
|
|
@ -4,12 +4,11 @@ use anyhow::Result;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
use tokio::{io::{AsyncReadExt, stdin}, select, sync::mpsc, time};
|
||||||
use yazi_binding::Permit;
|
use yazi_binding::Permit;
|
||||||
use yazi_term::TERM;
|
|
||||||
use yazi_macro::{succ, writef};
|
use yazi_macro::{succ, writef};
|
||||||
use yazi_parser::VoidForm;
|
use yazi_parser::VoidForm;
|
||||||
use yazi_scheduler::AppProxy;
|
use yazi_scheduler::AppProxy;
|
||||||
use yazi_shared::data::Data;
|
use yazi_shared::data::Data;
|
||||||
use yazi_term::{YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
use yazi_term::{TERM, YIELD_TO_SUBPROCESS, sequence::EraseScreen};
|
||||||
use yazi_tty::TTY;
|
use yazi_tty::TTY;
|
||||||
|
|
||||||
use crate::{Actor, Ctx};
|
use crate::{Actor, Ctx};
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ use anyhow::Result;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use yazi_actor::Ctx;
|
use yazi_actor::Ctx;
|
||||||
use yazi_config::keymap::Key;
|
use yazi_config::keymap::Key;
|
||||||
use yazi_macro::{act, emit};
|
use yazi_macro::{act, emit, writef};
|
||||||
use yazi_shared::event::{ActionCow, Event, NEED_RENDER};
|
use yazi_shared::event::{ActionCow, Event, NEED_RENDER};
|
||||||
use yazi_term::event::{Event as TermEvent, KeyEvent, MouseEvent};
|
use yazi_term::{event::{Event as TermEvent, KeyEvent, MouseEvent}, sequence::{ConfirmDrag, ConfirmDrop, FinishDrop, PresentDrag, PresentDragIcon, StartDrag, StartDrop}};
|
||||||
|
use yazi_tty::TTY;
|
||||||
use yazi_widgets::input::InputMode;
|
use yazi_widgets::input::InputMode;
|
||||||
|
|
||||||
use crate::{Executor, Router, app::App};
|
use crate::{Executor, Router, app::App};
|
||||||
|
|
@ -29,6 +30,53 @@ impl<'a> Dispatcher<'a> {
|
||||||
Event::Term(TermEvent::FocusIn) => self.dispatch_focus(),
|
Event::Term(TermEvent::FocusIn) => self.dispatch_focus(),
|
||||||
Event::Term(TermEvent::FocusOut) => Ok(()),
|
Event::Term(TermEvent::FocusOut) => Ok(()),
|
||||||
Event::Term(TermEvent::Paste(str)) => self.dispatch_paste(str),
|
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(())
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = &result {
|
if let Err(e) = &result {
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ bitflags = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
ratatui = { workspace = true }
|
ratatui = { workspace = true }
|
||||||
|
strum = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::str;
|
use std::{num, str};
|
||||||
|
|
||||||
pub type Result<T, E = ParseError> = std::result::Result<T, E>;
|
pub type Result<T, E = ParseError> = std::result::Result<T, E>;
|
||||||
|
|
||||||
|
|
@ -19,3 +19,7 @@ pub enum ParseError {
|
||||||
impl From<str::Utf8Error> for ParseError {
|
impl From<str::Utf8Error> for ParseError {
|
||||||
fn from(_: str::Utf8Error) -> Self { Self::Invalid }
|
fn from(_: str::Utf8Error) -> Self { Self::Invalid }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<num::ParseIntError> for ParseError {
|
||||||
|
fn from(_: num::ParseIntError) -> Self { Self::Invalid }
|
||||||
|
}
|
||||||
|
|
|
||||||
121
yazi-term/src/event/dnd.rs
Normal file
121
yazi-term/src/event/dnd.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
use std::str::SplitWhitespace;
|
||||||
|
|
||||||
|
use strum::FromRepr;
|
||||||
|
|
||||||
|
use crate::parser::StateOsc72;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum DndEvent {
|
||||||
|
// Drag
|
||||||
|
DragOffer(DndDragOffer),
|
||||||
|
DragAccept(DndDragAccept),
|
||||||
|
DragChange(DndDragChange),
|
||||||
|
DragLand,
|
||||||
|
DragEnd(DndDragEnd),
|
||||||
|
DragSend(DndDragSend),
|
||||||
|
|
||||||
|
// Drop
|
||||||
|
DropEnter(DndDropEnter),
|
||||||
|
DropLeave,
|
||||||
|
DropReady(DndDropReady),
|
||||||
|
DropData(DndDropData),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDragOffer {
|
||||||
|
pub x: u32,
|
||||||
|
pub y: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDragAccept {
|
||||||
|
pub idx: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDragChange {
|
||||||
|
pub op: DndOp,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDragEnd {
|
||||||
|
pub canceled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDragSend {
|
||||||
|
pub idx: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDropEnter {
|
||||||
|
pub x: u32,
|
||||||
|
pub y: u32,
|
||||||
|
pub op: DndOp,
|
||||||
|
pub mimes: DndMimeList,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDropReady {
|
||||||
|
pub x: u32,
|
||||||
|
pub y: u32,
|
||||||
|
pub op: DndOp,
|
||||||
|
pub mimes: DndMimeList,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndDropData {
|
||||||
|
pub idx: u8,
|
||||||
|
pub payload: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DndEvent {
|
||||||
|
pub(crate) fn from_state(s: StateOsc72) -> Option<Self> {
|
||||||
|
Some(match s.r#type.unwrap_or_default() {
|
||||||
|
// Drag
|
||||||
|
b'o' => Self::DragOffer(DndDragOffer { x: s.x?.try_into().ok()?, y: s.y?.try_into().ok()? }),
|
||||||
|
b'e' if s.x? == 1 => Self::DragAccept(DndDragAccept { idx: s.y?.try_into().ok()? }),
|
||||||
|
b'e' if s.x? == 2 => Self::DragChange(DndDragChange { op: DndOp::from_repr(s.op?)? }),
|
||||||
|
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()? }),
|
||||||
|
|
||||||
|
// Drop
|
||||||
|
b'm' if s.x == Some(-1) && s.y == Some(-1) => Self::DropLeave,
|
||||||
|
b'm' => Self::DropEnter(DndDropEnter {
|
||||||
|
x: s.x?.try_into().ok()?,
|
||||||
|
y: s.y?.try_into().ok()?,
|
||||||
|
op: DndOp::from_repr(s.op?)?,
|
||||||
|
mimes: DndMimeList::new(s.payload)?,
|
||||||
|
}),
|
||||||
|
b'M' => Self::DropReady(DndDropReady {
|
||||||
|
x: s.x?.try_into().ok()?,
|
||||||
|
y: s.y?.try_into().ok()?,
|
||||||
|
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 }),
|
||||||
|
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Operation
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, FromRepr, PartialEq)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum DndOp {
|
||||||
|
Copy = 1,
|
||||||
|
Move = 2,
|
||||||
|
Either = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- MIME list
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct DndMimeList(String);
|
||||||
|
|
||||||
|
impl DndMimeList {
|
||||||
|
pub fn new(b: Vec<u8>) -> Option<Self> { Some(Self(String::from_utf8(b).ok()?)) }
|
||||||
|
|
||||||
|
pub fn iter(&self) -> SplitWhitespace<'_> { self.0.split_whitespace() }
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{Dimension, event::{KeyEvent, MouseEvent}};
|
use crate::{Dimension, event::{DndEvent, KeyEvent, MouseEvent}};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
|
|
@ -8,4 +8,5 @@ pub enum Event {
|
||||||
FocusIn,
|
FocusIn,
|
||||||
FocusOut,
|
FocusOut,
|
||||||
Paste(String),
|
Paste(String),
|
||||||
|
Dnd(DndEvent),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
yazi_macro::mod_flat!(event keyboard modifiers mouse);
|
yazi_macro::mod_flat!(dnd event keyboard modifiers mouse);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
yazi_macro::mod_flat!(csi ground parser state);
|
yazi_macro::mod_flat!(csi ground osc parser state);
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
yazi_macro::mod_flat!(windows);
|
yazi_macro::mod_flat!(windows);
|
||||||
|
|
|
||||||
40
yazi-term/src/parser/osc.rs
Normal file
40
yazi-term/src/parser/osc.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::{ParseError, Result, parser::{Parser, State}};
|
||||||
|
|
||||||
|
impl Parser {
|
||||||
|
pub(super) fn parse_osc72(&mut self) -> Result<()> {
|
||||||
|
debug_assert!(self.seq.starts_with(b"\x1b]72;"));
|
||||||
|
debug_assert!(self.seq.ends_with(b"\x1b\\"));
|
||||||
|
|
||||||
|
let mut it = self.seq[5..self.seq.len() - 2].splitn(2, |&b| b == b';');
|
||||||
|
let meta = str::from_utf8(it.next().ok_or(ParseError::Invalid)?)?;
|
||||||
|
let payload = it.next().unwrap_or(&[]);
|
||||||
|
|
||||||
|
let State::Osc72(state) = &mut self.state else { unreachable!() };
|
||||||
|
state.has_more = false; // reset has_more for this new sequence
|
||||||
|
|
||||||
|
for part in meta.split(':') {
|
||||||
|
match part.split_once('=').ok_or(ParseError::Invalid)? {
|
||||||
|
("t", v) if let Some(b) = v.bytes().next() => state.r#type = Some(b),
|
||||||
|
("x", v) => state.x = Some(v.parse()?),
|
||||||
|
("y", v) => state.y = Some(v.parse()?),
|
||||||
|
("o", v) => state.op = Some(v.parse()?),
|
||||||
|
("m", "1") => state.has_more = true,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For `t=r`, the presence of a payload indicates more data is coming,
|
||||||
|
// even if `m=1` is not set.
|
||||||
|
if state.r#type == Some(b'r') && !payload.is_empty() {
|
||||||
|
state.has_more = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit payload size to 1MiB to prevent potential DoS
|
||||||
|
if state.payload.len() + payload.len() > 1 << 20 {
|
||||||
|
return Err(ParseError::Invalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.payload.extend(payload);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use std::{collections::VecDeque, num::NonZeroU8, str};
|
use std::{collections::VecDeque, mem, num::NonZeroU8, str};
|
||||||
|
|
||||||
use yazi_shim::utf8_char_width;
|
use yazi_shim::utf8_char_width;
|
||||||
|
|
||||||
use super::state::State;
|
use super::state::State;
|
||||||
use crate::event::{Event, KeyCode, KeyEvent, Modifiers};
|
use crate::event::{DndEvent, Event, KeyCode, KeyEvent, Modifiers};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Parser {
|
pub struct Parser {
|
||||||
state: State,
|
pub(super) state: State,
|
||||||
pub(super) seq: Vec<u8>,
|
pub(super) seq: Vec<u8>,
|
||||||
pub(crate) events: VecDeque<Event>,
|
pub(crate) events: VecDeque<Event>,
|
||||||
}
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step(&mut self, b: u8) {
|
fn step(&mut self, b: u8) {
|
||||||
match self.state {
|
match &self.state {
|
||||||
State::Ground => self.on_ground(b),
|
State::Ground => self.on_ground(b),
|
||||||
State::Esc => self.on_esc(b),
|
State::Esc => self.on_esc(b),
|
||||||
State::EscO => self.on_esco(b),
|
State::EscO => self.on_esco(b),
|
||||||
|
|
@ -38,9 +38,10 @@ impl Parser {
|
||||||
State::NormalMouse => self.on_normal_mouse(b),
|
State::NormalMouse => self.on_normal_mouse(b),
|
||||||
State::BracketedPaste => self.on_bracketed_paste(b),
|
State::BracketedPaste => self.on_bracketed_paste(b),
|
||||||
State::Osc | State::OscSt => self.on_osc(b),
|
State::Osc | State::OscSt => self.on_osc(b),
|
||||||
|
State::Osc72(_) => self.on_osc72(b),
|
||||||
State::Dcs | State::DcsSt => self.on_dcs(b),
|
State::Dcs | State::DcsSt => self.on_dcs(b),
|
||||||
State::Utf8(n) => self.on_utf8(b, n),
|
State::Utf8(n) => self.on_utf8(b, *n),
|
||||||
State::AltUtf8(n) => self.on_alt_utf8(b, n),
|
State::AltUtf8(n) => self.on_alt_utf8(b, *n),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,9 +52,12 @@ impl Parser {
|
||||||
/// been seen but no follow-up bytes arrived), this emits a bare
|
/// been seen but no follow-up bytes arrived), this emits a bare
|
||||||
/// [`KeyCode::Escape`] event and resets to [`State::Ground`].
|
/// [`KeyCode::Escape`] event and resets to [`State::Ground`].
|
||||||
pub fn flush(&mut self) {
|
pub fn flush(&mut self) {
|
||||||
if self.state == State::Esc {
|
match &self.state {
|
||||||
self.emit_key(KeyCode::Escape);
|
State::Esc => self.emit_key(KeyCode::Escape),
|
||||||
|
State::Osc72(s) if s.has_more => return,
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.reset();
|
self.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -193,8 +197,11 @@ impl Parser {
|
||||||
fn on_osc(&mut self, b: u8) {
|
fn on_osc(&mut self, b: u8) {
|
||||||
self.seq.push(b);
|
self.seq.push(b);
|
||||||
|
|
||||||
match (self.state, b) {
|
match (&self.state, b) {
|
||||||
(State::Osc, b'\x07') => self.reset(), // BEL — OSC complete (discard)
|
(State::Osc, b'\x07') => self.reset(), // BEL — OSC complete (discard)
|
||||||
|
(State::Osc, _) if self.seq.starts_with(b"\x1b]72;") => {
|
||||||
|
self.state = State::Osc72(Default::default());
|
||||||
|
}
|
||||||
(State::Osc, b'\x1B') => self.state = State::OscSt,
|
(State::Osc, b'\x1B') => self.state = State::OscSt,
|
||||||
(State::Osc, _) => {} // keep accumulating
|
(State::Osc, _) => {} // keep accumulating
|
||||||
(State::OscSt, b'\\') => self.reset(), // ST (`\x1B\\`) — OSC complete (discard)
|
(State::OscSt, b'\\') => self.reset(), // ST (`\x1B\\`) — OSC complete (discard)
|
||||||
|
|
@ -204,10 +211,34 @@ impl Parser {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_osc72(&mut self, b: u8) {
|
||||||
|
self.seq.push(b);
|
||||||
|
|
||||||
|
if !self.seq.ends_with(b"\x1b\\") {
|
||||||
|
return;
|
||||||
|
} else if self.parse_osc72().is_err() {
|
||||||
|
return self.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
match mem::take(&mut self.state) {
|
||||||
|
State::Osc72(s) if s.has_more => {
|
||||||
|
self.seq.clear();
|
||||||
|
self.state = State::Osc72(s);
|
||||||
|
}
|
||||||
|
State::Osc72(s) => {
|
||||||
|
if let Some(e) = DndEvent::from_state(s) {
|
||||||
|
self.emit(Event::Dnd(e));
|
||||||
|
}
|
||||||
|
self.reset();
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn on_dcs(&mut self, b: u8) {
|
fn on_dcs(&mut self, b: u8) {
|
||||||
self.seq.push(b);
|
self.seq.push(b);
|
||||||
|
|
||||||
match (self.state, b) {
|
match (&self.state, b) {
|
||||||
(State::Dcs, b'\x1B') => self.state = State::DcsSt,
|
(State::Dcs, b'\x1B') => self.state = State::DcsSt,
|
||||||
(State::Dcs, _) => {}
|
(State::Dcs, _) => {}
|
||||||
(State::DcsSt, b'\\') => self.reset(), // ST — DCS complete (discard)
|
(State::DcsSt, b'\\') => self.reset(), // ST — DCS complete (discard)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub(crate) enum State {
|
pub(crate) enum State {
|
||||||
/// Normal ground state.
|
/// Normal ground state.
|
||||||
|
#[default]
|
||||||
Ground,
|
Ground,
|
||||||
/// Saw `\x1B`, waiting for the next byte.
|
/// Saw `\x1B`, waiting for the next byte.
|
||||||
Esc,
|
Esc,
|
||||||
|
|
@ -16,6 +17,8 @@ pub(crate) enum State {
|
||||||
BracketedPaste,
|
BracketedPaste,
|
||||||
/// Inside an OSC sequence (`\x1B]` … BEL or ST).
|
/// Inside an OSC sequence (`\x1B]` … BEL or ST).
|
||||||
Osc,
|
Osc,
|
||||||
|
/// Inside an OSC 72 (DnD) sequence (`\x1B]72;` … ST).
|
||||||
|
Osc72(StateOsc72),
|
||||||
/// Inside OSC, just saw `\x1B` (potential start of ST = `\x1B\\`).
|
/// Inside OSC, just saw `\x1B` (potential start of ST = `\x1B\\`).
|
||||||
OscSt,
|
OscSt,
|
||||||
/// Inside a DCS sequence (`\x1BP` … ST).
|
/// Inside a DCS sequence (`\x1BP` … ST).
|
||||||
|
|
@ -27,3 +30,13 @@ pub(crate) enum State {
|
||||||
/// `\x1B` + mid-UTF-8 character: `n` continuation bytes still needed.
|
/// `\x1B` + mid-UTF-8 character: `n` continuation bytes still needed.
|
||||||
AltUtf8(NonZeroU8),
|
AltUtf8(NonZeroU8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
pub(crate) struct StateOsc72 {
|
||||||
|
pub(crate) r#type: Option<u8>,
|
||||||
|
pub(crate) x: Option<i32>,
|
||||||
|
pub(crate) y: Option<i32>,
|
||||||
|
pub(crate) op: Option<u8>,
|
||||||
|
pub(crate) payload: Vec<u8>,
|
||||||
|
pub(crate) has_more: bool,
|
||||||
|
}
|
||||||
|
|
|
||||||
170
yazi-term/src/sequence/dnd.rs
Normal file
170
yazi-term/src/sequence/dnd.rs
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
use std::{fmt::{self, Display}, str};
|
||||||
|
|
||||||
|
use base64::{Engine, engine::general_purpose};
|
||||||
|
|
||||||
|
/// Enable drag support: `OSC 72 ; t=o:x=1 ; machine id ST`
|
||||||
|
pub struct EnableDrag<'a>(pub &'a str);
|
||||||
|
|
||||||
|
impl Display for EnableDrag<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "\x1b]72;t=o:x=1;{}\x1b\\", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable drop support: `OSC 72 ; t=a ; MIME list ST`
|
||||||
|
pub struct EnableDrop<'a>(pub &'a [&'a str]);
|
||||||
|
|
||||||
|
impl Display for EnableDrop<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "\x1b]72;t=a;{}\x1b\\", ListDndMimes(self.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable drag support: `OSC 72 ; t=o:x=2 ST`
|
||||||
|
pub struct DisableDrag;
|
||||||
|
|
||||||
|
impl Display for DisableDrag {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b]72;t=o:x=2\x1b\\") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable drop support: `OSC 72 ; t=A ST`
|
||||||
|
pub struct DisableDrop;
|
||||||
|
|
||||||
|
impl Display for DisableDrop {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b]72;t=A\x1b\\") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ConfirmDrag<'_> {
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Confirm dropped data: `OSC 72 ; t=m:o=O ; MIME list ST`
|
||||||
|
pub enum ConfirmDrop<'a> {
|
||||||
|
Reject,
|
||||||
|
Copy(&'a [&'a str]),
|
||||||
|
Move(&'a [&'a str]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ConfirmDrop<'_> {
|
||||||
|
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)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start dragging: `OSC 72 ; t=P:x=-1 ST`
|
||||||
|
pub struct StartDrag;
|
||||||
|
|
||||||
|
impl Display for StartDrag {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("\x1b]72;t=P:x=-1\x1b\\") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start requesting dropped data: `OSC 72 ; t=r:x=idx ST`
|
||||||
|
pub struct StartDrop(pub u8);
|
||||||
|
|
||||||
|
impl Display for StartDrop {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "\x1b]72;t=r:x={}\x1b\\", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Present drag data: `OSC 72 ; t=p:x=idx ; base64 encoded data ST`
|
||||||
|
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 chunks = b64.len().div_ceil(4096);
|
||||||
|
|
||||||
|
for (i, chunk) in b64.chunks(4096).enumerate() {
|
||||||
|
let s = unsafe { str::from_utf8_unchecked(chunk) };
|
||||||
|
if i == 0 {
|
||||||
|
write!(f, "\x1b]72;t=p:x={}:m={};{s}\x1b\\", self.0, (chunks > 1) as u8)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "\x1b]72;m={};{s}\x1b\\", (i + 1 < chunks) as u8)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "\x1b]72;t=p:x={}\x1b\\", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Present drag icon data:
|
||||||
|
/// `OSC 72 ; t=p:x=-1:y=fmt:X=width:Y=height:o=opacity ; base64 payload ST`
|
||||||
|
pub struct PresentDragIcon<'a> {
|
||||||
|
pub format: u8,
|
||||||
|
pub opacity: u16,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub payload: &'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 chunks = b64.len().div_ceil(4096);
|
||||||
|
|
||||||
|
for (i, chunk) in b64.chunks(4096).enumerate() {
|
||||||
|
let s = unsafe { str::from_utf8_unchecked(chunk) };
|
||||||
|
if i == 0 {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"\x1b]72;t=p:x=-1:y={}:X={}:Y={}:o={}:m={};{s}\x1b\\",
|
||||||
|
self.format,
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
self.opacity,
|
||||||
|
(chunks > 1) as u8
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
write!(f, "\x1b]72;m={};{s}\x1b\\", (i + 1 < chunks) as u8)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish requesting dropped data: `OSC 72 ; t=r:o=operation ST`
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum FinishDrop {
|
||||||
|
Copy = 1,
|
||||||
|
Move = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FinishDrop {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "\x1b]72;t=r:o={}\x1b\\", *self as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write MIME types separated by spaces.
|
||||||
|
struct ListDndMimes<'a>(&'a [&'a str]);
|
||||||
|
|
||||||
|
impl Display for ListDndMimes<'_> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for (i, &m) in self.0.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
write!(f, " ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{m}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1 +1 @@
|
||||||
yazi_macro::mod_flat!(clipboard csi_u cursor 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);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use yazi_config::YAZI;
|
||||||
use yazi_emulator::{Emulator, Mux, TMUX};
|
use yazi_emulator::{Emulator, Mux, TMUX};
|
||||||
use yazi_macro::writef;
|
use yazi_macro::writef;
|
||||||
use yazi_shim::cell::SyncCell;
|
use yazi_shim::cell::SyncCell;
|
||||||
use yazi_term::{TERM, event::{Event, KeyEventKind}, sequence::{DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableFocusChange, EnableMouseCapture, EnterAlternateScreen, If, LeaveAlternateScreen, PopKeyboardFlags, PushKeyboardFlags, RequestCursorBlink, RequestCursorStyle, RequestDA1, RequestKeyboardFlags, RestoreBackground, RestoreCursorStyle, SetBackground, SetTitle, ShowCursor}, stream::EventStream};
|
use yazi_term::{TERM, event::{Event, KeyEventKind}, sequence::{DisableBracketedPaste, DisableDrag, DisableDrop, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste, EnableDrag, EnableDrop, EnableFocusChange, EnableMouseCapture, EnterAlternateScreen, If, LeaveAlternateScreen, PopKeyboardFlags, PushKeyboardFlags, RequestCursorBlink, RequestCursorStyle, RequestDA1, RequestKeyboardFlags, RestoreBackground, RestoreCursorStyle, SetBackground, SetTitle, ShowCursor}, stream::EventStream};
|
||||||
use yazi_tty::{TTY, TtyWriter};
|
use yazi_tty::{TTY, TtyWriter};
|
||||||
|
|
||||||
use crate::{RatermBackend, RatermOption, RatermState};
|
use crate::{RatermBackend, RatermOption, RatermState};
|
||||||
|
|
@ -42,10 +42,12 @@ impl Raterm {
|
||||||
let opt = RatermOption::default();
|
let opt = RatermOption::default();
|
||||||
writef!(
|
writef!(
|
||||||
TTY.writer(),
|
TTY.writer(),
|
||||||
"{}{RequestCursorStyle}{RequestCursorBlink}{RequestKeyboardFlags}{RequestDA1}{}{}{EnableBracketedPaste}{EnableFocusChange}{}",
|
"{}{RequestCursorStyle}{RequestCursorBlink}{RequestKeyboardFlags}{RequestDA1}{}{}{EnableBracketedPaste}{EnableFocusChange}{}{}{}",
|
||||||
If(!TMUX.get(), EnterAlternateScreen),
|
If(!TMUX.get(), EnterAlternateScreen),
|
||||||
If(TMUX.get(), EnterAlternateScreen),
|
If(TMUX.get(), EnterAlternateScreen),
|
||||||
SetBackground(&opt.bg),
|
SetBackground(&opt.bg),
|
||||||
|
EnableDrag(""),
|
||||||
|
EnableDrop(&["text/uri-list"]),
|
||||||
If(opt.mouse, EnableMouseCapture),
|
If(opt.mouse, EnableMouseCapture),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|
@ -80,7 +82,7 @@ impl Raterm {
|
||||||
|
|
||||||
_ = writef!(
|
_ = writef!(
|
||||||
TTY.writer(),
|
TTY.writer(),
|
||||||
"{}{}{}{}{}{DisableFocusChange}{DisableBracketedPaste}{LeaveAlternateScreen}{ShowCursor}",
|
"{}{DisableDrop}{DisableDrag}{}{}{}{}{DisableFocusChange}{DisableBracketedPaste}{LeaveAlternateScreen}{ShowCursor}",
|
||||||
If(state.mouse, DisableMouseCapture),
|
If(state.mouse, DisableMouseCapture),
|
||||||
If(state.bg, RestoreBackground),
|
If(state.bg, RestoreBackground),
|
||||||
If(state.csi_u, PopKeyboardFlags),
|
If(state.csi_u, PopKeyboardFlags),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue