mirror of
https://github.com/sxyazi/yazi.git
synced 2026-07-22 23:31:05 +00:00
feat: flavor (#753)
This commit is contained in:
parent
2efda755f1
commit
4d8e276a6f
12 changed files with 155 additions and 83 deletions
|
|
@ -1,4 +1,4 @@
|
|||
use std::{ffi::OsString, fs, path::{Path, PathBuf}, process};
|
||||
use std::{ffi::OsString, path::{Path, PathBuf}, process};
|
||||
|
||||
use clap::Parser;
|
||||
use serde::Serialize;
|
||||
|
|
@ -16,7 +16,6 @@ pub struct Boot {
|
|||
pub config_dir: PathBuf,
|
||||
pub flavor_dir: PathBuf,
|
||||
pub plugin_dir: PathBuf,
|
||||
pub state_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Boot {
|
||||
|
|
@ -37,7 +36,7 @@ impl Boot {
|
|||
|
||||
impl Default for Boot {
|
||||
fn default() -> Self {
|
||||
let config_dir = Xdg::config_dir().unwrap();
|
||||
let config_dir = Xdg::config_dir();
|
||||
let (cwd, file) = Self::parse_entry(ARGS.entry.as_deref());
|
||||
|
||||
let boot = Self {
|
||||
|
|
@ -47,13 +46,10 @@ impl Default for Boot {
|
|||
flavor_dir: config_dir.join("flavors"),
|
||||
plugin_dir: config_dir.join("plugins"),
|
||||
config_dir,
|
||||
state_dir: Xdg::state_dir().unwrap(),
|
||||
};
|
||||
|
||||
fs::create_dir_all(&boot.flavor_dir).expect("Failed to create flavor directory");
|
||||
fs::create_dir_all(&boot.plugin_dir).expect("Failed to create plugin directory");
|
||||
fs::create_dir_all(&boot.state_dir).expect("Failed to create state directory");
|
||||
|
||||
std::fs::create_dir_all(&boot.flavor_dir).expect("Failed to create flavor directory");
|
||||
std::fs::create_dir_all(&boot.plugin_dir).expect("Failed to create plugin directory");
|
||||
boot
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +71,7 @@ impl Default for Args {
|
|||
if args.clear_cache {
|
||||
if PREVIEW.cache_dir == Xdg::cache_dir() {
|
||||
println!("Clearing cache directory: \n{:?}", PREVIEW.cache_dir);
|
||||
fs::remove_dir_all(&PREVIEW.cache_dir).unwrap();
|
||||
std::fs::remove_dir_all(&PREVIEW.cache_dir).unwrap();
|
||||
} else {
|
||||
println!(
|
||||
"You've changed the default cache directory, for your data's safety, please clear it manually: \n{:?}",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
|
||||
# vim:fileencoding=utf-8:foldmethod=marker
|
||||
|
||||
# : Flavor {{{
|
||||
|
||||
[flavor]
|
||||
use = ""
|
||||
|
||||
# : }}}
|
||||
|
||||
# : Manager {{{
|
||||
|
||||
[manager]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(clippy::module_inception)]
|
||||
|
||||
use yazi_shared::RoCell;
|
||||
use yazi_shared::{RoCell, Xdg};
|
||||
|
||||
pub mod keymap;
|
||||
mod layout;
|
||||
|
|
@ -23,11 +23,11 @@ pub(crate) use pattern::*;
|
|||
pub(crate) use preset::*;
|
||||
pub use priority::*;
|
||||
|
||||
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
|
||||
|
||||
static MERGED_YAZI: RoCell<String> = RoCell::new();
|
||||
static MERGED_KEYMAP: RoCell<String> = RoCell::new();
|
||||
static MERGED_THEME: RoCell<String> = RoCell::new();
|
||||
static MERGED_YAZI: RoCell<String> = RoCell::new();
|
||||
|
||||
pub static LAYOUT: RoCell<arc_swap::ArcSwap<Layout>> = RoCell::new();
|
||||
|
||||
pub static KEYMAP: RoCell<keymap::Keymap> = RoCell::new();
|
||||
pub static LOG: RoCell<log::Log> = RoCell::new();
|
||||
|
|
@ -42,12 +42,12 @@ pub static SELECT: RoCell<popup::Select> = RoCell::new();
|
|||
pub static WHICH: RoCell<which::Which> = RoCell::new();
|
||||
|
||||
pub fn init() {
|
||||
LAYOUT.with(Default::default);
|
||||
|
||||
let config_dir = yazi_shared::Xdg::config_dir().unwrap();
|
||||
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));
|
||||
MERGED_YAZI.init(Preset::yazi(&config_dir));
|
||||
|
||||
LAYOUT.with(Default::default);
|
||||
|
||||
KEYMAP.with(Default::default);
|
||||
LOG.with(Default::default);
|
||||
|
|
|
|||
|
|
@ -1,23 +1,35 @@
|
|||
use std::{mem, path::{Path, PathBuf}};
|
||||
|
||||
use anyhow::Context;
|
||||
use toml::{Table, Value};
|
||||
|
||||
use crate::theme::Flavor;
|
||||
|
||||
pub(crate) struct Preset;
|
||||
|
||||
impl Preset {
|
||||
#[inline]
|
||||
pub(crate) fn keymap(dir: &Path) -> String {
|
||||
Self::merge_str(dir.join("keymap.toml"), include_str!("../preset/keymap.toml"))
|
||||
pub(crate) fn yazi(p: &Path) -> String {
|
||||
Self::merge_path(p.join("yazi.toml"), include_str!("../preset/yazi.toml"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn theme(dir: &Path) -> String {
|
||||
Self::merge_str(dir.join("theme.toml"), include_str!("../preset/theme.toml"))
|
||||
pub(crate) fn keymap(p: &Path) -> String {
|
||||
Self::merge_path(p.join("keymap.toml"), include_str!("../preset/keymap.toml"))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn yazi(dir: &Path) -> String {
|
||||
Self::merge_str(dir.join("yazi.toml"), include_str!("../preset/yazi.toml"))
|
||||
pub(crate) fn theme(p: &Path) -> String {
|
||||
let Ok(user) = std::fs::read_to_string(p.join("theme.toml")) else {
|
||||
return include_str!("../preset/theme.toml").to_owned();
|
||||
};
|
||||
let Some(use_) = Flavor::parse_use(&user) else {
|
||||
return Self::merge_str(&user, include_str!("../preset/theme.toml"));
|
||||
};
|
||||
|
||||
let p = p.join(format!("flavors/{}.yazi/flavor.toml", use_));
|
||||
let flavor = std::fs::read_to_string(&p)
|
||||
.with_context(|| format!("Failed to load flavor {:?}", p))
|
||||
.unwrap();
|
||||
|
||||
Self::merge_str(&user, &Self::merge_str(&flavor, include_str!("../preset/theme.toml")))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -25,6 +37,24 @@ impl Preset {
|
|||
*a = b.into_iter().chain(mem::take(a)).chain(c).collect();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn merge_str(user: &str, base: &str) -> String {
|
||||
let mut t = user.parse().unwrap();
|
||||
Self::merge(&mut t, base.parse().unwrap(), 2);
|
||||
|
||||
t.to_string()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn merge_path(user: PathBuf, base: &str) -> String {
|
||||
let s = std::fs::read_to_string(user).unwrap_or_default();
|
||||
if s.is_empty() {
|
||||
return base.to_string();
|
||||
}
|
||||
|
||||
Self::merge_str(&s, base)
|
||||
}
|
||||
|
||||
fn merge(a: &mut Table, b: Table, max: u8) {
|
||||
for (k, v) in b {
|
||||
let Some(a) = a.get_mut(&k) else {
|
||||
|
|
@ -45,12 +75,4 @@ impl Preset {
|
|||
*a = v;
|
||||
}
|
||||
}
|
||||
|
||||
fn merge_str(user: PathBuf, base: &str) -> String {
|
||||
let mut user = std::fs::read_to_string(user).unwrap_or_default().parse::<Table>().unwrap();
|
||||
let base = base.parse::<Table>().unwrap();
|
||||
|
||||
Self::merge(&mut user, base, 2);
|
||||
user.to_string()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@ use std::{fs, path::PathBuf, time::{self, SystemTime}};
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
use yazi_shared::{fs::expand_path, Xdg};
|
||||
use yazi_shared::fs::expand_path;
|
||||
|
||||
use crate::{validation::check_validation, MERGED_YAZI};
|
||||
use crate::{validation::check_validation, Xdg, MERGED_YAZI};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct Preview {
|
||||
|
|
|
|||
23
yazi-config/src/theme/flavor.rs
Normal file
23
yazi-config/src/theme/flavor.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Flavor {
|
||||
#[serde(rename = "use")]
|
||||
pub use_: String,
|
||||
}
|
||||
|
||||
impl Flavor {
|
||||
pub fn parse_use(s: &str) -> Option<String> {
|
||||
#[derive(Deserialize)]
|
||||
struct Outer {
|
||||
flavor: Inner,
|
||||
}
|
||||
#[derive(Deserialize)]
|
||||
struct Inner {
|
||||
#[serde(rename = "use")]
|
||||
pub use_: String,
|
||||
}
|
||||
|
||||
toml::from_str::<Outer>(s).ok().map(|o| o.flavor.use_).filter(|s| !s.is_empty())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
mod color;
|
||||
mod filetype;
|
||||
mod flavor;
|
||||
mod icon;
|
||||
mod is;
|
||||
mod style;
|
||||
|
|
@ -7,6 +8,7 @@ mod theme;
|
|||
|
||||
pub use color::*;
|
||||
pub use filetype::*;
|
||||
pub use flavor::*;
|
||||
pub use icon::*;
|
||||
pub use is::*;
|
||||
pub use style::*;
|
||||
|
|
|
|||
|
|
@ -2,11 +2,48 @@ use std::path::PathBuf;
|
|||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use validator::Validate;
|
||||
use yazi_shared::fs::expand_path;
|
||||
use yazi_shared::{fs::expand_path, Xdg};
|
||||
|
||||
use super::{Filetype, Icon, Style};
|
||||
use super::{Filetype, Flavor, Icon, Style};
|
||||
use crate::{validation::check_validation, MERGED_THEME};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Theme {
|
||||
pub flavor: Flavor,
|
||||
pub manager: Manager,
|
||||
status: Status,
|
||||
pub input: Input,
|
||||
pub select: Select,
|
||||
pub completion: Completion,
|
||||
pub tasks: Tasks,
|
||||
pub which: Which,
|
||||
pub help: Help,
|
||||
|
||||
// File-specific styles
|
||||
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
|
||||
pub filetypes: Vec<Filetype>,
|
||||
#[serde(rename = "icon", deserialize_with = "Icon::deserialize", skip_serializing)]
|
||||
pub icons: Vec<Icon>,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
let mut theme: Self = toml::from_str(&MERGED_THEME).unwrap();
|
||||
|
||||
check_validation(theme.manager.validate());
|
||||
check_validation(theme.which.validate());
|
||||
|
||||
if theme.flavor.use_.is_empty() {
|
||||
theme.manager.syntect_theme = expand_path(&theme.manager.syntect_theme);
|
||||
} else {
|
||||
theme.manager.syntect_theme =
|
||||
Xdg::config_dir().join(format!("flavors/{}.yazi/tmtheme.xml", theme.flavor.use_));
|
||||
}
|
||||
|
||||
theme
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Validate)]
|
||||
pub struct Manager {
|
||||
cwd: Style,
|
||||
|
|
@ -123,34 +160,3 @@ pub struct Help {
|
|||
pub hovered: Style,
|
||||
pub footer: Style,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Theme {
|
||||
pub manager: Manager,
|
||||
status: Status,
|
||||
pub input: Input,
|
||||
pub select: Select,
|
||||
pub completion: Completion,
|
||||
pub tasks: Tasks,
|
||||
pub which: Which,
|
||||
pub help: Help,
|
||||
|
||||
// File-specific styles
|
||||
#[serde(rename = "filetype", deserialize_with = "Filetype::deserialize", skip_serializing)]
|
||||
pub filetypes: Vec<Filetype>,
|
||||
#[serde(rename = "icon", deserialize_with = "Icon::deserialize", skip_serializing)]
|
||||
pub icons: Vec<Icon>,
|
||||
}
|
||||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
let mut theme: Self = toml::from_str(&MERGED_THEME).unwrap();
|
||||
|
||||
check_validation(theme.manager.validate());
|
||||
check_validation(theme.which.validate());
|
||||
|
||||
theme.manager.syntect_theme = expand_path(&theme.manager.syntect_theme);
|
||||
|
||||
theme
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,4 @@ pub mod which;
|
|||
pub use clipboard::*;
|
||||
pub use step::*;
|
||||
|
||||
pub fn init() {
|
||||
CLIPBOARD.with(Default::default);
|
||||
|
||||
yazi_scheduler::init();
|
||||
}
|
||||
pub fn init() { CLIPBOARD.with(Default::default); }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ pub(super) struct Logs;
|
|||
|
||||
impl Logs {
|
||||
pub(super) fn start() {
|
||||
let appender = tracing_appender::rolling::never(Xdg::state_dir().unwrap(), "yazi.log");
|
||||
let state_dir = Xdg::state_dir();
|
||||
std::fs::create_dir_all(&state_dir).expect("Failed to create state directory");
|
||||
|
||||
let appender = tracing_appender::rolling::never(state_dir, "yazi.log");
|
||||
let (handle, guard) = tracing_appender::non_blocking(appender);
|
||||
|
||||
// let filter = EnvFilter::from_default_env();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::{cell::UnsafeCell, fmt::{self, Display}, ops::Deref};
|
||||
use std::{cell::UnsafeCell, fmt::{self, Display}, mem, ops::Deref};
|
||||
|
||||
// Read-only cell. It's safe to use this in a static variable, but it's not safe
|
||||
// to mutate it. This is useful for storing static data that is expensive to
|
||||
|
|
@ -13,6 +13,7 @@ impl<T> RoCell<T> {
|
|||
|
||||
#[inline]
|
||||
pub fn init(&self, value: T) {
|
||||
debug_assert!(!self.is_initialized());
|
||||
unsafe {
|
||||
*self.0.get() = Some(value);
|
||||
}
|
||||
|
|
@ -26,18 +27,30 @@ impl<T> RoCell<T> {
|
|||
self.init(f());
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn replace(&self, value: T) -> T {
|
||||
debug_assert!(self.is_initialized());
|
||||
unsafe { mem::replace(&mut *self.0.get(), Some(value)).unwrap_unchecked() }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drop(&self) {
|
||||
unsafe {
|
||||
*self.0.get() = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_initialized(&self) -> bool { unsafe { (*self.0.get()).is_some() } }
|
||||
}
|
||||
|
||||
impl<T> Deref for RoCell<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target { unsafe { (*self.0.get()).as_ref().unwrap() } }
|
||||
fn deref(&self) -> &Self::Target {
|
||||
debug_assert!(self.is_initialized());
|
||||
unsafe { (*self.0.get()).as_ref().unwrap_unchecked() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Display for RoCell<T>
|
||||
|
|
|
|||
|
|
@ -5,14 +5,16 @@ use crate::fs::expand_path;
|
|||
pub struct Xdg;
|
||||
|
||||
impl Xdg {
|
||||
pub fn config_dir() -> Option<PathBuf> {
|
||||
if let Some(s) = env::var_os("YAZI_CONFIG_HOME").filter(|s| !s.is_empty()) {
|
||||
return Some(expand_path(s));
|
||||
pub fn config_dir() -> PathBuf {
|
||||
if let Some(p) = env::var_os("YAZI_CONFIG_HOME").map(expand_path).filter(|p| p.is_absolute()) {
|
||||
return p;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
dirs::config_dir().map(|p| p.join("yazi").join("config"))
|
||||
dirs::config_dir()
|
||||
.map(|p| p.join("yazi").join("config"))
|
||||
.expect("Failed to get config directory")
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -21,13 +23,14 @@ impl Xdg {
|
|||
.filter(|p| p.is_absolute())
|
||||
.or_else(|| dirs::home_dir().map(|h| h.join(".config")))
|
||||
.map(|p| p.join("yazi"))
|
||||
.expect("Failed to get config directory")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn state_dir() -> Option<PathBuf> {
|
||||
pub fn state_dir() -> PathBuf {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
dirs::data_dir().map(|p| p.join("yazi").join("state"))
|
||||
dirs::data_dir().map(|p| p.join("yazi").join("state")).expect("Failed to get state directory")
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
|
|
@ -36,6 +39,7 @@ impl Xdg {
|
|||
.filter(|p| p.is_absolute())
|
||||
.or_else(|| dirs::home_dir().map(|h| h.join(".local/state")))
|
||||
.map(|p| p.join("yazi"))
|
||||
.expect("Failed to get state directory")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue