mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-25 08:41:05 +00:00
Merge branch 'sxyazi:main' into main
This commit is contained in:
commit
17a00d2334
116 changed files with 1394 additions and 977 deletions
23
.github/ISSUE_TEMPLATE/bug.yml
vendored
23
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -12,21 +12,14 @@ body:
|
|||
- Linux Wayland
|
||||
- macOS
|
||||
- Windows
|
||||
- Windows WSL
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: terminal
|
||||
attributes:
|
||||
label: What terminal are you running Yazi in?
|
||||
placeholder: "ex: Kitty v0.30.1"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Yazi version
|
||||
description: Please do a `yazi -V` and paste the output here.
|
||||
placeholder: "ex: yazi 0.1.5 (3867c29 2023-11-25)"
|
||||
placeholder: "ex: kitty v0.32.2"
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
|
|
@ -38,6 +31,18 @@ body:
|
|||
- Not tried, and I'll explain why below
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: debug
|
||||
attributes:
|
||||
label: "`yazi --debug` output"
|
||||
description: Please do a `yazi --debug` and paste the output here.
|
||||
value: |
|
||||
<!-- Paste the output between the backticks below: -->
|
||||
```sh
|
||||
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
|
|
|
|||
39
Cargo.lock
generated
39
Cargo.lock
generated
|
|
@ -230,9 +230,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.15.3"
|
||||
version = "3.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
|
|
@ -269,9 +269,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.89"
|
||||
version = "1.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0ba8f7aaa012f30d5b2861462f6708eccd49c3c39863fe083a308035f63d723"
|
||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
|
@ -281,9 +281,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.34"
|
||||
version = "0.4.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
|
||||
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
|
|
@ -293,9 +293,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.1"
|
||||
version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
|
||||
checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
|
|
@ -303,9 +303,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.1"
|
||||
version = "4.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
|
|
@ -2689,7 +2689,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yazi-adaptor"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
|
|
@ -2708,7 +2708,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yazi-boot"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap_complete",
|
||||
|
|
@ -2716,13 +2716,14 @@ dependencies = [
|
|||
"clap_complete_nushell",
|
||||
"serde",
|
||||
"vergen",
|
||||
"yazi-adaptor",
|
||||
"yazi-config",
|
||||
"yazi-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yazi-config"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
|
|
@ -2741,7 +2742,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yazi-core"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.0",
|
||||
|
|
@ -2772,7 +2773,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yazi-fm"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"better-panic",
|
||||
|
|
@ -2803,7 +2804,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yazi-plugin"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"ansi-to-tui",
|
||||
"anyhow",
|
||||
|
|
@ -2840,7 +2841,7 @@ checksum = "f4b6c8e12e39ac0f79fa96f36e5b88e0da8d230691abd729eec709b43c74f632"
|
|||
|
||||
[[package]]
|
||||
name = "yazi-proxy"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"mlua",
|
||||
|
|
@ -2851,7 +2852,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yazi-scheduler"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-priority-channel",
|
||||
|
|
@ -2873,7 +2874,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "yazi-shared"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.2",
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"language":"en","flagWords":[],"version":"0.2","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"]}
|
||||
{"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"]}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-adaptor"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -9,8 +9,8 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.3" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.3" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "^1"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use std::{env, fmt::Display, io::{Read, Write}, path::Path, sync::Arc};
|
||||
use std::{env, fmt::Display, path::Path, sync::Arc};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use anyhow::Result;
|
||||
use ratatui::layout::Rect;
|
||||
use tracing::warn;
|
||||
use yazi_shared::{env_exists, term::Term};
|
||||
|
||||
use super::{Iterm2, Kitty, KittyOld};
|
||||
use crate::{ueberzug::Ueberzug, Sixel, SHOWN, TMUX};
|
||||
use crate::{ueberzug::Ueberzug, Emulator, Sixel, SHOWN, TMUX};
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Adaptor {
|
||||
|
|
@ -22,82 +21,73 @@ pub enum Adaptor {
|
|||
Chafa,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
enum Emulator {
|
||||
Unknown(Vec<Adaptor>),
|
||||
Kitty,
|
||||
Konsole,
|
||||
Iterm2,
|
||||
WezTerm,
|
||||
Foot,
|
||||
Ghostty,
|
||||
BlackBox,
|
||||
VSCode,
|
||||
Tabby,
|
||||
Hyper,
|
||||
Mintty,
|
||||
Neovim,
|
||||
impl Display for Adaptor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Kitty => write!(f, "kitty"),
|
||||
Self::KittyOld => write!(f, "kitty"),
|
||||
Self::Iterm2 => write!(f, "iterm2"),
|
||||
Self::Sixel => write!(f, "sixel"),
|
||||
Self::X11 => write!(f, "x11"),
|
||||
Self::Wayland => write!(f, "wayland"),
|
||||
Self::Chafa => write!(f, "chafa"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Adaptor {
|
||||
fn emulator() -> Emulator {
|
||||
if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") {
|
||||
return Emulator::Neovim;
|
||||
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
match self {
|
||||
Self::Kitty => Kitty::image_show(path, rect).await,
|
||||
Self::KittyOld => KittyOld::image_show(path, rect).await,
|
||||
Self::Iterm2 => Iterm2::image_show(path, rect).await,
|
||||
Self::Sixel => Sixel::image_show(path, rect).await,
|
||||
_ => Ueberzug::image_show(path, rect).await,
|
||||
}
|
||||
|
||||
let vars = [
|
||||
("KITTY_WINDOW_ID", Emulator::Kitty),
|
||||
("KONSOLE_VERSION", Emulator::Konsole),
|
||||
("ITERM_SESSION_ID", Emulator::Iterm2),
|
||||
("WEZTERM_EXECUTABLE", Emulator::WezTerm),
|
||||
("GHOSTTY_RESOURCES_DIR", Emulator::Ghostty),
|
||||
("VSCODE_INJECTION", Emulator::VSCode),
|
||||
("TABBY_CONFIG_DIRECTORY", Emulator::Tabby),
|
||||
];
|
||||
match vars.into_iter().find(|v| env_exists(v.0)) {
|
||||
Some(var) => return var.1,
|
||||
None => warn!("[Adaptor] No special environment variables detected"),
|
||||
}
|
||||
|
||||
let (term, program) = Self::via_env();
|
||||
match program.as_str() {
|
||||
"iTerm.app" => return Emulator::Iterm2,
|
||||
"WezTerm" => return Emulator::WezTerm,
|
||||
"ghostty" => return Emulator::Ghostty,
|
||||
"BlackBox" => return Emulator::BlackBox,
|
||||
"vscode" => return Emulator::VSCode,
|
||||
"Tabby" => return Emulator::Tabby,
|
||||
"Hyper" => return Emulator::Hyper,
|
||||
"mintty" => return Emulator::Mintty,
|
||||
_ => warn!("[Adaptor] Unknown TERM_PROGRAM: {program}"),
|
||||
}
|
||||
match term.as_str() {
|
||||
"xterm-kitty" => return Emulator::Kitty,
|
||||
"foot" => return Emulator::Foot,
|
||||
"foot-extra" => return Emulator::Foot,
|
||||
"xterm-ghostty" => return Emulator::Ghostty,
|
||||
_ => warn!("[Adaptor] Unknown TERM: {term}"),
|
||||
}
|
||||
|
||||
Self::via_csi().unwrap_or(Emulator::Unknown(vec![]))
|
||||
}
|
||||
|
||||
pub(super) fn detect() -> Self {
|
||||
let mut protocols = match Self::emulator() {
|
||||
Emulator::Unknown(adapters) => adapters,
|
||||
Emulator::Kitty => vec![Self::Kitty],
|
||||
Emulator::Konsole => vec![Self::KittyOld, Self::Iterm2, Self::Sixel],
|
||||
Emulator::Iterm2 => vec![Self::Iterm2, Self::Sixel],
|
||||
Emulator::WezTerm => vec![Self::Iterm2, Self::Sixel],
|
||||
Emulator::Foot => vec![Self::Sixel],
|
||||
Emulator::Ghostty => vec![Self::KittyOld],
|
||||
Emulator::BlackBox => vec![Self::Sixel],
|
||||
Emulator::VSCode => vec![Self::Iterm2, Self::Sixel],
|
||||
Emulator::Tabby => vec![Self::Iterm2, Self::Sixel],
|
||||
Emulator::Hyper => vec![Self::Iterm2, Self::Sixel],
|
||||
Emulator::Mintty => vec![Self::Iterm2],
|
||||
Emulator::Neovim => vec![],
|
||||
};
|
||||
pub fn image_hide(self) -> Result<()> {
|
||||
if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) }
|
||||
}
|
||||
|
||||
pub fn image_erase(self, rect: Rect) -> Result<()> {
|
||||
match self {
|
||||
Self::Kitty => Kitty::image_erase(rect),
|
||||
Self::Iterm2 => Iterm2::image_erase(rect),
|
||||
Self::KittyOld => KittyOld::image_erase(),
|
||||
Self::Sixel => Sixel::image_erase(rect),
|
||||
_ => Ueberzug::image_erase(rect),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) }
|
||||
|
||||
pub(super) fn start(self) { Ueberzug::start(self); }
|
||||
|
||||
#[inline]
|
||||
pub(super) fn shown_store(rect: Rect, size: (u32, u32)) {
|
||||
SHOWN.store(Some(Arc::new(
|
||||
Term::ratio()
|
||||
.map(|(r1, r2)| Rect {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: (size.0 as f64 / r1).ceil() as u16,
|
||||
height: (size.1 as f64 / r2).ceil() as u16,
|
||||
})
|
||||
.unwrap_or(rect),
|
||||
)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn needs_ueberzug(self) -> bool {
|
||||
!matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel)
|
||||
}
|
||||
}
|
||||
|
||||
impl Adaptor {
|
||||
pub fn matches() -> Self {
|
||||
let mut protocols = Emulator::detect().adapters();
|
||||
|
||||
#[cfg(windows)]
|
||||
protocols.retain(|p| *p == Self::Iterm2);
|
||||
|
|
@ -129,134 +119,4 @@ impl Adaptor {
|
|||
warn!("[Adaptor] Falling back to chafa");
|
||||
Self::Chafa
|
||||
}
|
||||
|
||||
fn via_env() -> (String, String) {
|
||||
fn tmux_env(name: &str) -> Result<String> {
|
||||
let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?;
|
||||
|
||||
String::from_utf8(output.stdout)?
|
||||
.trim()
|
||||
.strip_prefix(&format!("{name}="))
|
||||
.map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string()))
|
||||
}
|
||||
|
||||
let mut term = env::var("TERM").unwrap_or_default();
|
||||
let mut program = env::var("TERM_PROGRAM").unwrap_or_default();
|
||||
|
||||
if *TMUX {
|
||||
term = tmux_env("TERM").unwrap_or(term);
|
||||
program = tmux_env("TERM_PROGRAM").unwrap_or(program);
|
||||
}
|
||||
|
||||
(term, program)
|
||||
}
|
||||
|
||||
fn via_csi() -> Result<Emulator> {
|
||||
enable_raw_mode()?;
|
||||
std::io::stdout().write_all(b"\x1b[>q\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c")?;
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
let mut buf = String::with_capacity(200);
|
||||
loop {
|
||||
let mut c = [0; 1];
|
||||
if stdin.read(&mut c)? == 0 {
|
||||
break;
|
||||
}
|
||||
if c[0] == b'c' && buf.contains("\x1b[?") {
|
||||
break;
|
||||
}
|
||||
buf.push(c[0] as char);
|
||||
}
|
||||
|
||||
disable_raw_mode().ok();
|
||||
let names = [
|
||||
("kitty", Emulator::Kitty),
|
||||
("Konsole", Emulator::Konsole),
|
||||
("iTerm2", Emulator::Iterm2),
|
||||
("WezTerm", Emulator::WezTerm),
|
||||
("foot", Emulator::Foot),
|
||||
("ghostty", Emulator::Ghostty),
|
||||
];
|
||||
|
||||
for (name, emulator) in names.iter() {
|
||||
if buf.contains(name) {
|
||||
return Ok(emulator.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut adapters = Vec::with_capacity(2);
|
||||
if buf.contains("\x1b_Gi=31;OK") {
|
||||
adapters.push(Adaptor::KittyOld);
|
||||
}
|
||||
if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| buf.contains(s)) {
|
||||
adapters.push(Adaptor::Sixel);
|
||||
}
|
||||
|
||||
Ok(Emulator::Unknown(adapters))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Adaptor {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Kitty => write!(f, "kitty"),
|
||||
Self::KittyOld => write!(f, "kitty"),
|
||||
Self::Iterm2 => write!(f, "iterm2"),
|
||||
Self::Sixel => write!(f, "sixel"),
|
||||
Self::X11 => write!(f, "x11"),
|
||||
Self::Wayland => write!(f, "wayland"),
|
||||
Self::Chafa => write!(f, "chafa"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Adaptor {
|
||||
pub(super) fn start(self) { Ueberzug::start(self); }
|
||||
|
||||
pub async fn image_show(self, path: &Path, rect: Rect) -> Result<(u32, u32)> {
|
||||
match self {
|
||||
Self::Kitty => Kitty::image_show(path, rect).await,
|
||||
Self::KittyOld => KittyOld::image_show(path, rect).await,
|
||||
Self::Iterm2 => Iterm2::image_show(path, rect).await,
|
||||
Self::Sixel => Sixel::image_show(path, rect).await,
|
||||
_ => Ueberzug::image_show(path, rect).await,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image_hide(self) -> Result<()> {
|
||||
if let Some(rect) = SHOWN.swap(None) { self.image_erase(*rect) } else { Ok(()) }
|
||||
}
|
||||
|
||||
pub fn image_erase(self, rect: Rect) -> Result<()> {
|
||||
match self {
|
||||
Self::Kitty => Kitty::image_erase(rect),
|
||||
Self::Iterm2 => Iterm2::image_erase(rect),
|
||||
Self::KittyOld => KittyOld::image_erase(),
|
||||
Self::Sixel => Sixel::image_erase(rect),
|
||||
_ => Ueberzug::image_erase(rect),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn shown_load(self) -> Option<Rect> { SHOWN.load_full().map(|r| *r) }
|
||||
|
||||
#[inline]
|
||||
pub(super) fn shown_store(rect: Rect, size: (u32, u32)) {
|
||||
SHOWN.store(Some(Arc::new(
|
||||
Term::ratio()
|
||||
.map(|(r1, r2)| Rect {
|
||||
x: rect.x,
|
||||
y: rect.y,
|
||||
width: (size.0 as f64 / r1).ceil() as u16,
|
||||
height: (size.1 as f64 / r2).ceil() as u16,
|
||||
})
|
||||
.unwrap_or(rect),
|
||||
)));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(super) fn needs_ueberzug(self) -> bool {
|
||||
!matches!(self, Self::Kitty | Self::KittyOld | Self::Iterm2 | Self::Sixel)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
155
yazi-adaptor/src/emulator.rs
Normal file
155
yazi-adaptor/src/emulator.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
use std::{env, io::{Read, Write}};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tracing::warn;
|
||||
use yazi_shared::env_exists;
|
||||
|
||||
use crate::{Adaptor, TMUX};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Emulator {
|
||||
Unknown(Vec<Adaptor>),
|
||||
Kitty,
|
||||
Konsole,
|
||||
Iterm2,
|
||||
WezTerm,
|
||||
Foot,
|
||||
Ghostty,
|
||||
BlackBox,
|
||||
VSCode,
|
||||
Tabby,
|
||||
Hyper,
|
||||
Mintty,
|
||||
Neovim,
|
||||
}
|
||||
|
||||
impl Emulator {
|
||||
pub fn adapters(self) -> Vec<Adaptor> {
|
||||
match self {
|
||||
Self::Unknown(adapters) => adapters,
|
||||
Self::Kitty => vec![Adaptor::Kitty],
|
||||
Self::Konsole => vec![Adaptor::KittyOld, Adaptor::Iterm2, Adaptor::Sixel],
|
||||
Self::Iterm2 => vec![Adaptor::Iterm2, Adaptor::Sixel],
|
||||
Self::WezTerm => vec![Adaptor::Iterm2, Adaptor::Sixel],
|
||||
Self::Foot => vec![Adaptor::Sixel],
|
||||
Self::Ghostty => vec![Adaptor::KittyOld],
|
||||
Self::BlackBox => vec![Adaptor::Sixel],
|
||||
Self::VSCode => vec![Adaptor::Iterm2, Adaptor::Sixel],
|
||||
Self::Tabby => vec![Adaptor::Iterm2, Adaptor::Sixel],
|
||||
Self::Hyper => vec![Adaptor::Iterm2, Adaptor::Sixel],
|
||||
Self::Mintty => vec![Adaptor::Iterm2],
|
||||
Self::Neovim => vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Emulator {
|
||||
pub fn detect() -> Self {
|
||||
if env_exists("NVIM_LOG_FILE") && env_exists("NVIM") {
|
||||
return Self::Neovim;
|
||||
}
|
||||
|
||||
let vars = [
|
||||
("KITTY_WINDOW_ID", Self::Kitty),
|
||||
("KONSOLE_VERSION", Self::Konsole),
|
||||
("ITERM_SESSION_ID", Self::Iterm2),
|
||||
("WEZTERM_EXECUTABLE", Self::WezTerm),
|
||||
("GHOSTTY_RESOURCES_DIR", Self::Ghostty),
|
||||
("VSCODE_INJECTION", Self::VSCode),
|
||||
("TABBY_CONFIG_DIRECTORY", Self::Tabby),
|
||||
];
|
||||
match vars.into_iter().find(|v| env_exists(v.0)) {
|
||||
Some(var) => return var.1,
|
||||
None => warn!("[Adaptor] No special environment variables detected"),
|
||||
}
|
||||
|
||||
let (term, program) = Self::via_env();
|
||||
match program.as_str() {
|
||||
"iTerm.app" => return Self::Iterm2,
|
||||
"WezTerm" => return Self::WezTerm,
|
||||
"ghostty" => return Self::Ghostty,
|
||||
"BlackBox" => return Self::BlackBox,
|
||||
"vscode" => return Self::VSCode,
|
||||
"Tabby" => return Self::Tabby,
|
||||
"Hyper" => return Self::Hyper,
|
||||
"mintty" => return Self::Mintty,
|
||||
_ => warn!("[Adaptor] Unknown TERM_PROGRAM: {program}"),
|
||||
}
|
||||
match term.as_str() {
|
||||
"xterm-kitty" => return Self::Kitty,
|
||||
"foot" => return Self::Foot,
|
||||
"foot-extra" => return Self::Foot,
|
||||
"xterm-ghostty" => return Self::Ghostty,
|
||||
_ => warn!("[Adaptor] Unknown TERM: {term}"),
|
||||
}
|
||||
|
||||
Self::via_csi().unwrap_or(Self::Unknown(vec![]))
|
||||
}
|
||||
|
||||
pub fn via_env() -> (String, String) {
|
||||
fn tmux_env(name: &str) -> Result<String> {
|
||||
let output = std::process::Command::new("tmux").args(["show-environment", name]).output()?;
|
||||
|
||||
String::from_utf8(output.stdout)?
|
||||
.trim()
|
||||
.strip_prefix(&format!("{name}="))
|
||||
.map_or_else(|| Err(anyhow!("")), |s| Ok(s.to_string()))
|
||||
}
|
||||
|
||||
let mut term = env::var("TERM").unwrap_or_default();
|
||||
let mut program = env::var("TERM_PROGRAM").unwrap_or_default();
|
||||
|
||||
if *TMUX {
|
||||
term = tmux_env("TERM").unwrap_or(term);
|
||||
program = tmux_env("TERM_PROGRAM").unwrap_or(program);
|
||||
}
|
||||
|
||||
(term, program)
|
||||
}
|
||||
|
||||
pub fn via_csi() -> Result<Self> {
|
||||
enable_raw_mode()?;
|
||||
std::io::stdout().write_all(b"\x1b[>q\x1b_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\\x1b[c")?;
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
let mut stdin = std::io::stdin().lock();
|
||||
let mut buf = String::with_capacity(200);
|
||||
loop {
|
||||
let mut c = [0; 1];
|
||||
if stdin.read(&mut c)? == 0 {
|
||||
break;
|
||||
}
|
||||
if c[0] == b'c' && buf.contains("\x1b[?") {
|
||||
break;
|
||||
}
|
||||
buf.push(c[0] as char);
|
||||
}
|
||||
|
||||
disable_raw_mode().ok();
|
||||
let names = [
|
||||
("kitty", Self::Kitty),
|
||||
("Konsole", Self::Konsole),
|
||||
("iTerm2", Self::Iterm2),
|
||||
("WezTerm", Self::WezTerm),
|
||||
("foot", Self::Foot),
|
||||
("ghostty", Self::Ghostty),
|
||||
];
|
||||
|
||||
for (name, emulator) in names.iter() {
|
||||
if buf.contains(name) {
|
||||
return Ok(emulator.clone());
|
||||
}
|
||||
}
|
||||
|
||||
let mut adapters = Vec::with_capacity(2);
|
||||
if buf.contains("\x1b_Gi=31;OK") {
|
||||
adapters.push(Adaptor::KittyOld);
|
||||
}
|
||||
if ["?4;", "?4c", ";4;", ";4c"].iter().any(|s| buf.contains(s)) {
|
||||
adapters.push(Adaptor::Sixel);
|
||||
}
|
||||
|
||||
Ok(Self::Unknown(adapters))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#![allow(clippy::unit_arg)]
|
||||
|
||||
mod adaptor;
|
||||
mod emulator;
|
||||
mod image;
|
||||
mod iterm2;
|
||||
mod kitty;
|
||||
|
|
@ -8,7 +9,8 @@ mod kitty_old;
|
|||
mod sixel;
|
||||
mod ueberzug;
|
||||
|
||||
use adaptor::*;
|
||||
pub use adaptor::*;
|
||||
pub use emulator::*;
|
||||
use iterm2::*;
|
||||
use kitty::*;
|
||||
use kitty_old::*;
|
||||
|
|
@ -20,7 +22,7 @@ pub use crate::image::*;
|
|||
pub static ADAPTOR: RoCell<Adaptor> = RoCell::new();
|
||||
|
||||
// Tmux support
|
||||
static TMUX: RoCell<bool> = RoCell::new();
|
||||
pub static TMUX: RoCell<bool> = RoCell::new();
|
||||
static ESCAPE: RoCell<&'static str> = RoCell::new();
|
||||
static START: RoCell<&'static str> = RoCell::new();
|
||||
static CLOSE: RoCell<&'static str> = RoCell::new();
|
||||
|
|
@ -36,7 +38,7 @@ pub fn init() {
|
|||
|
||||
SHOWN.with(Default::default);
|
||||
|
||||
ADAPTOR.init(Adaptor::detect());
|
||||
ADAPTOR.init(Adaptor::matches());
|
||||
ADAPTOR.start();
|
||||
|
||||
if *TMUX {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use anyhow::{bail, Result};
|
|||
use imagesize::ImageSize;
|
||||
use ratatui::layout::Rect;
|
||||
use tokio::{io::AsyncWriteExt, process::{Child, Command}, sync::mpsc::{self, UnboundedSender}};
|
||||
use tracing::debug;
|
||||
use tracing::{debug, warn};
|
||||
use yazi_config::PREVIEW;
|
||||
use yazi_shared::RoCell;
|
||||
|
||||
|
|
@ -71,14 +71,17 @@ impl Ueberzug {
|
|||
}
|
||||
|
||||
fn create_demon(adaptor: Adaptor) -> Result<Child> {
|
||||
Ok(
|
||||
Command::new("ueberzug")
|
||||
.args(["layer", "-so", &adaptor.to_string()])
|
||||
.kill_on_drop(true)
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn()?,
|
||||
)
|
||||
let result = Command::new("ueberzug")
|
||||
.args(["layer", "-so", &adaptor.to_string()])
|
||||
.kill_on_drop(true)
|
||||
.stdin(Stdio::piped())
|
||||
.stderr(Stdio::null())
|
||||
.spawn();
|
||||
|
||||
if let Err(ref e) = result {
|
||||
warn!("ueberzug spawning failed: {}", e);
|
||||
}
|
||||
Ok(result?)
|
||||
}
|
||||
|
||||
fn adjust_rect(mut rect: Rect) -> Rect {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-boot"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -9,8 +9,9 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.3" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.3" }
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||
|
||||
# External dependencies
|
||||
clap = { version = "^4", features = [ "derive" ] }
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ pub struct Args {
|
|||
#[arg(long, action)]
|
||||
pub clear_cache: bool,
|
||||
|
||||
/// Print debug information
|
||||
#[arg(long, action)]
|
||||
pub debug: bool,
|
||||
|
||||
/// Print version
|
||||
#[arg(short = 'V', long)]
|
||||
pub version: bool,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ffi::OsString, path::{Path, PathBuf}, process};
|
||||
use std::{env, ffi::OsString, path::{Path, PathBuf}, process};
|
||||
|
||||
use clap::Parser;
|
||||
use serde::Serialize;
|
||||
|
|
@ -32,6 +32,63 @@ impl Boot {
|
|||
|
||||
(parent.unwrap().to_owned(), Some(entry.file_name().unwrap().to_owned()))
|
||||
}
|
||||
|
||||
fn action_version() {
|
||||
println!(
|
||||
"yazi {} ({} {})",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
env!("VERGEN_BUILD_DATE")
|
||||
);
|
||||
}
|
||||
|
||||
fn action_debug() {
|
||||
print!("Yazi\n ");
|
||||
Self::action_version();
|
||||
|
||||
println!("\nEnvironment");
|
||||
println!(
|
||||
" OS: {}-{} ({})",
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH,
|
||||
std::env::consts::FAMILY
|
||||
);
|
||||
println!(" Debug: {}", cfg!(debug_assertions));
|
||||
|
||||
println!("\nEmulator");
|
||||
println!(" Emulator.via_env: {:?}", yazi_adaptor::Emulator::via_env());
|
||||
println!(" Emulator.via_csi: {:?}", yazi_adaptor::Emulator::via_csi());
|
||||
println!(" Emulator.detect: {:?}", yazi_adaptor::Emulator::detect());
|
||||
|
||||
println!("\nAdaptor");
|
||||
println!(" Adaptor.matches: {:?}", yazi_adaptor::Adaptor::matches());
|
||||
|
||||
println!("\ntmux");
|
||||
println!(" TMUX: {:?}", *yazi_adaptor::TMUX);
|
||||
|
||||
println!("\nZellij");
|
||||
println!(" ZELLIJ_SESSION_NAME: {:?}", env::var_os("ZELLIJ_SESSION_NAME"));
|
||||
|
||||
println!("\nDesktop");
|
||||
println!(" XDG_SESSION_TYPE: {:?}", env::var_os("XDG_SESSION_TYPE"));
|
||||
println!(" WAYLAND_DISPLAY: {:?}", env::var_os("WAYLAND_DISPLAY"));
|
||||
println!(" DISPLAY: {:?}", env::var_os("DISPLAY"));
|
||||
|
||||
println!("\nUeberzug");
|
||||
println!(" Version: {:?}", std::process::Command::new("ueberzug").arg("--version").output());
|
||||
|
||||
println!("\nWSL");
|
||||
println!(
|
||||
" /proc/sys/fs/binfmt_misc/WSLInterop: {:?}",
|
||||
std::fs::symlink_metadata("/proc/sys/fs/binfmt_misc/WSLInterop").is_ok()
|
||||
);
|
||||
|
||||
println!("\n\n--------------------------------------------------");
|
||||
println!(
|
||||
"When reporting a bug, please also upload the `yazi.log` log file - only upload the most recent content by time."
|
||||
);
|
||||
println!("You can find it in the {:?} directory.", Xdg::state_dir());
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Boot {
|
||||
|
|
@ -58,13 +115,13 @@ impl Default for Args {
|
|||
fn default() -> Self {
|
||||
let args = Self::parse();
|
||||
|
||||
if args.debug {
|
||||
Boot::action_debug();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
if args.version {
|
||||
println!(
|
||||
"yazi {} ({} {})",
|
||||
env!("CARGO_PKG_VERSION"),
|
||||
env!("VERGEN_GIT_SHA"),
|
||||
env!("VERGEN_BUILD_DATE")
|
||||
);
|
||||
Boot::action_version();
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-config"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -9,7 +9,7 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.3" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "^1"
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ keymap = [
|
|||
{ on = [ "y" ], run = "yank", desc = "Copy the selected files" },
|
||||
{ on = [ "Y" ], run = "unyank", desc = "Cancel the yank status of files" },
|
||||
{ on = [ "x" ], run = "yank --cut", desc = "Cut the selected files" },
|
||||
{ on = [ "X" ], run = "unyank", desc = "Cancel the yank status of files" },
|
||||
{ on = [ "p" ], run = "paste", desc = "Paste the files" },
|
||||
{ on = [ "P" ], run = "paste --force", desc = "Paste the files (overwrite if the destination exists)" },
|
||||
{ on = [ "-" ], run = "link", desc = "Symlink the absolute path of files" },
|
||||
|
|
@ -276,6 +277,9 @@ keymap = [
|
|||
{ on = [ "<Up>" ], run = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<Down>" ], run = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "<C-p>" ], run = "arrow -1", desc = "Move cursor up" },
|
||||
{ on = [ "<C-n>" ], run = "arrow 1", desc = "Move cursor down" },
|
||||
|
||||
{ on = [ "~" ], run = "help", desc = "Open help" }
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -31,14 +31,14 @@ marker_marked = { fg = "lightyellow", bg = "lightyellow" }
|
|||
marker_selected = { fg = "lightblue", bg = "lightblue" }
|
||||
|
||||
# Tab
|
||||
tab_active = { fg = "black", bg = "white" }
|
||||
tab_inactive = { fg = "white", bg = "darkgray" }
|
||||
tab_active = { reversed = true }
|
||||
tab_inactive = {}
|
||||
tab_width = 1
|
||||
|
||||
# Count
|
||||
count_copied = { fg = "black", bg = "lightgreen" }
|
||||
count_cut = { fg = "black", bg = "lightred" }
|
||||
count_selected = { fg = "black", bg = "lightblue" }
|
||||
count_copied = { fg = "white", bg = "green" }
|
||||
count_cut = { fg = "white", bg = "red" }
|
||||
count_selected = { fg = "white", bg = "blue" }
|
||||
|
||||
# Border
|
||||
border_symbol = "│"
|
||||
|
|
@ -55,12 +55,12 @@ syntect_theme = ""
|
|||
[status]
|
||||
separator_open = ""
|
||||
separator_close = ""
|
||||
separator_style = { fg = "darkgray", bg = "darkgray" }
|
||||
separator_style = { fg = "gray", bg = "gray" }
|
||||
|
||||
# Mode
|
||||
mode_normal = { fg = "black", bg = "lightblue", bold = true }
|
||||
mode_select = { fg = "black", bg = "lightgreen", bold = true }
|
||||
mode_unset = { fg = "black", bg = "lightmagenta", bold = true }
|
||||
mode_normal = { bg = "blue", bold = true }
|
||||
mode_select = { bg = "red", bold = true }
|
||||
mode_unset = { bg = "red", bold = true }
|
||||
|
||||
# Progress
|
||||
progress_label = { bold = true }
|
||||
|
|
@ -68,10 +68,10 @@ progress_normal = { fg = "blue", bg = "black" }
|
|||
progress_error = { fg = "red", bg = "black" }
|
||||
|
||||
# Permissions
|
||||
permissions_t = { fg = "lightgreen" }
|
||||
permissions_r = { fg = "lightyellow" }
|
||||
permissions_w = { fg = "lightred" }
|
||||
permissions_x = { fg = "lightcyan" }
|
||||
permissions_t = { fg = "green" }
|
||||
permissions_r = { fg = "yellow" }
|
||||
permissions_w = { fg = "red" }
|
||||
permissions_x = { fg = "cyan" }
|
||||
permissions_s = { fg = "darkgray" }
|
||||
|
||||
# : }}}
|
||||
|
|
@ -81,7 +81,7 @@ permissions_s = { fg = "darkgray" }
|
|||
|
||||
[select]
|
||||
border = { fg = "blue" }
|
||||
active = { fg = "magenta" }
|
||||
active = { fg = "magenta", bold = true }
|
||||
inactive = {}
|
||||
|
||||
# : }}}
|
||||
|
|
@ -102,7 +102,7 @@ selected = { reversed = true }
|
|||
|
||||
[completion]
|
||||
border = { fg = "blue" }
|
||||
active = { bg = "darkgray" }
|
||||
active = { reversed = true }
|
||||
inactive = {}
|
||||
|
||||
# Icons
|
||||
|
|
@ -118,7 +118,7 @@ icon_command = ""
|
|||
[tasks]
|
||||
border = { fg = "blue" }
|
||||
title = {}
|
||||
hovered = { underline = true }
|
||||
hovered = { fg = "magenta", underline = true }
|
||||
|
||||
# : }}}
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ cols = 3
|
|||
mask = { bg = "black" }
|
||||
cand = { fg = "lightcyan" }
|
||||
rest = { fg = "darkgray" }
|
||||
desc = { fg = "magenta" }
|
||||
desc = { fg = "lightmagenta" }
|
||||
separator = " "
|
||||
separator_style = { fg = "darkgray" }
|
||||
|
||||
|
|
@ -140,10 +140,10 @@ separator_style = { fg = "darkgray" }
|
|||
# : Help {{{
|
||||
|
||||
[help]
|
||||
on = { fg = "magenta" }
|
||||
run = { fg = "cyan" }
|
||||
desc = { fg = "gray" }
|
||||
hovered = { bg = "darkgray", bold = true }
|
||||
on = { fg = "cyan" }
|
||||
run = { fg = "magenta" }
|
||||
desc = {}
|
||||
hovered = { reversed = true, bold = true }
|
||||
footer = { fg = "black", bg = "white" }
|
||||
|
||||
# : }}}
|
||||
|
|
@ -172,7 +172,7 @@ rules = [
|
|||
# Images
|
||||
{ mime = "image/*", fg = "cyan" },
|
||||
|
||||
# Videos
|
||||
# Media
|
||||
{ mime = "video/*", fg = "yellow" },
|
||||
{ mime = "audio/*", fg = "yellow" },
|
||||
|
||||
|
|
|
|||
|
|
@ -192,3 +192,5 @@ sort_reverse = false
|
|||
|
||||
[log]
|
||||
enabled = false
|
||||
|
||||
[headsup]
|
||||
|
|
|
|||
34
yazi-config/src/headsup/headsup.rs
Normal file
34
yazi-config/src/headsup/headsup.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::MERGED_YAZI;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Headsup {
|
||||
// TODO: remove this once Yazi 0.3 is released --
|
||||
pub disable_exec_warn: bool,
|
||||
}
|
||||
|
||||
impl Default for Headsup {
|
||||
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() }
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Headsup {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
headsup: Shadow,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Shadow {
|
||||
#[serde(default)]
|
||||
disable_exec_warn: bool,
|
||||
}
|
||||
|
||||
let outer = Outer::deserialize(deserializer)?;
|
||||
|
||||
Ok(Self { disable_exec_warn: outer.headsup.disable_exec_warn })
|
||||
}
|
||||
}
|
||||
3
yazi-config/src/headsup/mod.rs
Normal file
3
yazi-config/src/headsup/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
mod headsup;
|
||||
|
||||
pub use headsup::*;
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
use std::{borrow::Cow, collections::VecDeque};
|
||||
use std::{borrow::Cow, collections::VecDeque, sync::atomic::Ordering};
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use yazi_shared::event::Cmd;
|
||||
|
||||
use super::Key;
|
||||
use crate::DEPRECATED_EXEC;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Control {
|
||||
|
|
@ -61,6 +62,9 @@ impl<'de> Deserialize<'de> for Control {
|
|||
#[derive(Deserialize)]
|
||||
struct VecCmd(#[serde(deserialize_with = "super::run_deserialize")] Vec<Cmd>);
|
||||
|
||||
if shadow.exec.is_some() {
|
||||
DEPRECATED_EXEC.store(true, Ordering::Relaxed);
|
||||
}
|
||||
let Some(run) = shadow.run.or(shadow.exec) else {
|
||||
return Err(serde::de::Error::custom("missing field `run` within `[keymap]`"));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use yazi_shared::{RoCell, Xdg};
|
||||
|
||||
pub mod headsup;
|
||||
pub mod keymap;
|
||||
mod layout;
|
||||
mod log;
|
||||
|
|
@ -23,12 +24,17 @@ pub(crate) use pattern::*;
|
|||
pub(crate) use preset::*;
|
||||
pub use priority::*;
|
||||
|
||||
// TODO: remove this once Yazi 0.3 is released --
|
||||
pub static DEPRECATED_EXEC: std::sync::atomic::AtomicBool =
|
||||
std::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
static MERGED_YAZI: RoCell<String> = RoCell::new();
|
||||
static MERGED_KEYMAP: RoCell<String> = RoCell::new();
|
||||
static MERGED_THEME: RoCell<String> = RoCell::new();
|
||||
|
||||
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
|
||||
|
||||
pub static HEADSUP: RoCell<headsup::Headsup> = RoCell::new();
|
||||
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
|
||||
pub static LOG: RoCell<log::Log> = RoCell::new();
|
||||
pub static MANAGER: RoCell<manager::Manager> = RoCell::new();
|
||||
|
|
@ -49,6 +55,7 @@ pub fn init() {
|
|||
|
||||
LAYOUT.with(Default::default);
|
||||
|
||||
HEADSUP.with(Default::default);
|
||||
KEYMAP.with(Default::default);
|
||||
LOG.with(Default::default);
|
||||
MANAGER.with(Default::default);
|
||||
|
|
@ -60,4 +67,18 @@ pub fn init() {
|
|||
INPUT.with(Default::default);
|
||||
SELECT.with(Default::default);
|
||||
WHICH.with(Default::default);
|
||||
|
||||
// TODO: remove this once Yazi 0.3 is released --
|
||||
if !HEADSUP.disable_exec_warn && DEPRECATED_EXEC.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
println!(
|
||||
r#"
|
||||
WARNING: `exec` will be deprecated in the next major version v0.3 and replaced by `run`.
|
||||
|
||||
Please replace all `exec = ...` with `run = ...`, in your `yazi.toml` and `keymap.toml`.
|
||||
|
||||
---
|
||||
Add `disable_exec_warn = true` to your `yazi.toml` under `[headsup]` to suppress this warning.
|
||||
"#
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::BTreeMap, path::Path};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use indexmap::IndexSet;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
|
@ -10,7 +10,7 @@ use crate::{open::OpenRule, Preset, MERGED_YAZI};
|
|||
#[derive(Debug)]
|
||||
pub struct Open {
|
||||
rules: Vec<OpenRule>,
|
||||
openers: BTreeMap<String, IndexSet<Opener>>,
|
||||
openers: HashMap<String, IndexSet<Opener>>,
|
||||
}
|
||||
|
||||
impl Default for Open {
|
||||
|
|
@ -65,7 +65,7 @@ impl<'de> Deserialize<'de> for Open {
|
|||
{
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
opener: BTreeMap<String, Vec<Opener>>,
|
||||
opener: HashMap<String, Vec<Opener>>,
|
||||
open: OuterOpen,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
|
||||
use crate::DEPRECATED_EXEC;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Opener {
|
||||
pub run: String,
|
||||
|
|
@ -47,6 +51,9 @@ impl<'de> Deserialize<'de> for Opener {
|
|||
let shadow = Shadow::deserialize(deserializer)?;
|
||||
|
||||
// TODO: remove this once Yazi 0.3 is released --
|
||||
if shadow.exec.is_some() {
|
||||
DEPRECATED_EXEC.store(true, Ordering::Relaxed);
|
||||
}
|
||||
let run = shadow.run.or(shadow.exec).unwrap_or_default();
|
||||
// TODO: -- remove this once Yazi 0.3 is released
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use yazi_shared::{event::Cmd, Condition};
|
||||
|
||||
use crate::{Pattern, Priority};
|
||||
use crate::{Pattern, Priority, DEPRECATED_EXEC};
|
||||
|
||||
pub struct PluginRule {
|
||||
pub id: u8,
|
||||
|
|
@ -50,6 +52,9 @@ impl<'de> Deserialize<'de> for PluginRule {
|
|||
#[derive(Deserialize)]
|
||||
struct WrappedCmd(#[serde(deserialize_with = "super::run_deserialize")] Cmd);
|
||||
|
||||
if shadow.exec.is_some() {
|
||||
DEPRECATED_EXEC.store(true, Ordering::Relaxed);
|
||||
}
|
||||
let Some(run) = shadow.run.or(shadow.exec) else {
|
||||
return Err(serde::de::Error::custom("missing field `run` within `[plugin]`"));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ use std::{borrow::Cow, process};
|
|||
use validator::{ValidationErrors, ValidationErrorsKind};
|
||||
|
||||
pub fn check_validation(res: Result<(), ValidationErrors>) {
|
||||
let Err(errors) = res else {
|
||||
return;
|
||||
};
|
||||
let Err(errors) = res else { return };
|
||||
|
||||
for (field, kind) in errors.into_errors() {
|
||||
match kind {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-core"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -9,13 +9,13 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.3" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "0.2.3" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.3" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "0.2.3" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.3" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.3" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.3" }
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "^1"
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use std::{cell::RefCell, ffi::OsString};
|
||||
use std::ffi::OsString;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use yazi_shared::RoCell;
|
||||
|
||||
pub static CLIPBOARD: RoCell<Clipboard> = RoCell::new();
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Clipboard {
|
||||
content: RefCell<OsString>,
|
||||
content: Mutex<OsString>,
|
||||
}
|
||||
|
||||
impl Clipboard {
|
||||
|
|
@ -18,11 +19,11 @@ impl Clipboard {
|
|||
use yazi_shared::in_ssh_connection;
|
||||
|
||||
if in_ssh_connection() {
|
||||
return self.content.borrow().clone();
|
||||
return self.content.lock().clone();
|
||||
}
|
||||
|
||||
let all = [
|
||||
("pbpaste", &[] as &[&str]),
|
||||
("pbpaste", &[][..]),
|
||||
("wl-paste", &[]),
|
||||
("xclip", &["-o", "-selection", "clipboard"]),
|
||||
("xsel", &["-ob"]),
|
||||
|
|
@ -36,7 +37,7 @@ impl Clipboard {
|
|||
return OsString::from_vec(output.stdout);
|
||||
}
|
||||
}
|
||||
self.content.borrow().clone()
|
||||
self.content.lock().clone()
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
|
|
@ -48,7 +49,7 @@ impl Clipboard {
|
|||
return s.into();
|
||||
}
|
||||
|
||||
self.content.borrow().clone()
|
||||
self.content.lock().clone()
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
|
|
@ -59,13 +60,13 @@ impl Clipboard {
|
|||
use tokio::{io::AsyncWriteExt, process::Command};
|
||||
use yazi_shared::in_ssh_connection;
|
||||
|
||||
*self.content.borrow_mut() = s.as_ref().to_owned();
|
||||
*self.content.lock() = s.as_ref().to_owned();
|
||||
if in_ssh_connection() {
|
||||
execute!(stdout(), osc52::SetClipboard::new(s.as_ref())).ok();
|
||||
}
|
||||
|
||||
let all = [
|
||||
("pbcopy", &[] as &[&str]),
|
||||
("pbcopy", &[][..]),
|
||||
("wl-copy", &[]),
|
||||
("xclip", &["-selection", "clipboard"]),
|
||||
("xsel", &["-ib"]),
|
||||
|
|
@ -80,9 +81,7 @@ impl Clipboard {
|
|||
.kill_on_drop(true)
|
||||
.spawn();
|
||||
|
||||
let Ok(mut child) = cmd else {
|
||||
continue;
|
||||
};
|
||||
let Ok(mut child) = cmd else { continue };
|
||||
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
if stdin.write_all(s.as_ref().as_encoded_bytes()).await.is_err() {
|
||||
|
|
@ -101,7 +100,7 @@ impl Clipboard {
|
|||
use clipboard_win::{formats, set_clipboard};
|
||||
|
||||
let s = s.as_ref().to_owned();
|
||||
*self.content.borrow_mut() = s.clone();
|
||||
*self.content.lock() = s.clone();
|
||||
|
||||
tokio::task::spawn_blocking(move || set_clipboard(formats::Unicode, s.to_string_lossy()))
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -40,9 +40,7 @@ impl Completion {
|
|||
let mut dir = fs::read_dir(&parent).await?;
|
||||
let mut cache = vec![];
|
||||
while let Ok(Some(f)) = dir.next_entry().await {
|
||||
let Ok(meta) = f.metadata().await else {
|
||||
continue;
|
||||
};
|
||||
let Ok(meta) = f.metadata().await else { continue };
|
||||
|
||||
cache.push(format!(
|
||||
"{}{}",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Completion {
|
||||
pub(super) caches: BTreeMap<String, Vec<String>>,
|
||||
pub(super) caches: HashMap<String, Vec<String>>,
|
||||
pub(super) cands: Vec<String>,
|
||||
pub(super) offset: usize,
|
||||
pub cursor: usize,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet}, mem, ops::Deref, sync::atomic::Ordering};
|
||||
use std::{collections::{HashMap, HashSet}, mem, ops::Deref, sync::atomic::Ordering};
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::{fs::{self, DirEntry}, select, sync::mpsc::{self, UnboundedReceiver}};
|
||||
|
|
@ -14,7 +14,7 @@ pub struct Files {
|
|||
version: u64,
|
||||
pub(crate) revision: u64,
|
||||
|
||||
pub sizes: BTreeMap<Url, u64>,
|
||||
pub sizes: HashMap<Url, u64>,
|
||||
|
||||
sorter: FilesSorter,
|
||||
filter: Option<Filter>,
|
||||
|
|
@ -128,7 +128,7 @@ impl Files {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn update_size(&mut self, sizes: BTreeMap<Url, u64>) {
|
||||
pub fn update_size(&mut self, sizes: HashMap<Url, u64>) {
|
||||
if sizes.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
@ -146,7 +146,7 @@ impl Files {
|
|||
|
||||
macro_rules! go {
|
||||
($dist:expr, $src:expr, $inc:literal) => {
|
||||
let mut todo: BTreeMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect();
|
||||
let mut todo: HashMap<_, _> = $src.into_iter().map(|f| (f.url(), f)).collect();
|
||||
for f in &$dist {
|
||||
if todo.remove(&f.url).is_some() && todo.is_empty() {
|
||||
break;
|
||||
|
|
@ -176,7 +176,7 @@ impl Files {
|
|||
|
||||
macro_rules! go {
|
||||
($dist:expr, $src:expr, $inc:literal) => {
|
||||
let mut todo: BTreeSet<_> = $src.into_iter().collect();
|
||||
let mut todo: HashSet<_> = $src.into_iter().collect();
|
||||
let len = $dist.len();
|
||||
|
||||
$dist.retain(|f| !todo.remove(&f.url));
|
||||
|
|
@ -217,7 +217,7 @@ impl Files {
|
|||
};
|
||||
}
|
||||
|
||||
let mut urls: BTreeSet<_> = urls.into_iter().collect();
|
||||
let mut urls: HashSet<_> = urls.into_iter().collect();
|
||||
if !urls.is_empty() {
|
||||
go!(self.items, urls, 1);
|
||||
}
|
||||
|
|
@ -228,8 +228,8 @@ impl Files {
|
|||
|
||||
pub fn update_updating(
|
||||
&mut self,
|
||||
files: BTreeMap<Url, File>,
|
||||
) -> (BTreeMap<Url, File>, BTreeMap<Url, File>) {
|
||||
files: HashMap<Url, File>,
|
||||
) -> (HashMap<Url, File>, HashMap<Url, File>) {
|
||||
if files.is_empty() {
|
||||
return Default::default();
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ impl Files {
|
|||
|| !f.url.file_name().is_some_and(|s| filter.matches(s))
|
||||
})
|
||||
} else if self.show_hidden {
|
||||
(BTreeMap::new(), files)
|
||||
(HashMap::new(), files)
|
||||
} else {
|
||||
files.into_iter().partition(|(_, f)| f.is_hidden())
|
||||
};
|
||||
|
|
@ -271,7 +271,7 @@ impl Files {
|
|||
(hidden, items)
|
||||
}
|
||||
|
||||
pub fn update_upserting(&mut self, files: BTreeMap<Url, File>) {
|
||||
pub fn update_upserting(&mut self, files: HashMap<Url, File>) {
|
||||
if files.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cmp::Ordering, collections::BTreeMap, mem};
|
||||
use std::{cmp::Ordering, collections::HashMap, mem};
|
||||
|
||||
use yazi_config::manager::SortBy;
|
||||
use yazi_shared::{fs::{File, Url}, natsort};
|
||||
|
|
@ -12,7 +12,7 @@ pub struct FilesSorter {
|
|||
}
|
||||
|
||||
impl FilesSorter {
|
||||
pub(super) fn sort(&self, items: &mut Vec<File>, sizes: &BTreeMap<Url, u64>) {
|
||||
pub(super) fn sort(&self, items: &mut Vec<File>, sizes: &HashMap<Url, u64>) {
|
||||
if items.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@ use crate::input::Input;
|
|||
|
||||
impl Input {
|
||||
pub fn show(&mut self, opt: impl TryInto<InputOpt>) {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
let Ok(opt) = opt.try_into() else { return };
|
||||
|
||||
self.close(false);
|
||||
self.visible = true;
|
||||
|
|
|
|||
129
yazi-core/src/manager/commands/bulk_rename.rs
Normal file
129
yazi-core/src/manager/commands/bulk_rename.rs
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
use std::{collections::HashMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||
use yazi_config::{OPEN, PREVIEW};
|
||||
use yazi_plugin::external::{self, ShellOpt};
|
||||
use yazi_proxy::{AppProxy, HIDER, WATCHER};
|
||||
use yazi_shared::{fs::{accessible, max_common_root, File, FilesOp, Url}, term::Term, Defer};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
impl Manager {
|
||||
pub(super) fn bulk_rename(&self) {
|
||||
let Some(opener) = OPEN.block_opener("bulk.txt", "text/plain") else {
|
||||
return AppProxy::notify_warn("Bulk rename", "No text opener found");
|
||||
};
|
||||
|
||||
let cwd = self.cwd().clone();
|
||||
let old: Vec<_> = self.selected_or_hovered();
|
||||
|
||||
let root = max_common_root(&old);
|
||||
let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect();
|
||||
|
||||
tokio::spawn(async move {
|
||||
let tmp = PREVIEW.tmpfile("bulk");
|
||||
let s = old.iter().map(|o| o.as_os_str()).collect::<Vec<_>>().join(OsStr::new("\n"));
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&tmp)
|
||||
.await?
|
||||
.write_all(s.as_encoded_bytes())
|
||||
.await?;
|
||||
|
||||
let _permit = HIDER.acquire().await.unwrap();
|
||||
let _defer1 = Defer::new(AppProxy::resume);
|
||||
let _defer2 = Defer::new(|| tokio::spawn(fs::remove_file(tmp.clone())));
|
||||
AppProxy::stop().await;
|
||||
|
||||
let mut child = external::shell(ShellOpt {
|
||||
cmd: (*opener.run).into(),
|
||||
args: vec![OsString::new(), tmp.to_owned().into()],
|
||||
piped: false,
|
||||
orphan: false,
|
||||
})?;
|
||||
child.wait().await?;
|
||||
|
||||
let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect();
|
||||
Self::bulk_rename_do(cwd, root, old, new).await
|
||||
});
|
||||
}
|
||||
|
||||
async fn bulk_rename_do(
|
||||
cwd: Url,
|
||||
root: PathBuf,
|
||||
old: Vec<PathBuf>,
|
||||
new: Vec<PathBuf>,
|
||||
) -> Result<()> {
|
||||
Term::clear(&mut stdout())?;
|
||||
if old.len() != new.len() {
|
||||
println!("Number of old and new differ, press ENTER to exit");
|
||||
stdin().read_exact(&mut [0]).await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect();
|
||||
if todo.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
{
|
||||
let mut stdout = BufWriter::new(stdout().lock());
|
||||
for (o, n) in &todo {
|
||||
writeln!(stdout, "{} -> {}", o.display(), n.display())?;
|
||||
}
|
||||
write!(stdout, "Continue to rename? (y/N): ")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
|
||||
let mut buf = [0; 10];
|
||||
_ = stdin().read(&mut buf).await?;
|
||||
if buf[0] != b'y' && buf[0] != b'Y' {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
let (mut failed, mut succeeded) = (Vec::new(), HashMap::with_capacity(todo.len()));
|
||||
for (o, n) in todo {
|
||||
let (old, new) = (root.join(&o), root.join(&n));
|
||||
|
||||
if accessible(&new).await {
|
||||
failed.push((o, n, anyhow!("Destination already exists")));
|
||||
} else if let Err(e) = fs::rename(&old, &new).await {
|
||||
failed.push((o, n, e.into()));
|
||||
} else if let Ok(f) = File::from(new.into()).await {
|
||||
succeeded.insert(Url::from(old), f);
|
||||
} else {
|
||||
failed.push((o, n, anyhow!("Failed to retrieve file info")));
|
||||
}
|
||||
}
|
||||
|
||||
if !succeeded.is_empty() {
|
||||
FilesOp::Upserting(cwd, succeeded).emit();
|
||||
}
|
||||
drop(_permit);
|
||||
|
||||
if !failed.is_empty() {
|
||||
Self::output_failed(failed).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn output_failed(failed: Vec<(PathBuf, PathBuf, anyhow::Error)>) -> Result<()> {
|
||||
Term::clear(&mut stdout())?;
|
||||
|
||||
{
|
||||
let mut stdout = BufWriter::new(stdout().lock());
|
||||
writeln!(stdout, "Failed to rename:")?;
|
||||
for (o, n, e) in failed {
|
||||
writeln!(stdout, "{} -> {}: {e}", o.display(), n.display())?;
|
||||
}
|
||||
writeln!(stdout, "\nPress ENTER to exit")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
|
||||
stdin().read_exact(&mut [0]).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ use std::path::PathBuf;
|
|||
use tokio::fs;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_proxy::{InputProxy, ManagerProxy};
|
||||
use yazi_shared::{event::Cmd, fs::{File, FilesOp, Url}};
|
||||
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
|
|
@ -26,7 +26,7 @@ impl Manager {
|
|||
};
|
||||
|
||||
let path = cwd.join(&name);
|
||||
if !opt.force && fs::symlink_metadata(&path).await.is_ok() {
|
||||
if !opt.force && accessible(&path).await {
|
||||
match InputProxy::show(InputCfg::overwrite()).recv().await {
|
||||
Some(Ok(c)) if c == "y" || c == "Y" => (),
|
||||
_ => return Ok(()),
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use yazi_shared::{event::Cmd, fs::Url, render};
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ impl Manager {
|
|||
self.peek(false);
|
||||
|
||||
// Refresh watcher
|
||||
let mut to_watch = BTreeSet::new();
|
||||
let mut to_watch = HashSet::with_capacity(3 * self.tabs.len());
|
||||
for tab in self.tabs.iter() {
|
||||
to_watch.insert(&tab.current.cwd);
|
||||
if let Some(ref p) = tab.parent {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
mod bulk_rename;
|
||||
mod close;
|
||||
mod create;
|
||||
mod hover;
|
||||
|
|
|
|||
|
|
@ -16,13 +16,15 @@ impl From<Cmd> for Opt {
|
|||
impl Manager {
|
||||
pub fn paste(&mut self, opt: impl Into<Opt>, tasks: &Tasks) {
|
||||
let opt = opt.into() as Opt;
|
||||
let (src, dest) = (self.yanked.iter().collect::<Vec<_>>(), self.cwd());
|
||||
|
||||
let dest = self.cwd();
|
||||
if self.yanked.cut {
|
||||
tasks.file_cut(&self.yanked, dest, opt.force);
|
||||
tasks.file_cut(&src, dest, opt.force);
|
||||
|
||||
self.tabs.iter_mut().for_each(|t| _ = t.selected.remove_many(&src, false));
|
||||
self.unyank(());
|
||||
} else {
|
||||
tasks.file_copy(&self.yanked, dest, opt.force, opt.follow);
|
||||
tasks.file_copy(&src, dest, opt.force, opt.follow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,8 +52,13 @@ impl Manager {
|
|||
|
||||
pub fn remove_do(&mut self, opt: impl Into<Opt>, tasks: &Tasks) {
|
||||
let opt = opt.into() as Opt;
|
||||
|
||||
self.tabs.iter_mut().for_each(|t| {
|
||||
t.selected.remove_many(&opt.targets, false);
|
||||
});
|
||||
|
||||
for u in &opt.targets {
|
||||
self.active_mut().selected.remove(u);
|
||||
self.yanked.remove(u);
|
||||
}
|
||||
|
||||
tasks.file_remove(opt.targets, opt.permanently);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
use std::{collections::BTreeMap, ffi::{OsStr, OsString}, io::{stdout, BufWriter, Write}, path::PathBuf};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::{anyhow, bail, Result};
|
||||
use tokio::{fs::{self, OpenOptions}, io::{stdin, AsyncReadExt, AsyncWriteExt}};
|
||||
use yazi_config::{popup::InputCfg, OPEN, PREVIEW};
|
||||
use yazi_plugin::external::{self, ShellOpt};
|
||||
use yazi_proxy::{AppProxy, InputProxy, ManagerProxy};
|
||||
use yazi_scheduler::BLOCKER;
|
||||
use yazi_shared::{event::Cmd, fs::{max_common_root, File, FilesOp, Url}, term::Term, Defer};
|
||||
use anyhow::Result;
|
||||
use tokio::fs;
|
||||
use yazi_config::popup::InputCfg;
|
||||
use yazi_proxy::{InputProxy, ManagerProxy, WATCHER};
|
||||
use yazi_shared::{event::Cmd, fs::{accessible, File, FilesOp, Url}};
|
||||
|
||||
use crate::manager::Manager;
|
||||
|
||||
|
|
@ -27,32 +25,6 @@ impl From<Cmd> for Opt {
|
|||
}
|
||||
|
||||
impl Manager {
|
||||
fn empty_url_part(url: &Url, by: &str) -> String {
|
||||
if by == "all" {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let ext = url.extension();
|
||||
match by {
|
||||
"stem" => ext.map_or_else(String::new, |s| format!(".{}", s.to_string_lossy().into_owned())),
|
||||
"ext" if ext.is_some() => format!("{}.", url.file_stem().unwrap().to_string_lossy()),
|
||||
"dot_ext" if ext.is_some() => url.file_stem().unwrap().to_string_lossy().into_owned(),
|
||||
_ => url.file_name().map_or_else(String::new, |s| s.to_string_lossy().into_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn rename_and_hover(old: Url, new: Url) -> Result<()> {
|
||||
fs::rename(&old, &new).await?;
|
||||
if old.parent() != new.parent() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file = File::from(new.clone()).await?;
|
||||
FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit();
|
||||
FilesOp::Upserting(file.parent().unwrap(), BTreeMap::from_iter([(old, file)])).emit();
|
||||
Ok(ManagerProxy::hover(Some(new)))
|
||||
}
|
||||
|
||||
pub fn rename(&mut self, opt: impl Into<Opt>) {
|
||||
if !self.active_mut().try_escape_visual() {
|
||||
return;
|
||||
|
|
@ -84,117 +56,45 @@ impl Manager {
|
|||
};
|
||||
|
||||
let new = hovered.parent().unwrap().join(name);
|
||||
if opt.force || fs::symlink_metadata(&new).await.is_err() {
|
||||
Self::rename_and_hover(hovered, Url::from(new)).await.ok();
|
||||
if opt.force || !accessible(&new).await {
|
||||
Self::rename_do(hovered, Url::from(new)).await.ok();
|
||||
return;
|
||||
}
|
||||
|
||||
let mut result = InputProxy::show(InputCfg::overwrite());
|
||||
if let Some(Ok(choice)) = result.recv().await {
|
||||
if choice == "y" || choice == "Y" {
|
||||
Self::rename_and_hover(hovered, Url::from(new)).await.ok();
|
||||
Self::rename_do(hovered, Url::from(new)).await.ok();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
fn bulk_rename(&self) {
|
||||
let old: Vec<_> = self.selected_or_hovered();
|
||||
async fn rename_do(old: Url, new: Url) -> Result<()> {
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
|
||||
let root = max_common_root(&old);
|
||||
let old: Vec<_> = old.into_iter().map(|p| p.strip_prefix(&root).unwrap().to_owned()).collect();
|
||||
fs::rename(&old, &new).await?;
|
||||
if old.parent() != new.parent() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let tmp = PREVIEW.tmpfile("bulk");
|
||||
tokio::spawn(async move {
|
||||
let Some(opener) = OPEN.block_opener("bulk.txt", "text/plain") else {
|
||||
bail!("No opener for bulk rename");
|
||||
};
|
||||
|
||||
{
|
||||
let s = old.iter().map(|o| o.as_os_str()).collect::<Vec<_>>().join(OsStr::new("\n"));
|
||||
OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&tmp)
|
||||
.await?
|
||||
.write_all(s.as_encoded_bytes())
|
||||
.await?;
|
||||
}
|
||||
|
||||
let _guard = BLOCKER.acquire().await.unwrap();
|
||||
let _defer = Defer::new(|| {
|
||||
AppProxy::resume();
|
||||
tokio::spawn(fs::remove_file(tmp.clone()))
|
||||
});
|
||||
AppProxy::stop().await;
|
||||
|
||||
let mut child = external::shell(ShellOpt {
|
||||
cmd: (*opener.run).into(),
|
||||
args: vec![OsString::new(), tmp.to_owned().into()],
|
||||
piped: false,
|
||||
orphan: false,
|
||||
})?;
|
||||
child.wait().await?;
|
||||
|
||||
let new: Vec<_> = fs::read_to_string(&tmp).await?.lines().map(PathBuf::from).collect();
|
||||
Self::bulk_rename_do(root, old, new).await
|
||||
});
|
||||
let file = File::from(new.clone()).await?;
|
||||
FilesOp::Deleting(file.parent().unwrap(), vec![new.clone()]).emit();
|
||||
FilesOp::Upserting(file.parent().unwrap(), HashMap::from_iter([(old, file)])).emit();
|
||||
Ok(ManagerProxy::hover(Some(new)))
|
||||
}
|
||||
|
||||
async fn bulk_rename_do(root: PathBuf, old: Vec<PathBuf>, new: Vec<PathBuf>) -> Result<()> {
|
||||
Term::clear(&mut stdout())?;
|
||||
if old.len() != new.len() {
|
||||
println!("Number of old and new differ, press ENTER to exit");
|
||||
stdin().read_exact(&mut [0]).await?;
|
||||
return Ok(());
|
||||
fn empty_url_part(url: &Url, by: &str) -> String {
|
||||
if by == "all" {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let todo: Vec<_> = old.into_iter().zip(new).filter(|(o, n)| o != n).collect();
|
||||
if todo.is_empty() {
|
||||
return Ok(());
|
||||
let ext = url.extension();
|
||||
match by {
|
||||
"stem" => ext.map_or_else(String::new, |s| format!(".{}", s.to_string_lossy().into_owned())),
|
||||
"ext" if ext.is_some() => format!("{}.", url.file_stem().unwrap().to_string_lossy()),
|
||||
"dot_ext" if ext.is_some() => url.file_stem().unwrap().to_string_lossy().into_owned(),
|
||||
_ => url.file_name().map_or_else(String::new, |s| s.to_string_lossy().into_owned()),
|
||||
}
|
||||
|
||||
{
|
||||
let mut stdout = BufWriter::new(stdout().lock());
|
||||
for (o, n) in &todo {
|
||||
writeln!(stdout, "{} -> {}", o.display(), n.display())?;
|
||||
}
|
||||
write!(stdout, "Continue to rename? (y/N): ")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
|
||||
let mut buf = [0; 10];
|
||||
_ = stdin().read(&mut buf).await?;
|
||||
if buf[0] != b'y' && buf[0] != b'Y' {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut failed = vec![];
|
||||
for (o, n) in todo {
|
||||
if fs::symlink_metadata(&n).await.is_ok() {
|
||||
failed.push((o, n, anyhow!("Destination already exists")));
|
||||
continue;
|
||||
}
|
||||
if let Err(e) = fs::rename(root.join(&o), root.join(&n)).await {
|
||||
failed.push((o, n, e.into()));
|
||||
}
|
||||
}
|
||||
if failed.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Term::clear(&mut stdout())?;
|
||||
{
|
||||
let mut stdout = BufWriter::new(stdout().lock());
|
||||
writeln!(stdout, "Failed to rename:")?;
|
||||
for (o, n, e) in failed {
|
||||
writeln!(stdout, "{} -> {}: {e}", o.display(), n.display())?;
|
||||
}
|
||||
writeln!(stdout, "\nPress ENTER to exit")?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
|
||||
stdin().read_exact(&mut [0]).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,8 +16,33 @@ impl TryFrom<Cmd> for Opt {
|
|||
}
|
||||
|
||||
impl Manager {
|
||||
pub fn update_files(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut ops = vec![opt.op];
|
||||
for u in self.watcher.linked.read().from_dir(ops[0].url()) {
|
||||
ops.push(ops[0].chroot(u));
|
||||
}
|
||||
|
||||
for op in ops {
|
||||
let idx = self.tabs.idx;
|
||||
self.yanked.apply_op(&op);
|
||||
|
||||
for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) {
|
||||
Self::update_tab(tab, Cow::Borrowed(&op), tasks);
|
||||
}
|
||||
Self::update_tab(self.active_mut(), Cow::Owned(op), tasks);
|
||||
}
|
||||
|
||||
self.active_mut().apply_files_attrs();
|
||||
}
|
||||
|
||||
fn update_tab(tab: &mut Tab, op: Cow<FilesOp>, tasks: &Tasks) {
|
||||
let url = op.url();
|
||||
tab.selected.apply_op(&op);
|
||||
|
||||
if tab.current.cwd == *url {
|
||||
Self::update_current(tab, op, tasks);
|
||||
} else if matches!(&tab.parent, Some(p) if p.cwd == *url) {
|
||||
|
|
@ -92,26 +117,4 @@ impl Manager {
|
|||
tab.leave(());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_files(&mut self, opt: impl TryInto<Opt>, tasks: &Tasks) {
|
||||
let Ok(opt) = opt.try_into() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut ops = vec![opt.op];
|
||||
for u in self.watcher.linked.read().from_dir(ops[0].url()) {
|
||||
ops.push(ops[0].chroot(u));
|
||||
}
|
||||
|
||||
for op in ops {
|
||||
let idx = self.tabs.idx;
|
||||
for (_, tab) in self.tabs.iter_mut().enumerate().filter(|(i, _)| *i != idx) {
|
||||
Self::update_tab(tab, Cow::Borrowed(&op), tasks);
|
||||
}
|
||||
|
||||
Self::update_tab(self.active_mut(), Cow::Owned(op), tasks);
|
||||
}
|
||||
|
||||
self.active_mut().apply_files_attrs();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{collections::BTreeMap, ops::{Deref, DerefMut}};
|
||||
use std::{collections::HashMap, ops::{Deref, DerefMut}};
|
||||
|
||||
use yazi_shared::fs::Url;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Linked(BTreeMap<Url, Url> /* from ==> to */);
|
||||
pub struct Linked(HashMap<Url, Url> /* from ==> to */);
|
||||
|
||||
impl Deref for Linked {
|
||||
type Target = BTreeMap<Url, Url>;
|
||||
type Target = HashMap<Url, Url>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet}, sync::Arc, time::{Duration, SystemTime}};
|
||||
use std::{collections::{HashMap, HashSet}, sync::Arc, time::{Duration, SystemTime}};
|
||||
|
||||
use anyhow::Result;
|
||||
use notify::{event::{MetadataKind, ModifyKind}, EventKind, RecommendedWatcher, RecursiveMode, Watcher as _Watcher};
|
||||
|
|
@ -7,6 +7,7 @@ use tokio::{fs, pin, sync::mpsc::{self, UnboundedReceiver}};
|
|||
use tokio_stream::{wrappers::UnboundedReceiverStream, StreamExt};
|
||||
use tracing::error;
|
||||
use yazi_plugin::isolate;
|
||||
use yazi_proxy::WATCHER;
|
||||
use yazi_shared::fs::{File, FilesOp, Url};
|
||||
|
||||
use super::Linked;
|
||||
|
|
@ -14,7 +15,7 @@ use crate::folder::{Files, Folder};
|
|||
|
||||
pub struct Watcher {
|
||||
watcher: RecommendedWatcher,
|
||||
watched: Arc<RwLock<BTreeSet<Url>>>,
|
||||
watched: Arc<RwLock<HashSet<Url>>>,
|
||||
pub linked: Arc<RwLock<Linked>>,
|
||||
}
|
||||
|
||||
|
|
@ -25,9 +26,7 @@ impl Watcher {
|
|||
{
|
||||
let tx = tx.clone();
|
||||
move |res: Result<notify::Event, notify::Error>| {
|
||||
let Ok(event) = res else {
|
||||
return;
|
||||
};
|
||||
let Ok(event) = res else { return };
|
||||
|
||||
match event.kind {
|
||||
EventKind::Create(_) => {}
|
||||
|
|
@ -60,11 +59,11 @@ impl Watcher {
|
|||
instance
|
||||
}
|
||||
|
||||
pub(super) fn watch(&mut self, mut new: BTreeSet<&Url>) {
|
||||
pub(super) fn watch(&mut self, mut new: HashSet<&Url>) {
|
||||
new.retain(|&u| u.is_regular());
|
||||
let (to_unwatch, to_watch): (BTreeSet<_>, BTreeSet<_>) = {
|
||||
let (to_unwatch, to_watch): (HashSet<_>, HashSet<_>) = {
|
||||
let guard = self.watched.read();
|
||||
let old: BTreeSet<_> = guard.iter().collect();
|
||||
let old: HashSet<_> = guard.iter().collect();
|
||||
(
|
||||
old.difference(&new).map(|&x| x.clone()).collect(),
|
||||
new.difference(&old).map(|&x| x.clone()).collect(),
|
||||
|
|
@ -143,17 +142,15 @@ impl Watcher {
|
|||
|
||||
async fn on_changed(rx: UnboundedReceiver<Url>) {
|
||||
// TODO: revert this once a new notification is implemented
|
||||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(100, Duration::from_millis(20));
|
||||
let rx = UnboundedReceiverStream::new(rx).chunks_timeout(1000, Duration::from_millis(50));
|
||||
pin!(rx);
|
||||
|
||||
while let Some(urls) = rx.next().await {
|
||||
let urls: BTreeSet<_> = urls.into_iter().collect();
|
||||
let _permit = WATCHER.acquire().await.unwrap();
|
||||
let mut reload = Vec::with_capacity(urls.len());
|
||||
|
||||
for u in urls {
|
||||
let Some(parent) = u.parent_url() else {
|
||||
continue;
|
||||
};
|
||||
for u in urls.into_iter().collect::<HashSet<_>>() {
|
||||
let Some(parent) = u.parent_url() else { continue };
|
||||
|
||||
let Ok(file) = File::from(u.clone()).await else {
|
||||
FilesOp::Deleting(parent, vec![u]).emit();
|
||||
|
|
@ -163,7 +160,7 @@ impl Watcher {
|
|||
if !file.is_dir() {
|
||||
reload.push(file.clone());
|
||||
}
|
||||
FilesOp::Upserting(parent, BTreeMap::from_iter([(u, file)])).emit();
|
||||
FilesOp::Upserting(parent, HashMap::from_iter([(u, file)])).emit();
|
||||
}
|
||||
|
||||
if reload.is_empty() {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::{collections::HashSet, ops::Deref};
|
||||
use std::{collections::HashSet, ops::{Deref, DerefMut}};
|
||||
|
||||
use yazi_shared::fs::Url;
|
||||
use yazi_shared::fs::{FilesOp, Url};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Yanked {
|
||||
|
|
@ -13,3 +13,22 @@ impl Deref for Yanked {
|
|||
|
||||
fn deref(&self) -> &Self::Target { &self.urls }
|
||||
}
|
||||
|
||||
impl DerefMut for Yanked {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.urls }
|
||||
}
|
||||
|
||||
impl Yanked {
|
||||
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||
let (removal, addition) = match op {
|
||||
FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]),
|
||||
FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => {
|
||||
urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, f.url())).unzip()
|
||||
}
|
||||
_ => (vec![], vec![]),
|
||||
};
|
||||
|
||||
self.urls.retain(|u| !removal.contains(&u));
|
||||
self.urls.extend(addition);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ impl Tab {
|
|||
|
||||
while let Some(result) = rx.next().await {
|
||||
let done = result.is_ok();
|
||||
let (Ok(s) | Err(InputError::Typed(s))) = result else {
|
||||
continue;
|
||||
};
|
||||
let (Ok(s) | Err(InputError::Typed(s))) = result else { continue };
|
||||
|
||||
emit!(Call(
|
||||
Cmd::args("filter_do", vec![s])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
use yazi_plugin::external::{self, FzfOpt, ZoxideOpt};
|
||||
use yazi_proxy::{AppProxy, TabProxy};
|
||||
use yazi_scheduler::BLOCKER;
|
||||
use yazi_proxy::{AppProxy, TabProxy, HIDER};
|
||||
use yazi_shared::{event::Cmd, fs::ends_with_slash, Defer};
|
||||
|
||||
use crate::tab::Tab;
|
||||
|
|
@ -37,7 +36,7 @@ impl Tab {
|
|||
|
||||
let cwd = self.current.cwd.clone();
|
||||
tokio::spawn(async move {
|
||||
let _guard = BLOCKER.acquire().await.unwrap();
|
||||
let _permit = HIDER.acquire().await.unwrap();
|
||||
let _defer = Defer::new(AppProxy::resume);
|
||||
AppProxy::stop().await;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeSet;
|
||||
use std::collections::HashSet;
|
||||
|
||||
use yazi_shared::{event::Cmd, render};
|
||||
|
||||
|
|
@ -18,9 +18,9 @@ impl Tab {
|
|||
let idx = self.current.cursor;
|
||||
|
||||
if opt.unset {
|
||||
self.mode = Mode::Unset(idx, BTreeSet::from([idx]));
|
||||
self.mode = Mode::Unset(idx, HashSet::from([idx]));
|
||||
} else {
|
||||
self.mode = Mode::Select(idx, BTreeSet::from([idx]));
|
||||
self.mode = Mode::Select(idx, HashSet::from([idx]));
|
||||
};
|
||||
render!();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use yazi_shared::fs::Url;
|
||||
|
|
@ -7,7 +7,7 @@ use crate::folder::{Files, Filter, FilterCase};
|
|||
|
||||
pub struct Finder {
|
||||
pub filter: Filter,
|
||||
matched: BTreeMap<Url, u8>,
|
||||
matched: HashMap<Url, u8>,
|
||||
revision: u64,
|
||||
}
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ impl Finder {
|
|||
|
||||
impl Finder {
|
||||
#[inline]
|
||||
pub fn matched(&self) -> &BTreeMap<Url, u8> { &self.matched }
|
||||
pub fn matched(&self) -> &HashMap<Url, u8> { &self.matched }
|
||||
|
||||
#[inline]
|
||||
pub fn matched_idx(&self, url: &Url) -> Option<u8> { self.matched.get(url).copied() }
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use std::{collections::BTreeSet, fmt::Display, mem};
|
||||
use std::{collections::HashSet, fmt::Display, mem};
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
||||
pub enum Mode {
|
||||
#[default]
|
||||
Normal,
|
||||
Select(usize, BTreeSet<usize>),
|
||||
Unset(usize, BTreeSet<usize>),
|
||||
Select(usize, HashSet<usize>),
|
||||
Unset(usize, HashSet<usize>),
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut BTreeSet<usize>)> {
|
||||
pub fn visual_mut(&mut self) -> Option<(usize, &mut HashSet<usize>)> {
|
||||
match self {
|
||||
Mode::Normal => None,
|
||||
Mode::Select(start, indices) => Some((*start, indices)),
|
||||
|
|
@ -17,7 +17,7 @@ impl Mode {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn take_visual(&mut self) -> Option<(usize, BTreeSet<usize>)> {
|
||||
pub fn take_visual(&mut self) -> Option<(usize, HashSet<usize>)> {
|
||||
match mem::take(self) {
|
||||
Mode::Normal => None,
|
||||
Mode::Select(start, indices) => Some((start, indices)),
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
use std::{collections::{BTreeSet, HashMap}, ops::Deref};
|
||||
use std::{collections::{HashMap, HashSet}, ops::Deref};
|
||||
|
||||
use yazi_shared::fs::Url;
|
||||
use yazi_shared::fs::{FilesOp, Url};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Selected {
|
||||
inner: BTreeSet<Url>,
|
||||
inner: HashSet<Url>,
|
||||
parents: HashMap<Url, usize>,
|
||||
}
|
||||
|
||||
impl Deref for Selected {
|
||||
type Target = BTreeSet<Url>;
|
||||
type Target = HashSet<Url>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
|
@ -63,27 +63,27 @@ impl Selected {
|
|||
#[inline]
|
||||
pub fn remove(&mut self, url: &Url) -> bool { self.remove_same(&[url]) == 1 }
|
||||
|
||||
pub fn remove_many(&mut self, urls: &[&Url], same: bool) -> usize {
|
||||
pub fn remove_many(&mut self, urls: &[impl AsRef<Url>], same: bool) -> usize {
|
||||
if same {
|
||||
return self.remove_same(urls);
|
||||
}
|
||||
|
||||
let mut grouped: HashMap<_, Vec<_>> = Default::default();
|
||||
for &u in urls {
|
||||
if let Some(p) = u.parent_url() {
|
||||
for u in urls {
|
||||
if let Some(p) = u.as_ref().parent_url() {
|
||||
grouped.entry(p).or_default().push(u);
|
||||
}
|
||||
}
|
||||
grouped.into_values().map(|v| self.remove_same(&v)).sum()
|
||||
}
|
||||
|
||||
fn remove_same(&mut self, urls: &[&Url]) -> usize {
|
||||
let count = urls.iter().map(|&u| self.inner.remove(u)).filter(|&b| b).count();
|
||||
fn remove_same(&mut self, urls: &[impl AsRef<Url>]) -> usize {
|
||||
let count = urls.iter().map(|u| self.inner.remove(u.as_ref())).filter(|&b| b).count();
|
||||
if count == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut parent = urls[0].parent_url();
|
||||
let mut parent = urls[0].as_ref().parent_url();
|
||||
while let Some(u) = parent {
|
||||
let n = self.parents.get_mut(&u).unwrap();
|
||||
|
||||
|
|
@ -101,6 +101,23 @@ impl Selected {
|
|||
self.inner.clear();
|
||||
self.parents.clear();
|
||||
}
|
||||
|
||||
pub fn apply_op(&mut self, op: &FilesOp) {
|
||||
let (removal, addition) = match op {
|
||||
FilesOp::Deleting(_, urls) => (urls.iter().collect(), vec![]),
|
||||
FilesOp::Updating(_, urls) | FilesOp::Upserting(_, urls) => {
|
||||
urls.iter().filter(|(u, _)| self.contains(u)).map(|(u, f)| (u, &f.url)).unzip()
|
||||
}
|
||||
_ => (vec![], vec![]),
|
||||
};
|
||||
|
||||
if !removal.is_empty() {
|
||||
self.remove_many(&removal, !op.url().is_search());
|
||||
}
|
||||
if !addition.is_empty() {
|
||||
self.add_many(&addition, !op.url().is_search());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::task::JoinHandle;
|
||||
|
|
@ -14,7 +14,7 @@ pub struct Tab {
|
|||
pub parent: Option<Folder>,
|
||||
|
||||
pub backstack: Backstack<Url>,
|
||||
pub history: BTreeMap<Url, Folder>,
|
||||
pub history: HashMap<Url, Folder>,
|
||||
pub selected: Selected,
|
||||
|
||||
pub preview: Preview,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::tasks::Tasks;
|
|||
|
||||
impl Tasks {
|
||||
pub fn cancel(&mut self, _: Cmd) {
|
||||
let id = self.scheduler.running.lock().get_id(self.cursor);
|
||||
let id = self.scheduler.ongoing.lock().get_id(self.cursor);
|
||||
if id.map(|id| self.scheduler.cancel(id)) != Some(true) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,26 +2,25 @@ use std::io::{stdout, Write};
|
|||
|
||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||
use tokio::{io::{stdin, AsyncReadExt}, select, sync::mpsc, time};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_scheduler::BLOCKER;
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
use yazi_shared::{event::Cmd, term::Term, Defer};
|
||||
|
||||
use crate::tasks::Tasks;
|
||||
|
||||
impl Tasks {
|
||||
pub fn inspect(&self, _: Cmd) {
|
||||
let Some(id) = self.scheduler.running.lock().get_id(self.cursor) else {
|
||||
let Some(id) = self.scheduler.ongoing.lock().get_id(self.cursor) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let scheduler = self.scheduler.clone();
|
||||
tokio::spawn(async move {
|
||||
let _guard = BLOCKER.acquire().await.unwrap();
|
||||
let _permit = HIDER.acquire().await.unwrap();
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
let buffered = {
|
||||
let mut running = scheduler.running.lock();
|
||||
let Some(task) = running.get_mut(id) else { return };
|
||||
let mut ongoing = scheduler.ongoing.lock();
|
||||
let Some(task) = ongoing.get_mut(id) else { return };
|
||||
|
||||
task.logger = Some(tx);
|
||||
task.logs.clone()
|
||||
|
|
@ -47,7 +46,7 @@ impl Tasks {
|
|||
stdout.write_all(b"\r\n").ok();
|
||||
}
|
||||
_ = time::sleep(time::Duration::from_millis(500)) => {
|
||||
if scheduler.running.lock().get(id).is_none() {
|
||||
if scheduler.ongoing.lock().get(id).is_none() {
|
||||
stdout().write_all(b"Task finished, press `q` to quit\r\n").ok();
|
||||
break;
|
||||
}
|
||||
|
|
@ -61,7 +60,7 @@ impl Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(task) = scheduler.running.lock().get_mut(id) {
|
||||
if let Some(task) = scheduler.ongoing.lock().get_mut(id) {
|
||||
task.logger = None;
|
||||
}
|
||||
while answer != b'q' {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use serde::Serialize;
|
||||
use yazi_scheduler::Running;
|
||||
use yazi_scheduler::Ongoing;
|
||||
|
||||
#[derive(Clone, Copy, Default, Eq, PartialEq, Serialize)]
|
||||
pub struct TasksProgress {
|
||||
|
|
@ -11,14 +11,14 @@ pub struct TasksProgress {
|
|||
pub processed: u64,
|
||||
}
|
||||
|
||||
impl From<&Running> for TasksProgress {
|
||||
fn from(running: &Running) -> Self {
|
||||
impl From<&Ongoing> for TasksProgress {
|
||||
fn from(ongoing: &Ongoing) -> Self {
|
||||
let mut progress = Self::default();
|
||||
if running.is_empty() {
|
||||
if ongoing.is_empty() {
|
||||
return progress;
|
||||
}
|
||||
|
||||
for task in running.values() {
|
||||
for task in ongoing.values() {
|
||||
progress.total += task.total;
|
||||
progress.succ += task.succ;
|
||||
progress.fail += task.fail;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::{BTreeMap, HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration};
|
||||
use std::{collections::{HashMap, HashSet}, ffi::OsStr, mem, sync::Arc, time::Duration};
|
||||
|
||||
use tokio::time::sleep;
|
||||
use tracing::debug;
|
||||
|
|
@ -29,13 +29,13 @@ impl Tasks {
|
|||
summaries: Default::default(),
|
||||
};
|
||||
|
||||
let running = tasks.scheduler.running.clone();
|
||||
let ongoing = tasks.scheduler.ongoing.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut last = TasksProgress::default();
|
||||
loop {
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
|
||||
let new = TasksProgress::from(&*running.lock());
|
||||
let new = TasksProgress::from(&*ongoing.lock());
|
||||
if last != new {
|
||||
last = new;
|
||||
emit!(Call(Cmd::new("update_progress").with_data(new), Layer::App));
|
||||
|
|
@ -52,12 +52,12 @@ impl Tasks {
|
|||
}
|
||||
|
||||
pub fn paginate(&self) -> Vec<TaskSummary> {
|
||||
let running = self.scheduler.running.lock();
|
||||
running.values().take(Self::limit()).map(Into::into).collect()
|
||||
let ongoing = self.scheduler.ongoing.lock();
|
||||
ongoing.values().take(Self::limit()).map(Into::into).collect()
|
||||
}
|
||||
|
||||
pub fn file_open(&self, hovered: &Url, targets: &[(Url, String)]) {
|
||||
let mut openers = BTreeMap::new();
|
||||
let mut openers = HashMap::new();
|
||||
for (url, mime) in targets {
|
||||
if let Some(opener) = OPEN.openers(url, mime).and_then(|o| o.first().copied()) {
|
||||
openers.entry(opener).or_insert_with(|| vec![hovered]).push(url);
|
||||
|
|
@ -78,10 +78,10 @@ impl Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_cut(&self, src: &HashSet<Url>, dest: &Url, force: bool) {
|
||||
for u in src {
|
||||
pub fn file_cut(&self, src: &[&Url], dest: &Url, force: bool) {
|
||||
for &u in src {
|
||||
let to = dest.join(u.file_name().unwrap());
|
||||
if force && u == &to {
|
||||
if force && *u == to {
|
||||
debug!("file_cut: same file, skipping {:?}", to);
|
||||
} else {
|
||||
self.scheduler.file_cut(u.clone(), to, force);
|
||||
|
|
@ -89,10 +89,10 @@ impl Tasks {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn file_copy(&self, src: &HashSet<Url>, dest: &Url, force: bool, follow: bool) {
|
||||
for u in src {
|
||||
pub fn file_copy(&self, src: &[&Url], dest: &Url, force: bool, follow: bool) {
|
||||
for &u in src {
|
||||
let to = dest.join(u.file_name().unwrap());
|
||||
if force && u == &to {
|
||||
if force && *u == to {
|
||||
debug!("file_copy: same file, skipping {:?}", to);
|
||||
} else {
|
||||
self.scheduler.file_copy(u.clone(), to, force, follow);
|
||||
|
|
@ -219,5 +219,5 @@ impl Tasks {
|
|||
|
||||
impl Tasks {
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize { self.scheduler.running.lock().len() }
|
||||
pub fn len(&self) -> usize { self.scheduler.ongoing.lock().len() }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-fm"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -9,14 +9,14 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.3" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "0.2.3" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.3" }
|
||||
yazi-core = { path = "../yazi-core", version = "0.2.3" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "0.2.3" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.3" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.3" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.3" }
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||
yazi-core = { path = "../yazi-core", version = "0.2.4" }
|
||||
yazi-plugin = { path = "../yazi-plugin", version = "0.2.4" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
||||
yazi-scheduler = { path = "../yazi-scheduler", version = "0.2.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "^1"
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::{backend::{Backend, CrosstermBackend}, CompletedFrame};
|
||||
use yazi_plugin::elements::COLLISION;
|
||||
|
||||
use crate::{app::App, lives::Lives, root::{Root, COLLISION}};
|
||||
use crate::{app::App, lives::Lives, root::Root};
|
||||
|
||||
impl App {
|
||||
pub(crate) fn render(&mut self) {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::path::MAIN_SEPARATOR;
|
|||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
|
||||
use yazi_config::{popup::{Offset, Position}, THEME};
|
||||
|
||||
use crate::{widgets, Ctx};
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Completion<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -28,7 +28,7 @@ impl<'a> Widget for Completion<'a> {
|
|||
&THEME.completion.icon_file
|
||||
};
|
||||
|
||||
let mut item = ListItem::new(format!(" {} {}", icon, x));
|
||||
let mut item = ListItem::new(format!(" {icon} {x}"));
|
||||
if i == self.cx.completion.rel_cursor() {
|
||||
item = item.style(THEME.completion.active);
|
||||
} else {
|
||||
|
|
@ -54,7 +54,7 @@ impl<'a> Widget for Completion<'a> {
|
|||
area.height = rect.height.saturating_sub(area.y).min(area.height);
|
||||
}
|
||||
|
||||
widgets::Clear.render(area, buf);
|
||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||
List::new(items)
|
||||
.block(
|
||||
Block::bordered().border_type(BorderType::Rounded).border_style(THEME.completion.border),
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@ impl Progress {
|
|||
let mut f = || {
|
||||
let comp: Table = LUA.globals().raw_get("Progress")?;
|
||||
for widget in comp.call_method::<_, Vec<AnyUserData>>("partial_render", ())? {
|
||||
let Some(w) = cast_to_renderable(widget) else {
|
||||
continue;
|
||||
};
|
||||
let Some(w) = cast_to_renderable(widget) else { continue };
|
||||
|
||||
let area = w.area();
|
||||
w.render(buf);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout::{self, Constraint, Rect}, text::Line, widg
|
|||
use yazi_config::THEME;
|
||||
|
||||
use super::Bindings;
|
||||
use crate::{widgets, Ctx};
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -15,7 +15,7 @@ impl<'a> Layout<'a> {
|
|||
impl<'a> Widget for Layout<'a> {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let help = &self.cx.help;
|
||||
widgets::Clear.render(area, buf);
|
||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||
|
||||
let chunks = layout::Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).split(area);
|
||||
Line::styled(
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use yazi_core::input::InputMode;
|
|||
use yazi_plugin::external::Highlighter;
|
||||
use yazi_shared::term::Term;
|
||||
|
||||
use crate::{widgets, Ctx};
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Input<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -37,7 +37,7 @@ impl<'a> Widget for Input<'a> {
|
|||
let input = &self.cx.input;
|
||||
let area = self.cx.area(&input.position);
|
||||
|
||||
widgets::Clear.render(area, buf);
|
||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||
Paragraph::new(self.highlighted_value().unwrap_or_else(|_| Line::from(input.value())))
|
||||
.block(
|
||||
Block::bordered()
|
||||
|
|
|
|||
25
yazi-fm/src/lives/iter.rs
Normal file
25
yazi-fm/src/lives/iter.rs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
use mlua::AnyUserData;
|
||||
|
||||
use super::SCOPE;
|
||||
|
||||
pub(super) struct Iter<I: Iterator<Item = T>, T> {
|
||||
inner: I,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = T> + 'static, T: 'static> Iter<I, T> {
|
||||
#[inline]
|
||||
pub(super) fn make(inner: I) -> mlua::Result<AnyUserData<'static>> {
|
||||
SCOPE.create_any_userdata(Self { inner, count: 0 })
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Iterator<Item = T>, T> Iterator for Iter<I, T> {
|
||||
type Item = (usize, T);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let next = self.inner.next()?;
|
||||
self.count += 1;
|
||||
Some((self.count, next))
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ impl Lives {
|
|||
("active", super::Tab::make(cx.manager.active())?),
|
||||
("tabs", super::Tabs::make(&cx.manager.tabs)?),
|
||||
("tasks", super::Tasks::make(&cx.tasks)?),
|
||||
("yanked", scope.create_any_userdata_ref(&cx.manager.yanked)?),
|
||||
("yanked", super::Yanked::make(&cx.manager.yanked)?),
|
||||
])?,
|
||||
)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ mod config;
|
|||
mod file;
|
||||
mod files;
|
||||
mod folder;
|
||||
mod iter;
|
||||
mod lives;
|
||||
mod mode;
|
||||
mod preview;
|
||||
|
|
@ -17,6 +18,7 @@ use config::*;
|
|||
use file::*;
|
||||
use files::*;
|
||||
use folder::*;
|
||||
use iter::*;
|
||||
pub(super) use lives::*;
|
||||
use mode::*;
|
||||
use preview::*;
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
use std::{collections::{btree_set, BTreeSet}, ops::Deref};
|
||||
use std::{collections::{hash_set, HashSet}, ops::Deref};
|
||||
|
||||
use mlua::{AnyUserData, Lua, MetaMethod, UserDataMethods, UserDataRefMut};
|
||||
use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataMethods, UserDataRefMut};
|
||||
use yazi_plugin::{bindings::Cast, url::Url};
|
||||
|
||||
use super::SCOPE;
|
||||
use super::{Iter, SCOPE};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub(super) struct Selected {
|
||||
inner: *const BTreeSet<yazi_shared::fs::Url>,
|
||||
inner: *const HashSet<yazi_shared::fs::Url>,
|
||||
}
|
||||
|
||||
impl Deref for Selected {
|
||||
type Target = BTreeSet<yazi_shared::fs::Url>;
|
||||
type Target = HashSet<yazi_shared::fs::Url>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { self.inner() }
|
||||
}
|
||||
|
||||
impl Selected {
|
||||
#[inline]
|
||||
pub(crate) fn make(inner: &BTreeSet<yazi_shared::fs::Url>) -> mlua::Result<AnyUserData<'static>> {
|
||||
pub(super) fn make(inner: &HashSet<yazi_shared::fs::Url>) -> mlua::Result<AnyUserData<'static>> {
|
||||
SCOPE.create_any_userdata(Self { inner })
|
||||
}
|
||||
|
||||
|
|
@ -27,11 +27,17 @@ impl Selected {
|
|||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||
|
||||
reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
|
||||
let iter = lua.create_function(|lua, mut iter: UserDataRefMut<SelectedIter>| {
|
||||
Ok(if let Some(url) = iter.0.next() { Some(Url::cast(lua, url.clone())?) } else { None })
|
||||
})?;
|
||||
let iter = lua.create_function(
|
||||
|lua, mut iter: UserDataRefMut<Iter<hash_set::Iter<yazi_shared::fs::Url>, _>>| {
|
||||
if let Some(next) = iter.next() {
|
||||
(next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua)
|
||||
} else {
|
||||
().into_lua_multi(lua)
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok((iter, SelectedIter::make(me.inner())))
|
||||
Ok((iter, Iter::make(me.inner().iter())))
|
||||
});
|
||||
})?;
|
||||
|
||||
|
|
@ -39,14 +45,5 @@ impl Selected {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn inner(&self) -> &'static BTreeSet<yazi_shared::fs::Url> { unsafe { &*self.inner } }
|
||||
}
|
||||
|
||||
struct SelectedIter(btree_set::Iter<'static, yazi_shared::fs::Url>);
|
||||
|
||||
impl SelectedIter {
|
||||
#[inline]
|
||||
fn make(selected: &'static BTreeSet<yazi_shared::fs::Url>) -> mlua::Result<AnyUserData<'static>> {
|
||||
SCOPE.create_any_userdata(Self(selected.iter()))
|
||||
}
|
||||
fn inner(&self) -> &'static HashSet<yazi_shared::fs::Url> { unsafe { &*self.inner } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ impl Deref for Tasks {
|
|||
|
||||
impl Tasks {
|
||||
#[inline]
|
||||
pub(crate) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData<'static>> {
|
||||
pub(super) fn make(inner: &yazi_core::tasks::Tasks) -> mlua::Result<AnyUserData<'static>> {
|
||||
SCOPE.create_any_userdata(Self { inner })
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,48 @@
|
|||
use mlua::{Lua, MetaMethod, UserDataFields, UserDataMethods};
|
||||
use std::{collections::hash_set, ops::Deref};
|
||||
|
||||
pub(super) struct Yanked;
|
||||
use mlua::{AnyUserData, IntoLuaMulti, Lua, MetaMethod, UserDataFields, UserDataMethods, UserDataRefMut};
|
||||
use yazi_plugin::{bindings::Cast, url::Url};
|
||||
|
||||
use super::{Iter, SCOPE};
|
||||
|
||||
pub(super) struct Yanked {
|
||||
inner: *const yazi_core::manager::Yanked,
|
||||
}
|
||||
|
||||
impl Deref for Yanked {
|
||||
type Target = yazi_core::manager::Yanked;
|
||||
|
||||
fn deref(&self) -> &Self::Target { self.inner() }
|
||||
}
|
||||
|
||||
impl Yanked {
|
||||
#[inline]
|
||||
pub(super) fn make(inner: &yazi_core::manager::Yanked) -> mlua::Result<AnyUserData<'static>> {
|
||||
SCOPE.create_any_userdata(Self { inner })
|
||||
}
|
||||
|
||||
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
|
||||
lua.register_userdata_type::<yazi_core::manager::Yanked>(|reg| {
|
||||
lua.register_userdata_type::<Self>(|reg| {
|
||||
reg.add_field_method_get("is_cut", |_, me| Ok(me.cut));
|
||||
|
||||
reg.add_meta_method(MetaMethod::Len, |_, me, ()| Ok(me.len()));
|
||||
|
||||
reg.add_meta_method(MetaMethod::Pairs, |lua, me, ()| {
|
||||
let iter = lua.create_function(
|
||||
|lua, mut iter: UserDataRefMut<Iter<hash_set::Iter<yazi_shared::fs::Url>, _>>| {
|
||||
if let Some(next) = iter.next() {
|
||||
(next.0, Url::cast(lua, next.1.clone())?).into_lua_multi(lua)
|
||||
} else {
|
||||
().into_lua_multi(lua)
|
||||
}
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok((iter, Iter::make(me.inner().iter())))
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inner(&self) -> &'static yazi_core::manager::Yanked { unsafe { &*self.inner } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ mod select;
|
|||
mod signals;
|
||||
mod tasks;
|
||||
mod which;
|
||||
mod widgets;
|
||||
|
||||
use context::*;
|
||||
use executor::*;
|
||||
|
|
@ -46,10 +45,10 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
yazi_boot::init();
|
||||
|
||||
yazi_scheduler::init();
|
||||
|
||||
yazi_plugin::init();
|
||||
|
||||
yazi_proxy::init();
|
||||
|
||||
yazi_core::init();
|
||||
|
||||
app::App::serve().await
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use yazi_config::THEME;
|
|||
use yazi_core::notify::Message;
|
||||
use yazi_proxy::options::NotifyLevel;
|
||||
|
||||
use crate::{widgets::Clear, Ctx};
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -53,7 +53,7 @@ impl<'a> Widget for Layout<'a> {
|
|||
tile[i].offset(Offset { x: (100 - m.percent) as i32 * tile[i].width as i32 / 100, y: 0 });
|
||||
rect.width = area.width.saturating_sub(rect.x);
|
||||
|
||||
Clear.render(rect, buf);
|
||||
yazi_plugin::elements::Clear::default().render(rect, buf);
|
||||
Paragraph::new(m.content.as_str())
|
||||
.wrap(Wrap { trim: false })
|
||||
.block(
|
||||
|
|
|
|||
|
|
@ -1,12 +1,8 @@
|
|||
use std::sync::atomic::AtomicBool;
|
||||
|
||||
use ratatui::{buffer::Buffer, layout::{Constraint, Layout, Rect}, widgets::Widget};
|
||||
|
||||
use super::{completion, input, select, tasks, which};
|
||||
use crate::{components, help, Ctx};
|
||||
|
||||
pub(super) static COLLISION: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
pub(super) struct Root<'a> {
|
||||
cx: &'a Ctx,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}};
|
||||
use yazi_config::THEME;
|
||||
|
||||
use crate::{widgets, Ctx};
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Select<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -29,7 +29,7 @@ impl<'a> Widget for Select<'a> {
|
|||
})
|
||||
.collect();
|
||||
|
||||
widgets::Clear.render(area, buf);
|
||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||
List::new(items)
|
||||
.block(
|
||||
Block::bordered()
|
||||
|
|
|
|||
|
|
@ -43,8 +43,7 @@ impl Signals {
|
|||
#[cfg(unix)]
|
||||
fn spawn_system_task(&self) -> Result<JoinHandle<()>> {
|
||||
use libc::{SIGCONT, SIGHUP, SIGINT, SIGQUIT, SIGTERM};
|
||||
use yazi_proxy::AppProxy;
|
||||
use yazi_scheduler::BLOCKER;
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
|
||||
let mut signals = signal_hook_tokio::Signals::new([
|
||||
// Terminating signals
|
||||
|
|
@ -56,7 +55,7 @@ impl Signals {
|
|||
let tx = self.tx.clone();
|
||||
Ok(tokio::spawn(async move {
|
||||
while let Some(signal) = signals.next().await {
|
||||
if BLOCKER.try_acquire().is_err() {
|
||||
if HIDER.try_acquire().is_err() {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout::{self, Alignment, Constraint, Rect}, text:
|
|||
use yazi_config::THEME;
|
||||
use yazi_core::tasks::TASKS_PERCENT;
|
||||
|
||||
use crate::{widgets, Ctx};
|
||||
use crate::Ctx;
|
||||
|
||||
pub(crate) struct Layout<'a> {
|
||||
cx: &'a Ctx,
|
||||
|
|
@ -32,7 +32,7 @@ impl<'a> Widget for Layout<'a> {
|
|||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
let area = Self::area(area);
|
||||
|
||||
widgets::Clear.render(area, buf);
|
||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||
let block = Block::bordered()
|
||||
.title(Line::styled("Tasks", THEME.tasks.title))
|
||||
.title_alignment(Alignment::Center)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use ratatui::{buffer::Buffer, layout, layout::{Constraint, Rect}, widgets::{Bloc
|
|||
use yazi_config::THEME;
|
||||
|
||||
use super::Cand;
|
||||
use crate::{widgets, Ctx};
|
||||
use crate::Ctx;
|
||||
|
||||
const PADDING_X: u16 = 1;
|
||||
const PADDING_Y: u16 = 1;
|
||||
|
|
@ -39,14 +39,14 @@ impl Widget for Which<'_> {
|
|||
let chunks = {
|
||||
use Constraint::*;
|
||||
layout::Layout::horizontal(match cols {
|
||||
1 => &[Ratio(1, 1)] as &[Constraint],
|
||||
1 => &[Ratio(1, 1)][..],
|
||||
2 => &[Ratio(1, 2), Ratio(1, 2)],
|
||||
_ => &[Ratio(1, 3), Ratio(1, 3), Ratio(1, 3)],
|
||||
})
|
||||
.split(area)
|
||||
};
|
||||
|
||||
widgets::Clear.render(area, buf);
|
||||
yazi_plugin::elements::Clear::default().render(area, buf);
|
||||
Block::new().style(THEME.which.mask).render(area, buf);
|
||||
|
||||
for y in 0..area.height {
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
use std::sync::atomic::Ordering;
|
||||
|
||||
use ratatui::{buffer::Buffer, layout::Rect, widgets::Widget};
|
||||
use yazi_adaptor::ADAPTOR;
|
||||
|
||||
use crate::root::COLLISION;
|
||||
|
||||
pub(crate) struct Clear;
|
||||
|
||||
#[inline]
|
||||
const fn is_overlapping(a: &Rect, b: &Rect) -> bool {
|
||||
a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y
|
||||
}
|
||||
|
||||
fn overlap(a: &Rect, b: &Rect) -> Option<Rect> {
|
||||
if !is_overlapping(a, b) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let x = a.x.max(b.x);
|
||||
let y = a.y.max(b.y);
|
||||
let width = (a.x + a.width).min(b.x + b.width) - x;
|
||||
let height = (a.y + a.height).min(b.y + b.height) - y;
|
||||
Some(Rect { x, y, width, height })
|
||||
}
|
||||
|
||||
impl Widget for Clear {
|
||||
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||
ratatui::widgets::Clear.render(area, buf);
|
||||
|
||||
let Some(r) = ADAPTOR.shown_load().and_then(|r| overlap(&area, &r)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
ADAPTOR.image_erase(r).ok();
|
||||
COLLISION.store(true, Ordering::Relaxed);
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
buf.get_mut(x, y).set_skip(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
mod clear;
|
||||
|
||||
pub(super) use clear::*;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-plugin"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -9,11 +9,11 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.3" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "0.2.3" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.3" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.3" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.3" }
|
||||
yazi-adaptor = { path = "../yazi-adaptor", version = "0.2.4" }
|
||||
yazi-boot = { path = "../yazi-boot", version = "0.2.4" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||
yazi-proxy = { path = "../yazi-proxy", version = "0.2.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||
|
||||
# External dependencies
|
||||
ansi-to-tui = "^3"
|
||||
|
|
|
|||
|
|
@ -2,16 +2,12 @@ Header = {
|
|||
area = ui.Rect.default,
|
||||
}
|
||||
|
||||
function Header:cwd()
|
||||
function Header:cwd(max)
|
||||
local cwd = cx.active.current.cwd
|
||||
local readable = ya.readable_path(tostring(cwd))
|
||||
|
||||
local span
|
||||
if not cwd.is_search then
|
||||
span = ui.Span(ya.readable_path(tostring(cwd)))
|
||||
else
|
||||
span = ui.Span(string.format("%s (search: %s)", ya.readable_path(tostring(cwd)), cwd:frag()))
|
||||
end
|
||||
return span:style(THEME.manager.cwd)
|
||||
local text = cwd.is_search and string.format("%s (search: %s)", readable, cwd:frag()) or readable
|
||||
return ui.Span(ya.truncate(text, { max = max, rtl = true })):style(THEME.manager.cwd)
|
||||
end
|
||||
|
||||
function Header:count()
|
||||
|
|
@ -49,7 +45,7 @@ function Header:tabs()
|
|||
for i = 1, tabs do
|
||||
local text = i
|
||||
if THEME.manager.tab_width > 2 then
|
||||
text = ya.truncate(text .. " " .. cx.tabs[i]:name(), THEME.manager.tab_width)
|
||||
text = ya.truncate(text .. " " .. cx.tabs[i]:name(), { max = THEME.manager.tab_width })
|
||||
end
|
||||
if i == cx.tabs.idx then
|
||||
spans[#spans + 1] = ui.Span(" " .. text .. " "):style(THEME.manager.tab_active)
|
||||
|
|
@ -60,7 +56,18 @@ function Header:tabs()
|
|||
return ui.Line(spans)
|
||||
end
|
||||
|
||||
-- TODO: remove this function after v0.2.5 release
|
||||
function Header:layout(area)
|
||||
if not ya.deprecated_header_layout then
|
||||
ya.deprecated_header_layout = true
|
||||
ya.notify {
|
||||
title = "Deprecated API",
|
||||
content = "`Header:layout()` is deprecated, please apply the latest `Header:render()` in your `init.lua`",
|
||||
timeout = 5,
|
||||
level = "warn",
|
||||
}
|
||||
end
|
||||
|
||||
self.area = area
|
||||
|
||||
return ui.Layout()
|
||||
|
|
@ -70,12 +77,12 @@ function Header:layout(area)
|
|||
end
|
||||
|
||||
function Header:render(area)
|
||||
local chunks = self:layout(area)
|
||||
self.area = area
|
||||
|
||||
local left = ui.Line { self:cwd() }
|
||||
local right = ui.Line { self:count(), self:tabs() }
|
||||
local left = ui.Line { self:cwd(math.max(0, area.w - right:width())) }
|
||||
return {
|
||||
ui.Paragraph(chunks[1], { left }),
|
||||
ui.Paragraph(chunks[2], { right }):align(ui.Paragraph.RIGHT),
|
||||
ui.Paragraph(area, { left }),
|
||||
ui.Paragraph(area, { right }):align(ui.Paragraph.RIGHT),
|
||||
}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ mod cha;
|
|||
mod file;
|
||||
mod icon;
|
||||
mod input;
|
||||
mod permit;
|
||||
mod position;
|
||||
mod range;
|
||||
mod window;
|
||||
|
|
@ -14,6 +15,7 @@ pub use cha::*;
|
|||
pub use file::*;
|
||||
pub use icon::*;
|
||||
pub use input::*;
|
||||
pub use permit::*;
|
||||
pub use position::*;
|
||||
pub use range::*;
|
||||
pub use window::*;
|
||||
|
|
|
|||
42
yazi-plugin/src/bindings/permit.rs
Normal file
42
yazi-plugin/src/bindings/permit.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use std::{mem, ops::Deref};
|
||||
|
||||
use mlua::{prelude::LuaUserDataMethods, UserData};
|
||||
use tokio::sync::SemaphorePermit;
|
||||
|
||||
pub type PermitRef<'lua, F> = mlua::UserDataRef<'lua, Permit<F>>;
|
||||
|
||||
pub struct Permit<F: FnOnce()> {
|
||||
inner: Option<SemaphorePermit<'static>>,
|
||||
destruct: Option<F>,
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Deref for Permit<F> {
|
||||
type Target = Option<SemaphorePermit<'static>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target { &self.inner }
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Permit<F> {
|
||||
pub fn new(inner: SemaphorePermit<'static>, f: F) -> Self {
|
||||
Self { inner: Some(inner), destruct: Some(f) }
|
||||
}
|
||||
|
||||
fn dropping(&mut self) {
|
||||
if let Some(f) = self.destruct.take() {
|
||||
f();
|
||||
}
|
||||
if let Some(p) = self.inner.take() {
|
||||
mem::drop(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> Drop for Permit<F> {
|
||||
fn drop(&mut self) { self.dropping(); }
|
||||
}
|
||||
|
||||
impl<F: FnOnce()> UserData for Permit<F> {
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method_mut("drop", |_, me, ()| Ok(me.dropping()));
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ pub fn cast_to_renderable(ud: AnyUserData) -> Option<Box<dyn Renderable + Send>>
|
|||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Bar>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Clear>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Border>() {
|
||||
Some(Box::new(c))
|
||||
} else if let Ok(c) = ud.take::<crate::elements::Gauge>() {
|
||||
|
|
@ -67,9 +69,10 @@ impl<'lua> IntoLua<'lua> for ValueSendable {
|
|||
ValueSendable::Number(n) => Ok(Value::Number(n)),
|
||||
ValueSendable::String(s) => Ok(Value::String(lua.create_string(s)?)),
|
||||
ValueSendable::Table(t) => {
|
||||
let table = lua.create_table()?;
|
||||
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.into_lua(lua)?, v.into_lua(lua)?)?;
|
||||
table.raw_set(k, v)?;
|
||||
}
|
||||
Ok(Value::Table(table))
|
||||
}
|
||||
|
|
@ -113,6 +116,11 @@ pub enum ValueSendableKey {
|
|||
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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, Table, UserData, Value};
|
||||
use mlua::{AnyUserData, ExternalError, Lua, Table, UserData, Value};
|
||||
use ratatui::widgets::Borders;
|
||||
|
||||
use super::{RectRef, Renderable, Style};
|
||||
|
|
@ -26,12 +26,12 @@ impl Bar {
|
|||
|
||||
let bar = lua.create_table_from([
|
||||
// Direction
|
||||
("NONE", Borders::NONE.bits().into_lua(lua)?),
|
||||
("TOP", Borders::TOP.bits().into_lua(lua)?),
|
||||
("RIGHT", Borders::RIGHT.bits().into_lua(lua)?),
|
||||
("BOTTOM", Borders::BOTTOM.bits().into_lua(lua)?),
|
||||
("LEFT", Borders::LEFT.bits().into_lua(lua)?),
|
||||
("ALL", Borders::ALL.bits().into_lua(lua)?),
|
||||
("NONE", Borders::NONE.bits()),
|
||||
("TOP", Borders::TOP.bits()),
|
||||
("RIGHT", Borders::RIGHT.bits()),
|
||||
("BOTTOM", Borders::BOTTOM.bits()),
|
||||
("LEFT", Borders::LEFT.bits()),
|
||||
("ALL", Borders::ALL.bits()),
|
||||
])?;
|
||||
|
||||
bar.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{AnyUserData, ExternalError, IntoLua, Lua, Table, UserData, Value};
|
||||
use mlua::{AnyUserData, ExternalError, Lua, Table, UserData, Value};
|
||||
use ratatui::widgets::{Borders, Widget};
|
||||
|
||||
use super::{RectRef, Renderable, Style};
|
||||
|
|
@ -32,19 +32,19 @@ impl Border {
|
|||
|
||||
let border = lua.create_table_from([
|
||||
// Position
|
||||
("NONE", Borders::NONE.bits().into_lua(lua)?),
|
||||
("TOP", Borders::TOP.bits().into_lua(lua)?),
|
||||
("RIGHT", Borders::RIGHT.bits().into_lua(lua)?),
|
||||
("BOTTOM", Borders::BOTTOM.bits().into_lua(lua)?),
|
||||
("LEFT", Borders::LEFT.bits().into_lua(lua)?),
|
||||
("ALL", Borders::ALL.bits().into_lua(lua)?),
|
||||
("NONE", Borders::NONE.bits()),
|
||||
("TOP", Borders::TOP.bits()),
|
||||
("RIGHT", Borders::RIGHT.bits()),
|
||||
("BOTTOM", Borders::BOTTOM.bits()),
|
||||
("LEFT", Borders::LEFT.bits()),
|
||||
("ALL", Borders::ALL.bits()),
|
||||
// Type
|
||||
("PLAIN", PLAIN.into_lua(lua)?),
|
||||
("ROUNDED", ROUNDED.into_lua(lua)?),
|
||||
("DOUBLE", DOUBLE.into_lua(lua)?),
|
||||
("THICK", THICK.into_lua(lua)?),
|
||||
("QUADRANT_INSIDE", QUADRANT_INSIDE.into_lua(lua)?),
|
||||
("QUADRANT_OUTSIDE", QUADRANT_OUTSIDE.into_lua(lua)?),
|
||||
("PLAIN", PLAIN),
|
||||
("ROUNDED", ROUNDED),
|
||||
("DOUBLE", DOUBLE),
|
||||
("THICK", THICK),
|
||||
("QUADRANT_INSIDE", QUADRANT_INSIDE),
|
||||
("QUADRANT_OUTSIDE", QUADRANT_OUTSIDE),
|
||||
])?;
|
||||
|
||||
border.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
|
|
|||
75
yazi-plugin/src/elements/clear.rs
Normal file
75
yazi-plugin/src/elements/clear.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use mlua::{Lua, Table, UserData};
|
||||
use ratatui::layout::Rect;
|
||||
use yazi_adaptor::ADAPTOR;
|
||||
|
||||
use super::{RectRef, Renderable};
|
||||
|
||||
pub static COLLISION: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct Clear {
|
||||
pub area: ratatui::layout::Rect,
|
||||
}
|
||||
|
||||
impl Clear {
|
||||
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
|
||||
let new = lua.create_function(|_, (_, area): (Table, RectRef)| Ok(Clear { area: *area }))?;
|
||||
|
||||
let clear = lua.create_table()?;
|
||||
clear.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
||||
ui.raw_set("Clear", clear)
|
||||
}
|
||||
}
|
||||
|
||||
impl ratatui::widgets::Widget for Clear {
|
||||
fn render(self, area: Rect, buf: &mut ratatui::prelude::Buffer)
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ratatui::widgets::Clear.render(area, buf);
|
||||
|
||||
let Some(r) = ADAPTOR.shown_load().and_then(|r| overlap(&area, &r)) else {
|
||||
return;
|
||||
};
|
||||
|
||||
ADAPTOR.image_erase(r).ok();
|
||||
COLLISION.store(true, Ordering::Relaxed);
|
||||
for y in area.top()..area.bottom() {
|
||||
for x in area.left()..area.right() {
|
||||
buf.get_mut(x, y).set_skip(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderable for Clear {
|
||||
fn area(&self) -> ratatui::layout::Rect { self.area }
|
||||
|
||||
fn render(self: Box<Self>, buf: &mut ratatui::buffer::Buffer) {
|
||||
<Self as ratatui::widgets::Widget>::render(Default::default(), self.area, buf);
|
||||
}
|
||||
|
||||
fn clone_render(&self, buf: &mut ratatui::buffer::Buffer) { Box::new(*self).render(buf); }
|
||||
}
|
||||
|
||||
impl UserData for Clear {}
|
||||
|
||||
#[inline]
|
||||
const fn is_overlapping(a: &Rect, b: &Rect) -> bool {
|
||||
a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y
|
||||
}
|
||||
|
||||
fn overlap(a: &Rect, b: &Rect) -> Option<Rect> {
|
||||
if !is_overlapping(a, b) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let x = a.x.max(b.x);
|
||||
let y = a.y.max(b.y);
|
||||
let width = (a.x + a.width).min(b.x + b.width) - x;
|
||||
let height = (a.y + a.height).min(b.y + b.height) - y;
|
||||
Some(Rect { x, y, width, height })
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ pub fn pour(lua: &Lua) -> mlua::Result<()> {
|
|||
// Install
|
||||
super::Bar::install(lua, &ui)?;
|
||||
super::Border::install(lua, &ui)?;
|
||||
super::Clear::install(lua, &ui)?;
|
||||
super::Constraint::install(lua, &ui)?;
|
||||
super::Gauge::install(lua, &ui)?;
|
||||
super::Layout::install(lua, &ui)?;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{AnyUserData, IntoLua, Lua, Table, UserData, UserDataMethods};
|
||||
use mlua::{AnyUserData, Lua, Table, UserData, UserDataMethods};
|
||||
|
||||
use super::{Constraint, Rect, RectRef};
|
||||
use crate::bindings::Cast;
|
||||
|
|
@ -17,10 +17,7 @@ impl Layout {
|
|||
pub fn install(lua: &Lua, ui: &Table) -> mlua::Result<()> {
|
||||
let new = lua.create_function(|_, _: Table| Ok(Self::default()))?;
|
||||
|
||||
let layout = lua.create_table_from([
|
||||
("HORIZONTAL", HORIZONTAL.into_lua(lua)?),
|
||||
("VERTICAL", VERTICAL.into_lua(lua)?),
|
||||
])?;
|
||||
let layout = lua.create_table_from([("HORIZONTAL", HORIZONTAL), ("VERTICAL", VERTICAL)])?;
|
||||
|
||||
layout.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
mod bar;
|
||||
mod border;
|
||||
mod clear;
|
||||
mod constraint;
|
||||
mod elements;
|
||||
mod gauge;
|
||||
|
|
@ -16,6 +17,7 @@ mod style;
|
|||
|
||||
pub use bar::*;
|
||||
pub use border::*;
|
||||
pub use clear::*;
|
||||
pub use constraint::*;
|
||||
pub use elements::*;
|
||||
pub use gauge::*;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{AnyUserData, IntoLua, Lua, Table, UserDataFields, UserDataMethods, UserDataRef};
|
||||
use mlua::{AnyUserData, Lua, Table, UserDataFields, UserDataMethods, UserDataRef};
|
||||
|
||||
use super::PaddingRef;
|
||||
use crate::bindings::Cast;
|
||||
|
|
@ -18,10 +18,7 @@ impl Rect {
|
|||
})
|
||||
})?;
|
||||
|
||||
let rect = lua.create_table_from([(
|
||||
"default",
|
||||
Rect::cast(lua, ratatui::layout::Rect::default())?.into_lua(lua)?,
|
||||
)])?;
|
||||
let rect = lua.create_table_from([("default", Rect::cast(lua, Default::default())?)])?;
|
||||
|
||||
rect.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
||||
|
|
|
|||
1
yazi-plugin/src/external/shell.rs
vendored
1
yazi-plugin/src/external/shell.rs
vendored
|
|
@ -3,6 +3,7 @@ use std::{env, ffi::OsString, process::Stdio};
|
|||
use anyhow::Result;
|
||||
use tokio::process::{Child, Command};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ShellOpt {
|
||||
pub cmd: OsString,
|
||||
pub args: Vec<OsString>,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use mlua::{IntoLua, Lua, Value};
|
||||
use mlua::{IntoLuaMulti, Lua, Value};
|
||||
use tokio::fs;
|
||||
|
||||
use crate::{bindings::{Cast, Cha}, url::UrlRef};
|
||||
|
|
@ -10,28 +10,28 @@ pub fn install(lua: &Lua) -> mlua::Result<()> {
|
|||
(
|
||||
"write",
|
||||
lua.create_async_function(|lua, (url, data): (UrlRef, mlua::String)| async move {
|
||||
Ok(match fs::write(&*url, data).await {
|
||||
Ok(_) => (Value::Boolean(true), Value::Nil),
|
||||
Err(e) => (Value::Boolean(false), e.raw_os_error().into_lua(lua)?),
|
||||
})
|
||||
match fs::write(&*url, data).await {
|
||||
Ok(_) => (true, Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (false, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
})?,
|
||||
),
|
||||
(
|
||||
"cha",
|
||||
lua.create_async_function(|lua, url: UrlRef| async move {
|
||||
Ok(match fs::symlink_metadata(&*url).await {
|
||||
Ok(m) => (Cha::cast(lua, m)?.into_lua(lua)?, Value::Nil),
|
||||
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
|
||||
})
|
||||
match fs::symlink_metadata(&*url).await {
|
||||
Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
})?,
|
||||
),
|
||||
(
|
||||
"cha_follow",
|
||||
lua.create_async_function(|lua, url: UrlRef| async move {
|
||||
Ok(match fs::metadata(&*url).await {
|
||||
Ok(m) => (Cha::cast(lua, m)?.into_lua(lua)?, Value::Nil),
|
||||
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
|
||||
})
|
||||
match fs::metadata(&*url).await {
|
||||
Ok(m) => (Cha::cast(lua, m)?, Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
})?,
|
||||
),
|
||||
])?,
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ pub mod external;
|
|||
pub mod fs;
|
||||
pub mod isolate;
|
||||
mod loader;
|
||||
mod lua;
|
||||
mod opt;
|
||||
mod plugin;
|
||||
pub mod process;
|
||||
pub mod url;
|
||||
pub mod utils;
|
||||
|
|
@ -17,5 +17,7 @@ pub mod utils;
|
|||
pub use cast::*;
|
||||
pub use config::*;
|
||||
pub use loader::*;
|
||||
pub use lua::*;
|
||||
pub use opt::*;
|
||||
pub use plugin::*;
|
||||
|
||||
pub fn init() { crate::init_lua(); }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{borrow::Cow, collections::BTreeMap, ops::Deref};
|
||||
use std::{borrow::Cow, collections::HashMap, ops::Deref};
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
use mlua::{ExternalError, Table};
|
||||
|
|
@ -13,7 +13,7 @@ pub static LOADED: RoCell<Loader> = RoCell::new();
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Loader {
|
||||
loaded: RwLock<BTreeMap<String, Vec<u8>>>,
|
||||
loaded: RwLock<HashMap<String, Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl Loader {
|
||||
|
|
@ -67,7 +67,7 @@ impl Loader {
|
|||
}
|
||||
|
||||
impl Deref for Loader {
|
||||
type Target = RwLock<BTreeMap<String, Vec<u8>>>;
|
||||
type Target = RwLock<HashMap<String, Vec<u8>>>;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target { &self.loaded }
|
||||
|
|
|
|||
50
yazi-plugin/src/lua.rs
Normal file
50
yazi-plugin/src/lua.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use anyhow::Result;
|
||||
use mlua::Lua;
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_shared::RoCell;
|
||||
|
||||
pub static LUA: RoCell<Lua> = RoCell::new();
|
||||
|
||||
pub(super) fn init_lua() {
|
||||
let lua = Lua::new();
|
||||
stage_1(&lua).expect("failed to initialize Lua");
|
||||
stage_2(&lua);
|
||||
LUA.init(lua);
|
||||
}
|
||||
|
||||
fn stage_1(lua: &Lua) -> Result<()> {
|
||||
crate::Loader::init();
|
||||
crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?;
|
||||
crate::utils::init();
|
||||
crate::utils::install(lua)?;
|
||||
|
||||
// Base
|
||||
lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/ya.lua")).exec()?;
|
||||
crate::bindings::Cha::register(lua)?;
|
||||
crate::bindings::File::register(lua)?;
|
||||
crate::bindings::Icon::register(lua)?;
|
||||
crate::elements::pour(lua)?;
|
||||
crate::url::pour(lua)?;
|
||||
|
||||
// Components
|
||||
lua.load(include_str!("../preset/components/current.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/file.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/folder.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/header.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/manager.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/parent.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/preview.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/progress.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/status.lua")).exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stage_2(lua: &Lua) {
|
||||
lua.load(include_str!("../preset/setup.lua")).exec().unwrap();
|
||||
|
||||
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
|
||||
lua.load(b).exec().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
use anyhow::Result;
|
||||
use mlua::Lua;
|
||||
use yazi_boot::BOOT;
|
||||
use yazi_shared::RoCell;
|
||||
|
||||
pub static LUA: RoCell<Lua> = RoCell::new();
|
||||
|
||||
pub fn init() {
|
||||
fn stage_1(lua: &Lua) -> Result<()> {
|
||||
crate::Loader::init();
|
||||
crate::Config::new(lua).install_boot()?.install_manager()?.install_theme()?;
|
||||
crate::utils::init();
|
||||
crate::utils::install(lua)?;
|
||||
|
||||
// Base
|
||||
lua.load(include_str!("../preset/inspect/inspect.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/ya.lua")).exec()?;
|
||||
crate::bindings::Cha::register(lua)?;
|
||||
crate::bindings::File::register(lua)?;
|
||||
crate::bindings::Icon::register(lua)?;
|
||||
crate::elements::pour(lua)?;
|
||||
crate::url::pour(lua)?;
|
||||
|
||||
// Components
|
||||
lua.load(include_str!("../preset/components/current.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/file.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/folder.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/header.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/manager.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/parent.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/preview.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/progress.lua")).exec()?;
|
||||
lua.load(include_str!("../preset/components/status.lua")).exec()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stage_2(lua: &Lua) {
|
||||
lua.load(include_str!("../preset/setup.lua")).exec().unwrap();
|
||||
|
||||
if let Ok(b) = std::fs::read(BOOT.config_dir.join("init.lua")) {
|
||||
lua.load(b).exec().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
stage_1(&lua).expect("failed to initialize Lua");
|
||||
stage_2(&lua);
|
||||
LUA.init(lua);
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use mlua::{IntoLua, Table, UserData, Value};
|
||||
use mlua::{IntoLuaMulti, Table, UserData, Value};
|
||||
use tokio::{io::{AsyncBufReadExt, AsyncReadExt, BufReader}, process::{ChildStderr, ChildStdin, ChildStdout}, select};
|
||||
|
||||
use super::Status;
|
||||
|
|
@ -68,16 +68,14 @@ impl UserData for Child {
|
|||
}
|
||||
});
|
||||
methods.add_async_method_mut("wait", |lua, me, ()| async move {
|
||||
Ok(match me.inner.wait().await {
|
||||
Ok(status) => (Status::new(status).into_lua(lua)?, Value::Nil),
|
||||
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
|
||||
})
|
||||
match me.inner.wait().await {
|
||||
Ok(status) => (Status::new(status), Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
});
|
||||
methods.add_method_mut("start_kill", |lua, me, ()| {
|
||||
Ok(match me.inner.start_kill() {
|
||||
Ok(_) => (true, Value::Nil),
|
||||
Err(e) => (false, e.raw_os_error().into_lua(lua)?),
|
||||
})
|
||||
methods.add_method_mut("start_kill", |lua, me, ()| match me.inner.start_kill() {
|
||||
Ok(_) => (true, Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (false, e.raw_os_error()).into_lua_multi(lua),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::process::Stdio;
|
||||
|
||||
use mlua::{AnyUserData, IntoLua, Lua, Table, UserData, Value};
|
||||
use mlua::{AnyUserData, IntoLuaMulti, Lua, Table, UserData, Value};
|
||||
|
||||
use super::{output::Output, Child};
|
||||
|
||||
|
|
@ -23,9 +23,9 @@ impl Command {
|
|||
|
||||
let command = lua.create_table_from([
|
||||
// Stdio
|
||||
("NULL", NULL.into_lua(lua)?),
|
||||
("PIPED", PIPED.into_lua(lua)?),
|
||||
("INHERIT", INHERIT.into_lua(lua)?),
|
||||
("NULL", NULL),
|
||||
("PIPED", PIPED),
|
||||
("INHERIT", INHERIT),
|
||||
])?;
|
||||
|
||||
command.set_metatable(Some(lua.create_table_from([("__call", new)])?));
|
||||
|
|
@ -86,17 +86,15 @@ impl UserData for Command {
|
|||
});
|
||||
Ok(ud)
|
||||
});
|
||||
methods.add_method_mut("spawn", |lua, me, ()| {
|
||||
Ok(match me.inner.spawn() {
|
||||
Ok(child) => (Child::new(child).into_lua(lua)?, Value::Nil),
|
||||
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
|
||||
})
|
||||
methods.add_method_mut("spawn", |lua, me, ()| match me.inner.spawn() {
|
||||
Ok(child) => (Child::new(child), Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
});
|
||||
methods.add_async_method_mut("output", |lua, me, ()| async move {
|
||||
Ok(match me.inner.output().await {
|
||||
Ok(output) => (Output::new(output).into_lua(lua)?, Value::Nil),
|
||||
Err(e) => (Value::Nil, e.raw_os_error().into_lua(lua)?),
|
||||
})
|
||||
match me.inner.output().await {
|
||||
Ok(output) => (Output::new(output), Value::Nil).into_lua_multi(lua),
|
||||
Err(e) => (Value::Nil, e.raw_os_error()).into_lua_multi(lua),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
27
yazi-plugin/src/utils/app.rs
Normal file
27
yazi-plugin/src/utils/app.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
use mlua::{AnyUserData, ExternalError, Lua, Table};
|
||||
use yazi_proxy::{AppProxy, HIDER};
|
||||
|
||||
use super::Utils;
|
||||
use crate::bindings::{Permit, PermitRef};
|
||||
|
||||
impl Utils {
|
||||
pub(super) fn app(lua: &Lua, ya: &Table) -> mlua::Result<()> {
|
||||
ya.raw_set(
|
||||
"hide",
|
||||
lua.create_async_function(|lua, ()| async move {
|
||||
if lua.named_registry_value::<PermitRef<fn()>>("HIDE_PERMIT").is_ok_and(|h| h.is_some()) {
|
||||
return Err("Cannot hide while already hidden".into_lua_err());
|
||||
}
|
||||
|
||||
let permit = HIDER.acquire().await.unwrap();
|
||||
AppProxy::stop().await;
|
||||
|
||||
lua
|
||||
.set_named_registry_value("HIDE_PERMIT", Permit::new(permit, AppProxy::resume as fn()))?;
|
||||
lua.named_registry_value::<AnyUserData>("HIDE_PERMIT")
|
||||
})?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::collections::BTreeMap;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mlua::{ExternalError, Lua, Table, Value};
|
||||
use yazi_shared::{emit, event::Cmd, render, Layer};
|
||||
|
|
@ -7,9 +7,9 @@ use super::Utils;
|
|||
use crate::ValueSendable;
|
||||
|
||||
impl Utils {
|
||||
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, BTreeMap<String, String>)> {
|
||||
fn parse_args(t: Table) -> mlua::Result<(Vec<String>, HashMap<String, String>)> {
|
||||
let mut args = vec![];
|
||||
let mut named = BTreeMap::new();
|
||||
let mut named = HashMap::new();
|
||||
for result in t.pairs::<Value, Value>() {
|
||||
let (k, v) = result?;
|
||||
match k {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
mod app;
|
||||
mod cache;
|
||||
mod call;
|
||||
mod image;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ impl Utils {
|
|||
lua.create_async_function(
|
||||
|_, (name, calls, args): (String, usize, Variadic<Value>)| async move {
|
||||
let args = ValueSendable::try_from_variadic(args)?;
|
||||
let (tx, rx) = oneshot::channel::<ValueSendable>();
|
||||
let (tx, rx) = oneshot::channel::<Vec<ValueSendable>>();
|
||||
|
||||
let data = OptData {
|
||||
cb: Some({
|
||||
|
|
@ -27,9 +27,9 @@ impl Utils {
|
|||
self_args.push(arg.into_lua(lua)?);
|
||||
}
|
||||
|
||||
let value: ValueSendable =
|
||||
block.call::<_, Value>(Variadic::from_iter(self_args))?.try_into()?;
|
||||
tx.send(value).map_err(|_| "send failed".into_lua_err())
|
||||
let values =
|
||||
ValueSendable::try_from_variadic(block.call(Variadic::from_iter(self_args))?)?;
|
||||
tx.send(values).map_err(|_| "send failed".into_lua_err())
|
||||
})
|
||||
}),
|
||||
..Default::default()
|
||||
|
|
@ -40,8 +40,9 @@ impl Utils {
|
|||
Layer::App
|
||||
));
|
||||
|
||||
rx.await
|
||||
.map_err(|_| format!("Failed to execute sync block in `{name}` plugin").into_lua_err())
|
||||
Ok(Variadic::from_iter(rx.await.map_err(|_| {
|
||||
format!("Failed to execute sync block-{calls} in `{name}` plugin").into_lua_err()
|
||||
})?))
|
||||
},
|
||||
)?,
|
||||
)?;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use std::ops::ControlFlow;
|
||||
|
||||
use mlua::{Lua, Table};
|
||||
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use super::Utils;
|
||||
|
||||
|
|
@ -20,26 +20,35 @@ impl Utils {
|
|||
|
||||
ya.raw_set(
|
||||
"truncate",
|
||||
lua.create_function(|_, (text, max): (mlua::String, usize)| {
|
||||
let mut width = 0;
|
||||
let flow =
|
||||
text.to_string_lossy().chars().try_fold(String::with_capacity(max), |mut s, c| {
|
||||
width += c.width().unwrap_or(0);
|
||||
if s.width() < max {
|
||||
s.push(c);
|
||||
ControlFlow::Continue(s)
|
||||
} else {
|
||||
ControlFlow::Break(s)
|
||||
}
|
||||
});
|
||||
lua.create_function(|_, (text, t): (mlua::String, Table)| {
|
||||
let (max, text) = (t.raw_get("max")?, text.to_string_lossy());
|
||||
|
||||
Ok(match flow {
|
||||
ControlFlow::Break(s) => s,
|
||||
ControlFlow::Continue(s) => s,
|
||||
Ok(if t.raw_get("rtl").unwrap_or(false) {
|
||||
Self::truncate(text.chars().rev(), max).into_iter().rev().collect()
|
||||
} else {
|
||||
Self::truncate(text.chars(), max).into_iter().collect::<String>()
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn truncate(mut chars: impl Iterator<Item = char>, max: usize) -> Vec<char> {
|
||||
let mut width = 0;
|
||||
let flow = chars.try_fold(Vec::with_capacity(max), |mut v, c| {
|
||||
width += c.width().unwrap_or(0);
|
||||
if width < max {
|
||||
v.push(c);
|
||||
ControlFlow::Continue(v)
|
||||
} else {
|
||||
ControlFlow::Break(v)
|
||||
}
|
||||
});
|
||||
|
||||
match flow {
|
||||
ControlFlow::Break(v) => v,
|
||||
ControlFlow::Continue(v) => v,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ pub(super) struct Utils;
|
|||
pub fn install(lua: &mlua::Lua) -> mlua::Result<()> {
|
||||
let ya: mlua::Table = lua.create_table()?;
|
||||
|
||||
Utils::app(lua, &ya)?;
|
||||
Utils::cache(lua, &ya)?;
|
||||
Utils::call(lua, &ya)?;
|
||||
Utils::image(lua, &ya)?;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "yazi-proxy"
|
||||
version = "0.2.3"
|
||||
version = "0.2.4"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
authors = [ "sxyazi <sxyazi@gmail.com>" ]
|
||||
|
|
@ -9,8 +9,8 @@ homepage = "https://yazi-rs.github.io"
|
|||
repository = "https://github.com/sxyazi/yazi"
|
||||
|
||||
[dependencies]
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.3" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.3" }
|
||||
yazi-config = { path = "../yazi-config", version = "0.2.4" }
|
||||
yazi-shared = { path = "../yazi-shared", version = "0.2.4" }
|
||||
|
||||
# External dependencies
|
||||
anyhow = "^1"
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue