Merge branch 'main' into feat/independent-style-on-inner-section-of-status

This commit is contained in:
Anton Simonov 2024-03-09 17:38:11 +03:00
commit 408248d620
116 changed files with 1394 additions and 978 deletions

View file

@ -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
View file

@ -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",

View file

@ -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"]}

View file

@ -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"

View file

@ -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)
}
}

View 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))
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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" ] }

View file

@ -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,

View file

@ -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);
}

View file

@ -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"

View file

@ -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" }
]

View file

@ -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,15 +55,14 @@ syntect_theme = ""
[status]
separator_open = ""
separator_close = ""
# remove separator_style? probably in use on cand.rs:32
separator_style = { fg = "darkgray", bg = "darkgray" }
separator_style = { fg = "gray", bg = "gray" }
inner_section = { fg = "black", bg = "darkgray", bold = true }
# 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 }
@ -71,10 +70,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" }
# : }}}
@ -84,7 +83,7 @@ permissions_s = { fg = "darkgray" }
[select]
border = { fg = "blue" }
active = { fg = "magenta" }
active = { fg = "magenta", bold = true }
inactive = {}
# : }}}
@ -105,7 +104,7 @@ selected = { reversed = true }
[completion]
border = { fg = "blue" }
active = { bg = "darkgray" }
active = { reversed = true }
inactive = {}
# Icons
@ -121,7 +120,7 @@ icon_command = ""
[tasks]
border = { fg = "blue" }
title = {}
hovered = { underline = true }
hovered = { fg = "magenta", underline = true }
# : }}}
@ -133,7 +132,7 @@ cols = 3
mask = { bg = "black" }
cand = { fg = "lightcyan" }
rest = { fg = "darkgray" }
desc = { fg = "magenta" }
desc = { fg = "lightmagenta" }
separator = "  "
separator_style = { fg = "darkgray" }
@ -143,10 +142,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" }
# : }}}
@ -175,7 +174,7 @@ rules = [
# Images
{ mime = "image/*", fg = "cyan" },
# Videos
# Media
{ mime = "video/*", fg = "yellow" },
{ mime = "audio/*", fg = "yellow" },

View file

@ -192,3 +192,5 @@ sort_reverse = false
[log]
enabled = false
[headsup]

View 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 })
}
}

View file

@ -0,0 +1,3 @@
mod headsup;
pub use headsup::*;

View file

@ -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]`"));
};

View file

@ -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.
"#
);
}
}

View file

@ -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)]

View file

@ -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

View file

@ -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]`"));
};

View file

@ -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 {

View file

@ -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"

View file

@ -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

View file

@ -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!(
"{}{}",

View file

@ -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,

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View 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(())
}
}

View file

@ -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(()),

View file

@ -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 {

View file

@ -1,3 +1,4 @@
mod bulk_rename;
mod close;
mod create;
mod hover;

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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(())
}
}

View file

@ -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();
}
}

View file

@ -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 }
}

View file

@ -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() {

View file

@ -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);
}
}

View file

@ -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])

View file

@ -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;

View file

@ -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!();
}

View file

@ -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() }

View file

@ -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)),

View file

@ -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)]

View file

@ -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,

View file

@ -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;
}

View file

@ -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' {

View file

@ -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;

View file

@ -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() }
}

View file

@ -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"

View file

@ -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) {

View file

@ -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),

View file

@ -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);

View file

@ -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(

View file

@ -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
View 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))
}
}

View file

@ -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)?),
])?,
)?;

View file

@ -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::*;

View file

@ -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 } }
}

View file

@ -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 })
}

View file

@ -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 } }
}

View file

@ -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

View file

@ -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(

View file

@ -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,
}

View file

@ -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()

View file

@ -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;
}

View file

@ -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)

View file

@ -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 {

View file

@ -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);
}
}
}
}

View file

@ -1,3 +0,0 @@
mod clear;
pub(super) use clear::*;

View file

@ -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"

View file

@ -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

View file

@ -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::*;

View 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()));
}
}

View file

@ -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;

View file

@ -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)])?));

View file

@ -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)])?));

View 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 })
}

View file

@ -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)?;

View file

@ -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)])?));

View file

@ -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::*;

View file

@ -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)])?));

View file

@ -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>,

View file

@ -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),
}
})?,
),
])?,

View file

@ -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(); }

View file

@ -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
View 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();
}
}

View file

@ -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);
}

View file

@ -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),
});
}
}

View file

@ -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),
}
});
}
}

View 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(())
}
}

View file

@ -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 {

View file

@ -1,5 +1,6 @@
#![allow(clippy::module_inception)]
mod app;
mod cache;
mod call;
mod image;

View file

@ -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()
})?))
},
)?,
)?;

View file

@ -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,
}
}
}

View file

@ -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)?;

View file

@ -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