mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
feat: DDS (Data Distribution Service) (#826)
This commit is contained in:
parent
66d12da09d
commit
903f3da7e5
80 changed files with 1713 additions and 226 deletions
20
Cargo.lock
generated
20
Cargo.lock
generated
|
|
@ -2759,12 +2759,28 @@ dependencies = [
|
||||||
"yazi-adaptor",
|
"yazi-adaptor",
|
||||||
"yazi-boot",
|
"yazi-boot",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
|
"yazi-dds",
|
||||||
"yazi-plugin",
|
"yazi-plugin",
|
||||||
"yazi-proxy",
|
"yazi-proxy",
|
||||||
"yazi-scheduler",
|
"yazi-scheduler",
|
||||||
"yazi-shared",
|
"yazi-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "yazi-dds"
|
||||||
|
version = "0.2.4"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"mlua",
|
||||||
|
"parking_lot",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"yazi-boot",
|
||||||
|
"yazi-shared",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "yazi-fm"
|
name = "yazi-fm"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
|
@ -2790,6 +2806,7 @@ dependencies = [
|
||||||
"yazi-boot",
|
"yazi-boot",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
"yazi-core",
|
"yazi-core",
|
||||||
|
"yazi-dds",
|
||||||
"yazi-plugin",
|
"yazi-plugin",
|
||||||
"yazi-proxy",
|
"yazi-proxy",
|
||||||
"yazi-scheduler",
|
"yazi-scheduler",
|
||||||
|
|
@ -2802,6 +2819,7 @@ version = "0.2.4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi-to-tui",
|
"ansi-to-tui",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"arc-swap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"futures",
|
"futures",
|
||||||
"md-5",
|
"md-5",
|
||||||
|
|
@ -2821,6 +2839,7 @@ dependencies = [
|
||||||
"yazi-adaptor",
|
"yazi-adaptor",
|
||||||
"yazi-boot",
|
"yazi-boot",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
|
"yazi-dds",
|
||||||
"yazi-prebuild",
|
"yazi-prebuild",
|
||||||
"yazi-proxy",
|
"yazi-proxy",
|
||||||
"yazi-shared",
|
"yazi-shared",
|
||||||
|
|
@ -2861,6 +2880,7 @@ dependencies = [
|
||||||
"trash",
|
"trash",
|
||||||
"yazi-adaptor",
|
"yazi-adaptor",
|
||||||
"yazi-config",
|
"yazi-config",
|
||||||
|
"yazi-dds",
|
||||||
"yazi-plugin",
|
"yazi-plugin",
|
||||||
"yazi-proxy",
|
"yazi-proxy",
|
||||||
"yazi-shared",
|
"yazi-shared",
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,9 @@ Yazi (means "duck") is a terminal file manager written in Rust, based on non-blo
|
||||||
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals.
|
- 🖼️ **Built-in Support for Multiple Image Protocols**: Also integrated with Überzug++, covering almost all terminals.
|
||||||
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
|
- 🌟 **Built-in Code Highlighting and Image Decoding**: Combined with the pre-loading mechanism, greatly accelerates image and normal file loading.
|
||||||
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer, and custom preloader; Just some pieces of Lua.
|
- 🔌 **Concurrent Plugin System**: UI plugins (rewriting most of the UI), functional plugins, custom previewer, and custom preloader; Just some pieces of Lua.
|
||||||
|
- 📡 **Data Distribution Service**: Built on a client-server architecture (no additional server process required), integrated with a Lua-based publish-subscribe model, achieving cross-instance communication and state persistence.
|
||||||
- 🧰 Integration with fd, rg, fzf, zoxide
|
- 🧰 Integration with fd, rg, fzf, zoxide
|
||||||
- 💫 Vim-like input/select/notify component, auto-completion for cd paths
|
- 💫 Vim-like input/select/which/notify component, auto-completion for cd paths
|
||||||
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
|
- 🏷️ Multi-Tab Support, Cross-directory selection, Scrollable Preview (for videos, PDFs, archives, directories, code, etc.)
|
||||||
- 🔄 Bulk Renaming, Visual Mode, File Chooser
|
- 🔄 Bulk Renaming, Visual Mode, File Chooser
|
||||||
- 🎨 Theme System, Custom Layouts, Trash Bin, CSI u
|
- 🎨 Theme System, Custom Layouts, Trash Bin, CSI u
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
{"version":"0.2","flagWords":[],"language":"en","words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","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","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","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","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup"]}
|
{"version":"0.2","language":"en","flagWords":[],"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","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","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm","poppler","prebuild","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","imagesize","Upserting","prio","Ghostty","Catmull","Lanczos","cmds","unyank","scrolloff","headsup","unsub"]}
|
||||||
|
|
@ -22,7 +22,7 @@ image = "0.24.9"
|
||||||
imagesize = "0.12.0"
|
imagesize = "0.12.0"
|
||||||
kamadak-exif = "0.5.5"
|
kamadak-exif = "0.5.5"
|
||||||
ratatui = "0.26.1"
|
ratatui = "0.26.1"
|
||||||
tokio = { version = "1.36.0", features = [ "parking_lot", "io-util", "process" ] }
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,7 @@ impl Kitty {
|
||||||
for y in 0..rect.height {
|
for y in 0..rect.height {
|
||||||
write!(buf, "\x1b[{};{}H\x1b[38;5;1m", rect.y + y + 1, rect.x + 1)?;
|
write!(buf, "\x1b[{};{}H\x1b[38;5;1m", rect.y + y + 1, rect.x + 1)?;
|
||||||
for x in 0..rect.width {
|
for x in 0..rect.width {
|
||||||
write!(buf, "{}", '\u{10EEEE}')?;
|
write!(buf, "\u{10EEEE}")?;
|
||||||
write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;
|
write!(buf, "{}", *DIACRITICS.get(y as usize).unwrap_or(&DIACRITICS[0]))?;
|
||||||
write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?;
|
write!(buf, "{}", *DIACRITICS.get(x as usize).unwrap_or(&DIACRITICS[0]))?;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ pub struct Args {
|
||||||
/// Write the cwd on exit to this file
|
/// Write the cwd on exit to this file
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub cwd_file: Option<PathBuf>,
|
pub cwd_file: Option<PathBuf>,
|
||||||
/// Write the selected files on open emitted by the chooser mode
|
/// Write the selected files to this file on open fired
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
pub chooser_file: Option<PathBuf>,
|
pub chooser_file: Option<PathBuf>,
|
||||||
|
|
||||||
|
|
@ -20,6 +20,13 @@ pub struct Args {
|
||||||
#[arg(long, action)]
|
#[arg(long, action)]
|
||||||
pub clear_cache: bool,
|
pub clear_cache: bool,
|
||||||
|
|
||||||
|
/// Report the specified local events to stdout
|
||||||
|
#[arg(long, action)]
|
||||||
|
pub local_events: Option<String>,
|
||||||
|
/// Report the specified remote events to stdout
|
||||||
|
#[arg(long, action)]
|
||||||
|
pub remote_events: Option<String>,
|
||||||
|
|
||||||
/// Print debug information
|
/// Print debug information
|
||||||
#[arg(long, action)]
|
#[arg(long, action)]
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{env, ffi::OsString, fmt::Write, path::{Path, PathBuf}, process};
|
use std::{collections::HashSet, env, ffi::OsString, fmt::Write, path::{Path, PathBuf}, process};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
@ -13,9 +13,13 @@ pub struct Boot {
|
||||||
pub cwd: PathBuf,
|
pub cwd: PathBuf,
|
||||||
pub file: Option<OsString>,
|
pub file: Option<OsString>,
|
||||||
|
|
||||||
|
pub local_events: HashSet<String>,
|
||||||
|
pub remote_events: HashSet<String>,
|
||||||
|
|
||||||
pub config_dir: PathBuf,
|
pub config_dir: PathBuf,
|
||||||
pub flavor_dir: PathBuf,
|
pub flavor_dir: PathBuf,
|
||||||
pub plugin_dir: PathBuf,
|
pub plugin_dir: PathBuf,
|
||||||
|
pub state_dir: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Boot {
|
impl Boot {
|
||||||
|
|
@ -84,7 +88,7 @@ impl Boot {
|
||||||
writeln!(
|
writeln!(
|
||||||
s,
|
s,
|
||||||
" Version: {:?}",
|
" Version: {:?}",
|
||||||
Command::new(env::var_os("YAZI_FILE_TWO").unwrap_or("file".into())).arg("--version").output()
|
Command::new(env::var_os("YAZI_FILE_ONE").unwrap_or("file".into())).arg("--version").output()
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
writeln!(s, "\nText Opener")?;
|
writeln!(s, "\nText Opener")?;
|
||||||
|
|
@ -129,13 +133,28 @@ impl Default for Boot {
|
||||||
let config_dir = Xdg::config_dir();
|
let config_dir = Xdg::config_dir();
|
||||||
let (cwd, file) = Self::parse_entry(ARGS.entry.as_deref());
|
let (cwd, file) = Self::parse_entry(ARGS.entry.as_deref());
|
||||||
|
|
||||||
|
let local_events = ARGS
|
||||||
|
.local_events
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.split(',').map(|s| s.to_owned()).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let remote_events = ARGS
|
||||||
|
.remote_events
|
||||||
|
.as_ref()
|
||||||
|
.map(|s| s.split(',').map(|s| s.to_owned()).collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let boot = Self {
|
let boot = Self {
|
||||||
cwd,
|
cwd,
|
||||||
file,
|
file,
|
||||||
|
|
||||||
|
local_events,
|
||||||
|
remote_events,
|
||||||
|
|
||||||
flavor_dir: config_dir.join("flavors"),
|
flavor_dir: config_dir.join("flavors"),
|
||||||
plugin_dir: config_dir.join("plugins"),
|
plugin_dir: config_dir.join("plugins"),
|
||||||
config_dir,
|
config_dir,
|
||||||
|
state_dir: Xdg::state_dir(),
|
||||||
};
|
};
|
||||||
|
|
||||||
std::fs::create_dir_all(&boot.flavor_dir).expect("Failed to create flavor directory");
|
std::fs::create_dir_all(&boot.flavor_dir).expect("Failed to create flavor directory");
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,8 @@ impl Plugin {
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&rule| {
|
.filter(|&rule| {
|
||||||
rule.cond.as_ref().and_then(|c| c.eval(f)) != Some(false)
|
rule.cond.as_ref().and_then(|c| c.eval(f)) != Some(false)
|
||||||
&& (rule.name.as_ref().is_some_and(|n| n.match_path(path, is_folder))
|
&& (rule.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s))
|
||||||
|| rule.mime.as_ref().zip(mime).map_or(false, |(m, s)| m.matches(s)))
|
|| rule.name.as_ref().is_some_and(|n| n.match_path(path, is_folder)))
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ use yazi_shared::{event::Cmd, Condition};
|
||||||
|
|
||||||
use crate::{Pattern, Priority, DEPRECATED_EXEC};
|
use crate::{Pattern, Priority, DEPRECATED_EXEC};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct PluginRule {
|
pub struct PluginRule {
|
||||||
pub id: u8,
|
pub id: u8,
|
||||||
pub cond: Option<Condition>,
|
pub cond: Option<Condition>,
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ repository = "https://github.com/sxyazi/yazi"
|
||||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||||
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
||||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||||
|
yazi-dds = { path = "../yazi-dds", version = "0.2.4" }
|
||||||
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
||||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
||||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" }
|
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" }
|
||||||
|
|
@ -29,7 +30,7 @@ parking_lot = "0.12.1"
|
||||||
ratatui = "0.26.1"
|
ratatui = "0.26.1"
|
||||||
regex = "1.10.3"
|
regex = "1.10.3"
|
||||||
serde = "1.0.197"
|
serde = "1.0.197"
|
||||||
tokio = { version = "1.36.0", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs", "process", "io-std", "io-util" ] }
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
tokio-stream = "0.1.15"
|
tokio-stream = "0.1.15"
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
unicode-width = "0.1.11"
|
unicode-width = "0.1.11"
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,7 @@ impl Clipboard {
|
||||||
use tokio::{io::AsyncWriteExt, process::Command};
|
use tokio::{io::AsyncWriteExt, process::Command};
|
||||||
use yazi_shared::in_ssh_connection;
|
use yazi_shared::in_ssh_connection;
|
||||||
|
|
||||||
*self.content.lock() = s.as_ref().to_owned();
|
s.as_ref().clone_into(&mut self.content.lock());
|
||||||
if in_ssh_connection() {
|
if in_ssh_connection() {
|
||||||
execute!(BufWriter::new(stderr()), osc52::SetClipboard::new(s.as_ref())).ok();
|
execute!(BufWriter::new(stderr()), osc52::SetClipboard::new(s.as_ref())).ok();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,6 @@ impl Input {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(super) fn flush_value(&mut self) {
|
pub(super) fn flush_value(&mut self) {
|
||||||
self.ticket = self.ticket.wrapping_add(1);
|
self.ticket = self.ticket.wrapping_add(1);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ impl Manager {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let _permit = WATCHER.acquire().await.unwrap();
|
let permit = WATCHER.acquire().await.unwrap();
|
||||||
let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));
|
let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));
|
||||||
for (o, n) in todo {
|
for (o, n) in todo {
|
||||||
let (old, new) = (root.join(&o), root.join(&n));
|
let (old, new) = (root.join(&o), root.join(&n));
|
||||||
|
|
@ -96,7 +96,7 @@ impl Manager {
|
||||||
if !succeeded.is_empty() {
|
if !succeeded.is_empty() {
|
||||||
FilesOp::Upserting(cwd, succeeded).emit();
|
FilesOp::Upserting(cwd, succeeded).emit();
|
||||||
}
|
}
|
||||||
drop(_permit);
|
drop(permit);
|
||||||
|
|
||||||
if !failed.is_empty() {
|
if !failed.is_empty() {
|
||||||
Self::output_failed(failed).await?;
|
Self::output_failed(failed).await?;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use crate::{manager::Manager, tasks::Tasks};
|
||||||
impl Manager {
|
impl Manager {
|
||||||
pub fn close(&mut self, _: Cmd, tasks: &Tasks) {
|
pub fn close(&mut self, _: Cmd, tasks: &Tasks) {
|
||||||
if self.tabs.len() > 1 {
|
if self.tabs.len() > 1 {
|
||||||
return self.tabs.close(self.tabs.idx);
|
return self.tabs.close(self.tabs.cursor);
|
||||||
}
|
}
|
||||||
self.quit((), tasks);
|
self.quit((), tasks);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::collections::HashMap;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use yazi_config::popup::InputCfg;
|
use yazi_config::popup::InputCfg;
|
||||||
|
use yazi_dds::Pubsub;
|
||||||
use yazi_proxy::{InputProxy, ManagerProxy, WATCHER};
|
use yazi_proxy::{InputProxy, ManagerProxy, WATCHER};
|
||||||
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
|
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
|
||||||
|
|
||||||
|
|
@ -49,6 +50,7 @@ impl Manager {
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let tab = self.tabs.cursor;
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut result = InputProxy::show(InputCfg::rename().with_value(name).with_cursor(cursor));
|
let mut result = InputProxy::show(InputCfg::rename().with_value(name).with_cursor(cursor));
|
||||||
let Some(Ok(name)) = result.recv().await else {
|
let Some(Ok(name)) = result.recv().await else {
|
||||||
|
|
@ -57,20 +59,20 @@ impl Manager {
|
||||||
|
|
||||||
let new = hovered.parent().unwrap().join(name);
|
let new = hovered.parent().unwrap().join(name);
|
||||||
if opt.force || !accessible(&new).await {
|
if opt.force || !accessible(&new).await {
|
||||||
Self::rename_do(hovered, Url::from(new)).await.ok();
|
Self::rename_do(tab, hovered, Url::from(new)).await.ok();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = InputProxy::show(InputCfg::overwrite());
|
let mut result = InputProxy::show(InputCfg::overwrite());
|
||||||
if let Some(Ok(choice)) = result.recv().await {
|
if let Some(Ok(choice)) = result.recv().await {
|
||||||
if choice == "y" || choice == "Y" {
|
if choice == "y" || choice == "Y" {
|
||||||
Self::rename_do(hovered, Url::from(new)).await.ok();
|
Self::rename_do(tab, hovered, Url::from(new)).await.ok();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn rename_do(old: Url, new: Url) -> Result<()> {
|
async fn rename_do(tab: usize, old: Url, new: Url) -> Result<()> {
|
||||||
let _permit = WATCHER.acquire().await.unwrap();
|
let _permit = WATCHER.acquire().await.unwrap();
|
||||||
|
|
||||||
fs::rename(&old, &new).await?;
|
fs::rename(&old, &new).await?;
|
||||||
|
|
@ -79,6 +81,8 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
let file = File::from(new.clone()).await?;
|
let file = File::from(new.clone()).await?;
|
||||||
|
Pubsub::pub_from_rename(tab, &old, &new);
|
||||||
|
|
||||||
FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit();
|
FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit();
|
||||||
FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit();
|
FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit();
|
||||||
Ok(ManagerProxy::hover(Some(new)))
|
Ok(ManagerProxy::hover(Some(new)))
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use yazi_proxy::AppProxy;
|
|
||||||
use yazi_shared::event::Cmd;
|
use yazi_shared::event::Cmd;
|
||||||
|
|
||||||
use crate::manager::Manager;
|
use crate::manager::Manager;
|
||||||
|
|
@ -7,7 +6,7 @@ impl Manager {
|
||||||
pub fn suspend(&mut self, _: Cmd) {
|
pub fn suspend(&mut self, _: Cmd) {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
AppProxy::stop().await;
|
yazi_proxy::AppProxy::stop().await;
|
||||||
unsafe { libc::raise(libc::SIGTSTP) };
|
unsafe { libc::raise(libc::SIGTSTP) };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,11 @@ impl Tabs {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.items.remove(opt.idx).shutdown();
|
self.items.remove(opt.idx).shutdown();
|
||||||
if opt.idx <= self.idx {
|
if opt.idx <= self.cursor {
|
||||||
self.set_idx(self.absolute(1));
|
self.set_idx(self.absolute(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.reorder();
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,9 @@ impl Tabs {
|
||||||
tab.conf = self.active().conf.clone();
|
tab.conf = self.active().conf.clone();
|
||||||
tab.apply_files_attrs();
|
tab.apply_files_attrs();
|
||||||
|
|
||||||
self.items.insert(self.idx + 1, tab);
|
self.items.insert(self.cursor + 1, tab);
|
||||||
self.set_idx(self.idx + 1);
|
self.set_idx(self.cursor + 1);
|
||||||
|
self.reorder();
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,12 +15,13 @@ impl From<Cmd> for Opt {
|
||||||
impl Tabs {
|
impl Tabs {
|
||||||
pub fn swap(&mut self, opt: impl Into<Opt>) {
|
pub fn swap(&mut self, opt: impl Into<Opt>) {
|
||||||
let idx = self.absolute(opt.into().step);
|
let idx = self.absolute(opt.into().step);
|
||||||
if idx == self.idx {
|
if idx == self.cursor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.items.swap(self.idx, idx);
|
self.items.swap(self.cursor, idx);
|
||||||
self.set_idx(idx);
|
self.set_idx(idx);
|
||||||
|
self.reorder();
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,12 @@ impl Tabs {
|
||||||
pub fn switch(&mut self, opt: impl Into<Opt>) {
|
pub fn switch(&mut self, opt: impl Into<Opt>) {
|
||||||
let opt = opt.into() as Opt;
|
let opt = opt.into() as Opt;
|
||||||
let idx = if opt.relative {
|
let idx = if opt.relative {
|
||||||
(self.idx as isize + opt.step).rem_euclid(self.items.len() as isize) as usize
|
(self.cursor as isize + opt.step).rem_euclid(self.items.len() as isize) as usize
|
||||||
} else {
|
} else {
|
||||||
opt.step as usize
|
opt.step as usize
|
||||||
};
|
};
|
||||||
|
|
||||||
if idx == self.idx || idx >= self.items.len() {
|
if idx == self.cursor || idx >= self.items.len() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl Manager {
|
||||||
}
|
}
|
||||||
|
|
||||||
for op in ops {
|
for op in ops {
|
||||||
let idx = self.tabs.idx;
|
let idx = self.tabs.cursor;
|
||||||
self.yanked.apply_op(&op);
|
self.yanked.apply_op(&op);
|
||||||
|
|
||||||
for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) {
|
for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use yazi_plugin::ValueSendable;
|
use yazi_dds::ValueSendable;
|
||||||
use yazi_shared::{event::Cmd, fs::Url, render};
|
use yazi_shared::{event::Cmd, fs::Url, render};
|
||||||
|
|
||||||
use crate::{manager::Manager, tasks::Tasks};
|
use crate::{manager::Manager, tasks::Tasks};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use std::collections::HashSet;
|
use yazi_dds::Pubsub;
|
||||||
|
|
||||||
use yazi_shared::{event::Cmd, render};
|
use yazi_shared::{event::Cmd, render};
|
||||||
|
|
||||||
use crate::manager::{Manager, Yanked};
|
use crate::manager::{Manager, Yanked};
|
||||||
|
|
@ -18,13 +17,14 @@ impl Manager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selected: HashSet<_> = self.selected_or_hovered(false).into_iter().cloned().collect();
|
self.yanked = Yanked {
|
||||||
if selected.is_empty() {
|
cut: opt.into().cut,
|
||||||
return;
|
urls: self.selected_or_hovered(false).into_iter().cloned().collect(),
|
||||||
}
|
};
|
||||||
|
|
||||||
self.yanked = Yanked { cut: opt.into().cut, urls: selected };
|
|
||||||
self.active_mut().escape_select();
|
self.active_mut().escape_select();
|
||||||
|
Pubsub::pub_from_yank(self.yanked.cut, &self.yanked.urls);
|
||||||
|
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ use yazi_shared::fs::Url;
|
||||||
use crate::tab::Tab;
|
use crate::tab::Tab;
|
||||||
|
|
||||||
pub struct Tabs {
|
pub struct Tabs {
|
||||||
pub idx: usize,
|
pub cursor: usize,
|
||||||
pub(super) items: Vec<Tab>,
|
pub(super) items: Vec<Tab>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Tabs {
|
impl Tabs {
|
||||||
pub fn make() -> Self {
|
pub fn make() -> Self {
|
||||||
let mut tabs = Self { idx: 0, items: vec![Tab::from(Url::from(&BOOT.cwd))] };
|
let mut tabs = Self { cursor: 0, items: vec![Tab::from(Url::from(&BOOT.cwd))] };
|
||||||
if let Some(file) = &BOOT.file {
|
if let Some(file) = &BOOT.file {
|
||||||
tabs.items[0].reveal(Url::from(BOOT.cwd.join(file)));
|
tabs.items[0].reveal(Url::from(BOOT.cwd.join(file)));
|
||||||
}
|
}
|
||||||
|
|
@ -25,24 +25,28 @@ impl Tabs {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn absolute(&self, rel: isize) -> usize {
|
pub(super) fn absolute(&self, rel: isize) -> usize {
|
||||||
if rel > 0 {
|
if rel > 0 {
|
||||||
(self.idx + rel as usize).min(self.items.len() - 1)
|
(self.cursor + rel as usize).min(self.items.len() - 1)
|
||||||
} else {
|
} else {
|
||||||
self.idx.saturating_sub(rel.unsigned_abs())
|
self.cursor.saturating_sub(rel.unsigned_abs())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
pub(super) fn reorder(&mut self) {
|
||||||
|
self.items.iter_mut().enumerate().for_each(|(i, tab)| tab.idx = i);
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn set_idx(&mut self, idx: usize) {
|
pub(super) fn set_idx(&mut self, idx: usize) {
|
||||||
if self.idx == idx {
|
if self.cursor == idx {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the preview of the previous active tab
|
// Reset the preview of the previous active tab
|
||||||
if let Some(active) = self.items.get_mut(self.idx) {
|
if let Some(active) = self.items.get_mut(self.cursor) {
|
||||||
active.preview.reset_image();
|
active.preview.reset_image();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.idx = idx;
|
self.cursor = idx;
|
||||||
ManagerProxy::refresh();
|
ManagerProxy::refresh();
|
||||||
ManagerProxy::peek(true);
|
ManagerProxy::peek(true);
|
||||||
}
|
}
|
||||||
|
|
@ -50,10 +54,10 @@ impl Tabs {
|
||||||
|
|
||||||
impl Tabs {
|
impl Tabs {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn active(&self) -> &Tab { &self.items[self.idx] }
|
pub fn active(&self) -> &Tab { &self.items[self.cursor] }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.idx] }
|
pub(super) fn active_mut(&mut self) -> &mut Tab { &mut self.items[self.cursor] }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Tabs {
|
impl Deref for Tabs {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
use yazi_dds::Pubsub;
|
||||||
use yazi_proxy::ManagerProxy;
|
use yazi_proxy::ManagerProxy;
|
||||||
use yazi_shared::{event::Cmd, render};
|
use yazi_shared::{event::Cmd, render};
|
||||||
|
|
||||||
|
|
@ -37,6 +38,7 @@ impl Tab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pubsub::pub_from_hover(self.idx, self.current.hovered().map(|h| &h.url));
|
||||||
ManagerProxy::hover(None);
|
ManagerProxy::hover(None);
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ use std::{mem, time::Duration};
|
||||||
use tokio::{fs, pin};
|
use tokio::{fs, pin};
|
||||||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||||
use yazi_config::popup::InputCfg;
|
use yazi_config::popup::InputCfg;
|
||||||
|
use yazi_dds::Pubsub;
|
||||||
use yazi_proxy::{CompletionProxy, InputProxy, ManagerProxy, TabProxy};
|
use yazi_proxy::{CompletionProxy, InputProxy, ManagerProxy, TabProxy};
|
||||||
use yazi_shared::{event::Cmd, fs::{expand_path, Url}, render, Debounce, InputError};
|
use yazi_shared::{event::Cmd, fs::{expand_path, Url}, render, Debounce, InputError};
|
||||||
|
|
||||||
|
|
@ -64,6 +65,7 @@ impl Tab {
|
||||||
self.backstack.push(opt.target.clone());
|
self.backstack.push(opt.target.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Pubsub::pub_from_cd(self.idx, &self.current.cwd);
|
||||||
ManagerProxy::refresh();
|
ManagerProxy::refresh();
|
||||||
render!();
|
render!();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ use super::{Backstack, Config, Finder, Mode, Preview};
|
||||||
use crate::{folder::{Folder, FolderStage}, tab::Selected};
|
use crate::{folder::{Folder, FolderStage}, tab::Selected};
|
||||||
|
|
||||||
pub struct Tab {
|
pub struct Tab {
|
||||||
|
pub idx: usize,
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
pub conf: Config,
|
pub conf: Config,
|
||||||
pub current: Folder,
|
pub current: Folder,
|
||||||
|
|
@ -27,6 +28,7 @@ impl From<Url> for Tab {
|
||||||
let parent = url.parent_url().map(Folder::from);
|
let parent = url.parent_url().map(Folder::from);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
idx: 0,
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
current: Folder::from(url.clone()),
|
current: Folder::from(url.clone()),
|
||||||
parent,
|
parent,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use yazi_plugin::ValueSendable;
|
use yazi_dds::ValueSendable;
|
||||||
|
|
||||||
use super::Tasks;
|
use super::Tasks;
|
||||||
|
|
||||||
|
|
|
||||||
24
yazi-dds/Cargo.toml
Normal file
24
yazi-dds/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "yazi-dds"
|
||||||
|
version = "0.2.4"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||||
|
description = "Yazi data distribution service"
|
||||||
|
homepage = "https://yazi-rs.github.io"
|
||||||
|
repository = "https://github.com/sxyazi/yazi"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
||||||
|
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||||
|
|
||||||
|
# External dependencies
|
||||||
|
anyhow = "1.0.81"
|
||||||
|
mlua = { version = "0.9.6", features = [ "lua54", "vendored" ] }
|
||||||
|
parking_lot = "0.12.1"
|
||||||
|
serde = { version = "1.0.197", features = [ "derive" ] }
|
||||||
|
serde_json = "1.0.114"
|
||||||
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||||
106
yazi-dds/src/body/body.rs
Normal file
106
yazi-dds/src/body/body.rs
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
use anyhow::Result;
|
||||||
|
use mlua::{ExternalResult, IntoLua, Lua, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::{BodyBulk, BodyCd, BodyCustom, BodyHey, BodyHi, BodyHover, BodyRename, BodyTabs, BodyYank};
|
||||||
|
use crate::Payload;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Body<'a> {
|
||||||
|
Hi(BodyHi),
|
||||||
|
Hey(BodyHey),
|
||||||
|
Tabs(BodyTabs<'a>),
|
||||||
|
Cd(BodyCd<'a>),
|
||||||
|
Hover(BodyHover<'a>),
|
||||||
|
Rename(BodyRename<'a>),
|
||||||
|
Bulk(BodyBulk<'a>),
|
||||||
|
Yank(BodyYank<'a>),
|
||||||
|
Custom(BodyCustom),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Body<'a> {
|
||||||
|
pub fn from_str(kind: &str, body: &str) -> Result<Self> {
|
||||||
|
Ok(match kind {
|
||||||
|
"hi" => Body::Hi(serde_json::from_str(body)?),
|
||||||
|
"hey" => Body::Hey(serde_json::from_str(body)?),
|
||||||
|
"tabs" => Body::Tabs(serde_json::from_str(body)?),
|
||||||
|
"cd" => Body::Cd(serde_json::from_str(body)?),
|
||||||
|
"hover" => Body::Hover(serde_json::from_str(body)?),
|
||||||
|
"rename" => Body::Rename(serde_json::from_str(body)?),
|
||||||
|
"bulk" => Body::Bulk(serde_json::from_str(body)?),
|
||||||
|
"yank" => Body::Yank(serde_json::from_str(body)?),
|
||||||
|
_ => BodyCustom::from_str(kind, body)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_lua(kind: &str, value: Value) -> Result<Self> {
|
||||||
|
Ok(match kind {
|
||||||
|
"hi" | "hey" | "tabs" | "cd" | "hover" | "rename" | "bulk" | "yank" => {
|
||||||
|
Err("Cannot construct system event from Lua").into_lua_err()?
|
||||||
|
}
|
||||||
|
_ => BodyCustom::from_lua(kind, value)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn kind(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::Hi(_) => "hi",
|
||||||
|
Self::Hey(_) => "hey",
|
||||||
|
Self::Tabs(_) => "tabs",
|
||||||
|
Self::Cd(_) => "cd",
|
||||||
|
Self::Hover(_) => "hover",
|
||||||
|
Self::Rename(_) => "rename",
|
||||||
|
Self::Bulk(_) => "bulk",
|
||||||
|
Self::Yank(_) => "yank",
|
||||||
|
Self::Custom(b) => b.kind.as_str(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tab(kind: &str, body: &str) -> usize {
|
||||||
|
match kind {
|
||||||
|
"cd" | "hover" | "bulk" | "rename" => {}
|
||||||
|
_ => return 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
match Self::from_str(kind, body) {
|
||||||
|
Ok(Body::Cd(b)) => b.tab,
|
||||||
|
Ok(Body::Hover(b)) => b.tab,
|
||||||
|
Ok(Body::Bulk(b)) => b.tab,
|
||||||
|
Ok(Body::Rename(b)) => b.tab,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn upgrade(self) -> Payload<'a> {
|
||||||
|
let severity = match self {
|
||||||
|
Body::Hi(_) => 0,
|
||||||
|
Body::Hey(_) => 0,
|
||||||
|
Body::Tabs(_) => 10,
|
||||||
|
Body::Cd(_) => 20,
|
||||||
|
Body::Hover(_) => 30,
|
||||||
|
Body::Rename(_) => 0,
|
||||||
|
Body::Bulk(_) => 0,
|
||||||
|
Body::Yank(_) => 40,
|
||||||
|
Body::Custom(_) => 0,
|
||||||
|
};
|
||||||
|
Payload::new(self).with_severity(severity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for Body<'static> {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
|
match self {
|
||||||
|
Body::Hi(b) => b.into_lua(lua),
|
||||||
|
Body::Hey(b) => b.into_lua(lua),
|
||||||
|
Body::Tabs(b) => b.into_lua(lua),
|
||||||
|
Body::Cd(b) => b.into_lua(lua),
|
||||||
|
Body::Hover(b) => b.into_lua(lua),
|
||||||
|
Body::Rename(b) => b.into_lua(lua),
|
||||||
|
Body::Bulk(b) => b.into_lua(lua),
|
||||||
|
Body::Yank(b) => b.into_lua(lua),
|
||||||
|
Body::Custom(b) => b.into_lua(lua),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
70
yazi-dds/src/body/bulk.rs
Normal file
70
yazi-dds/src/body/bulk.rs
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
use std::{borrow::Cow, collections::{hash_map, HashMap}};
|
||||||
|
|
||||||
|
use mlua::{AnyUserData, IntoLua, IntoLuaMulti, Lua, MetaMethod, UserData, UserDataRefMut, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_shared::fs::Url;
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyBulk<'a> {
|
||||||
|
pub tab: usize,
|
||||||
|
pub changes: Cow<'a, HashMap<Url, Url>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BodyBulk<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn borrowed(tab: usize, changes: &'a HashMap<Url, Url>) -> Body<'a> {
|
||||||
|
Self { tab, changes: Cow::Borrowed(changes) }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyBulk<'static> {
|
||||||
|
#[inline]
|
||||||
|
pub fn owned(tab: usize, changes: &HashMap<Url, Url>) -> Body<'static> {
|
||||||
|
Self { tab, changes: Cow::Owned(changes.clone()) }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<BodyBulk<'a>> for Body<'a> {
|
||||||
|
fn from(value: BodyBulk<'a>) -> Self { Self::Bulk(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyBulk<'static> {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
|
lua
|
||||||
|
.create_any_userdata(BodyBulkIter {
|
||||||
|
tab: self.tab,
|
||||||
|
inner: self.changes.into_owned().into_iter(),
|
||||||
|
})?
|
||||||
|
.into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Iterator
|
||||||
|
pub struct BodyBulkIter {
|
||||||
|
pub tab: usize,
|
||||||
|
pub inner: hash_map::IntoIter<Url, Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for BodyBulkIter {
|
||||||
|
fn add_fields<'a, F: mlua::UserDataFields<'a, Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("tab", |_, me| Ok(me.tab));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_methods<'a, M: mlua::UserDataMethods<'a, Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.inner.len()));
|
||||||
|
|
||||||
|
methods.add_meta_function(MetaMethod::Pairs, |lua, me: AnyUserData| {
|
||||||
|
let iter = lua.create_function(|lua, mut me: UserDataRefMut<Self>| {
|
||||||
|
if let Some((from, to)) = me.inner.next() {
|
||||||
|
(lua.create_any_userdata(from)?, lua.create_any_userdata(to)?).into_lua_multi(lua)
|
||||||
|
} else {
|
||||||
|
().into_lua_multi(lua)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok((iter, me))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
46
yazi-dds/src/body/cd.rs
Normal file
46
yazi-dds/src/body/cd.rs
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use mlua::{IntoLua, Lua, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_shared::fs::Url;
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyCd<'a> {
|
||||||
|
pub owned: bool,
|
||||||
|
pub tab: usize,
|
||||||
|
pub url: Cow<'a, Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BodyCd<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn borrowed(tab: usize, url: &'a Url) -> Body<'a> {
|
||||||
|
Self { owned: false, tab, url: Cow::Borrowed(url) }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyCd<'static> {
|
||||||
|
#[inline]
|
||||||
|
pub fn owned(tab: usize) -> Body<'static> {
|
||||||
|
Self { owned: false, tab, url: Default::default() }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<BodyCd<'a>> for Body<'a> {
|
||||||
|
fn from(value: BodyCd<'a>) -> Self { Self::Cd(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyCd<'static> {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
|
if self.owned {
|
||||||
|
lua.create_table_from([
|
||||||
|
("tab", self.tab.into_lua(lua)?),
|
||||||
|
("url", lua.create_any_userdata(self.url.into_owned())?.into_lua(lua)?),
|
||||||
|
])?
|
||||||
|
} else {
|
||||||
|
lua.create_table_from([("tab", self.tab)])?
|
||||||
|
}
|
||||||
|
.into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
35
yazi-dds/src/body/custom.rs
Normal file
35
yazi-dds/src/body/custom.rs
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
use mlua::{IntoLua, Lua, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
use crate::ValueSendable;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyCustom {
|
||||||
|
#[serde(skip)]
|
||||||
|
pub kind: String,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub value: ValueSendable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyCustom {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_str(kind: &str, value: &str) -> anyhow::Result<Body<'static>> {
|
||||||
|
let mut me = serde_json::from_str::<Self>(value)?;
|
||||||
|
kind.clone_into(&mut me.kind);
|
||||||
|
Ok(me.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_lua(kind: &str, value: Value) -> mlua::Result<Body<'static>> {
|
||||||
|
Ok(Self { kind: kind.to_owned(), value: value.try_into()? }.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BodyCustom> for Body<'_> {
|
||||||
|
fn from(value: BodyCustom) -> Self { Self::Custom(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyCustom {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { self.value.into_lua(lua) }
|
||||||
|
}
|
||||||
22
yazi-dds/src/body/hey.rs
Normal file
22
yazi-dds/src/body/hey.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use mlua::{ExternalResult, IntoLua, Lua, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
use crate::Peer;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyHey {
|
||||||
|
pub peers: HashMap<u64, Peer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BodyHey> for Body<'_> {
|
||||||
|
fn from(value: BodyHey) -> Self { Self::Hey(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyHey {
|
||||||
|
fn into_lua(self, _: &Lua) -> mlua::Result<Value<'_>> {
|
||||||
|
Err("BodyHey cannot be converted to Lua").into_lua_err()
|
||||||
|
}
|
||||||
|
}
|
||||||
22
yazi-dds/src/body/hi.rs
Normal file
22
yazi-dds/src/body/hi.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use mlua::{ExternalResult, IntoLua, Lua, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyHi {
|
||||||
|
pub id: u64,
|
||||||
|
pub abilities: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BodyHi> for Body<'_> {
|
||||||
|
fn from(value: BodyHi) -> Self { Self::Hi(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyHi {
|
||||||
|
fn into_lua(self, _: &Lua) -> mlua::Result<Value<'_>> {
|
||||||
|
Err("BodyHi cannot be converted to Lua").into_lua_err()
|
||||||
|
}
|
||||||
|
}
|
||||||
44
yazi-dds/src/body/hover.rs
Normal file
44
yazi-dds/src/body/hover.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use mlua::{IntoLua, Lua, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_shared::fs::Url;
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyHover<'a> {
|
||||||
|
pub owned: bool,
|
||||||
|
pub tab: usize,
|
||||||
|
pub url: Option<Cow<'a, Url>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BodyHover<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn borrowed(tab: usize, url: Option<&'a Url>) -> Body<'a> {
|
||||||
|
Self { owned: false, tab, url: url.map(Cow::Borrowed) }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyHover<'static> {
|
||||||
|
#[inline]
|
||||||
|
pub fn owned(tab: usize) -> Body<'static> { Self { owned: false, tab, url: None }.into() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<BodyHover<'a>> for Body<'a> {
|
||||||
|
fn from(value: BodyHover<'a>) -> Self { Self::Hover(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyHover<'static> {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
|
if self.owned && self.url.is_some() {
|
||||||
|
lua.create_table_from([
|
||||||
|
("tab", self.tab.into_lua(lua)?),
|
||||||
|
("url", lua.create_any_userdata(self.url.unwrap().into_owned())?.into_lua(lua)?),
|
||||||
|
])?
|
||||||
|
} else {
|
||||||
|
lua.create_table_from([("tab", self.tab)])?
|
||||||
|
}
|
||||||
|
.into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
23
yazi-dds/src/body/mod.rs
Normal file
23
yazi-dds/src/body/mod.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#![allow(clippy::module_inception)]
|
||||||
|
|
||||||
|
mod body;
|
||||||
|
mod bulk;
|
||||||
|
mod cd;
|
||||||
|
mod custom;
|
||||||
|
mod hey;
|
||||||
|
mod hi;
|
||||||
|
mod hover;
|
||||||
|
mod rename;
|
||||||
|
mod tabs;
|
||||||
|
mod yank;
|
||||||
|
|
||||||
|
pub use body::*;
|
||||||
|
pub use bulk::*;
|
||||||
|
pub use cd::*;
|
||||||
|
pub use custom::*;
|
||||||
|
pub use hey::*;
|
||||||
|
pub use hi::*;
|
||||||
|
pub use hover::*;
|
||||||
|
pub use rename::*;
|
||||||
|
pub use tabs::*;
|
||||||
|
pub use yank::*;
|
||||||
44
yazi-dds/src/body/rename.rs
Normal file
44
yazi-dds/src/body/rename.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use mlua::{IntoLua, Lua, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_shared::fs::Url;
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyRename<'a> {
|
||||||
|
pub tab: usize,
|
||||||
|
pub from: Cow<'a, Url>,
|
||||||
|
pub to: Cow<'a, Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BodyRename<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn borrowed(tab: usize, from: &'a Url, to: &'a Url) -> Body<'a> {
|
||||||
|
Self { tab, from: Cow::Borrowed(from), to: Cow::Borrowed(to) }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyRename<'static> {
|
||||||
|
#[inline]
|
||||||
|
pub fn owned(tab: usize, from: &Url, to: &Url) -> Body<'static> {
|
||||||
|
Self { tab, from: Cow::Owned(from.clone()), to: Cow::Owned(to.clone()) }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<BodyRename<'a>> for Body<'a> {
|
||||||
|
fn from(value: BodyRename<'a>) -> Self { Self::Rename(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyRename<'static> {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
|
lua
|
||||||
|
.create_table_from([
|
||||||
|
("tab", self.tab.into_lua(lua)?),
|
||||||
|
("from", lua.create_any_userdata(self.from.into_owned())?.into_lua(lua)?),
|
||||||
|
("to", lua.create_any_userdata(self.to.into_owned())?.into_lua(lua)?),
|
||||||
|
])?
|
||||||
|
.into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
83
yazi-dds/src/body/tabs.rs
Normal file
83
yazi-dds/src/body/tabs.rs
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use mlua::{IntoLua, Lua, MetaMethod, UserData, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_shared::fs::Url;
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyTabs<'a> {
|
||||||
|
pub owned: bool,
|
||||||
|
pub cursor: usize,
|
||||||
|
pub items: Vec<BodyTabsItem<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BodyTabs<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn borrowed(cursor: usize, urls: &[&'a Url]) -> Body<'a> {
|
||||||
|
Self {
|
||||||
|
owned: false,
|
||||||
|
cursor,
|
||||||
|
items: urls.iter().map(|&u| BodyTabsItem { url: Cow::Borrowed(u) }).collect(),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyTabs<'static> {
|
||||||
|
#[inline]
|
||||||
|
pub fn owned(cursor: usize) -> Body<'static> {
|
||||||
|
Self { owned: false, cursor, items: Default::default() }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<BodyTabs<'a>> for Body<'a> {
|
||||||
|
fn from(value: BodyTabs<'a>) -> Self { Self::Tabs(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyTabs<'static> {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value<'_>> {
|
||||||
|
if self.owned {
|
||||||
|
BodyTabsIter::from(self).into_lua(lua)
|
||||||
|
} else {
|
||||||
|
lua.create_table_from([("cursor", self.cursor)])?.into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Item
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyTabsItem<'a> {
|
||||||
|
pub url: Cow<'a, Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for BodyTabsItem<'static> {
|
||||||
|
fn add_fields<'a, F: mlua::UserDataFields<'a, Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("url", |lua, me| lua.create_any_userdata(me.url.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Iterator
|
||||||
|
pub struct BodyTabsIter {
|
||||||
|
pub cursor: usize,
|
||||||
|
pub items: Vec<BodyTabsItem<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BodyTabs<'static>> for BodyTabsIter {
|
||||||
|
fn from(value: BodyTabs<'static>) -> Self { Self { cursor: value.cursor, items: value.items } }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for BodyTabsIter {
|
||||||
|
fn add_fields<'a, F: mlua::UserDataFields<'a, Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("cursor", |_, me| Ok(me.cursor));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_methods<'a, M: mlua::UserDataMethods<'a, Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.items.len()));
|
||||||
|
|
||||||
|
methods.add_meta_method(MetaMethod::Index, |_, me, idx: usize| {
|
||||||
|
if idx > me.items.len() || idx == 0 { Ok(None) } else { Ok(Some(me.items[idx - 1].clone())) }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
72
yazi-dds/src/body/yank.rs
Normal file
72
yazi-dds/src/body/yank.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use std::{borrow::Cow, collections::HashSet};
|
||||||
|
|
||||||
|
use mlua::{IntoLua, Lua, MetaMethod, UserData, Value};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_shared::fs::Url;
|
||||||
|
|
||||||
|
use super::Body;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct BodyYank<'a> {
|
||||||
|
pub owned: bool,
|
||||||
|
pub cut: bool,
|
||||||
|
pub urls: Cow<'a, HashSet<Url>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BodyYank<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub fn borrowed(cut: bool, urls: &'a HashSet<Url>) -> Body<'a> {
|
||||||
|
Self { owned: false, cut, urls: Cow::Borrowed(urls) }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BodyYank<'static> {
|
||||||
|
#[inline]
|
||||||
|
pub fn owned(cut: bool) -> Body<'static> {
|
||||||
|
Self { owned: false, cut, urls: Default::default() }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<BodyYank<'a>> for Body<'a> {
|
||||||
|
fn from(value: BodyYank<'a>) -> Self { Self::Yank(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for BodyYank<'static> {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value<'_>> {
|
||||||
|
if self.owned {
|
||||||
|
BodyYankIter::from(self).into_lua(lua)
|
||||||
|
} else {
|
||||||
|
lua.create_table_from([("cut", self.cut)])?.into_lua(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Iterator
|
||||||
|
pub struct BodyYankIter {
|
||||||
|
pub cut: bool,
|
||||||
|
pub urls: Vec<Url>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<BodyYank<'static>> for BodyYankIter {
|
||||||
|
fn from(value: BodyYank) -> Self {
|
||||||
|
Self { cut: value.cut, urls: value.urls.into_owned().into_iter().collect() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserData for BodyYankIter {
|
||||||
|
fn add_fields<'a, F: mlua::UserDataFields<'a, Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("is_cut", |_, me| Ok(me.cut));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_methods<'a, M: mlua::UserDataMethods<'a, Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.urls.len()));
|
||||||
|
|
||||||
|
methods.add_meta_method(MetaMethod::Index, |lua, me, idx: usize| {
|
||||||
|
if idx > me.urls.len() || idx == 0 {
|
||||||
|
Ok(None)
|
||||||
|
} else {
|
||||||
|
Some(lua.create_any_userdata(me.urls[idx - 1].clone())).transpose()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
151
yazi-dds/src/client.rs
Normal file
151
yazi-dds/src/client.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
use std::{collections::{HashMap, HashSet}, mem, str::FromStr};
|
||||||
|
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader, Lines, ReadHalf, WriteHalf}, select, sync::mpsc, task::JoinHandle, time};
|
||||||
|
use yazi_shared::RoCell;
|
||||||
|
|
||||||
|
use crate::{body::Body, Payload, Pubsub, Server};
|
||||||
|
|
||||||
|
pub(super) static ID: RoCell<u64> = RoCell::new();
|
||||||
|
pub(super) static PEERS: RoCell<RwLock<HashMap<u64, Peer>>> = RoCell::new();
|
||||||
|
pub(super) static QUEUE: RoCell<mpsc::UnboundedSender<String>> = RoCell::new();
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use tokio::net::UnixStream;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Client {
|
||||||
|
pub(super) id: u64,
|
||||||
|
pub(super) tx: mpsc::UnboundedSender<String>,
|
||||||
|
pub(super) abilities: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Peer {
|
||||||
|
pub(super) abilities: HashSet<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Client {
|
||||||
|
pub(super) fn serve(mut rx: mpsc::UnboundedReceiver<String>) {
|
||||||
|
while rx.try_recv().is_ok() {}
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut server = None;
|
||||||
|
let (mut lines, mut writer) = Self::connect(&mut server).await;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
select! {
|
||||||
|
Some(payload) = rx.recv() => {
|
||||||
|
if writer.write_all(payload.as_bytes()).await.is_err() {
|
||||||
|
(lines, writer) = Self::reconnect(&mut server).await;
|
||||||
|
writer.write_all(payload.as_bytes()).await.ok(); // Retry once
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(next) = lines.next_line() => {
|
||||||
|
let Some(line) = next else {
|
||||||
|
(lines, writer) = Self::reconnect(&mut server).await;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if line.starts_with("hey,") {
|
||||||
|
Self::handle_hey(line);
|
||||||
|
} else {
|
||||||
|
Payload::from_str(&line).map(|p| p.flush(false).emit()).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn push(payload: Payload) { QUEUE.send(format!("{}\n", payload)).ok(); }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn able(&self, ability: &str) -> bool { self.abilities.contains(ability) }
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
async fn connect(
|
||||||
|
server: &mut Option<JoinHandle<()>>,
|
||||||
|
) -> (Lines<BufReader<ReadHalf<UnixStream>>>, WriteHalf<UnixStream>) {
|
||||||
|
let mut first = true;
|
||||||
|
loop {
|
||||||
|
if let Ok(stream) = UnixStream::connect("/tmp/yazi.sock").await {
|
||||||
|
Pubsub::pub_from_hi();
|
||||||
|
let (reader, writer) = tokio::io::split(stream);
|
||||||
|
return (BufReader::new(reader).lines(), writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.take().map(|h| h.abort());
|
||||||
|
*server = Server::make().await.ok();
|
||||||
|
if server.is_some() {
|
||||||
|
super::STATE.load().await.ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
if mem::replace(&mut first, false) && server.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
time::sleep(time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
async fn connect(
|
||||||
|
server: &mut Option<JoinHandle<()>>,
|
||||||
|
) -> (Lines<BufReader<ReadHalf<TcpStream>>>, WriteHalf<TcpStream>) {
|
||||||
|
let mut first = true;
|
||||||
|
loop {
|
||||||
|
if let Ok(stream) = TcpStream::connect("127.0.0.1:33581").await {
|
||||||
|
Pubsub::pub_from_hi();
|
||||||
|
let (reader, writer) = tokio::io::split(stream);
|
||||||
|
return (BufReader::new(reader).lines(), writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.take().map(|h| h.abort());
|
||||||
|
*server = Server::make().await.ok();
|
||||||
|
if mem::replace(&mut first, false) && server.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
time::sleep(time::Duration::from_secs(1)).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
async fn reconnect(
|
||||||
|
server: &mut Option<JoinHandle<()>>,
|
||||||
|
) -> (Lines<BufReader<ReadHalf<UnixStream>>>, WriteHalf<UnixStream>) {
|
||||||
|
PEERS.write().clear();
|
||||||
|
|
||||||
|
time::sleep(time::Duration::from_millis(500)).await;
|
||||||
|
Self::connect(server).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
async fn reconnect(
|
||||||
|
server: &mut Option<JoinHandle<()>>,
|
||||||
|
) -> (Lines<BufReader<ReadHalf<TcpStream>>>, WriteHalf<TcpStream>) {
|
||||||
|
PEERS.write().clear();
|
||||||
|
|
||||||
|
time::sleep(time::Duration::from_millis(500)).await;
|
||||||
|
Self::connect(server).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_hey(s: String) {
|
||||||
|
if let Ok(Body::Hey(mut hey)) = Payload::from_str(&s).map(|p| p.body) {
|
||||||
|
hey.peers.retain(|&id, _| id != *ID);
|
||||||
|
*PEERS.write() = hey.peers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Peer {
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn new(abilities: &HashSet<String>) -> Self { Self { abilities: abilities.clone() } }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn able(&self, ability: &str) -> bool { self.abilities.contains(ability) }
|
||||||
|
}
|
||||||
34
yazi-dds/src/lib.rs
Normal file
34
yazi-dds/src/lib.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
#![allow(clippy::option_map_unit_fn)]
|
||||||
|
pub mod body;
|
||||||
|
mod client;
|
||||||
|
mod payload;
|
||||||
|
mod pubsub;
|
||||||
|
mod sendable;
|
||||||
|
mod server;
|
||||||
|
mod state;
|
||||||
|
|
||||||
|
pub use client::*;
|
||||||
|
pub use payload::*;
|
||||||
|
pub use pubsub::*;
|
||||||
|
pub use sendable::*;
|
||||||
|
use server::*;
|
||||||
|
pub use state::*;
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
|
||||||
|
|
||||||
|
// Client
|
||||||
|
ID.init(yazi_shared::timestamp_us());
|
||||||
|
PEERS.with(Default::default);
|
||||||
|
QUEUE.init(tx);
|
||||||
|
|
||||||
|
// Server
|
||||||
|
CLIENTS.with(Default::default);
|
||||||
|
STATE.with(Default::default);
|
||||||
|
|
||||||
|
// Pubsub
|
||||||
|
LOCAL.with(Default::default);
|
||||||
|
REMOTE.with(Default::default);
|
||||||
|
|
||||||
|
Client::serve(rx);
|
||||||
|
}
|
||||||
95
yazi-dds/src/payload.rs
Normal file
95
yazi-dds/src/payload.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
use std::{fmt::Display, io::Write, str::FromStr};
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use yazi_boot::BOOT;
|
||||||
|
use yazi_shared::{emit, event::Cmd, Layer};
|
||||||
|
|
||||||
|
use crate::body::Body;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Payload<'a> {
|
||||||
|
pub receiver: u64,
|
||||||
|
pub severity: u8,
|
||||||
|
pub body: Body<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Payload<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn new(body: Body<'a>) -> Self { Self { receiver: 0, severity: 0, body } }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn with_receiver(mut self, receiver: u64) -> Self {
|
||||||
|
self.receiver = receiver;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(super) fn with_severity(mut self, severity: u8) -> Self {
|
||||||
|
self.severity = severity;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn flush(self, force: bool) -> Self {
|
||||||
|
let b = force
|
||||||
|
|| if self.receiver == 0 {
|
||||||
|
BOOT.remote_events.contains(self.body.kind())
|
||||||
|
} else if let Body::Custom(b) = &self.body {
|
||||||
|
BOOT.local_events.contains(&b.kind)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if b {
|
||||||
|
writeln!(std::io::stdout(), "{self}").ok();
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Payload<'static> {
|
||||||
|
pub(super) fn emit(self) {
|
||||||
|
emit!(Call(Cmd::new("accept_payload").with_data(self), Layer::App));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Payload<'_> {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let mut parts = s.splitn(4, ',');
|
||||||
|
|
||||||
|
let kind = parts.next().ok_or_else(|| anyhow!("empty kind"))?;
|
||||||
|
|
||||||
|
let receiver =
|
||||||
|
parts.next().and_then(|s| s.parse().ok()).ok_or_else(|| anyhow!("invalid receiver"))?;
|
||||||
|
|
||||||
|
let severity =
|
||||||
|
parts.next().and_then(|s| s.parse().ok()).ok_or_else(|| anyhow!("invalid severity"))?;
|
||||||
|
|
||||||
|
let body = parts.next().ok_or_else(|| anyhow!("empty body"))?;
|
||||||
|
|
||||||
|
Ok(Self { receiver, severity, body: Body::from_str(kind, body)? })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Payload<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let result = match &self.body {
|
||||||
|
Body::Hi(b) => serde_json::to_string(b),
|
||||||
|
Body::Hey(b) => serde_json::to_string(b),
|
||||||
|
Body::Tabs(b) => serde_json::to_string(b),
|
||||||
|
Body::Cd(b) => serde_json::to_string(b),
|
||||||
|
Body::Hover(b) => serde_json::to_string(b),
|
||||||
|
Body::Rename(b) => serde_json::to_string(b),
|
||||||
|
Body::Bulk(b) => serde_json::to_string(b),
|
||||||
|
Body::Yank(b) => serde_json::to_string(b),
|
||||||
|
Body::Custom(b) => serde_json::to_string(b),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(s) = result {
|
||||||
|
write!(f, "{},{},{},{s}", self.body.kind(), self.receiver, self.severity)
|
||||||
|
} else {
|
||||||
|
Err(std::fmt::Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
156
yazi-dds/src/pubsub.rs
Normal file
156
yazi-dds/src/pubsub.rs
Normal file
|
|
@ -0,0 +1,156 @@
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
|
use mlua::Function;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use yazi_boot::BOOT;
|
||||||
|
use yazi_shared::{fs::Url, RoCell};
|
||||||
|
|
||||||
|
use crate::{body::{Body, BodyCd, BodyHi, BodyHover, BodyRename, BodyTabs, BodyYank}, Client, Payload, ID, PEERS};
|
||||||
|
|
||||||
|
pub static LOCAL: RoCell<RwLock<HashMap<String, HashMap<String, Function<'static>>>>> =
|
||||||
|
RoCell::new();
|
||||||
|
|
||||||
|
pub static REMOTE: RoCell<RwLock<HashMap<String, HashMap<String, Function<'static>>>>> =
|
||||||
|
RoCell::new();
|
||||||
|
|
||||||
|
macro_rules! sub {
|
||||||
|
($var:ident) => {
|
||||||
|
|plugin: &str, kind: &str, f: Function<'static>| {
|
||||||
|
let mut var = $var.write();
|
||||||
|
let Some(map) = var.get_mut(kind) else {
|
||||||
|
var.insert(kind.to_owned(), HashMap::from_iter([(plugin.to_owned(), f)]));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
if !map.contains_key(plugin) {
|
||||||
|
map.insert(plugin.to_owned(), f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! unsub {
|
||||||
|
($var:ident) => {
|
||||||
|
|plugin: &str, kind: &str| {
|
||||||
|
let mut var = $var.write();
|
||||||
|
let Some(map) = var.get_mut(kind) else { return false };
|
||||||
|
|
||||||
|
if map.remove(plugin).is_none() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if map.is_empty() {
|
||||||
|
var.remove(kind);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Pubsub;
|
||||||
|
|
||||||
|
impl Pubsub {
|
||||||
|
pub fn sub(plugin: &str, kind: &str, f: Function<'static>) -> bool {
|
||||||
|
sub!(LOCAL)(plugin, kind, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub_remote(plugin: &str, kind: &str, f: Function<'static>) -> bool {
|
||||||
|
sub!(REMOTE)(plugin, kind, f) && Self::pub_from_hi()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unsub(plugin: &str, kind: &str) -> bool { unsub!(LOCAL)(plugin, kind) }
|
||||||
|
|
||||||
|
pub fn unsub_remote(plugin: &str, kind: &str) -> bool {
|
||||||
|
unsub!(REMOTE)(plugin, kind) && Self::pub_from_hi()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_(body: Body<'static>) { body.upgrade().with_receiver(*ID).flush(false).emit(); }
|
||||||
|
|
||||||
|
pub fn pub_to(receiver: u64, body: Body<'static>) {
|
||||||
|
if receiver == *ID {
|
||||||
|
return Self::pub_(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
let (kind, peers) = (body.kind(), PEERS.read());
|
||||||
|
if receiver == 0 && peers.values().any(|c| c.able(kind)) {
|
||||||
|
Client::push(body.upgrade());
|
||||||
|
} else if peers.get(&receiver).is_some_and(|c| c.able(kind)) {
|
||||||
|
Client::push(body.upgrade().with_receiver(receiver));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_static(severity: u8, body: Body) {
|
||||||
|
let (kind, peers) = (body.kind(), PEERS.read());
|
||||||
|
if peers.values().any(|c| c.able(kind)) {
|
||||||
|
Client::push(body.upgrade().with_severity(severity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_from_hi() -> bool {
|
||||||
|
Client::push(Payload::new(
|
||||||
|
BodyHi { id: *ID, abilities: REMOTE.read().keys().cloned().collect() }.into(),
|
||||||
|
));
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_from_tabs(tab: usize, urls: &[&Url]) {
|
||||||
|
if LOCAL.read().contains_key("tabs") {
|
||||||
|
Self::pub_(BodyTabs::owned(tab));
|
||||||
|
}
|
||||||
|
if PEERS.read().values().any(|p| p.able("tabs")) {
|
||||||
|
Client::push(BodyTabs::borrowed(tab, urls).upgrade());
|
||||||
|
}
|
||||||
|
if BOOT.local_events.contains("tabs") {
|
||||||
|
BodyTabs::borrowed(tab, urls).upgrade().flush(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_from_cd(tab: usize, url: &Url) {
|
||||||
|
if LOCAL.read().contains_key("cd") {
|
||||||
|
Self::pub_(BodyCd::owned(tab));
|
||||||
|
}
|
||||||
|
if PEERS.read().values().any(|p| p.able("cd")) {
|
||||||
|
Client::push(BodyCd::borrowed(tab, url).upgrade());
|
||||||
|
}
|
||||||
|
if BOOT.local_events.contains("cd") {
|
||||||
|
BodyCd::borrowed(tab, url).upgrade().flush(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_from_hover(tab: usize, url: Option<&Url>) {
|
||||||
|
if LOCAL.read().contains_key("hover") {
|
||||||
|
Self::pub_(BodyHover::owned(tab));
|
||||||
|
}
|
||||||
|
if PEERS.read().values().any(|p| p.able("hover")) {
|
||||||
|
Client::push(BodyHover::borrowed(tab, url).upgrade());
|
||||||
|
}
|
||||||
|
if BOOT.local_events.contains("hover") {
|
||||||
|
BodyHover::borrowed(tab, url).upgrade().flush(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_from_rename(tab: usize, from: &Url, to: &Url) {
|
||||||
|
if LOCAL.read().contains_key("rename") {
|
||||||
|
Self::pub_(BodyRename::owned(tab, from, to));
|
||||||
|
}
|
||||||
|
if PEERS.read().values().any(|p| p.able("rename")) {
|
||||||
|
Client::push(BodyRename::borrowed(tab, from, to).upgrade());
|
||||||
|
}
|
||||||
|
if BOOT.local_events.contains("rename") {
|
||||||
|
BodyRename::borrowed(tab, from, to).upgrade().flush(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pub_from_yank(cut: bool, urls: &HashSet<Url>) {
|
||||||
|
if LOCAL.read().contains_key("yank") {
|
||||||
|
Self::pub_(BodyYank::owned(cut));
|
||||||
|
}
|
||||||
|
if PEERS.read().values().any(|p| p.able("yank")) {
|
||||||
|
Client::push(BodyYank::borrowed(cut, urls).upgrade());
|
||||||
|
}
|
||||||
|
if BOOT.local_events.contains("yank") {
|
||||||
|
BodyYank::borrowed(cut, urls).upgrade().flush(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
132
yazi-dds/src/sendable.rs
Normal file
132
yazi-dds/src/sendable.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use mlua::{ExternalError, IntoLua, Lua, Value, Variadic};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use yazi_shared::OrderedFloat;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ValueSendable {
|
||||||
|
Nil,
|
||||||
|
Boolean(bool),
|
||||||
|
Integer(i64),
|
||||||
|
Number(f64),
|
||||||
|
String(Vec<u8>),
|
||||||
|
Table(HashMap<ValueSendableKey, ValueSendable>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSendable {
|
||||||
|
pub fn try_from_variadic(values: Variadic<Value>) -> mlua::Result<Vec<Self>> {
|
||||||
|
let mut vec = Vec::with_capacity(values.len());
|
||||||
|
for value in values {
|
||||||
|
vec.push(Self::try_from(value)?);
|
||||||
|
}
|
||||||
|
Ok(vec)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_table_string(self) -> HashMap<String, String> {
|
||||||
|
let Self::Table(table) = self else {
|
||||||
|
return Default::default();
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut map = HashMap::with_capacity(table.len());
|
||||||
|
for pair in table {
|
||||||
|
let (ValueSendableKey::String(k), Self::String(v)) = pair else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if let (Ok(k), Ok(v)) = (String::from_utf8(k), String::from_utf8(v)) {
|
||||||
|
map.insert(k, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<Value<'a>> for ValueSendable {
|
||||||
|
type Error = mlua::Error;
|
||||||
|
|
||||||
|
fn try_from(value: Value) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
Value::Nil => Self::Nil,
|
||||||
|
Value::Boolean(b) => Self::Boolean(b),
|
||||||
|
Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?,
|
||||||
|
Value::Integer(n) => Self::Integer(n),
|
||||||
|
Value::Number(n) => Self::Number(n),
|
||||||
|
Value::String(s) => Self::String(s.as_bytes().to_vec()),
|
||||||
|
Value::Table(t) => {
|
||||||
|
let mut map = HashMap::with_capacity(t.len().map(|l| l as usize)?);
|
||||||
|
for result in t.pairs::<Value, Value>() {
|
||||||
|
let (k, v) = result?;
|
||||||
|
map.insert(Self::try_from(k)?.try_into()?, v.try_into()?);
|
||||||
|
}
|
||||||
|
Self::Table(map)
|
||||||
|
}
|
||||||
|
Value::Function(_) => Err("function is not supported".into_lua_err())?,
|
||||||
|
Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
|
||||||
|
Value::UserData(_) => Err("userdata is not supported".into_lua_err())?,
|
||||||
|
Value::Error(_) => Err("error is not supported".into_lua_err())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for ValueSendable {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
|
match self {
|
||||||
|
Self::Nil => Ok(Value::Nil),
|
||||||
|
Self::Boolean(v) => Ok(Value::Boolean(v)),
|
||||||
|
Self::Integer(v) => Ok(Value::Integer(v)),
|
||||||
|
Self::Number(v) => Ok(Value::Number(v)),
|
||||||
|
Self::String(v) => Ok(Value::String(lua.create_string(v)?)),
|
||||||
|
Self::Table(v) => {
|
||||||
|
let seq_len = v.keys().filter(|&k| !k.is_numeric()).count();
|
||||||
|
let table = lua.create_table_with_capacity(seq_len, v.len() - seq_len)?;
|
||||||
|
for (k, v) in v {
|
||||||
|
table.raw_set(k, v)?;
|
||||||
|
}
|
||||||
|
Ok(Value::Table(table))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ValueSendableKey {
|
||||||
|
Nil,
|
||||||
|
Boolean(bool),
|
||||||
|
Integer(i64),
|
||||||
|
Number(OrderedFloat),
|
||||||
|
String(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ValueSendableKey {
|
||||||
|
#[inline]
|
||||||
|
fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryInto<ValueSendableKey> for ValueSendable {
|
||||||
|
type Error = mlua::Error;
|
||||||
|
|
||||||
|
fn try_into(self) -> Result<ValueSendableKey, Self::Error> {
|
||||||
|
Ok(match self {
|
||||||
|
Self::Nil => ValueSendableKey::Nil,
|
||||||
|
Self::Boolean(v) => ValueSendableKey::Boolean(v),
|
||||||
|
Self::Integer(v) => ValueSendableKey::Integer(v),
|
||||||
|
Self::Number(v) => ValueSendableKey::Number(OrderedFloat::new(v)),
|
||||||
|
Self::String(v) => ValueSendableKey::String(v),
|
||||||
|
Self::Table(_) => Err("table is not supported".into_lua_err())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoLua<'_> for ValueSendableKey {
|
||||||
|
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
||||||
|
match self {
|
||||||
|
Self::Nil => Ok(Value::Nil),
|
||||||
|
Self::Boolean(k) => Ok(Value::Boolean(k)),
|
||||||
|
Self::Integer(k) => Ok(Value::Integer(k)),
|
||||||
|
Self::Number(k) => Ok(Value::Number(k.get())),
|
||||||
|
Self::String(k) => Ok(Value::String(lua.create_string(k)?)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
125
yazi-dds/src/server.rs
Normal file
125
yazi-dds/src/server.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
use std::{collections::HashMap, str::FromStr, time::Duration};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc, task::JoinHandle, time};
|
||||||
|
use yazi_shared::RoCell;
|
||||||
|
|
||||||
|
use crate::{body::{Body, BodyHey}, Client, Payload, Peer, STATE};
|
||||||
|
|
||||||
|
pub(super) static CLIENTS: RoCell<RwLock<HashMap<u64, Client>>> = RoCell::new();
|
||||||
|
|
||||||
|
pub(super) struct Server;
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub(super) async fn make() -> Result<JoinHandle<()>> {
|
||||||
|
CLIENTS.write().clear();
|
||||||
|
let listener = Self::bind().await?;
|
||||||
|
|
||||||
|
Ok(tokio::spawn(async move {
|
||||||
|
while let Ok((stream, _)) = listener.accept().await {
|
||||||
|
let (tx, mut rx) = mpsc::unbounded_channel::<String>();
|
||||||
|
let (reader, mut writer) = tokio::io::split(stream);
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let mut id = None;
|
||||||
|
let mut lines = BufReader::new(reader).lines();
|
||||||
|
loop {
|
||||||
|
select! {
|
||||||
|
Some(payload) = rx.recv() => {
|
||||||
|
if writer.write_all(payload.as_bytes()).await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = time::sleep(Duration::from_secs(5)) => {
|
||||||
|
if writer.write_u8(b'\n').await.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Some(mut line)) = lines.next_line() => {
|
||||||
|
if line.starts_with("hi,") {
|
||||||
|
Self::handle_hi(line, &mut id, tx.clone());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut parts = line.splitn(4, ',');
|
||||||
|
let Some(id) = id else { continue };
|
||||||
|
let Some(kind) = parts.next() else { continue };
|
||||||
|
let Some(receiver) = parts.next().and_then(|s| s.parse().ok()) else { continue };
|
||||||
|
let Some(severity) = parts.next().and_then(|s| s.parse::<u8>().ok()) else { continue };
|
||||||
|
|
||||||
|
let clients = CLIENTS.read();
|
||||||
|
let clients: Vec<_> = if receiver == 0 {
|
||||||
|
clients.values().filter(|c| c.id != id && c.able(kind)).collect()
|
||||||
|
} else if let Some(c) = clients.get(&receiver).filter(|c| c.id != id && c.able(kind)) {
|
||||||
|
vec![c]
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
|
|
||||||
|
if clients.is_empty() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if receiver == 0 && severity > 0 {
|
||||||
|
let Some(body) = parts.next() else { continue };
|
||||||
|
STATE.add(format!("{}_{severity}_{kind}", Body::tab(kind, body)), &line);
|
||||||
|
}
|
||||||
|
|
||||||
|
line.push('\n');
|
||||||
|
clients.into_iter().for_each(|c| _ = c.tx.send(line.clone()));
|
||||||
|
}
|
||||||
|
else => break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::handle_bye(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
#[inline]
|
||||||
|
async fn bind() -> Result<tokio::net::UnixListener> {
|
||||||
|
tokio::fs::remove_file("/tmp/yazi.sock").await.ok();
|
||||||
|
Ok(tokio::net::UnixListener::bind("/tmp/yazi.sock")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
#[inline]
|
||||||
|
async fn bind() -> Result<tokio::net::TcpListener> {
|
||||||
|
Ok(tokio::net::TcpListener::bind("127.0.0.1:33581").await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_hi(s: String, id: &mut Option<u64>, tx: mpsc::UnboundedSender<String>) {
|
||||||
|
let Ok(Body::Hi(hi)) = Payload::from_str(&s).map(|p| p.body) else { return };
|
||||||
|
|
||||||
|
let mut clients = CLIENTS.write();
|
||||||
|
id.replace(hi.id).and_then(|id| clients.remove(&id));
|
||||||
|
|
||||||
|
if let Some(ref state) = *STATE.read() {
|
||||||
|
state.values().for_each(|s| _ = tx.send(format!("{s}\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
clients.insert(hi.id, Client { id: hi.id, tx, abilities: hi.abilities });
|
||||||
|
Self::handle_hey(&clients);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_hey(clients: &HashMap<u64, Client>) {
|
||||||
|
let payload = format!(
|
||||||
|
"{}\n",
|
||||||
|
Payload::new(
|
||||||
|
BodyHey { peers: clients.values().map(|c| (c.id, Peer::new(&c.abilities))).collect() }
|
||||||
|
.into()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
clients.values().for_each(|c| _ = c.tx.send(payload.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_bye(id: Option<u64>) {
|
||||||
|
let mut clients = CLIENTS.write();
|
||||||
|
if id.and_then(|id| clients.remove(&id)).is_some() {
|
||||||
|
Self::handle_hey(&clients);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
yazi-dds/src/state.rs
Normal file
87
yazi-dds/src/state.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
use std::{collections::HashMap, mem, ops::Deref, sync::atomic::{AtomicU64, Ordering}, time::UNIX_EPOCH};
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use parking_lot::RwLock;
|
||||||
|
use tokio::{fs::{self, File, OpenOptions}, io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter}};
|
||||||
|
use yazi_boot::BOOT;
|
||||||
|
use yazi_shared::{timestamp_us, RoCell};
|
||||||
|
|
||||||
|
use crate::{body::Body, CLIENTS};
|
||||||
|
|
||||||
|
pub static STATE: RoCell<State> = RoCell::new();
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct State {
|
||||||
|
inner: RwLock<Option<HashMap<String, String>>>,
|
||||||
|
last: AtomicU64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for State {
|
||||||
|
type Target = RwLock<Option<HashMap<String, String>>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target { &self.inner }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn add(&self, key: String, value: &str) {
|
||||||
|
if let Some(ref mut inner) = *self.inner.write() {
|
||||||
|
inner.insert(key, value.to_owned());
|
||||||
|
self.last.store(timestamp_us(), Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn load(&self) -> Result<()> {
|
||||||
|
let mut buf = BufReader::new(File::open(BOOT.state_dir.join(".dds")).await?);
|
||||||
|
let mut line = String::new();
|
||||||
|
|
||||||
|
let mut inner = HashMap::new();
|
||||||
|
while buf.read_line(&mut line).await? > 0 {
|
||||||
|
let mut parts = line.splitn(4, ',');
|
||||||
|
let Some(kind) = parts.next() else { continue };
|
||||||
|
let Some(_) = parts.next() else { continue };
|
||||||
|
let Some(severity) = parts.next().and_then(|s| s.parse::<u8>().ok()) else { continue };
|
||||||
|
let Some(body) = parts.next() else { continue };
|
||||||
|
inner.insert(format!("{}_{severity}_{kind}", Body::tab(kind, body)), mem::take(&mut line));
|
||||||
|
}
|
||||||
|
|
||||||
|
let clients = CLIENTS.read();
|
||||||
|
for payload in inner.values() {
|
||||||
|
clients.values().for_each(|c| _ = c.tx.send(format!("{payload}\n")));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.inner.write().replace(inner);
|
||||||
|
self.last.store(timestamp_us(), Ordering::Relaxed);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn drain(&self) -> Result<()> {
|
||||||
|
let Some(inner) = self.inner.write().take() else { return Ok(()) };
|
||||||
|
if self.skip().await.unwrap_or(false) {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = BufWriter::new(
|
||||||
|
OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(BOOT.state_dir.join(".dds"))
|
||||||
|
.await?,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut state = inner.into_iter().collect::<Vec<_>>();
|
||||||
|
state.sort_unstable_by(|(a, _), (b, _)| a.cmp(b));
|
||||||
|
for (_, v) in state {
|
||||||
|
buf.write_all(v.as_bytes()).await?;
|
||||||
|
buf.write_u8(b'\n').await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn skip(&self) -> Result<bool> {
|
||||||
|
let meta = fs::symlink_metadata(BOOT.state_dir.join(".dds")).await?;
|
||||||
|
let modified = meta.modified()?.duration_since(UNIX_EPOCH)?.as_micros();
|
||||||
|
Ok(modified >= self.last.load(Ordering::Relaxed) as u128)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,6 +13,7 @@ yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||||
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
||||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||||
yazi-core = { path = "../yazi-core", version = "0.2.4" }
|
yazi-core = { path = "../yazi-core", version = "0.2.4" }
|
||||||
|
yazi-dds = { path = "../yazi-dds", version = "0.2.4" }
|
||||||
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
||||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
||||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" }
|
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" }
|
||||||
|
|
@ -27,7 +28,7 @@ futures = "0.3.30"
|
||||||
mlua = { version = "0.9.6", features = [ "lua54", "vendored" ] }
|
mlua = { version = "0.9.6", features = [ "lua54", "vendored" ] }
|
||||||
ratatui = "0.26.1"
|
ratatui = "0.26.1"
|
||||||
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||||
tokio = { version = "1.36.0", features = [ "parking_lot" ] }
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
unicode-width = "0.1.11"
|
unicode-width = "0.1.11"
|
||||||
|
|
||||||
|
|
|
||||||
31
yazi-fm/src/app/commands/accept_payload.rs
Normal file
31
yazi-fm/src/app/commands/accept_payload.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use mlua::IntoLua;
|
||||||
|
use tracing::error;
|
||||||
|
use yazi_dds::{Payload, LOCAL, REMOTE};
|
||||||
|
use yazi_plugin::LUA;
|
||||||
|
use yazi_shared::event::Cmd;
|
||||||
|
|
||||||
|
use crate::{app::App, lives::Lives};
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
pub(crate) fn accept_payload(&mut self, mut cmd: Cmd) {
|
||||||
|
let Some(payload) = cmd.take_data::<Payload>() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let kind = payload.body.kind().to_owned();
|
||||||
|
let map = if payload.receiver == 0 { REMOTE.read() } else { LOCAL.read() };
|
||||||
|
let Some(map) = map.get(&kind).filter(|&m| !m.is_empty()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
_ = Lives::scope(&self.cx, |_| {
|
||||||
|
let body = payload.body.into_lua(&LUA)?;
|
||||||
|
for f in map.values() {
|
||||||
|
if let Err(e) = f.call::<_, ()>(body.clone()) {
|
||||||
|
error!("Failed to call `{kind}` handler: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
mod accept_payload;
|
||||||
mod notify;
|
mod notify;
|
||||||
mod plugin;
|
mod plugin;
|
||||||
mod quit;
|
mod quit;
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ use std::fmt::Display;
|
||||||
|
|
||||||
use mlua::TableExt;
|
use mlua::TableExt;
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use yazi_plugin::{OptData, LOADED, LUA};
|
use yazi_plugin::{OptData, LOADER, LUA};
|
||||||
use yazi_shared::{emit, event::Cmd, Layer};
|
use yazi_shared::{emit, event::Cmd, Defer, Layer};
|
||||||
|
|
||||||
use crate::{app::App, lives::Lives};
|
use crate::{app::App, lives::Lives};
|
||||||
|
|
||||||
|
|
@ -18,12 +18,12 @@ impl App {
|
||||||
return self.cx.tasks.plugin_micro(opt.name, opt.data.args);
|
return self.cx.tasks.plugin_micro(opt.name, opt.data.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if LOADED.read().contains_key(&opt.name) {
|
if LOADER.read().contains_key(&opt.name) {
|
||||||
return self.plugin_do(opt);
|
return self.plugin_do(opt);
|
||||||
}
|
}
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if LOADED.ensure(&opt.name).await.is_ok() {
|
if LOADER.ensure(&opt.name).await.is_ok() {
|
||||||
Self::_plugin_do(opt.name, opt.data);
|
Self::_plugin_do(opt.name, opt.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -40,7 +40,10 @@ impl App {
|
||||||
Err(e) => return warn!("{e}"),
|
Err(e) => return warn!("{e}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let plugin = match LOADED.load(&opt.name) {
|
LOADER.set_running(Some(&opt.name));
|
||||||
|
let _defer = Defer::new(|| LOADER.set_running(None));
|
||||||
|
|
||||||
|
let plugin = match LOADER.load(&opt.name) {
|
||||||
Ok(plugin) => plugin,
|
Ok(plugin) => plugin,
|
||||||
Err(e) => return warn!("{e}"),
|
Err(e) => return warn!("{e}"),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ impl App {
|
||||||
pub(crate) fn quit(&mut self, opt: EventQuit) -> ! {
|
pub(crate) fn quit(&mut self, opt: EventQuit) -> ! {
|
||||||
self.cx.tasks.shutdown();
|
self.cx.tasks.shutdown();
|
||||||
self.cx.manager.shutdown();
|
self.cx.manager.shutdown();
|
||||||
|
futures::executor::block_on(yazi_dds::STATE.drain()).ok();
|
||||||
|
|
||||||
if !opt.no_cwd_file {
|
if !opt.no_cwd_file {
|
||||||
self.cwd_to_file();
|
self.cwd_to_file();
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ impl<'a> Executor<'a> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
on!(accept_payload);
|
||||||
on!(notify);
|
on!(notify);
|
||||||
on!(plugin);
|
on!(plugin);
|
||||||
on!(plugin_do);
|
on!(plugin_do);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ impl Tabs {
|
||||||
|
|
||||||
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||||
lua.register_userdata_type::<Self>(|reg| {
|
lua.register_userdata_type::<Self>(|reg| {
|
||||||
reg.add_field_method_get("idx", |_, me| Ok(me.idx + 1));
|
reg.add_field_method_get("idx", |_, me| Ok(me.cursor + 1));
|
||||||
|
|
||||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,12 @@ async fn main() -> anyhow::Result<()> {
|
||||||
|
|
||||||
yazi_boot::init();
|
yazi_boot::init();
|
||||||
|
|
||||||
yazi_plugin::init();
|
|
||||||
|
|
||||||
yazi_proxy::init();
|
yazi_proxy::init();
|
||||||
|
|
||||||
|
yazi_dds::init();
|
||||||
|
|
||||||
|
yazi_plugin::init();
|
||||||
|
|
||||||
yazi_core::init();
|
yazi_core::init();
|
||||||
|
|
||||||
app::App::serve().await
|
app::App::serve().await
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@ repository = "https://github.com/sxyazi/yazi"
|
||||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||||
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
||||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||||
|
yazi-dds = { path = "../yazi-dds", version = "0.2.4" }
|
||||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||||
|
|
||||||
# External dependencies
|
# External dependencies
|
||||||
ansi-to-tui = "3.1.0"
|
ansi-to-tui = "3.1.0"
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.81"
|
||||||
|
arc-swap = "1.7.0"
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
md-5 = "0.10.6"
|
md-5 = "0.10.6"
|
||||||
|
|
@ -29,7 +31,7 @@ serde_json = "1.0.114"
|
||||||
shell-escape = "0.1.5"
|
shell-escape = "0.1.5"
|
||||||
shell-words = "1.1.0"
|
shell-words = "1.1.0"
|
||||||
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
|
||||||
tokio = { version = "1.36.0", features = [ "parking_lot", "rt-multi-thread" ] }
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
tokio-util = "0.7.10"
|
tokio-util = "0.7.10"
|
||||||
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
|
||||||
unicode-width = "0.1.11"
|
unicode-width = "0.1.11"
|
||||||
|
|
|
||||||
|
|
@ -32,8 +32,8 @@ impl<'a> TryFrom<mlua::Table<'a>> for Position {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for Position {
|
impl IntoLua<'_> for Position {
|
||||||
fn into_lua(self, lua: &'lua mlua::Lua) -> mlua::Result<mlua::Value> {
|
fn into_lua(self, lua: &mlua::Lua) -> mlua::Result<mlua::Value> {
|
||||||
lua
|
lua
|
||||||
.create_table_from([
|
.create_table_from([
|
||||||
(1.into_lua(lua)?, self.origin.to_string().into_lua(lua)?),
|
(1.into_lua(lua)?, self.origin.to_string().into_lua(lua)?),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
use std::collections::HashMap;
|
use mlua::AnyUserData;
|
||||||
|
|
||||||
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, Value, Variadic};
|
|
||||||
use yazi_shared::OrderedFloat;
|
|
||||||
|
|
||||||
use crate::elements::Renderable;
|
use crate::elements::Renderable;
|
||||||
|
|
||||||
|
|
@ -22,128 +19,3 @@ pub fn cast_to_renderable(ud: AnyUserData) -> Option<Box<dyn Renderable + Send>>
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum ValueSendable {
|
|
||||||
Nil,
|
|
||||||
Boolean(bool),
|
|
||||||
Integer(i64),
|
|
||||||
Number(f64),
|
|
||||||
String(Vec<u8>),
|
|
||||||
Table(HashMap<ValueSendableKey, ValueSendable>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> TryFrom<Value<'a>> for ValueSendable {
|
|
||||||
type Error = mlua::Error;
|
|
||||||
|
|
||||||
fn try_from(value: Value) -> Result<Self, Self::Error> {
|
|
||||||
Ok(match value {
|
|
||||||
Value::Nil => ValueSendable::Nil,
|
|
||||||
Value::Boolean(b) => ValueSendable::Boolean(b),
|
|
||||||
Value::LightUserData(_) => Err("light userdata is not supported".into_lua_err())?,
|
|
||||||
Value::Integer(n) => ValueSendable::Integer(n),
|
|
||||||
Value::Number(n) => ValueSendable::Number(n),
|
|
||||||
Value::String(s) => ValueSendable::String(s.as_bytes().to_vec()),
|
|
||||||
Value::Table(t) => {
|
|
||||||
let mut map = HashMap::with_capacity(t.len().map(|l| l as usize)?);
|
|
||||||
for result in t.pairs::<Value, Value>() {
|
|
||||||
let (k, v) = result?;
|
|
||||||
map.insert(ValueSendable::try_from(k)?.try_into()?, v.try_into()?);
|
|
||||||
}
|
|
||||||
ValueSendable::Table(map)
|
|
||||||
}
|
|
||||||
Value::Function(_) => Err("function is not supported".into_lua_err())?,
|
|
||||||
Value::Thread(_) => Err("thread is not supported".into_lua_err())?,
|
|
||||||
Value::UserData(_) => Err("userdata is not supported".into_lua_err())?,
|
|
||||||
Value::Error(_) => Err("error is not supported".into_lua_err())?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for ValueSendable {
|
|
||||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
|
||||||
match self {
|
|
||||||
ValueSendable::Nil => Ok(Value::Nil),
|
|
||||||
ValueSendable::Boolean(b) => Ok(Value::Boolean(b)),
|
|
||||||
ValueSendable::Integer(n) => Ok(Value::Integer(n)),
|
|
||||||
ValueSendable::Number(n) => Ok(Value::Number(n)),
|
|
||||||
ValueSendable::String(s) => Ok(Value::String(lua.create_string(s)?)),
|
|
||||||
ValueSendable::Table(t) => {
|
|
||||||
let seq_len = t.keys().filter(|&k| !k.is_numeric()).count();
|
|
||||||
let table = lua.create_table_with_capacity(seq_len, t.len() - seq_len)?;
|
|
||||||
for (k, v) in t {
|
|
||||||
table.raw_set(k, v)?;
|
|
||||||
}
|
|
||||||
Ok(Value::Table(table))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueSendable {
|
|
||||||
pub fn try_from_variadic(values: Variadic<Value>) -> mlua::Result<Vec<ValueSendable>> {
|
|
||||||
let mut vec = Vec::with_capacity(values.len());
|
|
||||||
for value in values {
|
|
||||||
vec.push(ValueSendable::try_from(value)?);
|
|
||||||
}
|
|
||||||
Ok(vec)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_table_string(self) -> HashMap<String, String> {
|
|
||||||
let ValueSendable::Table(table) = self else {
|
|
||||||
return Default::default();
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut map = HashMap::with_capacity(table.len());
|
|
||||||
for pair in table {
|
|
||||||
let (ValueSendableKey::String(k), ValueSendable::String(v)) = pair else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if let (Ok(k), Ok(v)) = (String::from_utf8(k), String::from_utf8(v)) {
|
|
||||||
map.insert(k, v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq)]
|
|
||||||
pub enum ValueSendableKey {
|
|
||||||
Nil,
|
|
||||||
Boolean(bool),
|
|
||||||
Integer(i64),
|
|
||||||
Number(OrderedFloat),
|
|
||||||
String(Vec<u8>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ValueSendableKey {
|
|
||||||
#[inline]
|
|
||||||
fn is_numeric(&self) -> bool { matches!(self, Self::Integer(_) | Self::Number(_)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryInto<ValueSendableKey> for ValueSendable {
|
|
||||||
type Error = mlua::Error;
|
|
||||||
|
|
||||||
fn try_into(self) -> Result<ValueSendableKey, Self::Error> {
|
|
||||||
Ok(match self {
|
|
||||||
ValueSendable::Nil => ValueSendableKey::Nil,
|
|
||||||
ValueSendable::Boolean(b) => ValueSendableKey::Boolean(b),
|
|
||||||
ValueSendable::Integer(n) => ValueSendableKey::Integer(n),
|
|
||||||
ValueSendable::Number(n) => ValueSendableKey::Number(OrderedFloat::new(n)),
|
|
||||||
ValueSendable::String(s) => ValueSendableKey::String(s),
|
|
||||||
ValueSendable::Table(_) => Err("table is not supported".into_lua_err())?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for ValueSendableKey {
|
|
||||||
fn into_lua(self, lua: &Lua) -> mlua::Result<Value> {
|
|
||||||
match self {
|
|
||||||
ValueSendableKey::Nil => Ok(Value::Nil),
|
|
||||||
ValueSendableKey::Boolean(b) => Ok(Value::Boolean(b)),
|
|
||||||
ValueSendableKey::Integer(n) => Ok(Value::Integer(n)),
|
|
||||||
ValueSendableKey::Number(n) => Ok(Value::Number(n.get())),
|
|
||||||
ValueSendableKey::String(s) => Ok(Value::String(lua.create_string(s)?)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use mlua::{AnyUserData, Lua, Table};
|
use mlua::{AnyUserData, Lua};
|
||||||
|
|
||||||
use crate::cast_to_renderable;
|
use crate::cast_to_renderable;
|
||||||
|
|
||||||
pub fn pour(lua: &Lua) -> mlua::Result<()> {
|
pub fn pour(lua: &Lua) -> mlua::Result<()> {
|
||||||
let ui: Table = lua.create_table()?;
|
let ui = lua.create_table()?;
|
||||||
|
|
||||||
// Register
|
// Register
|
||||||
super::Padding::register(lua)?;
|
super::Padding::register(lua)?;
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
use mlua::{ExternalError, ExternalResult, Table, TableExt};
|
use mlua::{ExternalError, ExternalResult, Table, TableExt};
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
|
use yazi_dds::ValueSendable;
|
||||||
|
|
||||||
use super::slim_lua;
|
use super::slim_lua;
|
||||||
use crate::{ValueSendable, LOADED};
|
use crate::LOADER;
|
||||||
|
|
||||||
pub async fn entry(name: String, args: Vec<ValueSendable>) -> mlua::Result<()> {
|
pub async fn entry(name: String, args: Vec<ValueSendable>) -> mlua::Result<()> {
|
||||||
LOADED.ensure(&name).await.into_lua_err()?;
|
LOADER.ensure(&name).await.into_lua_err()?;
|
||||||
|
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let lua = slim_lua(&name)?;
|
let lua = slim_lua(&name)?;
|
||||||
let plugin: Table = if let Some(b) = LOADED.read().get(&name) {
|
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
|
||||||
lua.load(b).call(())?
|
lua.load(b).call(())?
|
||||||
} else {
|
} else {
|
||||||
return Err("unloaded plugin".into_lua_err());
|
return Err("unloaded plugin".into_lua_err());
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use yazi_config::LAYOUT;
|
||||||
use yazi_shared::{emit, event::Cmd, Layer};
|
use yazi_shared::{emit, event::Cmd, Layer};
|
||||||
|
|
||||||
use super::slim_lua;
|
use super::slim_lua;
|
||||||
use crate::{bindings::{Cast, File, Window}, elements::Rect, OptData, LOADED, LUA};
|
use crate::{bindings::{Cast, File, Window}, elements::Rect, OptData, LOADER, LUA};
|
||||||
|
|
||||||
pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> CancellationToken {
|
pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> CancellationToken {
|
||||||
let ct = CancellationToken::new();
|
let ct = CancellationToken::new();
|
||||||
|
|
@ -15,7 +15,7 @@ pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> Cancellation
|
||||||
let (ct1, ct2) = (ct.clone(), ct.clone());
|
let (ct1, ct2) = (ct.clone(), ct.clone());
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let future = async {
|
let future = async {
|
||||||
LOADED.ensure(&name).await.into_lua_err()?;
|
LOADER.ensure(&name).await.into_lua_err()?;
|
||||||
|
|
||||||
let lua = slim_lua(&name)?;
|
let lua = slim_lua(&name)?;
|
||||||
lua.set_hook(
|
lua.set_hook(
|
||||||
|
|
@ -25,7 +25,7 @@ pub fn peek(cmd: &Cmd, file: yazi_shared::fs::File, skip: usize) -> Cancellation
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let plugin: Table = if let Some(b) = LOADED.read().get(&name) {
|
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
|
||||||
lua.load(b).call(())?
|
lua.load(b).call(())?
|
||||||
} else {
|
} else {
|
||||||
return Err("unloaded plugin".into_lua_err());
|
return Err("unloaded plugin".into_lua_err());
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,19 @@ use tokio::runtime::Handle;
|
||||||
use yazi_config::LAYOUT;
|
use yazi_config::LAYOUT;
|
||||||
|
|
||||||
use super::slim_lua;
|
use super::slim_lua;
|
||||||
use crate::{bindings::{Cast, File}, elements::Rect, LOADED};
|
use crate::{bindings::{Cast, File}, elements::Rect, LOADER};
|
||||||
|
|
||||||
pub async fn preload(
|
pub async fn preload(
|
||||||
name: &str,
|
name: &str,
|
||||||
files: Vec<yazi_shared::fs::File>,
|
files: Vec<yazi_shared::fs::File>,
|
||||||
multi: bool,
|
multi: bool,
|
||||||
) -> mlua::Result<u8> {
|
) -> mlua::Result<u8> {
|
||||||
LOADED.ensure(name).await.into_lua_err()?;
|
LOADER.ensure(name).await.into_lua_err()?;
|
||||||
|
|
||||||
let name = name.to_owned();
|
let name = name.to_owned();
|
||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
let lua = slim_lua(&name)?;
|
let lua = slim_lua(&name)?;
|
||||||
let plugin: Table = if let Some(b) = LOADED.read().get(&name) {
|
let plugin: Table = if let Some(b) = LOADER.read().get(&name) {
|
||||||
lua.load(b).call(())?
|
lua.load(b).call(())?
|
||||||
} else {
|
} else {
|
||||||
return Err("unloaded plugin".into_lua_err());
|
return Err("unloaded plugin".into_lua_err());
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ mod loader;
|
||||||
mod lua;
|
mod lua;
|
||||||
mod opt;
|
mod opt;
|
||||||
pub mod process;
|
pub mod process;
|
||||||
|
pub mod pubsub;
|
||||||
pub mod url;
|
pub mod url;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use std::{borrow::Cow, collections::HashMap, ops::Deref};
|
use std::{borrow::Cow, collections::HashMap, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{bail, Result};
|
||||||
use mlua::{ExternalError, Table};
|
use mlua::{ExternalError, Table};
|
||||||
|
|
@ -9,19 +9,24 @@ use yazi_shared::RoCell;
|
||||||
|
|
||||||
use crate::LUA;
|
use crate::LUA;
|
||||||
|
|
||||||
pub static LOADED: RoCell<Loader> = RoCell::new();
|
pub static LOADER: RoCell<Loader> = RoCell::new();
|
||||||
|
|
||||||
|
pub(super) static RUNNING: RoCell<arc_swap::ArcSwapOption<String>> = RoCell::new();
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Loader {
|
pub struct Loader {
|
||||||
loaded: RwLock<HashMap<String, Vec<u8>>>,
|
cache: RwLock<HashMap<String, Vec<u8>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Loader {
|
impl Loader {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(super) fn init() { LOADED.with(Default::default); }
|
pub(super) fn init() {
|
||||||
|
LOADER.with(Default::default);
|
||||||
|
RUNNING.with(Default::default);
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn ensure(&self, name: &str) -> Result<()> {
|
pub async fn ensure(&self, name: &str) -> Result<()> {
|
||||||
if self.loaded.read().contains_key(name) {
|
if self.cache.read().contains_key(name) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,7 +47,7 @@ impl Loader {
|
||||||
}))
|
}))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.loaded.write().insert(name.to_owned(), b.into_owned());
|
self.cache.write().insert(name.to_owned(), b.into_owned());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,11 +69,19 @@ impl Loader {
|
||||||
loaded.raw_set(name, t.clone())?;
|
loaded.raw_set(name, t.clone())?;
|
||||||
Ok(t)
|
Ok(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_running(&self, name: Option<&str>) {
|
||||||
|
if let Some(s) = name {
|
||||||
|
RUNNING.store(Some(Arc::new(s.to_owned())));
|
||||||
|
} else {
|
||||||
|
RUNNING.store(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Loader {
|
impl Deref for Loader {
|
||||||
type Target = RwLock<HashMap<String, Vec<u8>>>;
|
type Target = RwLock<HashMap<String, Vec<u8>>>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn deref(&self) -> &Self::Target { &self.loaded }
|
fn deref(&self) -> &Self::Target { &self.cache }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,13 @@ use yazi_shared::RoCell;
|
||||||
pub static LUA: RoCell<Lua> = RoCell::new();
|
pub static LUA: RoCell<Lua> = RoCell::new();
|
||||||
|
|
||||||
pub(super) fn init_lua() {
|
pub(super) fn init_lua() {
|
||||||
let lua = Lua::new();
|
LUA.init(Lua::new());
|
||||||
stage_1(&lua).expect("failed to initialize Lua");
|
|
||||||
stage_2(&lua);
|
stage_1(&LUA).expect("failed to initialize Lua");
|
||||||
LUA.init(lua);
|
stage_2(&LUA);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stage_1(lua: &Lua) -> Result<()> {
|
fn stage_1(lua: &'static Lua) -> Result<()> {
|
||||||
crate::Loader::init();
|
crate::Loader::init();
|
||||||
crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?;
|
crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?;
|
||||||
crate::utils::init();
|
crate::utils::init();
|
||||||
|
|
@ -24,6 +24,7 @@ fn stage_1(lua: &Lua) -> Result<()> {
|
||||||
crate::bindings::File::register(lua)?;
|
crate::bindings::File::register(lua)?;
|
||||||
crate::bindings::Icon::register(lua)?;
|
crate::bindings::Icon::register(lua)?;
|
||||||
crate::elements::pour(lua)?;
|
crate::elements::pour(lua)?;
|
||||||
|
crate::pubsub::Pubsub::install(lua)?;
|
||||||
crate::url::pour(lua)?;
|
crate::url::pour(lua)?;
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
|
|
@ -40,7 +41,7 @@ fn stage_1(lua: &Lua) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stage_2(lua: &Lua) {
|
fn stage_2(lua: &'static Lua) {
|
||||||
lua.load(include_str!("../preset/setup.lua")).exec().unwrap();
|
lua.load(include_str!("../preset/setup.lua")).exec().unwrap();
|
||||||
|
|
||||||
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
|
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
use anyhow::bail;
|
use anyhow::bail;
|
||||||
use mlua::{Lua, Table};
|
use mlua::{Lua, Table};
|
||||||
|
use yazi_dds::ValueSendable;
|
||||||
use yazi_shared::event::Cmd;
|
use yazi_shared::event::Cmd;
|
||||||
|
|
||||||
use crate::ValueSendable;
|
|
||||||
|
|
||||||
pub struct Opt {
|
pub struct Opt {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub sync: bool,
|
pub sync: bool,
|
||||||
|
|
|
||||||
5
yazi-plugin/src/pubsub/mod.rs
Normal file
5
yazi-plugin/src/pubsub/mod.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#![allow(clippy::module_inception)]
|
||||||
|
|
||||||
|
mod pubsub;
|
||||||
|
|
||||||
|
pub use pubsub::*;
|
||||||
93
yazi-plugin/src/pubsub/pubsub.rs
Normal file
93
yazi-plugin/src/pubsub/pubsub.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
use mlua::{ExternalResult, Function, Lua, Value};
|
||||||
|
use yazi_dds::body::Body;
|
||||||
|
|
||||||
|
use crate::RUNNING;
|
||||||
|
|
||||||
|
pub struct Pubsub;
|
||||||
|
|
||||||
|
impl Pubsub {
|
||||||
|
pub fn install(lua: &'static Lua) -> mlua::Result<()> {
|
||||||
|
let ps = lua.create_table()?;
|
||||||
|
|
||||||
|
ps.raw_set(
|
||||||
|
"pub",
|
||||||
|
lua.create_function(|_, (kind, value): (mlua::String, Value)| {
|
||||||
|
yazi_dds::Pubsub::pub_(Body::from_lua(kind.to_str()?, value).into_lua_err()?);
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ps.raw_set(
|
||||||
|
"pub_to",
|
||||||
|
lua.create_function(|_, (receiver, kind, value): (u64, mlua::String, Value)| {
|
||||||
|
yazi_dds::Pubsub::pub_to(receiver, Body::from_lua(kind.to_str()?, value).into_lua_err()?);
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ps.raw_set(
|
||||||
|
"pub_static",
|
||||||
|
lua.create_function(|_, (severity, kind, value): (u8, mlua::String, Value)| {
|
||||||
|
if severity < 1 {
|
||||||
|
return Err("Severity must be at least 1").into_lua_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
yazi_dds::Pubsub::pub_static(
|
||||||
|
severity,
|
||||||
|
Body::from_lua(kind.to_str()?, value).into_lua_err()?,
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ps.raw_set(
|
||||||
|
"sub",
|
||||||
|
lua.create_function(|_, (kind, f): (mlua::String, Function)| {
|
||||||
|
let Some(name) = &*RUNNING.load() else {
|
||||||
|
return Err("`sub()` must be called in a sync plugin").into_lua_err();
|
||||||
|
};
|
||||||
|
if !yazi_dds::Pubsub::sub(name, kind.to_str()?, f) {
|
||||||
|
return Err("`sub()` called twice").into_lua_err();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ps.raw_set(
|
||||||
|
"sub_remote",
|
||||||
|
lua.create_function(|_, (kind, f): (mlua::String, Function)| {
|
||||||
|
let Some(name) = &*RUNNING.load() else {
|
||||||
|
return Err("`sub_remote()` must be called in a sync plugin").into_lua_err();
|
||||||
|
};
|
||||||
|
if !yazi_dds::Pubsub::sub_remote(name, kind.to_str()?, f) {
|
||||||
|
return Err("`sub_remote()` called twice").into_lua_err();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ps.raw_set(
|
||||||
|
"unsub",
|
||||||
|
lua.create_function(|_, kind: mlua::String| {
|
||||||
|
if let Some(name) = &*RUNNING.load() {
|
||||||
|
Ok(yazi_dds::Pubsub::unsub(name, kind.to_str()?))
|
||||||
|
} else {
|
||||||
|
Err("`unsub()` must be called in a sync plugin").into_lua_err()
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ps.raw_set(
|
||||||
|
"unsub_remote",
|
||||||
|
lua.create_function(|_, kind: mlua::String| {
|
||||||
|
if let Some(name) = &*RUNNING.load() {
|
||||||
|
Ok(yazi_dds::Pubsub::unsub_remote(name, kind.to_str()?))
|
||||||
|
} else {
|
||||||
|
Err("`unsub_remote()` must be called in a sync plugin").into_lua_err()
|
||||||
|
}
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
lua.globals().raw_set("ps", ps)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use mlua::{ExternalError, Lua, Table, Value};
|
use mlua::{ExternalError, Lua, Table, Value};
|
||||||
|
use yazi_dds::ValueSendable;
|
||||||
use yazi_shared::{emit, event::Cmd, render, Layer};
|
use yazi_shared::{emit, event::Cmd, render, Layer};
|
||||||
|
|
||||||
use super::Utils;
|
use super::Utils;
|
||||||
use crate::ValueSendable;
|
|
||||||
|
|
||||||
impl Utils {
|
impl Utils {
|
||||||
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, HashMap<String, String>)> {
|
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, HashMap<String, String>)> {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
use mlua::{ExternalError, Function, IntoLua, Lua, Table, Value, Variadic};
|
use mlua::{ExternalError, Function, IntoLua, Lua, Table, Value, Variadic};
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
use yazi_dds::ValueSendable;
|
||||||
use yazi_shared::{emit, event::Cmd, Layer};
|
use yazi_shared::{emit, event::Cmd, Layer};
|
||||||
|
|
||||||
use super::Utils;
|
use super::Utils;
|
||||||
use crate::{OptData, ValueSendable};
|
use crate::OptData;
|
||||||
|
|
||||||
impl Utils {
|
impl Utils {
|
||||||
pub(super) fn plugin(lua: &Lua, ya: &Table) -> mlua::Result<()> {
|
pub(super) fn plugin(lua: &Lua, ya: &Table) -> mlua::Result<()> {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(super) static USERS_CACHE: yazi_shared::RoCell<uzers::UsersCache> = yazi_shared::RoCell::new();
|
pub(super) static USERS_CACHE: yazi_shared::RoCell<uzers::UsersCache> = yazi_shared::RoCell::new();
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
pub(super) static HOSTNAME_CACHE: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
|
pub(super) static HOSTNAME_CACHE: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
pub(super) struct Utils;
|
pub(super) struct Utils;
|
||||||
|
|
||||||
pub fn install(lua: &mlua::Lua) -> mlua::Result<()> {
|
pub fn install(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||||
let ya: mlua::Table = lua.create_table()?;
|
let ya = lua.create_table()?;
|
||||||
|
|
||||||
Utils::app(lua, &ya)?;
|
Utils::app(lua, &ya)?;
|
||||||
Utils::cache(lua, &ya)?;
|
Utils::cache(lua, &ya)?;
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||||
# External dependencies
|
# External dependencies
|
||||||
anyhow = "1.0.81"
|
anyhow = "1.0.81"
|
||||||
mlua = { version = "0.9.6", features = [ "lua54", "vendored" ] }
|
mlua = { version = "0.9.6", features = [ "lua54", "vendored" ] }
|
||||||
tokio = { version = "1.36.0", features = [ "parking_lot" ] }
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ impl AppProxy {
|
||||||
emit!(Call(Cmd::new("resume"), Layer::App));
|
emit!(Call(Cmd::new("resume"), Layer::App));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn notify(opt: NotifyOpt) {
|
pub fn notify(opt: NotifyOpt) {
|
||||||
emit!(Call(Cmd::new("notify").with_data(opt), Layer::App));
|
emit!(Call(Cmd::new("notify").with_data(opt), Layer::App));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ repository = "https://github.com/sxyazi/yazi"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||||
|
yazi-dds = { path = "../yazi-dds", version = "0.2.4" }
|
||||||
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
||||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
||||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||||
|
|
@ -23,7 +24,7 @@ crossterm = "0.27.0"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
regex = "1.10.3"
|
regex = "1.10.3"
|
||||||
tokio = { version = "1.36.0", features = [ "parking_lot", "rt-multi-thread" ] }
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
tokio-stream = "0.1.15"
|
tokio-stream = "0.1.15"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use yazi_plugin::ValueSendable;
|
use yazi_dds::ValueSendable;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PluginOp {
|
pub enum PluginOp {
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use futures::{future::BoxFuture, FutureExt};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle};
|
use tokio::{fs, select, sync::{mpsc::{self, UnboundedReceiver}, oneshot}, task::JoinHandle};
|
||||||
use yazi_config::{open::Opener, plugin::PluginRule, TASKS};
|
use yazi_config::{open::Opener, plugin::PluginRule, TASKS};
|
||||||
use yazi_plugin::ValueSendable;
|
use yazi_dds::ValueSendable;
|
||||||
use yazi_shared::{fs::{unique_path, Url}, Throttle};
|
use yazi_shared::{fs::{unique_path, Url}, Throttle};
|
||||||
|
|
||||||
use super::{Ongoing, TaskProg, TaskStage};
|
use super::{Ongoing, TaskProg, TaskStage};
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ percent-encoding = "2.3.1"
|
||||||
ratatui = "0.26.1"
|
ratatui = "0.26.1"
|
||||||
regex = "1.10.3"
|
regex = "1.10.3"
|
||||||
serde = "1.0.197"
|
serde = "1.0.197"
|
||||||
tokio = { version = "1.36.0", features = [ "parking_lot", "macros", "rt-multi-thread", "sync", "time", "fs" ] }
|
tokio = { version = "1.36.0", features = [ "full" ] }
|
||||||
|
|
||||||
[target."cfg(unix)".dependencies]
|
[target."cfg(unix)".dependencies]
|
||||||
libc = "0.2.153"
|
libc = "0.2.153"
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ impl ConditionOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Condition {
|
pub struct Condition {
|
||||||
ops: Vec<ConditionOp>,
|
ops: Vec<ConditionOp>,
|
||||||
}
|
}
|
||||||
|
|
@ -53,11 +54,11 @@ impl FromStr for Condition {
|
||||||
|
|
||||||
fn from_str(expr: &str) -> Result<Self, Self::Err> {
|
fn from_str(expr: &str) -> Result<Self, Self::Err> {
|
||||||
let cond = Self::build(expr);
|
let cond = Self::build(expr);
|
||||||
if cond.eval(|_| true).is_some() {
|
if cond.eval(|_| true).is_none() {
|
||||||
Ok(cond)
|
bail!("Invalid condition: {expr}");
|
||||||
} else {
|
|
||||||
bail!("Invalid condition: {}", expr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(cond)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
use std::{ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
use std::{ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter}, ops::{Deref, DerefMut}, path::{Path, PathBuf}};
|
||||||
|
|
||||||
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#');
|
||||||
|
|
||||||
|
|
@ -212,3 +213,19 @@ impl From<&str> for UrlScheme {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Serialize for Url {
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
serializer.collect_str(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Url {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
Ok(Url::from(s))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
pub struct OrderedFloat(f64);
|
pub struct OrderedFloat(f64);
|
||||||
|
|
||||||
impl OrderedFloat {
|
impl OrderedFloat {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue