Merge branch 'main' into preview-word-wrap

This commit is contained in:
Artyom Artamonov 2024-06-22 01:44:43 +05:00
commit c078fb8513
42 changed files with 404 additions and 289 deletions

85
Cargo.lock generated
View File

@ -218,6 +218,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block2"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
dependencies = [
"objc2",
]
[[package]]
name = "bstr"
version = "1.9.1"
@ -970,9 +979,9 @@ dependencies = [
[[package]]
name = "imagesize"
version = "0.12.0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d73f573d8e8d63e6d5020011d3255b28c3ba85d6cf870a07184ed23de9284"
checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "indexmap"
@ -1150,15 +1159,6 @@ dependencies = [
"which",
]
[[package]]
name = "malloc_buf"
version = "0.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb"
dependencies = [
"libc",
]
[[package]]
name = "md-5"
version = "0.10.6"
@ -1205,9 +1205,9 @@ dependencies = [
[[package]]
name = "mlua"
version = "0.9.8"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e340c022072f3208a4105458286f4985ba5355bfe243c3073afe45cbe9ecf491"
checksum = "d111deb18a9c9bd33e1541309f4742523bfab01d276bfa9a27519f6de9c11dc7"
dependencies = [
"bstr",
"erased-serde",
@ -1223,9 +1223,9 @@ dependencies = [
[[package]]
name = "mlua-sys"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5552e7e4e22ada0463dfdeee6caf6dc057a189fdc83136408a8f950a5e5c5540"
checksum = "a088ed0723df7567f569ba018c5d48c23c501f3878b190b04144dfa5ebfa8abc"
dependencies = [
"cc",
"cfg-if",
@ -1328,12 +1328,37 @@ dependencies = [
]
[[package]]
name = "objc"
version = "0.2.7"
name = "objc-sys"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1"
checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310"
[[package]]
name = "objc2"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804"
dependencies = [
"malloc_buf",
"objc-sys",
"objc2-encode",
]
[[package]]
name = "objc2-encode"
version = "4.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8"
[[package]]
name = "objc2-foundation"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8"
dependencies = [
"bitflags 2.5.0",
"block2",
"libc",
"objc2",
]
[[package]]
@ -1652,9 +1677,9 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]]
name = "rustc-hash"
version = "1.1.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
[[package]]
name = "rustix"
@ -2210,17 +2235,18 @@ dependencies = [
[[package]]
name = "trash"
version = "4.1.1"
version = "5.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c254b119cf49bdde3dfef21b1dc492dc8026b75566ca24aa77993eccd7cbc1b5"
checksum = "8d8fbfb70b1fad5c0b788f9b2e1bf4d04e5ac6efa828f1ed9ee462c50ff9cf05"
dependencies = [
"chrono",
"libc",
"log",
"objc",
"objc2",
"objc2-foundation",
"once_cell",
"scopeguard",
"url",
"urlencoding",
"windows",
]
@ -2284,6 +2310,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "urlencoding"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "utf8parse"
version = "0.2.1"
@ -2758,7 +2790,6 @@ dependencies = [
"indexmap",
"ratatui",
"serde",
"shell-words",
"toml",
"validator",
"yazi-shared",
@ -2924,7 +2955,6 @@ dependencies = [
"bitflags 2.5.0",
"crossterm",
"dirs",
"filetime",
"futures",
"libc",
"parking_lot",
@ -2934,7 +2964,6 @@ dependencies = [
"serde",
"shell-words",
"tokio",
"tracing",
]
[[package]]

View File

@ -13,15 +13,15 @@ yazi-config = { path = "../yazi-config", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
ansi-to-tui = "3.1.0"
ansi-to-tui = "=3.1.0"
anyhow = "1.0.86"
arc-swap = "1.7.1"
base64 = "0.22.1"
color_quant = "1.1.0"
crossterm = "0.27.0"
futures = "0.3.30"
image = "0.24.9"
imagesize = "0.12.0"
image = "=0.24.9"
imagesize = "0.13.0"
kamadak-exif = "0.5.5"
ratatui = "0.26.3"
scopeguard = "1.2.0"

View File

@ -37,6 +37,10 @@ impl Display for Adapter {
impl Adapter {
pub async fn image_show(self, path: &Path, max: Rect) -> Result<Rect> {
if max.is_empty() {
return Ok(Rect::default());
}
match self {
Self::Kitty => Kitty::image_show(path, max).await,
Self::KittyOld => KittyOld::image_show(path, max).await,

View File

@ -22,6 +22,8 @@ pub(super) enum Command {
PubStatic(CommandPubStatic),
/// Manage packages.
Pack(CommandPack),
/// Subscribe to messages from all remote instances.
Sub(CommandSub),
}
#[derive(clap::Args)]
@ -109,3 +111,10 @@ pub(super) struct CommandPack {
#[arg(short = 'u', long)]
pub(super) upgrade: bool,
}
#[derive(clap::Args)]
pub(super) struct CommandSub {
/// The kind of messages to subscribe to, separated by commas if multiple.
#[arg(index = 1)]
pub(super) kinds: String,
}

View File

@ -46,6 +46,13 @@ async fn main() -> anyhow::Result<()> {
package::Package::add_to_config(repo).await?;
}
}
Command::Sub(cmd) => {
yazi_dds::init();
yazi_dds::Client::draw(cmd.kinds.split(',').collect()).await?;
tokio::signal::ctrl_c().await?;
}
}
Ok(())

View File

@ -20,6 +20,5 @@ globset = "0.4.14"
indexmap = "2.2.6"
ratatui = "0.26.3"
serde = { version = "1.0.203", features = [ "derive" ] }
shell-words = "1.1.0"
toml = { version = "0.8.14", features = [ "preserve_order" ] }
validator = { version = "0.18.1", features = [ "derive" ] }

View File

@ -30,8 +30,8 @@ ueberzug_offset = [ 0, 0, 0, 0 ]
[opener]
edit = [
{ run = '${EDITOR:=vi} "$@"', desc = "$EDITOR", block = true, for = "unix" },
{ run = 'code "%*"', orphan = true, desc = "code", for = "windows" },
{ run = 'code -w "%*"', block = true, desc = "code (block)", for = "windows" },
{ run = 'code %*', orphan = true, desc = "code", for = "windows" },
{ run = 'code -w %*', block = true, desc = "code (block)", for = "windows" },
]
open = [
{ run = 'xdg-open "$1"', desc = "Open", for = "linux" },

View File

@ -1,8 +1,10 @@
use std::str::FromStr;
use serde::{Deserialize, Deserializer};
use yazi_shared::Layer;
use super::Control;
use crate::{Preset, MERGED_KEYMAP};
use crate::Preset;
#[derive(Debug)]
pub struct Keymap {
@ -14,6 +16,28 @@ pub struct Keymap {
pub completion: Vec<Control>,
}
impl Keymap {
#[inline]
pub fn get(&self, layer: Layer) -> &Vec<Control> {
match layer {
Layer::App => unreachable!(),
Layer::Manager => &self.manager,
Layer::Tasks => &self.tasks,
Layer::Select => &self.select,
Layer::Input => &self.input,
Layer::Help => &self.help,
Layer::Completion => &self.completion,
Layer::Which => unreachable!(),
}
}
}
impl FromStr for Keymap {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
}
impl<'de> Deserialize<'de> for Keymap {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@ -62,23 +86,3 @@ impl<'de> Deserialize<'de> for Keymap {
})
}
}
impl Default for Keymap {
fn default() -> Self { toml::from_str(&MERGED_KEYMAP).unwrap() }
}
impl Keymap {
#[inline]
pub fn get(&self, layer: Layer) -> &Vec<Control> {
match layer {
Layer::App => unreachable!(),
Layer::Manager => &self.manager,
Layer::Tasks => &self.tasks,
Layer::Select => &self.select,
Layer::Input => &self.input,
Layer::Help => &self.help,
Layer::Completion => &self.completion,
Layer::Which => unreachable!(),
}
}
}

View File

@ -1,5 +1,7 @@
#![allow(clippy::module_inception)]
use std::str::FromStr;
use yazi_shared::{RoCell, Xdg};
pub mod keymap;
@ -15,7 +17,6 @@ pub mod preview;
mod priority;
mod tasks;
pub mod theme;
mod validation;
pub mod which;
pub use layout::*;
@ -23,10 +24,6 @@ pub(crate) use pattern::*;
pub(crate) use preset::*;
pub use priority::*;
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 KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
@ -43,23 +40,23 @@ pub static WHICH: RoCell<which::Which> = RoCell::new();
pub fn init() -> anyhow::Result<()> {
let config_dir = Xdg::config_dir();
MERGED_YAZI.init(Preset::yazi(&config_dir)?);
MERGED_KEYMAP.init(Preset::keymap(&config_dir)?);
MERGED_THEME.init(Preset::theme(&config_dir)?);
let yazi_toml = &Preset::yazi(&config_dir)?;
let keymap_toml = &Preset::keymap(&config_dir)?;
let theme_toml = &Preset::theme(&config_dir)?;
LAYOUT.with(Default::default);
KEYMAP.with(Default::default);
LOG.with(Default::default);
MANAGER.with(Default::default);
OPEN.with(Default::default);
PLUGIN.with(Default::default);
PREVIEW.with(Default::default);
TASKS.with(Default::default);
THEME.with(Default::default);
INPUT.with(Default::default);
SELECT.with(Default::default);
WHICH.with(Default::default);
KEYMAP.init(<_>::from_str(keymap_toml)?);
LOG.init(<_>::from_str(yazi_toml)?);
MANAGER.init(<_>::from_str(yazi_toml)?);
OPEN.init(<_>::from_str(yazi_toml)?);
PLUGIN.init(<_>::from_str(yazi_toml)?);
PREVIEW.init(<_>::from_str(yazi_toml)?);
TASKS.init(<_>::from_str(yazi_toml)?);
THEME.init(<_>::from_str(theme_toml)?);
INPUT.init(<_>::from_str(yazi_toml)?);
SELECT.init(<_>::from_str(yazi_toml)?);
WHICH.init(<_>::from_str(yazi_toml)?);
Ok(())
}

View File

@ -1,14 +1,16 @@
use serde::{Deserialize, Deserializer};
use std::str::FromStr;
use crate::MERGED_YAZI;
use serde::{Deserialize, Deserializer};
#[derive(Debug)]
pub struct Log {
pub enabled: bool,
}
impl Default for Log {
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() }
impl FromStr for Log {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
}
impl<'de> Deserialize<'de> for Log {

View File

@ -1,8 +1,9 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use validator::Validate;
use super::{ManagerRatio, MouseEvents, SortBy};
use crate::{validation::check_validation, MERGED_YAZI};
#[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Manager {
@ -24,16 +25,18 @@ pub struct Manager {
pub mouse_events: MouseEvents,
}
impl Default for Manager {
fn default() -> Self {
impl FromStr for Manager {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
manager: Manager,
}
let manager = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().manager;
let manager = toml::from_str::<Outer>(s)?.manager;
manager.validate()?;
check_validation(manager.validate());
manager
Ok(manager)
}
}

View File

@ -16,7 +16,6 @@ bitflags! {
}
impl MouseEvents {
#[inline]
pub const fn draggable(self) -> bool { self.contains(Self::DRAG) }
}

View File

@ -1,11 +1,11 @@
use std::{collections::HashMap, path::Path};
use std::{collections::HashMap, path::Path, str::FromStr};
use indexmap::IndexSet;
use serde::{Deserialize, Deserializer};
use yazi_shared::MIME_DIR;
use super::Opener;
use crate::{open::OpenRule, Preset, MERGED_YAZI};
use crate::{open::OpenRule, Preset};
#[derive(Debug)]
pub struct Open {
@ -13,10 +13,6 @@ pub struct Open {
openers: HashMap<String, IndexSet<Opener>>,
}
impl Default for Open {
fn default() -> Self { toml::from_str(&MERGED_YAZI).unwrap() }
}
impl Open {
pub fn openers<P, M>(&self, path: P, mime: M) -> Option<IndexSet<&Opener>>
where
@ -58,6 +54,12 @@ impl Open {
}
}
impl FromStr for Open {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> { toml::from_str(s) }
}
impl<'de> Deserialize<'de> for Open {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where

View File

@ -6,7 +6,9 @@ use crate::{Pattern, Priority};
#[derive(Debug, Deserialize)]
pub struct Fetcher {
#[serde(skip)]
pub id: u8,
pub idx: u8,
pub id: String,
pub cond: Option<Condition>,
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
@ -24,6 +26,6 @@ pub struct FetcherProps {
impl From<&Fetcher> for FetcherProps {
fn from(fetcher: &Fetcher) -> Self {
Self { id: fetcher.id, name: fetcher.run.name.to_owned(), prio: fetcher.prio }
Self { id: fetcher.idx, name: fetcher.run.name.to_owned(), prio: fetcher.prio }
}
}

View File

@ -1,10 +1,10 @@
use std::path::Path;
use std::{path::Path, str::FromStr};
use serde::Deserialize;
use yazi_shared::MIME_DIR;
use super::{Fetcher, Preloader, Previewer};
use crate::{plugin::MAX_PREWORKERS, Preset, MERGED_YAZI};
use crate::{plugin::MAX_PREWORKERS, Preset};
#[derive(Deserialize)]
pub struct Plugin {
@ -13,65 +13,6 @@ pub struct Plugin {
pub previewers: Vec<Previewer>,
}
impl Default for Plugin {
fn default() -> Self {
#[derive(Deserialize)]
struct Outer {
plugin: Shadow,
}
#[derive(Deserialize)]
struct Shadow {
fetchers: Vec<Fetcher>,
#[serde(default)]
prepend_fetchers: Vec<Fetcher>,
#[serde(default)]
append_fetchers: Vec<Fetcher>,
preloaders: Vec<Preloader>,
#[serde(default)]
prepend_preloaders: Vec<Preloader>,
#[serde(default)]
append_preloaders: Vec<Preloader>,
previewers: Vec<Previewer>,
#[serde(default)]
prepend_previewers: Vec<Previewer>,
#[serde(default)]
append_previewers: Vec<Previewer>,
}
let mut shadow = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().plugin;
if shadow.append_previewers.iter().any(|r| r.any_file()) {
shadow.previewers.retain(|r| !r.any_file());
}
if shadow.append_previewers.iter().any(|r| r.any_dir()) {
shadow.previewers.retain(|r| !r.any_dir());
}
Preset::mix(&mut shadow.fetchers, shadow.prepend_fetchers, shadow.append_fetchers);
Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders);
Preset::mix(&mut shadow.previewers, shadow.prepend_previewers, shadow.append_previewers);
if shadow.fetchers.len() + shadow.preloaders.len() > MAX_PREWORKERS as usize {
panic!("Fetchers and preloaders exceed the limit of {MAX_PREWORKERS}");
}
for (i, p) in shadow.fetchers.iter_mut().enumerate() {
p.id = i as u8;
}
for (i, p) in shadow.preloaders.iter_mut().enumerate() {
p.id = shadow.fetchers.len() as u8 + i as u8;
}
Self {
fetchers: shadow.fetchers,
preloaders: shadow.preloaders,
previewers: shadow.previewers,
}
}
}
impl Plugin {
pub fn fetchers(
&self,
@ -118,3 +59,63 @@ impl Plugin {
})
}
}
impl FromStr for Plugin {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
plugin: Shadow,
}
#[derive(Deserialize)]
struct Shadow {
fetchers: Vec<Fetcher>,
#[serde(default)]
prepend_fetchers: Vec<Fetcher>,
#[serde(default)]
append_fetchers: Vec<Fetcher>,
preloaders: Vec<Preloader>,
#[serde(default)]
prepend_preloaders: Vec<Preloader>,
#[serde(default)]
append_preloaders: Vec<Preloader>,
previewers: Vec<Previewer>,
#[serde(default)]
prepend_previewers: Vec<Previewer>,
#[serde(default)]
append_previewers: Vec<Previewer>,
}
let mut shadow = toml::from_str::<Outer>(s)?.plugin;
if shadow.append_previewers.iter().any(|r| r.any_file()) {
shadow.previewers.retain(|r| !r.any_file());
}
if shadow.append_previewers.iter().any(|r| r.any_dir()) {
shadow.previewers.retain(|r| !r.any_dir());
}
Preset::mix(&mut shadow.fetchers, shadow.prepend_fetchers, shadow.append_fetchers);
Preset::mix(&mut shadow.preloaders, shadow.prepend_preloaders, shadow.append_preloaders);
Preset::mix(&mut shadow.previewers, shadow.prepend_previewers, shadow.append_previewers);
if shadow.fetchers.len() + shadow.preloaders.len() > MAX_PREWORKERS as usize {
panic!("Fetchers and preloaders exceed the limit of {MAX_PREWORKERS}");
}
for (i, p) in shadow.fetchers.iter_mut().enumerate() {
p.idx = i as u8;
}
for (i, p) in shadow.preloaders.iter_mut().enumerate() {
p.idx = shadow.fetchers.len() as u8 + i as u8;
}
Ok(Self {
fetchers: shadow.fetchers,
preloaders: shadow.preloaders,
previewers: shadow.previewers,
})
}
}

View File

@ -6,7 +6,8 @@ use crate::{Pattern, Priority};
#[derive(Debug, Deserialize)]
pub struct Preloader {
#[serde(skip)]
pub id: u8,
pub idx: u8,
pub name: Option<Pattern>,
pub mime: Option<Pattern>,
pub run: Cmd,
@ -25,6 +26,6 @@ pub struct PreloaderProps {
impl From<&Preloader> for PreloaderProps {
fn from(preloader: &Preloader) -> Self {
Self { id: preloader.id, name: preloader.run.name.to_owned(), prio: preloader.prio }
Self { id: preloader.idx, name: preloader.run.name.to_owned(), prio: preloader.prio }
}
}

View File

@ -1,7 +1,8 @@
use std::str::FromStr;
use serde::Deserialize;
use super::{Offset, Origin};
use crate::MERGED_YAZI;
#[derive(Deserialize)]
pub struct Input {
@ -63,18 +64,19 @@ pub struct Input {
pub quit_offset: Offset,
}
impl Default for Input {
fn default() -> Self {
impl Input {
pub const fn border(&self) -> u16 { 2 }
}
impl FromStr for Input {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
input: Input,
}
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().input
Ok(toml::from_str::<Outer>(s)?.input)
}
}
impl Input {
#[inline]
pub const fn border(&self) -> u16 { 2 }
}

View File

@ -1,7 +1,8 @@
use std::str::FromStr;
use serde::Deserialize;
use super::{Offset, Origin};
use crate::MERGED_YAZI;
#[derive(Deserialize)]
pub struct Select {
@ -11,18 +12,19 @@ pub struct Select {
pub open_offset: Offset,
}
impl Default for Select {
fn default() -> Self {
impl Select {
pub const fn border(&self) -> u16 { 2 }
}
impl FromStr for Select {
type Err = toml::de::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
select: Select,
}
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().select
Ok(toml::from_str::<Outer>(s)?.select)
}
}
impl Select {
#[inline]
pub const fn border(&self) -> u16 { 2 }
}

View File

@ -1,4 +1,4 @@
use std::{mem, path::{Path, PathBuf}};
use std::{borrow::Cow, mem, path::{Path, PathBuf}};
use anyhow::{anyhow, Context, Result};
use toml::{Table, Value};
@ -8,17 +8,17 @@ use crate::theme::Flavor;
pub(crate) struct Preset;
impl Preset {
pub(crate) fn yazi(p: &Path) -> Result<String> {
pub(crate) fn yazi(p: &Path) -> Result<Cow<str>> {
Self::merge_path(p.join("yazi.toml"), include_str!("../preset/yazi.toml"))
}
pub(crate) fn keymap(p: &Path) -> Result<String> {
pub(crate) fn keymap(p: &Path) -> Result<Cow<str>> {
Self::merge_path(p.join("keymap.toml"), include_str!("../preset/keymap.toml"))
}
pub(crate) fn theme(p: &Path) -> Result<String> {
pub(crate) fn theme(p: &Path) -> Result<Cow<str>> {
let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else {
return Ok(include_str!("../preset/theme.toml").to_owned());
return Ok(include_str!("../preset/theme.toml").into());
};
let Some(use_) = Flavor::parse_use(&user) else {
return Self::merge_str(&user, include_str!("../preset/theme.toml"));
@ -37,18 +37,18 @@ impl Preset {
}
#[inline]
pub(crate) fn merge_str(user: &str, base: &str) -> Result<String> {
pub(crate) fn merge_str(user: &str, base: &str) -> Result<Cow<'static, str>> {
let mut t = user.parse()?;
Self::merge(&mut t, base.parse()?, 2);
Ok(t.to_string())
Ok(t.to_string().into())
}
#[inline]
fn merge_path(user: PathBuf, base: &str) -> Result<String> {
fn merge_path(user: PathBuf, base: &str) -> Result<Cow<str>> {
let s = std::fs::read_to_string(&user).unwrap_or_default();
if s.is_empty() {
return Ok(base.to_string());
return Ok(base.into());
}
Self::merge_str(&s, base).with_context(|| anyhow!("Loading {user:?}"))

View File

@ -1,10 +1,11 @@
use std::{path::PathBuf, time::{self, SystemTime}};
use std::{path::PathBuf, str::FromStr, time::{self, SystemTime}};
use anyhow::Context;
use serde::{Deserialize, Serialize};
use validator::Validate;
use yazi_shared::fs::expand_path;
use crate::{validation::check_validation, Xdg, MERGED_YAZI};
use crate::Xdg;
#[derive(Debug, Serialize)]
pub struct Preview {
@ -23,8 +24,18 @@ pub struct Preview {
pub ueberzug_offset: (f32, f32, f32, f32),
}
impl Default for Preview {
fn default() -> Self {
impl Preview {
#[inline]
pub fn tmpfile(&self, prefix: &str) -> PathBuf {
let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos();
self.cache_dir.join(format!("{prefix}-{}", nanos / 1000))
}
}
impl FromStr for Preview {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
preview: Shadow,
@ -48,14 +59,14 @@ impl Default for Preview {
ueberzug_offset: (f32, f32, f32, f32),
}
let preview = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().preview;
check_validation(preview.validate());
let preview = toml::from_str::<Outer>(s)?.preview;
preview.validate()?;
let cache_dir =
preview.cache_dir.filter(|p| !p.is_empty()).map_or_else(Xdg::cache_dir, expand_path);
std::fs::create_dir_all(&cache_dir).expect("Failed to create cache directory");
std::fs::create_dir_all(&cache_dir).context("Failed to create cache directory")?;
Preview {
Ok(Preview {
tab_size: preview.tab_size,
word_wrap: preview.word_wrap,
max_width: preview.max_width,
@ -69,14 +80,6 @@ impl Default for Preview {
ueberzug_scale: preview.ueberzug_scale,
ueberzug_offset: preview.ueberzug_offset,
}
}
}
impl Preview {
#[inline]
pub fn tmpfile(&self, prefix: &str) -> PathBuf {
let nanos = SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap().as_nanos();
self.cache_dir.join(format!("{prefix}-{}", nanos / 1000))
})
}
}

View File

@ -1,8 +1,8 @@
use std::str::FromStr;
use serde::Deserialize;
use validator::Validate;
use crate::{validation::check_validation, MERGED_YAZI};
#[derive(Debug, Deserialize, Validate)]
pub struct Tasks {
#[validate(range(min = 1, message = "Cannot be less than 1"))]
@ -18,16 +18,18 @@ pub struct Tasks {
pub suppress_preload: bool,
}
impl Default for Tasks {
fn default() -> Self {
impl FromStr for Tasks {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
tasks: Tasks,
}
let tasks = toml::from_str::<Outer>(&MERGED_YAZI).unwrap().tasks;
check_validation(tasks.validate());
let tasks = toml::from_str::<Outer>(s)?.tasks;
tasks.validate()?;
tasks
Ok(tasks)
}
}

View File

@ -1,11 +1,10 @@
use std::path::PathBuf;
use std::{path::PathBuf, str::FromStr};
use serde::{Deserialize, Serialize};
use validator::Validate;
use yazi_shared::{fs::expand_path, theme::Style, Xdg};
use super::{Filetype, Flavor, Icons};
use crate::{validation::check_validation, MERGED_THEME};
#[derive(Deserialize, Serialize)]
pub struct Theme {
@ -27,12 +26,13 @@ pub struct Theme {
pub icons: Icons,
}
impl Default for Theme {
fn default() -> Self {
let mut theme: Self = toml::from_str(&MERGED_THEME).unwrap();
impl FromStr for Theme {
type Err = anyhow::Error;
check_validation(theme.manager.validate());
check_validation(theme.which.validate());
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut theme: Self = toml::from_str(s)?;
theme.manager.validate()?;
theme.which.validate()?;
if theme.flavor.use_.is_empty() {
theme.manager.syntect_theme = expand_path(&theme.manager.syntect_theme);
@ -41,7 +41,7 @@ impl Default for Theme {
Xdg::config_dir().join(format!("flavors/{}.yazi/tmtheme.xml", theme.flavor.use_));
}
theme
Ok(theme)
}
}

View File

@ -1,29 +0,0 @@
use std::{borrow::Cow, process};
use validator::{ValidationErrors, ValidationErrorsKind};
pub fn check_validation(res: Result<(), ValidationErrors>) {
let Err(errors) = res else { return };
for (field, kind) in errors.into_errors() {
match kind {
ValidationErrorsKind::Struct(errors) => check_validation(Err(*errors)),
ValidationErrorsKind::List(errors) => {
for (i, errors) in errors {
eprint!("Config `{field}[{i}]` format error: ");
check_validation(Err(*errors));
eprintln!();
}
}
ValidationErrorsKind::Field(error) => {
for e in error {
eprintln!(
"Config `{field}` format error: {}\n",
e.message.unwrap_or(Cow::Borrowed("unknown error"))
);
}
}
}
}
process::exit(1);
}

View File

@ -1,8 +1,9 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use validator::Validate;
use super::SortBy;
use crate::MERGED_YAZI;
#[derive(Debug, Deserialize, Serialize, Validate)]
pub struct Which {
@ -13,13 +14,15 @@ pub struct Which {
pub sort_translit: bool,
}
impl Default for Which {
fn default() -> Self {
impl FromStr for Which {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
#[derive(Deserialize)]
struct Outer {
which: Which,
}
toml::from_str::<Outer>(&MERGED_YAZI).unwrap().which
Ok(toml::from_str::<Outer>(s)?.which)
}
}

View File

@ -19,11 +19,11 @@ impl Tasks {
for p in PLUGIN.fetchers(&f.url, mime, factors) {
match loaded.get_mut(&f.url) {
Some(n) if *n & (1 << p.id) != 0 => continue,
Some(n) => *n |= 1 << p.id,
None => _ = loaded.insert(f.url.clone(), 1 << p.id),
Some(n) if *n & (1 << p.idx) != 0 => continue,
Some(n) => *n |= 1 << p.idx,
None => _ = loaded.insert(f.url.clone(), 1 << p.idx),
}
tasks[p.id as usize].push(f.clone());
tasks[p.idx as usize].push(f.clone());
}
}
@ -41,9 +41,9 @@ impl Tasks {
let mime = if f.is_dir() { Some(MIME_DIR) } else { mimetype.get(&f.url).map(|s| &**s) };
for p in PLUGIN.preloaders(&f.url, mime) {
match loaded.get_mut(&f.url) {
Some(n) if *n & (1 << p.id) != 0 => continue,
Some(n) => *n |= 1 << p.id,
None => _ = loaded.insert(f.url.clone(), 1 << p.id),
Some(n) if *n & (1 << p.idx) != 0 => continue,
Some(n) => *n |= 1 << p.idx,
None => _ = loaded.insert(f.url.clone(), 1 << p.idx),
}
self.scheduler.preload_paged(p, f);
}

View File

@ -18,7 +18,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
mlua = { version = "0.9.8", features = [ "lua54" ] }
mlua = { version = "0.9.9", features = [ "lua54" ] }
parking_lot = "0.12.3"
serde = { version = "1.0.203", features = [ "derive" ] }
serde_json = "1.0.117"

View File

@ -5,15 +5,17 @@ use serde::{Deserialize, Serialize};
use super::Body;
/// The client handshake
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyHi<'a> {
pub abilities: HashSet<Cow<'a, String>>,
/// Specifies the kinds of events that the client can handle
pub abilities: HashSet<Cow<'a, str>>,
pub version: String,
}
impl<'a> BodyHi<'a> {
#[inline]
pub fn borrowed(abilities: HashSet<&'a String>) -> Body<'a> {
pub fn borrowed(abilities: HashSet<&'a str>) -> Body<'a> {
Self {
abilities: abilities.into_iter().map(Cow::Borrowed).collect(),
version: Self::version(),

View File

@ -1,6 +1,6 @@
use std::{collections::{HashMap, HashSet}, mem, str::FromStr};
use anyhow::{bail, Result};
use anyhow::{bail, Context, Result};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time};
@ -27,6 +27,7 @@ pub struct Peer {
}
impl Client {
/// Connect to an existing server or start a new one.
pub(super) fn serve() {
let mut rx = QUEUE_RX.drop();
while rx.try_recv().is_ok() {}
@ -52,7 +53,7 @@ impl Client {
if line.is_empty() {
continue;
} else if line.starts_with("hey,") {
Self::handle_hey(line);
Self::handle_hey(&line);
} else {
Payload::from_str(&line).map(|p| p.emit()).ok();
}
@ -62,6 +63,7 @@ impl Client {
});
}
/// Connect to an existing server to send a single message.
pub async fn shot(kind: &str, receiver: u64, severity: Option<u16>, body: &str) -> Result<()> {
Body::validate(kind)?;
@ -100,6 +102,40 @@ impl Client {
Ok(())
}
/// Connect to an existing server and listen in on the messages that are being
/// sent by other yazi instances:
/// - If no server is running, fail right away;
/// - If a server is closed, attempt to reconnect forever.
pub async fn draw(kinds: HashSet<&str>) -> Result<()> {
async fn make(kinds: &HashSet<&str>) -> Result<ClientReader> {
let (lines, mut writer) = Stream::connect().await?;
let hi = Payload::new(BodyHi::borrowed(kinds.clone()));
writer.write_all(format!("{hi}\n").as_bytes()).await?;
writer.flush().await?;
Ok(lines)
}
let mut lines = make(&kinds).await.context("No running Yazi instance found")?;
loop {
match lines.next_line().await? {
Some(s) => {
let kind = s.split(',').next();
if matches!(kind, Some(kind) if kinds.contains(kind)) {
println!("{s}");
}
}
None => loop {
if let Ok(new) = make(&kinds).await {
lines = new;
break;
} else {
time::sleep(time::Duration::from_secs(1)).await;
}
},
}
}
}
#[inline]
pub(super) fn push<'a>(payload: impl Into<Payload<'a>>) {
QUEUE_TX.send(format!("{}\n", payload.into())).ok();
@ -136,8 +172,8 @@ impl Client {
Self::connect(server).await
}
fn handle_hey(s: String) {
if let Ok(Body::Hey(mut hey)) = Payload::from_str(&s).map(|p| p.body) {
fn handle_hey(s: &str) {
if let Ok(Body::Hey(mut hey)) = Payload::from_str(s).map(|p| p.body) {
hey.peers.retain(|&id, _| id != *ID);
*PEERS.write() = hey.peers;
}

View File

@ -88,7 +88,7 @@ impl Pubsub {
pub fn pub_from_hi() -> bool {
let abilities = REMOTE.read().keys().cloned().collect();
let abilities = BOOT.remote_events.union(&abilities).collect();
let abilities = BOOT.remote_events.union(&abilities).map(|s| s.as_str()).collect();
Client::push(BodyHi::borrowed(abilities));
true

View File

@ -28,7 +28,7 @@ better-panic = "0.3.0"
crossterm = { version = "0.27.0", features = [ "event-stream" ] }
fdlimit = "0.3.0"
futures = "0.3.30"
mlua = { version = "0.9.8", features = [ "lua54" ] }
mlua = { version = "0.9.9", features = [ "lua54" ] }
ratatui = "0.26.3"
scopeguard = "1.2.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }

View File

@ -21,8 +21,6 @@ impl Filter {
}
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|_| {})?;
Ok(())
lua.register_userdata_type::<Self>(|_| {})
}
}

View File

@ -0,0 +1,26 @@
use std::ops::Deref;
use mlua::{AnyUserData, Lua};
use super::SCOPE;
pub(super) struct Finder {
inner: *const yazi_core::tab::Finder,
}
impl Deref for Finder {
type Target = yazi_core::tab::Finder;
fn deref(&self) -> &Self::Target { unsafe { &*self.inner } }
}
impl Finder {
#[inline]
pub(super) fn make(inner: &yazi_core::tab::Finder) -> mlua::Result<AnyUserData<'static>> {
SCOPE.create_any_userdata(Self { inner })
}
pub(super) fn register(lua: &Lua) -> mlua::Result<()> {
lua.register_userdata_type::<Self>(|_| {})
}
}

View File

@ -19,6 +19,7 @@ impl Lives {
super::File::register(&LUA)?;
super::Files::register(&LUA)?;
super::Filter::register(&LUA)?;
super::Finder::register(&LUA)?;
super::Folder::register(&LUA)?;
super::Mode::register(&LUA)?;
super::Preview::register(&LUA)?;

View File

@ -4,6 +4,7 @@ mod config;
mod file;
mod files;
mod filter;
mod finder;
mod folder;
mod iter;
mod lives;
@ -19,6 +20,7 @@ use config::*;
use file::*;
use files::*;
use filter::*;
use finder::*;
use folder::*;
use iter::*;
pub(super) use lives::*;

View File

@ -2,7 +2,7 @@ use std::ops::Deref;
use mlua::{AnyUserData, Lua, UserDataFields, UserDataMethods};
use super::{Config, Folder, Mode, Preview, Selected, SCOPE};
use super::{Config, Finder, Folder, Mode, Preview, Selected, SCOPE};
pub(super) struct Tab {
inner: *const yazi_core::tab::Tab,
@ -44,6 +44,7 @@ impl Tab {
reg.add_field_method_get("selected", |_, me| Selected::make(&me.selected));
reg.add_field_method_get("preview", |_, me| Preview::make(me));
reg.add_field_method_get("finder", |_, me| me.finder.as_ref().map(Finder::make).transpose());
})?;
Ok(())

View File

@ -21,13 +21,13 @@ yazi-proxy = { path = "../yazi-proxy", version = "0.2.5" }
yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
ansi-to-tui = "3.1.0"
ansi-to-tui = "=3.1.0"
anyhow = "1.0.86"
base64 = "0.22.1"
crossterm = "0.27.0"
futures = "0.3.30"
md-5 = "0.10.6"
mlua = { version = "0.9.8", features = [ "lua54", "serialize", "macros", "async" ] }
mlua = { version = "0.9.9", features = [ "lua54", "serialize", "macros", "async" ] }
parking_lot = "0.12.3"
ratatui = "0.26.3"
serde = "1.0.203"

View File

@ -10,11 +10,12 @@ impl Utils {
pub(super) fn text(lua: &Lua, ya: &Table) -> mlua::Result<()> {
ya.raw_set(
"quote",
lua.create_function(|_, s: mlua::String| {
#[cfg(unix)]
let s = shell_escape::unix::escape(s.to_str()?.into());
#[cfg(windows)]
let s = shell_escape::windows::escape(s.to_str()?.into());
lua.create_function(|_, (s, unix): (mlua::String, Option<bool>)| {
let s = match unix {
Some(true) => shell_escape::unix::escape(s.to_str()?.into()),
Some(false) => shell_escape::windows::escape(s.to_str()?.into()),
None => shell_escape::escape(s.to_str()?.into()),
};
Ok(s.into_owned())
})?,
)?;

View File

@ -18,5 +18,5 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
mlua = { version = "0.9.8", features = [ "lua54" ] }
mlua = { version = "0.9.9", features = [ "lua54" ] }
tokio = { version = "1.38.0", features = [ "full" ] }

View File

@ -30,4 +30,4 @@ tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_lev
libc = "0.2.155"
[target.'cfg(not(target_os = "android"))'.dependencies]
trash = "4.1.1"
trash = "5.0.0"

View File

@ -14,7 +14,6 @@ anyhow = "1.0.86"
bitflags = "2.5.0"
crossterm = "0.27.0"
dirs = "5.0.1"
filetime = "0.2.23"
futures = "0.3.30"
parking_lot = "0.12.3"
percent-encoding = "2.3.1"
@ -24,8 +23,5 @@ serde = { version = "1.0.203", features = [ "derive" ] }
shell-words = "1.1.0"
tokio = { version = "1.38.0", features = [ "full" ] }
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }
[target."cfg(unix)".dependencies]
libc = "0.2.155"

View File

@ -1,7 +1,6 @@
use std::{borrow::Cow, collections::{HashMap, VecDeque}, fs::Metadata, path::{Path, PathBuf}};
use anyhow::Result;
use filetime::{set_file_mtime, FileTime};
use tokio::{fs, io, select, sync::{mpsc, oneshot}, time};
#[inline]
@ -104,12 +103,23 @@ pub fn copy_with_progress(
tokio::spawn({
let (from, to) = (from.to_owned(), to.to_owned());
let mtime = FileTime::from_last_modification_time(meta);
let mut ft = std::fs::FileTimes::new();
meta.accessed().map(|t| ft = ft.set_accessed(t)).ok();
meta.modified().map(|t| ft = ft.set_modified(t)).ok();
#[cfg(target_os = "macos")]
{
use std::os::macos::fs::FileTimesExt;
meta.created().map(|t| ft = ft.set_created(t)).ok();
}
async move {
_ = match fs::copy(&from, &to).await {
Ok(len) => {
set_file_mtime(to, mtime).ok();
_ = tokio::task::spawn_blocking(move || {
std::fs::File::options().write(true).open(to).and_then(|f| f.set_times(ft)).ok();
})
.await;
tick_tx.send(Ok(len))
}
Err(e) => tick_tx.send(Err(e)),

View File

@ -13,7 +13,7 @@ impl<T> RoCell<T> {
#[inline]
pub fn init(&self, value: T) {
debug_assert!(!self.is_initialized());
debug_assert!(!self.initialized());
unsafe {
*self.0.get() = Some(value);
}
@ -29,25 +29,25 @@ impl<T> RoCell<T> {
#[inline]
pub fn replace(&self, value: T) -> T {
debug_assert!(self.is_initialized());
debug_assert!(self.initialized());
unsafe { mem::replace(&mut *self.0.get(), Some(value)).unwrap_unchecked() }
}
#[inline]
pub fn drop(&self) -> T {
debug_assert!(self.is_initialized());
debug_assert!(self.initialized());
unsafe { mem::take(&mut *self.0.get()).unwrap_unchecked() }
}
#[inline]
fn is_initialized(&self) -> bool { unsafe { (*self.0.get()).is_some() } }
fn initialized(&self) -> bool { unsafe { (*self.0.get()).is_some() } }
}
impl<T> Deref for RoCell<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
debug_assert!(self.is_initialized());
debug_assert!(self.initialized());
unsafe { (*self.0.get()).as_ref().unwrap_unchecked() }
}
}