From 33782f12240147a84ba8158670ea074e6c644788 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E9=9B=85=20=C2=B7=20Misaki=20Masa?=
 <sxyazi@gmail.com>
Date: Thu, 7 Mar 2024 17:26:18 +0800
Subject: [PATCH] feat: `cx.yanked` plugin API (#788)

---
 yazi-config/src/open/open.rs              |  6 ++--
 yazi-core/src/completion/completion.rs    |  4 +--
 yazi-core/src/folder/files.rs             | 20 +++++------
 yazi-core/src/folder/sorter.rs            |  4 +--
 yazi-core/src/manager/commands/hover.rs   |  4 +--
 yazi-core/src/manager/commands/rename.rs  |  4 +--
 yazi-core/src/manager/linked.rs           |  6 ++--
 yazi-core/src/manager/watcher.rs          | 14 ++++----
 yazi-core/src/tab/commands/visual_mode.rs |  6 ++--
 yazi-core/src/tab/finder.rs               |  6 ++--
 yazi-core/src/tab/mode.rs                 | 10 +++---
 yazi-core/src/tab/selected.rs             |  6 ++--
 yazi-core/src/tab/tab.rs                  |  4 +--
 yazi-core/src/tasks/tasks.rs              |  4 +--
 yazi-fm/src/lives/iter.rs                 | 25 +++++++++++++
 yazi-fm/src/lives/lives.rs                |  2 +-
 yazi-fm/src/lives/mod.rs                  |  2 ++
 yazi-fm/src/lives/selected.rs             | 43 +++++++++--------------
 yazi-fm/src/lives/tasks.rs                |  2 +-
 yazi-fm/src/lives/yanked.rs               | 41 +++++++++++++++++++--
 yazi-plugin/src/loader.rs                 |  6 ++--
 yazi-plugin/src/utils/call.rs             |  6 ++--
 yazi-scheduler/src/preload/preload.rs     |  6 ++--
 yazi-scheduler/src/running.rs             |  7 ++--
 yazi-shared/src/event/cmd.rs              |  4 +--
 yazi-shared/src/fs/op.rs                  |  8 ++---
 26 files changed, 150 insertions(+), 100 deletions(-)
 create mode 100644 yazi-fm/src/lives/iter.rs

diff --git a/yazi-config/src/open/open.rs b/yazi-config/src/open/open.rs
index a18ebfed..0ccd8983 100644
--- a/yazi-config/src/open/open.rs
+++ b/yazi-config/src/open/open.rs
@@ -1,4 +1,4 @@
-use std::{collections::BTreeMap, path::Path};
+use std::{collections::HashMap, path::Path};
 
 use indexmap::IndexSet;
 use serde::{Deserialize, Deserializer};
@@ -10,7 +10,7 @@ use crate::{open::OpenRule, Preset, MERGED_YAZI};
 #[derive(Debug)]
 pub struct Open {
 	rules:   Vec<OpenRule>,
-	openers: BTreeMap<String, IndexSet<Opener>>,
+	openers: HashMap<String, IndexSet<Opener>>,
 }
 
 impl Default for Open {
@@ -65,7 +65,7 @@ impl<'de> Deserialize<'de> for Open {
 	{
 		#[derive(Deserialize)]
 		struct Outer {
-			opener: BTreeMap<String, Vec<Opener>>,
+			opener: HashMap<String, Vec<Opener>>,
 			open:   OuterOpen,
 		}
 		#[derive(Deserialize)]
diff --git a/yazi-core/src/completion/completion.rs b/yazi-core/src/completion/completion.rs
index af5279e4..5cad5a00 100644
--- a/yazi-core/src/completion/completion.rs
+++ b/yazi-core/src/completion/completion.rs
@@ -1,8 +1,8 @@
-use std::collections::BTreeMap;
+use std::collections::HashMap;
 
 #[derive(Default)]
 pub struct Completion {
-	pub(super) caches: BTreeMap<String, Vec<String>>,
+	pub(super) caches: HashMap<String, Vec<String>>,
 	pub(super) cands:  Vec<String>,
 	pub(super) offset: usize,
 	pub cursor:        usize,
diff --git a/yazi-core/src/folder/files.rs b/yazi-core/src/folder/files.rs
index b4a61acc..6ca0d49c 100644
--- a/yazi-core/src/folder/files.rs
+++ b/yazi-core/src/folder/files.rs
@@ -1,4 +1,4 @@
-use std::{collections::{BTreeMap, BTreeSet}, mem, ops::Deref, sync::atomic::Ordering};
+use std::{collections::{HashMap, HashSet}, mem, ops::Deref, sync::atomic::Ordering};
 
 use anyhow::Result;
 use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
@@ -14,7 +14,7 @@ pub struct Files {
 	version:             u64,
 	pub(crate) revision: u64,
 
-	pub sizes: BTreeMap<Url, u64>,
+	pub sizes: HashMap<Url, u64>,
 
 	sorter:      FilesSorter,
 	filter:      Option<Filter>,
@@ -128,7 +128,7 @@ impl Files {
 		}
 	}
 
-	pub fn update_size(&mut self, sizes: BTreeMap<Url, u64>) {
+	pub fn update_size(&mut self, sizes: HashMap<Url, u64>) {
 		if sizes.is_empty() {
 			return;
 		}
@@ -146,7 +146,7 @@ impl Files {
 
 		macro_rules! go {
 			($dist:expr, $src:expr, $inc:literal) => {
-				let mut todo: BTreeMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect();
+				let mut todo: HashMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect();
 				for f in &$dist {
 					if todo.remove(&f.url).is_some() && todo.is_empty() {
 						break;
@@ -176,7 +176,7 @@ impl Files {
 
 		macro_rules! go {
 			($dist:expr, $src:expr, $inc:literal) => {
-				let mut todo: BTreeSet<_> = $src.into_iter().collect();
+				let mut todo: HashSet<_> = $src.into_iter().collect();
 				let len = $dist.len();
 
 				$dist.retain(|f| !todo.remove(&f.url));
@@ -217,7 +217,7 @@ impl Files {
 			};
 		}
 
-		let mut urls: BTreeSet<_> = urls.into_iter().collect();
+		let mut urls: HashSet<_> = urls.into_iter().collect();
 		if !urls.is_empty() {
 			go!(self.items, urls, 1);
 		}
@@ -228,8 +228,8 @@ impl Files {
 
 	pub fn update_updating(
 		&mut self,
-		files: BTreeMap<Url, File>,
-	) -> (BTreeMap<Url, File>, BTreeMap<Url, File>) {
+		files: HashMap<Url, File>,
+	) -> (HashMap<Url, File>, HashMap<Url, File>) {
 		if files.is_empty() {
 			return Default::default();
 		}
@@ -257,7 +257,7 @@ impl Files {
 					|| !f.url.file_name().is_some_and(|s| filter.matches(s))
 			})
 		} else if self.show_hidden {
-			(BTreeMap::new(), files)
+			(HashMap::new(), files)
 		} else {
 			files.into_iter().partition(|(_, f)| f.is_hidden())
 		};
@@ -271,7 +271,7 @@ impl Files {
 		(hidden, items)
 	}
 
-	pub fn update_upserting(&mut self, files: BTreeMap<Url, File>) {
+	pub fn update_upserting(&mut self, files: HashMap<Url, File>) {
 		if files.is_empty() {
 			return;
 		}
diff --git a/yazi-core/src/folder/sorter.rs b/yazi-core/src/folder/sorter.rs
index 779b22e2..5130ac68 100644
--- a/yazi-core/src/folder/sorter.rs
+++ b/yazi-core/src/folder/sorter.rs
@@ -1,4 +1,4 @@
-use std::{cmp::Ordering, collections::BTreeMap, mem};
+use std::{cmp::Ordering, collections::HashMap, mem};
 
 use yazi_config::manager::SortBy;
 use yazi_shared::{fs::{File, Url}, natsort};
@@ -12,7 +12,7 @@ pub struct FilesSorter {
 }
 
 impl FilesSorter {
-	pub(super) fn sort(&self, items: &mut Vec<File>, sizes: &BTreeMap<Url, u64>) {
+	pub(super) fn sort(&self, items: &mut Vec<File>, sizes: &HashMap<Url, u64>) {
 		if items.is_empty() {
 			return;
 		}
diff --git a/yazi-core/src/manager/commands/hover.rs b/yazi-core/src/manager/commands/hover.rs
index 4d46e1b8..94df57d0 100644
--- a/yazi-core/src/manager/commands/hover.rs
+++ b/yazi-core/src/manager/commands/hover.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeSet;
+use std::collections::HashSet;
 
 use yazi_shared::{event::Cmd, fs::Url, render};
 
@@ -31,7 +31,7 @@ impl Manager {
 		self.peek(false);
 
 		// Refresh watcher
-		let mut to_watch = BTreeSet::new();
+		let mut to_watch = HashSet::with_capacity(3 * self.tabs.len());
 		for tab in self.tabs.iter() {
 			to_watch.insert(&tab.current.cwd);
 			if let Some(ref p) = tab.parent {
diff --git a/yazi-core/src/manager/commands/rename.rs b/yazi-core/src/manager/commands/rename.rs
index a980e716..3cb990e4 100644
--- a/yazi-core/src/manager/commands/rename.rs
+++ b/yazi-core/src/manager/commands/rename.rs
@@ -1,4 +1,4 @@
-use std::{collections::BTreeMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf};
+use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf};
 
 use anyhow::{anyhow, bail, Result};
 use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
@@ -49,7 +49,7 @@ impl Manager {
 
 		let file = File::from(new.clone()).await?;
 		FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit();
-		FilesOp::Upserting(file.parent().unwrap(), BTreeMap::from_iter([(old, file)])).emit();
+		FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit();
 		Ok(ManagerProxy::hover(Some(new)))
 	}
 
diff --git a/yazi-core/src/manager/linked.rs b/yazi-core/src/manager/linked.rs
index 9d688feb..c29fc201 100644
--- a/yazi-core/src/manager/linked.rs
+++ b/yazi-core/src/manager/linked.rs
@@ -1,12 +1,12 @@
-use std::{collections::BTreeMap, ops::{Deref, DerefMut}};
+use std::{collections::HashMap, ops::{Deref, DerefMut}};
 
 use yazi_shared::fs::Url;
 
 #[derive(Default)]
-pub struct Linked(BTreeMap<Url, Url> /* from ==> to */);
+pub struct Linked(HashMap<Url, Url> /* from ==> to */);
 
 impl Deref for Linked {
-	type Target = BTreeMap<Url, Url>;
+	type Target = HashMap<Url, Url>;
 
 	fn deref(&self) -> &Self::Target { &self.0 }
 }
diff --git a/yazi-core/src/manager/watcher.rs b/yazi-core/src/manager/watcher.rs
index 5aece338..e8b0ecbf 100644
--- a/yazi-core/src/manager/watcher.rs
+++ b/yazi-core/src/manager/watcher.rs
@@ -1,4 +1,4 @@
-use std::{collections::{BTreeMap, BTreeSet}, sync::Arc, time::{Duration, SystemTime}};
+use std::{collections::{HashMap, HashSet}, sync::Arc, time::{Duration, SystemTime}};
 
 use anyhow::Result;
 use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
@@ -14,7 +14,7 @@ use crate::folder::{Files, Folder};
 
 pub struct Watcher {
 	watcher:    RecommendedWatcher,
-	watched:    Arc<RwLock<BTreeSet<Url>>>,
+	watched:    Arc<RwLock<HashSet<Url>>>,
 	pub linked: Arc<RwLock<Linked>>,
 }
 
@@ -60,11 +60,11 @@ impl Watcher {
 		instance
 	}
 
-	pub(super) fn watch(&mut self, mut new: BTreeSet<&Url>) {
+	pub(super) fn watch(&mut self, mut new: HashSet<&Url>) {
 		new.retain(|&u| u.is_regular());
-		let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = {
+		let (to_unwatch, to_watch): (HashSet<_>, HashSet<_>) = {
 			let guard = self.watched.read();
-			let old: BTreeSet<_> = guard.iter().collect();
+			let old: HashSet<_> = guard.iter().collect();
 			(
 				old.difference(&new).map(|&x| x.clone()).collect(),
 				new.difference(&old).map(|&x| x.clone()).collect(),
@@ -147,7 +147,7 @@ impl Watcher {
 		pin!(rx);
 
 		while let Some(urls) = rx.next().await {
-			let urls: BTreeSet<_> = urls.into_iter().collect();
+			let urls: HashSet<_> = urls.into_iter().collect();
 			let mut reload = Vec::with_capacity(urls.len());
 
 			for u in urls {
@@ -163,7 +163,7 @@ impl Watcher {
 				if !file.is_dir() {
 					reload.push(file.clone());
 				}
-				FilesOp::Upserting(parent, BTreeMap::from_iter([(u, file)])).emit();
+				FilesOp::Upserting(parent, HashMap::from_iter([(u, file)])).emit();
 			}
 
 			if reload.is_empty() {
diff --git a/yazi-core/src/tab/commands/visual_mode.rs b/yazi-core/src/tab/commands/visual_mode.rs
index 8e19ace5..6d618525 100644
--- a/yazi-core/src/tab/commands/visual_mode.rs
+++ b/yazi-core/src/tab/commands/visual_mode.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeSet;
+use std::collections::HashSet;
 
 use yazi_shared::{event::Cmd, render};
 
@@ -18,9 +18,9 @@ impl Tab {
 		let idx = self.current.cursor;
 
 		if opt.unset {
-			self.mode = Mode::Unset(idx, BTreeSet::from([idx]));
+			self.mode = Mode::Unset(idx, HashSet::from([idx]));
 		} else {
-			self.mode = Mode::Select(idx, BTreeSet::from([idx]));
+			self.mode = Mode::Select(idx, HashSet::from([idx]));
 		};
 		render!();
 	}
diff --git a/yazi-core/src/tab/finder.rs b/yazi-core/src/tab/finder.rs
index 72239ce9..2da8571a 100644
--- a/yazi-core/src/tab/finder.rs
+++ b/yazi-core/src/tab/finder.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeMap;
+use std::collections::HashMap;
 
 use anyhow::Result;
 use yazi_shared::fs::Url;
@@ -7,7 +7,7 @@ use crate::folder::{Files, Filter, FilterCase};
 
 pub struct Finder {
 	pub filter: Filter,
-	matched:    BTreeMap<Url, u8>,
+	matched:    HashMap<Url, u8>,
 	revision:   u64,
 }
 
@@ -63,7 +63,7 @@ impl Finder {
 
 impl Finder {
 	#[inline]
-	pub fn matched(&self) -> &BTreeMap<Url, u8> { &self.matched }
+	pub fn matched(&self) -> &HashMap<Url, u8> { &self.matched }
 
 	#[inline]
 	pub fn matched_idx(&self, url: &Url) -> Option<u8> { self.matched.get(url).copied() }
diff --git a/yazi-core/src/tab/mode.rs b/yazi-core/src/tab/mode.rs
index 05c8d8e5..98f27139 100644
--- a/yazi-core/src/tab/mode.rs
+++ b/yazi-core/src/tab/mode.rs
@@ -1,15 +1,15 @@
-use std::{collections::BTreeSet, fmt::Display, mem};
+use std::{collections::HashSet, fmt::Display, mem};
 
 #[derive(Clone, Debug, Default, Eq, PartialEq)]
 pub enum Mode {
 	#[default]
 	Normal,
-	Select(usize, BTreeSet<usize>),
-	Unset(usize, BTreeSet<usize>),
+	Select(usize, HashSet<usize>),
+	Unset(usize, HashSet<usize>),
 }
 
 impl Mode {
-	pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
+	pub fn visual_mut(&mut self) -> Option<(usize, &mut HashSet<usize>)> {
 		match self {
 			Mode::Normal => None,
 			Mode::Select(start, indices) => Some((*start, indices)),
@@ -17,7 +17,7 @@ impl Mode {
 		}
 	}
 
-	pub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {
+	pub fn take_visual(&mut self) -> Option<(usize, HashSet<usize>)> {
 		match mem::take(self) {
 			Mode::Normal => None,
 			Mode::Select(start, indices) => Some((start, indices)),
diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs
index 86bcd0d6..18e56f66 100644
--- a/yazi-core/src/tab/selected.rs
+++ b/yazi-core/src/tab/selected.rs
@@ -1,15 +1,15 @@
-use std::{collections::{BTreeSet, HashMap}, ops::Deref};
+use std::{collections::{HashMap, HashSet}, ops::Deref};
 
 use yazi_shared::fs::Url;
 
 #[derive(Default)]
 pub struct Selected {
-	inner:   BTreeSet<Url>,
+	inner:   HashSet<Url>,
 	parents: HashMap<Url, usize>,
 }
 
 impl Deref for Selected {
-	type Target = BTreeSet<Url>;
+	type Target = HashSet<Url>;
 
 	fn deref(&self) -> &Self::Target { &self.inner }
 }
diff --git a/yazi-core/src/tab/tab.rs b/yazi-core/src/tab/tab.rs
index 42445a09..0a36c1f4 100644
--- a/yazi-core/src/tab/tab.rs
+++ b/yazi-core/src/tab/tab.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeMap;
+use std::collections::HashMap;
 
 use anyhow::Result;
 use tokio::task::JoinHandle;
@@ -14,7 +14,7 @@ pub struct Tab {
 	pub parent:  Option<Folder>,
 
 	pub backstack: Backstack<Url>,
-	pub history:   BTreeMap<Url, Folder>,
+	pub history:   HashMap<Url, Folder>,
 	pub selected:  Selected,
 
 	pub preview:       Preview,
diff --git a/yazi-core/src/tasks/tasks.rs b/yazi-core/src/tasks/tasks.rs
index 22f6a6b8..5d8d20ae 100644
--- a/yazi-core/src/tasks/tasks.rs
+++ b/yazi-core/src/tasks/tasks.rs
@@ -1,4 +1,4 @@
-use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration};
+use std::{collections::{HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration};
 
 use tokio::time::sleep;
 use tracing::debug;
@@ -57,7 +57,7 @@ impl Tasks {
 	}
 
 	pub fn file_open(&self, hovered: &Url, targets: &[(Url, String)]) {
-		let mut openers = BTreeMap::new();
+		let mut openers = HashMap::new();
 		for (url, mime) in targets {
 			if let Some(opener) = OPEN.openers(url, mime).and_then(|o| o.first().copied()) {
 				openers.entry(opener).or_insert_with(|| vec![hovered]).push(url);
diff --git a/yazi-fm/src/lives/iter.rs b/yazi-fm/src/lives/iter.rs
new file mode 100644
index 00000000..4b0e0989
--- /dev/null
+++ b/yazi-fm/src/lives/iter.rs
@@ -0,0 +1,25 @@
+use mlua::AnyUserData;
+
+use super::SCOPE;
+
+pub(super) struct Iter<I: Iterator<Item = T>, T> {
+	inner: I,
+	count: usize,
+}
+
+impl<I: Iterator<Item = T> + 'static, T: 'static> Iter<I, T> {
+	#[inline]
+	pub(super) fn make(inner: I) -> mlua::Result<AnyUserData<'static>> {
+		SCOPE.create_any_userdata(Self { inner, count: 0 })
+	}
+}
+
+impl<I: Iterator<Item = T>, T> Iterator for Iter<I, T> {
+	type Item = (usize, T);
+
+	fn next(&mut self) -> Option<Self::Item> {
+		let next = self.inner.next()?;
+		self.count += 1;
+		Some((self.count, next))
+	}
+}
diff --git a/yazi-fm/src/lives/lives.rs b/yazi-fm/src/lives/lives.rs
index 3cbc9b5d..03e12cad 100644
--- a/yazi-fm/src/lives/lives.rs
+++ b/yazi-fm/src/lives/lives.rs
@@ -45,7 +45,7 @@ impl Lives {
 					("active", super::Tab::make(cx.manager.active())?),
 					("tabs", super::Tabs::make(&cx.manager.tabs)?),
 					("tasks", super::Tasks::make(&cx.tasks)?),
-					("yanked", scope.create_any_userdata_ref(&cx.manager.yanked)?),
+					("yanked", super::Yanked::make(&cx.manager.yanked)?),
 				])?,
 			)?;
 
diff --git a/yazi-fm/src/lives/mod.rs b/yazi-fm/src/lives/mod.rs
index 52e6ff63..d910d4c7 100644
--- a/yazi-fm/src/lives/mod.rs
+++ b/yazi-fm/src/lives/mod.rs
@@ -4,6 +4,7 @@ mod config;
 mod file;
 mod files;
 mod folder;
+mod iter;
 mod lives;
 mod mode;
 mod preview;
@@ -17,6 +18,7 @@ use config::*;
 use file::*;
 use files::*;
 use folder::*;
+use iter::*;
 pub(super) use lives::*;
 use mode::*;
 use preview::*;
diff --git a/yazi-fm/src/lives/selected.rs b/yazi-fm/src/lives/selected.rs
index 29fbfb34..57f7e16e 100644
--- a/yazi-fm/src/lives/selected.rs
+++ b/yazi-fm/src/lives/selected.rs
@@ -1,24 +1,24 @@
-use std::{collections::{btree_set, BTreeSet}, ops::Deref};
+use std::{collections::{hash_set, HashSet}, ops::Deref};
 
 use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataMethods, UserDataRefMut};
 use yazi_plugin::{bindings::Cast, url::Url};
 
-use super::SCOPE;
+use super::{Iter, SCOPE};
 
 #[derive(Clone, Copy)]
 pub(super) struct Selected {
-	inner: *const BTreeSet<yazi_shared::fs::Url>,
+	inner: *const HashSet<yazi_shared::fs::Url>,
 }
 
 impl Deref for Selected {
-	type Target = BTreeSet<yazi_shared::fs::Url>;
+	type Target = HashSet<yazi_shared::fs::Url>;
 
 	fn deref(&self) -> &Self::Target { self.inner() }
 }
 
 impl Selected {
 	#[inline]
-	pub(crate) fn make(inner: &BTreeSet<yazi_shared::fs::Url>) -> mlua::Result<AnyUserData<'static>> {
+	pub(super) fn make(inner: &HashSet<yazi_shared::fs::Url>) -> mlua::Result<AnyUserData<'static>> {
 		SCOPE.create_any_userdata(Self { inner })
 	}
 
@@ -27,16 +27,17 @@ impl Selected {
 			reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
 
 			reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
-				let iter = lua.create_function(|lua, mut iter: UserDataRefMut<SelectedIter>| {
-					if let Some(url) = iter.inner.next() {
-						iter.next += 1;
-						(iter.next, Url::cast(lua, url.clone())?).into_lua_multi(lua)
-					} else {
-						().into_lua_multi(lua)
-					}
-				})?;
+				let iter = lua.create_function(
+					|lua, mut iter: UserDataRefMut<Iter<hash_set::Iter<yazi_shared::fs::Url>, _>>| {
+						if let Some(next) = iter.next() {
+							(next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua)
+						} else {
+							().into_lua_multi(lua)
+						}
+					},
+				)?;
 
-				Ok((iter, SelectedIter::make(me.inner())))
+				Ok((iter, Iter::make(me.inner().iter())))
 			});
 		})?;
 
@@ -44,17 +45,5 @@ impl Selected {
 	}
 
 	#[inline]
-	fn inner(&self) -> &'static BTreeSet<yazi_shared::fs::Url> { unsafe { &*self.inner } }
-}
-
-struct SelectedIter {
-	next:  usize,
-	inner: btree_set::Iter<'static, yazi_shared::fs::Url>,
-}
-
-impl SelectedIter {
-	#[inline]
-	fn make(selected: &'static BTreeSet<yazi_shared::fs::Url>) -> mlua::Result<AnyUserData<'static>> {
-		SCOPE.create_any_userdata(Self { next: 0, inner: selected.iter() })
-	}
+	fn inner(&self) -> &'static HashSet<yazi_shared::fs::Url> { unsafe { &*self.inner } }
 }
diff --git a/yazi-fm/src/lives/tasks.rs b/yazi-fm/src/lives/tasks.rs
index cc83fe6b..481e7004 100644
--- a/yazi-fm/src/lives/tasks.rs
+++ b/yazi-fm/src/lives/tasks.rs
@@ -16,7 +16,7 @@ impl Deref for Tasks {
 
 impl Tasks {
 	#[inline]
-	pub(crate) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData<'static>> {
+	pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData<'static>> {
 		SCOPE.create_any_userdata(Self { inner })
 	}
 
diff --git a/yazi-fm/src/lives/yanked.rs b/yazi-fm/src/lives/yanked.rs
index 3051ecae..9c35b79b 100644
--- a/yazi-fm/src/lives/yanked.rs
+++ b/yazi-fm/src/lives/yanked.rs
@@ -1,13 +1,48 @@
-use mlua::{Lua, MetaMethod, UserDataFields, UserDataMethods};
+use std::{collections::hash_set, ops::Deref};
 
-pub(super) struct Yanked;
+use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRefMut};
+use yazi_plugin::{bindings::Cast, url::Url};
+
+use super::{Iter, SCOPE};
+
+pub(super) struct Yanked {
+	inner: *const yazi_core::manager::Yanked,
+}
+
+impl Deref for Yanked {
+	type Target = yazi_core::manager::Yanked;
+
+	fn deref(&self) -> &Self::Target { self.inner() }
+}
 
 impl Yanked {
+	#[inline]
+	pub(super) fn make(inner: &yazi_core::manager::Yanked) -> mlua::Result<AnyUserData<'static>> {
+		SCOPE.create_any_userdata(Self { inner })
+	}
+
 	pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
-		lua.register_userdata_type::<yazi_core::manager::Yanked>(|reg| {
+		lua.register_userdata_type::<Self>(|reg| {
 			reg.add_field_method_get("is_cut", |_, me| Ok(me.cut));
 
 			reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
+
+			reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
+				let iter = lua.create_function(
+					|lua, mut iter: UserDataRefMut<Iter<hash_set::Iter<yazi_shared::fs::Url>, _>>| {
+						if let Some(next) = iter.next() {
+							(next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua)
+						} else {
+							().into_lua_multi(lua)
+						}
+					},
+				)?;
+
+				Ok((iter, Iter::make(me.inner().iter())))
+			});
 		})
 	}
+
+	#[inline]
+	fn inner(&self) -> &'static yazi_core::manager::Yanked { unsafe { &*self.inner } }
 }
diff --git a/yazi-plugin/src/loader.rs b/yazi-plugin/src/loader.rs
index 3408b418..b8b20443 100644
--- a/yazi-plugin/src/loader.rs
+++ b/yazi-plugin/src/loader.rs
@@ -1,4 +1,4 @@
-use std::{borrow::Cow, collections::BTreeMap, ops::Deref};
+use std::{borrow::Cow, collections::HashMap, ops::Deref};
 
 use anyhow::{bail, Result};
 use mlua::{ExternalError, Table};
@@ -13,7 +13,7 @@ pub static LOADED: RoCell<Loader> = RoCell::new();
 
 #[derive(Default)]
 pub struct Loader {
-	loaded: RwLock<BTreeMap<String, Vec<u8>>>,
+	loaded: RwLock<HashMap<String, Vec<u8>>>,
 }
 
 impl Loader {
@@ -67,7 +67,7 @@ impl Loader {
 }
 
 impl Deref for Loader {
-	type Target = RwLock<BTreeMap<String, Vec<u8>>>;
+	type Target = RwLock<HashMap<String, Vec<u8>>>;
 
 	#[inline]
 	fn deref(&self) -> &Self::Target { &self.loaded }
diff --git a/yazi-plugin/src/utils/call.rs b/yazi-plugin/src/utils/call.rs
index aa966478..183adf6d 100644
--- a/yazi-plugin/src/utils/call.rs
+++ b/yazi-plugin/src/utils/call.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeMap;
+use std::collections::HashMap;
 
 use mlua::{ExternalError, Lua, Table, Value};
 use yazi_shared::{emit, event::Cmd, render, Layer};
@@ -7,9 +7,9 @@ use super::Utils;
 use crate::ValueSendable;
 
 impl Utils {
-	fn parse_args(t: Table) -> mlua::Result<(Vec<String>, BTreeMap<String, String>)> {
+	fn parse_args(t: Table) -> mlua::Result<(Vec<String>, HashMap<String, String>)> {
 		let mut args = vec![];
-		let mut named = BTreeMap::new();
+		let mut named = HashMap::new();
 		for result in t.pairs::<Value, Value>() {
 			let (k, v) = result?;
 			match k {
diff --git a/yazi-scheduler/src/preload/preload.rs b/yazi-scheduler/src/preload/preload.rs
index 19f49257..5c73a8fc 100644
--- a/yazi-scheduler/src/preload/preload.rs
+++ b/yazi-scheduler/src/preload/preload.rs
@@ -1,4 +1,4 @@
-use std::collections::{BTreeMap, BTreeSet, HashMap};
+use std::collections::{HashMap, HashSet};
 
 use anyhow::Result;
 use parking_lot::RwLock;
@@ -16,7 +16,7 @@ pub struct Preload {
 	prog:   mpsc::UnboundedSender<TaskProg>,
 
 	pub rule_loaded:  RwLock<HashMap<Url, u32>>,
-	pub size_loading: RwLock<BTreeSet<Url>>,
+	pub size_loading: RwLock<HashSet<Url>>,
 }
 
 impl Preload {
@@ -60,7 +60,7 @@ impl Preload {
 					}
 
 					let parent = buf[0].0.parent_url().unwrap();
-					FilesOp::Size(parent, BTreeMap::from_iter(buf)).emit();
+					FilesOp::Size(parent, HashMap::from_iter(buf)).emit();
 				});
 				self.prog.send(TaskProg::Adv(task.id, 1, 0))?;
 			}
diff --git a/yazi-scheduler/src/running.rs b/yazi-scheduler/src/running.rs
index 8f771f0a..86298d3c 100644
--- a/yazi-scheduler/src/running.rs
+++ b/yazi-scheduler/src/running.rs
@@ -1,4 +1,4 @@
-use std::collections::BTreeMap;
+use std::collections::HashMap;
 
 use futures::future::BoxFuture;
 use yazi_config::TASKS;
@@ -10,9 +10,8 @@ use crate::TaskKind;
 pub struct Running {
 	incr: usize,
 
-	pub(super) hooks:
-		BTreeMap<usize, Box<dyn (FnOnce(bool) -> BoxFuture<'static, ()>) + Send + Sync>>,
-	pub(super) all:   BTreeMap<usize, Task>,
+	pub(super) hooks: HashMap<usize, Box<dyn (FnOnce(bool) -> BoxFuture<'static, ()>) + Send + Sync>>,
+	pub(super) all:   HashMap<usize, Task>,
 }
 
 impl Running {
diff --git a/yazi-shared/src/event/cmd.rs b/yazi-shared/src/event/cmd.rs
index c519dd6b..f37c1941 100644
--- a/yazi-shared/src/event/cmd.rs
+++ b/yazi-shared/src/event/cmd.rs
@@ -1,10 +1,10 @@
-use std::{any::Any, collections::BTreeMap, fmt::{self, Display}, mem};
+use std::{any::Any, collections::HashMap, fmt::{self, Display}, mem};
 
 #[derive(Debug, Default)]
 pub struct Cmd {
 	pub name:  String,
 	pub args:  Vec<String>,
-	pub named: BTreeMap<String, String>,
+	pub named: HashMap<String, String>,
 	pub data:  Option<Box<dyn Any + Send>>,
 }
 
diff --git a/yazi-shared/src/fs/op.rs b/yazi-shared/src/fs/op.rs
index 61993dce..20d3c3be 100644
--- a/yazi-shared/src/fs/op.rs
+++ b/yazi-shared/src/fs/op.rs
@@ -1,4 +1,4 @@
-use std::{collections::BTreeMap, sync::atomic::{AtomicU64, Ordering}, time::SystemTime};
+use std::{collections::HashMap, sync::atomic::{AtomicU64, Ordering}, time::SystemTime};
 
 use super::File;
 use crate::{emit, event::Cmd, fs::Url, Layer};
@@ -10,12 +10,12 @@ pub enum FilesOp {
 	Full(Url, Vec<File>, Option<SystemTime>),
 	Part(Url, Vec<File>, u64),
 	Done(Url, Option<SystemTime>, u64),
-	Size(Url, BTreeMap<Url, u64>),
+	Size(Url, HashMap<Url, u64>),
 
 	Creating(Url, Vec<File>),
 	Deleting(Url, Vec<Url>),
-	Updating(Url, BTreeMap<Url, File>),
-	Upserting(Url, BTreeMap<Url, File>),
+	Updating(Url, HashMap<Url, File>),
+	Upserting(Url, HashMap<Url, File>),
 }
 
 impl FilesOp {