Skip to content

Commit

Permalink
feat: flavor (#753)
Browse files Browse the repository at this point in the history
  • Loading branch information
sxyazi committed Feb 29, 2024
1 parent 2efda75 commit 85d99ce
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 83 deletions.
14 changes: 5 additions & 9 deletions yazi-boot/src/boot.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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
}
}
Expand All @@ -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{:?}",
Expand Down
7 changes: 7 additions & 0 deletions yazi-config/preset/theme.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

# vim:fileencoding=utf-8:foldmethod=marker

# : Flavor {{{

[flavor]
use = ""

# : }}}

# : Manager {{{

[manager]
Expand Down
16 changes: 8 additions & 8 deletions yazi-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![allow(clippy::module_inception)]

use yazi_shared::RoCell;
use yazi_shared::{RoCell, Xdg};

pub mod keymap;
mod layout;
Expand All @@ -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();
Expand All @@ -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);
Expand Down
56 changes: 39 additions & 17 deletions yazi-config/src/preset.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,60 @@
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]
pub(crate) fn mix<T>(a: &mut Vec<T>, b: Vec<T>, c: Vec<T>) {
*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 {
Expand All @@ -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()
}
}
4 changes: 2 additions & 2 deletions yazi-config/src/preview/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
23 changes: 23 additions & 0 deletions yazi-config/src/theme/flavor.rs
Original file line number Diff line number Diff line change
@@ -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())
}
}
2 changes: 2 additions & 0 deletions yazi-config/src/theme/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
mod color;
mod filetype;
mod flavor;
mod icon;
mod is;
mod style;
mod theme;

pub use color::*;
pub use filetype::*;
pub use flavor::*;
pub use icon::*;
pub use is::*;
pub use style::*;
Expand Down
72 changes: 39 additions & 33 deletions yazi-config/src/theme/theme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
}
6 changes: 1 addition & 5 deletions yazi-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
5 changes: 4 additions & 1 deletion yazi-fm/src/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
18 changes: 16 additions & 2 deletions yazi-shared/src/ro_cell.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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);
}
Expand All @@ -26,18 +27,31 @@ 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;
}
}

#[cfg(debug_assertions)]
#[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>
Expand Down
Loading

0 comments on commit 85d99ce

Please sign in to comment.