feat: drag and drop - #4005
Merged
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds end-to-end drag-and-drop (DnD) support to Yazi using kitty’s OSC 72 DnD protocol, wiring terminal DnD events into the Lua UI runtime and providing preset Lua logic for offering selected files and accepting text/uri-list drops.
Changes:
- Introduces Lua-facing
rt.ttyqueue/flush APIs plus DnD event bindings to drive OSC 72 sequences and receive decoded drop payloads. - Adds preset Lua plugin/component support (
dnd.lua,Tip) and routes DnD events through the app actor into the LuaRootcomponent. - Refactors terminal DnD sequences (generic MIME lists) and unifies base64 configuration via
yazi-shim.
Reviewed changes
Copilot reviewed 49 out of 50 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| yazi-tty/src/writer.rs | Adjusts writer traits/imports for terminal output. |
| yazi-term/src/sequence/traits.rs | Adds internal trait to generalize MIME list inputs. |
| yazi-term/src/sequence/mod.rs | Exposes new traits module in sequence namespace. |
| yazi-term/src/sequence/dnd.rs | Refactors OSC 72 DnD sequences; uses shared base64 engine; generic MIME lists. |
| yazi-term/src/event/dnd.rs | Expands DnD event model, adds error events, decodes drop payload data. |
| yazi-shim/src/mlua/traits.rs | Adds Lua table extension for cloneable sequence iteration. |
| yazi-shim/src/mlua/string.rs | Introduces ByteString wrapper for byte-preserving Lua strings. |
| yazi-shim/src/mlua/sequence.rs | Adds cloneable SequenceIter over Lua tables using raw Lua FFI. |
| yazi-shim/src/mlua/mod.rs | Exposes new sequence and string mlua helpers. |
| yazi-shim/src/lib.rs | Exposes new base64 shim module. |
| yazi-shim/src/base64.rs | Defines a shared base64 engine configuration used across crates. |
| yazi-shim/Cargo.toml | Adds base64 dependency for shim base64 engine. |
| yazi-shared/Cargo.toml | Bumps memchr patch version. |
| yazi-scheduler/src/scheduler.rs | Changes file_cut API to accept FileInCut and return an Id. |
| yazi-scheduler/src/plugin/plugin.rs | Updates plugin entry submission to use From conversion. |
| yazi-scheduler/src/plugin/in.rs | Replaces into_job with From<PluginInEntry> for EntryJob. |
| yazi-scheduler/src/file/in.rs | Makes FileInCut public, adds constructor, and adds FromLua conversion. |
| yazi-runner/src/loader/loader.rs | Adds preset dnd plugin and tip component registration. |
| yazi-plugin/src/utils/utils.rs | Adds percent_decode utility to Lua utils composer. |
| yazi-plugin/src/utils/text.rs | Implements Lua percent_decode helper (byte-preserving). |
| yazi-plugin/src/utils/tasks.rs | Adds cut as a supported ya.task() spawn kind. |
| yazi-plugin/src/standard.rs | Loads new tip.lua component preset. |
| yazi-plugin/src/runtime/runtime.rs | Exposes rt.tty binding to Lua runtime. |
| yazi-plugin/preset/plugins/dnd.lua | Adds preset Lua DnD helper plugin for offering/cutting URI lists. |
| yazi-plugin/preset/components/tip.lua | Adds a UI tip component for drop affordance. |
| yazi-plugin/preset/components/root.lua | Adds drop routing and drop-target tracking at the root component level. |
| yazi-plugin/preset/components/rail.lua | Ignores non-legacy drag events (prevents DnD events affecting rails). |
| yazi-plugin/preset/components/current.lua | Adds DnD drag offering and drop acceptance logic + drop tip rendering. |
| yazi-plugin/Cargo.toml | Adds percent-encoding dependency for new percent-decode helper. |
| yazi-parser/src/spark/spark.rs | Adds app:dnd spark variant wiring. |
| yazi-parser/src/app/mod.rs | Registers new app-level dnd form module. |
| yazi-parser/src/app/dnd.rs | Adds DndForm to carry DndEvent through the actor system. |
| yazi-fm/src/dispatcher.rs | Routes terminal DnD events into app:dnd actor instead of inline handling. |
| yazi-fm/Cargo.toml | Bumps tikv-jemallocator on supported targets. |
| yazi-core/src/tasks/option.rs | Adds TaskOpt::Cut and TaskIn plumbing for cut tasks. |
| yazi-core/src/tasks/file.rs | Updates cut submission to use new FileInCut::new + scheduler API. |
| yazi-binding/src/tty.rs | Adds Lua Tty userdata to queue/flush OSC sequences for DnD. |
| yazi-binding/src/mouse.rs | Adds a constant type = "legacy" field to mouse events. |
| yazi-binding/src/lib.rs | Exposes new dnd and tty binding modules. |
| yazi-binding/src/dnd.rs | Adds Lua DndEvent userdata wrapper with cached fields (mimes/data). |
| yazi-binding/Cargo.toml | Adds dependency on yazi-tty for rt.tty implementation. |
| yazi-adapter/src/drivers/iip.rs | Cleans up trait imports (unnamed imports) for write traits. |
| yazi-actor/src/tasks/spawn.rs | Dispatches new TaskOpt::Cut to scheduler. |
| yazi-actor/src/app/mouse.rs | Refactors Lua root creation scope structure (indent/ownership changes). |
| yazi-actor/src/app/mod.rs | Registers new dnd app actor module. |
| yazi-actor/src/app/dnd.rs | Adds actor to forward DnD events into Lua Root:drag / Root:drop. |
| README.md | Documents Drag and Drop as a supported feature. |
| cspell.json | Adds new word entry for Mimelist. |
| CHANGELOG.md | Adds changelog entry for drag and drop and PR reference. |
| Cargo.lock | Updates dependency lockfile to match version bumps and new deps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1
to
3
| use std::{fmt::{self, Debug}, io::{self, BufWriter}, ops::Deref}; | ||
|
|
||
| use parking_lot::Mutex; |
Comment on lines
+17
to
+24
| 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()), | ||
| }) |
Comment on lines
+18
to
+31
| 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), |
Comment on lines
+239
to
+249
| 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")?, | ||
| )) |
| 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) |
sxyazi
added a commit
that referenced
this pull request
May 28, 2026
sxyazi
added a commit
that referenced
this pull request
May 28, 2026
sxyazi
added a commit
that referenced
this pull request
May 28, 2026
sxyazi
added a commit
that referenced
this pull request
May 28, 2026
sxyazi
added a commit
that referenced
this pull request
May 28, 2026
sxyazi
added a commit
that referenced
this pull request
May 28, 2026
sxyazi
added a commit
that referenced
this pull request
May 28, 2026
2 tasks
3 tasks
vishudhshah
added a commit
to vishudhshah/dotfiles
that referenced
this pull request
May 29, 2026
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up for:
Resolves #3968
With this PR, Yazi will support DnD, which means you can drag a file from Yazi to other apps, or drop a file from other apps into Yazi. Download the nightly build to try it out!
Note
For the feature to work, your terminal needs to support The Drag and Drop protocol.
The protocol is pretty new, and currently only kitty (>= v0.47.0) supports it, but I'm looking forward to more terminals supporting the protocol in the future.UPDATE: See https://yazi-rs.github.io/docs/dnd for the latest terminal support status.
Demo
eQEl7bve.mp4