theme: Add ThemeRegistry to load and watch themes dir. (#1148)
This commit is contained in:
parent
dcca730d65
commit
6ae7558c44
15 changed files with 869 additions and 738 deletions
87
Cargo.lock
generated
87
Cargo.lock
generated
|
|
@ -709,7 +709,7 @@ dependencies = [
|
|||
"bitflags 2.9.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.12.1",
|
||||
"lazy_static",
|
||||
"lazycell",
|
||||
"log",
|
||||
|
|
@ -732,7 +732,7 @@ dependencies = [
|
|||
"bitflags 2.9.1",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.11.0",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
|
|
@ -2574,6 +2574,15 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
|
||||
|
||||
[[package]]
|
||||
name = "fsevent-sys"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ftree"
|
||||
version = "1.2.0"
|
||||
|
|
@ -3156,6 +3165,7 @@ dependencies = [
|
|||
"itertools 0.13.0",
|
||||
"markdown",
|
||||
"markup5ever_rcdom",
|
||||
"notify",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"paste",
|
||||
|
|
@ -3625,7 +3635,7 @@ dependencies = [
|
|||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core 0.58.0",
|
||||
"windows-core 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3870,6 +3880,26 @@ version = "2.0.6"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdd168d97690d0b8c412d6b6c10360277f4d7ee495c5d0d5d5fe0854923255cc"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"inotify-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify-sys"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.4"
|
||||
|
|
@ -4113,6 +4143,26 @@ dependencies = [
|
|||
"libloading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a"
|
||||
dependencies = [
|
||||
"kqueue-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kqueue-sys"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kuchikiki"
|
||||
version = "0.8.2"
|
||||
|
|
@ -4541,6 +4591,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
|
@ -4688,6 +4739,34 @@ dependencies = [
|
|||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c533b4c39709f9ba5005d8002048266593c1cfaf3c5f0739d5b8ab0c6c504009"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"filetime",
|
||||
"fsevent-sys",
|
||||
"inotify",
|
||||
"kqueue",
|
||||
"libc",
|
||||
"log",
|
||||
"mio",
|
||||
"notify-types",
|
||||
"walkdir",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "notify-types"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "585d3cb5e12e01aed9e8a1f70d5c6b5e86fe2a6e48fc8cd0b3e0b8df6f6eb174"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntapi"
|
||||
version = "0.4.1"
|
||||
|
|
@ -4842,7 +4921,7 @@ version = "0.7.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77e878c846a8abae00dd069496dbe8751b16ac1c3d6bd2a7283a938e8228f90d"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro-crate 3.3.0",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.105",
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ rust-i18n = "3"
|
|||
raw-window-handle = "0.6.2"
|
||||
smol = "1"
|
||||
tracing = "0.1.41"
|
||||
notify = "7.0.0"
|
||||
|
||||
[workspace.dependencies.windows]
|
||||
features = ["Wdk", "Wdk_System", "Wdk_System_SystemServices"]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use gpui::*;
|
||||
use gpui_component::{
|
||||
highlighter::{HighlightTheme, Language},
|
||||
|
|
@ -83,7 +81,7 @@ impl Render for Example {
|
|||
.child(
|
||||
TextView::markdown("preview", self.input_state.read(cx).value()).style(
|
||||
TextViewStyle {
|
||||
highlight_theme: Rc::new(theme.clone()),
|
||||
highlight_theme: theme.clone(),
|
||||
is_dark,
|
||||
..Default::default()
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
use std::{collections::HashMap, sync::LazyLock};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Context;
|
||||
use gpui::{
|
||||
div, px, Action, App, InteractiveElement as _, ParentElement as _, Render, SharedString,
|
||||
};
|
||||
use gpui_component::{
|
||||
button::{Button, ButtonVariants},
|
||||
popup_menu::PopupMenuExt,
|
||||
IconName, Sizable, Theme, ThemeConfig, ThemeSet,
|
||||
IconName, Sizable, Theme, ThemeRegistry,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -23,54 +22,25 @@ struct State {
|
|||
pub fn init(cx: &mut App) {
|
||||
// Load last theme state
|
||||
let json = std::fs::read_to_string(STATE_FILE).unwrap_or(String::default());
|
||||
if let Ok(state) = serde_json::from_str::<State>(&json) {
|
||||
tracing::info!("apply theme: {:?}", state.theme);
|
||||
AppState::global_mut(cx).theme_name = Some(state.theme.clone());
|
||||
if let Some(theme) = THEMES.get(&state.theme) {
|
||||
Theme::global_mut(cx).apply_config(theme);
|
||||
tracing::info!("Load themes...");
|
||||
if let Err(err) = ThemeRegistry::watch_dir(PathBuf::from("./themes"), cx, move |cx| {
|
||||
if let Ok(state) = serde_json::from_str::<State>(&json) {
|
||||
tracing::info!("apply theme: {:?}", state.theme);
|
||||
AppState::global_mut(cx).theme_name = Some(state.theme.clone());
|
||||
|
||||
if let Some(theme) = ThemeRegistry::global(cx)
|
||||
.themes()
|
||||
.get(&state.theme)
|
||||
.cloned()
|
||||
{
|
||||
Theme::global_mut(cx).apply_config(&theme);
|
||||
}
|
||||
}
|
||||
}) {
|
||||
tracing::error!("Failed to watch themes directory: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
static THEMES: LazyLock<HashMap<SharedString, ThemeConfig>> = LazyLock::new(|| {
|
||||
fn parse_themes(source: &str) -> ThemeSet {
|
||||
serde_json::from_str(source)
|
||||
.context(format!("source: '{}'", source))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
let mut themes = HashMap::new();
|
||||
for source in [
|
||||
include_str!("../../../themes/adventure.json"),
|
||||
include_str!("../../../themes/alduin.json"),
|
||||
include_str!("../../../themes/ayu.json"),
|
||||
include_str!("../../../themes/catppuccin.json"),
|
||||
include_str!("../../../themes/everforest.json"),
|
||||
include_str!("../../../themes/fahrenheit.json"),
|
||||
include_str!("../../../themes/gruvbox.json"),
|
||||
include_str!("../../../themes/harper.json"),
|
||||
include_str!("../../../themes/hybrid.json"),
|
||||
include_str!("../../../themes/jellybeans.json"),
|
||||
include_str!("../../../themes/kibble.json"),
|
||||
include_str!("../../../themes/macos-classic.json"),
|
||||
include_str!("../../../themes/mandarin-square.json"),
|
||||
include_str!("../../../themes/matrix.json"),
|
||||
include_str!("../../../themes/mellifluous.json"),
|
||||
include_str!("../../../themes/molokai.json"),
|
||||
include_str!("../../../themes/solarized.json"),
|
||||
include_str!("../../../themes/spaceduck.json"),
|
||||
include_str!("../../../themes/tokyonight.json"),
|
||||
include_str!("../../../themes/twilight.json"),
|
||||
] {
|
||||
let theme_set = parse_themes(source);
|
||||
for theme in theme_set.themes {
|
||||
themes.insert(theme.name.clone(), theme);
|
||||
}
|
||||
}
|
||||
|
||||
themes
|
||||
});
|
||||
|
||||
#[derive(Action, Clone, PartialEq)]
|
||||
#[action(namespace = themes, no_json)]
|
||||
struct SwitchTheme(SharedString);
|
||||
|
|
@ -103,13 +73,10 @@ impl Render for ThemeSwitcher {
|
|||
.on_action(cx.listener(|this, switch: &SwitchTheme, _, cx| {
|
||||
this.current_theme_name = switch.0.clone();
|
||||
let theme_name = this.current_theme_name.clone();
|
||||
|
||||
if let Some(theme_config) = THEMES.get(&theme_name) {
|
||||
Theme::global_mut(cx).apply_config(theme_config);
|
||||
} else if theme_name == "default-light" {
|
||||
Theme::global_mut(cx).set_default_light();
|
||||
} else if theme_name == "default-dark" {
|
||||
Theme::global_mut(cx).set_default_dark();
|
||||
if let Some(theme_config) =
|
||||
ThemeRegistry::global(cx).themes().get(&theme_name).cloned()
|
||||
{
|
||||
Theme::global_mut(cx).apply_config(&theme_config);
|
||||
}
|
||||
|
||||
// Save AppState
|
||||
|
|
@ -129,26 +96,17 @@ impl Render for ThemeSwitcher {
|
|||
.small()
|
||||
.popup_menu({
|
||||
let current_theme_id = self.current_theme_name.clone();
|
||||
move |menu, _, _| {
|
||||
let mut menu = menu
|
||||
.scrollable()
|
||||
.max_h(px(600.))
|
||||
.menu_with_check(
|
||||
"Default Light",
|
||||
current_theme_id == "default-light",
|
||||
Box::new(SwitchTheme("default-light".into())),
|
||||
)
|
||||
.menu_with_check(
|
||||
"Default Dark",
|
||||
current_theme_id == "default-dark",
|
||||
Box::new(SwitchTheme("default-dark".into())),
|
||||
);
|
||||
move |menu, _, cx| {
|
||||
let mut menu = menu.scrollable().max_h(px(600.));
|
||||
|
||||
let mut names = THEMES.keys().collect::<Vec<&SharedString>>();
|
||||
names.sort();
|
||||
let names = ThemeRegistry::global(cx)
|
||||
.sorted_themes()
|
||||
.iter()
|
||||
.map(|theme| theme.name.clone())
|
||||
.collect::<Vec<SharedString>>();
|
||||
|
||||
for theme_name in names {
|
||||
let is_selected = *theme_name == current_theme_id;
|
||||
let is_selected = theme_name == current_theme_id;
|
||||
menu = menu.menu_with_check(
|
||||
theme_name.clone(),
|
||||
is_selected,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ smallvec.workspace = true
|
|||
smol.workspace = true
|
||||
tracing.workspace = true
|
||||
anyhow.workspace = true
|
||||
notify.workspace = true
|
||||
|
||||
enum-iterator = "2.1.0"
|
||||
futures-util = "0.3.31"
|
||||
|
|
|
|||
|
|
@ -2,11 +2,11 @@ use gpui::{App, FontWeight, HighlightStyle, Hsla, SharedString};
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
use std::{collections::HashMap, ops::Deref, sync::LazyLock};
|
||||
use std::{collections::HashMap, ops::Deref, sync::Arc};
|
||||
|
||||
use crate::{
|
||||
highlighter::{languages, Language},
|
||||
ActiveTheme, Colorize, ThemeMode,
|
||||
ActiveTheme, Colorize, ThemeMode, DEFAULT_THEME_COLORS,
|
||||
};
|
||||
|
||||
pub(super) fn init(cx: &mut App) {
|
||||
|
|
@ -58,15 +58,6 @@ pub(super) const HIGHLIGHT_NAMES: [&str; 40] = [
|
|||
"variant",
|
||||
];
|
||||
|
||||
const DEFAULT_DARK: LazyLock<HighlightTheme> = LazyLock::new(|| {
|
||||
let json = include_str!("./themes/dark.json");
|
||||
serde_json::from_str(json).unwrap()
|
||||
});
|
||||
const DEFAULT_LIGHT: LazyLock<HighlightTheme> = LazyLock::new(|| {
|
||||
let json = include_str!("./themes/light.json");
|
||||
serde_json::from_str(json).unwrap()
|
||||
});
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct LanguageConfig {
|
||||
pub name: SharedString,
|
||||
|
|
@ -460,12 +451,12 @@ impl Deref for HighlightTheme {
|
|||
}
|
||||
|
||||
impl HighlightTheme {
|
||||
pub fn default_dark() -> Self {
|
||||
DEFAULT_DARK.clone()
|
||||
pub fn default_dark() -> Arc<Self> {
|
||||
DEFAULT_THEME_COLORS[&ThemeMode::Dark].1.clone()
|
||||
}
|
||||
|
||||
pub fn default_light() -> Self {
|
||||
DEFAULT_LIGHT.clone()
|
||||
pub fn default_light() -> Arc<Self> {
|
||||
DEFAULT_THEME_COLORS[&ThemeMode::Light].1.clone()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -523,23 +514,8 @@ impl LanguageRegistry {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use gpui::rgb;
|
||||
|
||||
use crate::highlighter::LanguageConfig;
|
||||
|
||||
#[test]
|
||||
fn test_syntax_colors() {
|
||||
use super::{HighlightTheme, SyntaxColors};
|
||||
|
||||
let theme: HighlightTheme =
|
||||
serde_json::from_str(include_str!("./themes/light.json")).unwrap();
|
||||
let syntax: &SyntaxColors = &theme.style.syntax;
|
||||
|
||||
assert_eq!(syntax.style("keyword"), Some(rgb(0x0433ff).into()));
|
||||
assert_eq!(syntax.style("keyword.repeat"), Some(rgb(0x0433ff).into()));
|
||||
assert_eq!(syntax.style("foo"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registry() {
|
||||
use super::LanguageRegistry;
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
The default theme based on [zed-theme-macos-classic](https://github.com/huacnlee/zed-theme-macos-classic).
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
{
|
||||
"name": "macOS Classic Dark",
|
||||
"appearance": "dark",
|
||||
"style": {
|
||||
"editor.foreground": "#DDDDDD",
|
||||
"editor.background": "#000000",
|
||||
"editor.active_line.background": "#131313",
|
||||
"editor.line_number": "#8F8F8F",
|
||||
"editor.active_line_number": "#DDDDDD",
|
||||
"conflict": "#D2602D",
|
||||
"conflict.background": null,
|
||||
"conflict.border": null,
|
||||
"created": "#3f72e2",
|
||||
"created.background": "#0C4619",
|
||||
"created.border": null,
|
||||
"deleted": null,
|
||||
"deleted.background": "#46190C",
|
||||
"deleted.border": null,
|
||||
"error": null,
|
||||
"error.background": "#46190C",
|
||||
"error.border": "#802207",
|
||||
"hidden": "#9E9E9E",
|
||||
"hidden.background": null,
|
||||
"hidden.border": null,
|
||||
"hint": "#b283f8",
|
||||
"hint.background": "#250c4b",
|
||||
"hint.border": "#3f0891",
|
||||
"ignored": null,
|
||||
"ignored.background": null,
|
||||
"ignored.border": null,
|
||||
"info": null,
|
||||
"info.background": "#0C194D",
|
||||
"info.border": "#082190",
|
||||
"modified": "#B0A878",
|
||||
"modified.background": "#3A310E",
|
||||
"modified.border": null,
|
||||
"predictive": "#5D5945",
|
||||
"predictive.background": null,
|
||||
"predictive.border": null,
|
||||
"renamed": null,
|
||||
"renamed.background": null,
|
||||
"renamed.border": null,
|
||||
"success": null,
|
||||
"success.background": "#0C4619",
|
||||
"success.border": null,
|
||||
"unreachable": null,
|
||||
"unreachable.background": null,
|
||||
"unreachable.border": null,
|
||||
"warning": null,
|
||||
"warning.background": "#3A310E",
|
||||
"warning.border": "#7B6508",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#be9a52",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment": {
|
||||
"color": "#9E9E9E",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#9E9E9E",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#b5af9a",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#CACCCA",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#A86D3B",
|
||||
"font_style": "normal",
|
||||
"font_weight": null
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6F6D66",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#76BA53",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"tag": {
|
||||
"color": "#b5af9a",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#E1D797",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"title": {
|
||||
"color": "#A76D3B",
|
||||
"font_style": null,
|
||||
"font_weight": 600
|
||||
},
|
||||
"type": {
|
||||
"color": "#A86D3B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"property": {
|
||||
"color": "#CACCCA",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#E19773",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,175 +0,0 @@
|
|||
{
|
||||
"name": "macOS Classic Light",
|
||||
"appearance": "light",
|
||||
"style": {
|
||||
"editor.foreground": "#000000",
|
||||
"editor.background": "#ffffff",
|
||||
"editor.active_line.background": "#F5F5F5",
|
||||
"editor.line_number": "#929292",
|
||||
"editor.active_line_number": "#000000",
|
||||
"conflict": "#C5060B",
|
||||
"conflict.background": null,
|
||||
"conflict.border": null,
|
||||
"created": "#1642FF",
|
||||
"created.background": "#e5ffe9",
|
||||
"created.border": null,
|
||||
"deleted": null,
|
||||
"deleted.background": "#FBEAE5",
|
||||
"deleted.border": null,
|
||||
"error": null,
|
||||
"error.background": "#FBEAE5",
|
||||
"error.border": "#EC9F89",
|
||||
"hidden": "#6D6D6D",
|
||||
"hidden.background": null,
|
||||
"hidden.border": null,
|
||||
"hint": "#9e5dff",
|
||||
"hint.background": "#ece6ff",
|
||||
"hint.border": "#bc8fff",
|
||||
"ignored": null,
|
||||
"ignored.background": null,
|
||||
"ignored.border": null,
|
||||
"info": null,
|
||||
"info.background": "#E5EAFF",
|
||||
"info.border": "#8DA1FF",
|
||||
"modified": "#9e7008",
|
||||
"modified.background": "#fff2e5",
|
||||
"modified.border": null,
|
||||
"predictive": "#A4ABB6",
|
||||
"predictive.background": null,
|
||||
"predictive.border": null,
|
||||
"renamed": null,
|
||||
"renamed.background": null,
|
||||
"renamed.border": null,
|
||||
"success": null,
|
||||
"success.background": "#E5FFE5",
|
||||
"success.border": null,
|
||||
"unreachable": null,
|
||||
"unreachable.background": null,
|
||||
"unreachable.border": null,
|
||||
"warning": "#C99401",
|
||||
"warning.background": "#FFFBE5",
|
||||
"warning.border": "#D9CC89",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#957931",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment": {
|
||||
"color": "#007fff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#007fff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constant": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"function": {
|
||||
"color": "#0000A2",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#0000A2",
|
||||
"font_style": "normal",
|
||||
"font_weight": null
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6A7293",
|
||||
"font_style": "italic",
|
||||
"font_weight": null
|
||||
},
|
||||
"number": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#036A07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#d21f07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#d21f07",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"tag": {
|
||||
"color": "#0433ff",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#6F42C1",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"title": {
|
||||
"color": "#0433FF",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"type": {
|
||||
"color": "#6f42c1",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"property": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable": {
|
||||
"color": "#333333",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#C5060B",
|
||||
"font_style": null,
|
||||
"font_weight": null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use gpui::{px, rems, App, ElementId, IntoElement, Pixels, Rems, RenderOnce, SharedString, Window};
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ pub struct TextViewStyle {
|
|||
/// Base font size for headings, default is 14px.
|
||||
pub heading_base_font_size: Pixels,
|
||||
/// Highlight theme for code blocks. Default: [`HighlightTheme::default_light()`]
|
||||
pub highlight_theme: Rc<HighlightTheme>,
|
||||
pub highlight_theme: Arc<HighlightTheme>,
|
||||
pub is_dark: bool,
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +105,7 @@ impl Default for TextViewStyle {
|
|||
Self {
|
||||
paragraph_gap: rems(1.),
|
||||
heading_base_font_size: px(14.),
|
||||
highlight_theme: Rc::new(HighlightTheme::default_light().clone()),
|
||||
highlight_theme: HighlightTheme::default_light().clone(),
|
||||
is_dark: false,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,17 +4,23 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
ops::{Deref, DerefMut},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
mod color;
|
||||
mod registry;
|
||||
mod schema;
|
||||
mod theme_color;
|
||||
|
||||
pub use color::*;
|
||||
pub use registry::*;
|
||||
pub use schema::*;
|
||||
pub use theme_color::*;
|
||||
|
||||
pub fn init(cx: &mut App) {
|
||||
registry::init(cx);
|
||||
|
||||
Theme::sync_system_appearance(None, cx);
|
||||
Theme::sync_scrollbar_appearance(cx);
|
||||
}
|
||||
|
|
@ -33,11 +39,9 @@ impl ActiveTheme for App {
|
|||
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct Theme {
|
||||
pub colors: ThemeColor,
|
||||
pub light_theme: ThemeColor,
|
||||
pub dark_theme: ThemeColor,
|
||||
pub highlight_theme: Arc<HighlightTheme>,
|
||||
pub light_highlight_theme: Arc<HighlightTheme>,
|
||||
pub dark_highlight_theme: Arc<HighlightTheme>,
|
||||
pub light_theme: Rc<ThemeConfig>,
|
||||
pub dark_theme: Rc<ThemeConfig>,
|
||||
|
||||
pub mode: ThemeMode,
|
||||
pub font_family: SharedString,
|
||||
|
|
@ -58,7 +62,7 @@ pub struct Theme {
|
|||
|
||||
impl Default for Theme {
|
||||
fn default() -> Self {
|
||||
Self::from(ThemeColor::light())
|
||||
Self::from(ThemeColor::default())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,21 +101,21 @@ impl Theme {
|
|||
self.mode.is_dark()
|
||||
}
|
||||
|
||||
/// Sets the theme to default light.
|
||||
pub fn set_default_light(&mut self) {
|
||||
self.light_theme = ThemeColor::light();
|
||||
self.colors = ThemeColor::light();
|
||||
self.light_highlight_theme = Arc::new(HighlightTheme::default_light());
|
||||
self.highlight_theme = self.light_highlight_theme.clone();
|
||||
}
|
||||
// /// Sets the theme to default light.
|
||||
// pub fn set_default_light(&mut self) {
|
||||
// self.light_theme = ThemeColor::light();
|
||||
// self.colors = ThemeColor::light();
|
||||
// self.light_highlight_theme = Arc::new(HighlightTheme::default_light());
|
||||
// self.highlight_theme = self.light_highlight_theme.clone();
|
||||
// }
|
||||
|
||||
/// Sets the theme to default dark.
|
||||
pub fn set_default_dark(&mut self) {
|
||||
self.dark_theme = ThemeColor::dark();
|
||||
self.colors = ThemeColor::dark();
|
||||
self.dark_highlight_theme = Arc::new(HighlightTheme::default_dark());
|
||||
self.highlight_theme = self.dark_highlight_theme.clone();
|
||||
}
|
||||
// /// Sets the theme to default dark.
|
||||
// pub fn set_default_dark(&mut self) {
|
||||
// self.dark_theme = ThemeColor::dark();
|
||||
// self.colors = ThemeColor::dark();
|
||||
// self.dark_highlight_theme = Arc::new(HighlightTheme::default_dark());
|
||||
// self.highlight_theme = self.dark_highlight_theme.clone();
|
||||
// }
|
||||
|
||||
/// Sync the theme with the system appearance
|
||||
pub fn sync_system_appearance(window: Option<&mut Window>, cx: &mut App) {
|
||||
|
|
@ -137,23 +141,19 @@ impl Theme {
|
|||
pub fn change(mode: impl Into<ThemeMode>, window: Option<&mut Window>, cx: &mut App) {
|
||||
let mode = mode.into();
|
||||
if !cx.has_global::<Theme>() {
|
||||
let colors = match mode {
|
||||
ThemeMode::Light => ThemeColor::light(),
|
||||
ThemeMode::Dark => ThemeColor::dark(),
|
||||
};
|
||||
let mut theme = Theme::from(colors);
|
||||
theme.light_theme = ThemeColor::light();
|
||||
theme.dark_theme = ThemeColor::dark();
|
||||
let mut theme = Theme::default();
|
||||
theme.light_theme = ThemeRegistry::global(cx).default_light_theme().clone();
|
||||
theme.dark_theme = ThemeRegistry::global(cx).default_dark_theme().clone();
|
||||
cx.set_global(theme);
|
||||
}
|
||||
|
||||
let theme = cx.global_mut::<Theme>();
|
||||
theme.mode = mode;
|
||||
theme.colors = if mode.is_dark() {
|
||||
theme.dark_theme
|
||||
if mode.is_dark() {
|
||||
theme.apply_config(&theme.dark_theme.clone());
|
||||
} else {
|
||||
theme.light_theme
|
||||
};
|
||||
theme.apply_config(&theme.light_theme.clone());
|
||||
}
|
||||
|
||||
if let Some(window) = window {
|
||||
window.refresh();
|
||||
|
|
@ -163,12 +163,8 @@ impl Theme {
|
|||
|
||||
impl From<ThemeColor> for Theme {
|
||||
fn from(colors: ThemeColor) -> Self {
|
||||
let mode = ThemeMode::default();
|
||||
let light_highlight_theme = Arc::new(HighlightTheme::default_light());
|
||||
let dark_highlight_theme = Arc::new(HighlightTheme::default_dark());
|
||||
|
||||
Theme {
|
||||
mode,
|
||||
mode: ThemeMode::default(),
|
||||
transparent: Hsla::transparent_black(),
|
||||
font_size: px(16.),
|
||||
font_family: if cfg!(target_os = "macos") {
|
||||
|
|
@ -185,11 +181,9 @@ impl From<ThemeColor> for Theme {
|
|||
tile_grid_size: px(8.),
|
||||
tile_shadow: true,
|
||||
colors,
|
||||
light_theme: ThemeColor::light(),
|
||||
dark_theme: ThemeColor::dark(),
|
||||
highlight_theme: light_highlight_theme.clone(),
|
||||
light_highlight_theme: light_highlight_theme,
|
||||
dark_highlight_theme: dark_highlight_theme,
|
||||
light_theme: Rc::new(ThemeConfig::default()),
|
||||
dark_theme: Rc::new(ThemeConfig::default()),
|
||||
highlight_theme: HighlightTheme::default_light(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
256
crates/ui/src/theme/registry.rs
Normal file
256
crates/ui/src/theme/registry.rs
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
use crate::{highlighter::HighlightTheme, Theme, ThemeColor, ThemeConfig, ThemeMode, ThemeSet};
|
||||
use anyhow::Result;
|
||||
use gpui::{App, Global, SharedString};
|
||||
use notify::Watcher as _;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{Arc, LazyLock},
|
||||
};
|
||||
|
||||
const DEFAULT_THEME: &str = include_str!("../../../../themes/default.json");
|
||||
pub(crate) const DEFAULT_THEME_COLORS: LazyLock<
|
||||
HashMap<ThemeMode, (Arc<ThemeColor>, Arc<HighlightTheme>)>,
|
||||
> = LazyLock::new(|| {
|
||||
let mut colors = HashMap::new();
|
||||
|
||||
let themes: Vec<ThemeConfig> = serde_json::from_str::<ThemeSet>(DEFAULT_THEME)
|
||||
.expect("Failed to parse themes/default.json")
|
||||
.themes;
|
||||
|
||||
for theme in themes {
|
||||
let mut theme_color = ThemeColor::default();
|
||||
theme_color.apply_config(&theme, &ThemeColor::default());
|
||||
|
||||
let highlight_theme = HighlightTheme {
|
||||
name: theme.name.to_string(),
|
||||
appearance: theme.mode,
|
||||
style: theme.highlight.unwrap_or_default(),
|
||||
};
|
||||
|
||||
colors.insert(
|
||||
theme.mode,
|
||||
(Arc::new(theme_color), Arc::new(highlight_theme)),
|
||||
);
|
||||
}
|
||||
|
||||
colors
|
||||
});
|
||||
|
||||
pub(super) fn init(cx: &mut App) {
|
||||
cx.set_global(ThemeRegistry::default());
|
||||
ThemeRegistry::global_mut(cx).init_default_themes();
|
||||
|
||||
// Observe changes to the theme registry to apply changes to the active theme
|
||||
cx.observe_global::<ThemeRegistry>(|cx| {
|
||||
tracing::info!("Reload active theme...");
|
||||
let light_theme = Theme::global(cx).light_theme.name.clone();
|
||||
let dark_theme = Theme::global(cx).dark_theme.name.clone();
|
||||
if let Some(theme) = ThemeRegistry::global(cx)
|
||||
.themes()
|
||||
.get(&light_theme)
|
||||
.cloned()
|
||||
{
|
||||
Theme::global_mut(cx).light_theme = theme;
|
||||
}
|
||||
if let Some(theme) = ThemeRegistry::global(cx).themes().get(&dark_theme).cloned() {
|
||||
Theme::global_mut(cx).dark_theme = theme;
|
||||
}
|
||||
let mode = Theme::global(cx).mode;
|
||||
|
||||
Theme::change(mode, None, cx);
|
||||
cx.refresh_windows();
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ThemeRegistry {
|
||||
themes_dir: PathBuf,
|
||||
default_themes: HashMap<ThemeMode, Rc<ThemeConfig>>,
|
||||
themes: HashMap<SharedString, Rc<ThemeConfig>>,
|
||||
has_custom_themes: bool,
|
||||
}
|
||||
|
||||
impl Global for ThemeRegistry {}
|
||||
|
||||
impl ThemeRegistry {
|
||||
pub fn global(cx: &App) -> &Self {
|
||||
cx.global::<Self>()
|
||||
}
|
||||
|
||||
pub fn global_mut(cx: &mut App) -> &mut Self {
|
||||
cx.global_mut::<Self>()
|
||||
}
|
||||
|
||||
/// Watch themes directory.
|
||||
///
|
||||
/// And reload themes to trigger the `on_load` callback.
|
||||
pub fn watch_dir<F>(themes_dir: PathBuf, cx: &mut App, on_load: F) -> Result<()>
|
||||
where
|
||||
F: Fn(&mut App) + 'static,
|
||||
{
|
||||
Self::global_mut(cx).themes_dir = themes_dir.clone();
|
||||
|
||||
// Load theme in the background.
|
||||
cx.spawn(async move |cx| {
|
||||
_ = cx.update(|cx| {
|
||||
if let Err(err) = Self::_watch_themes_dir(themes_dir, cx) {
|
||||
tracing::error!("Failed to watch themes directory: {}", err);
|
||||
}
|
||||
|
||||
Self::reload_themes(cx);
|
||||
on_load(cx);
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns a reference to the map of themes (including default themes).
|
||||
pub fn themes(&self) -> &HashMap<SharedString, Rc<ThemeConfig>> {
|
||||
&self.themes
|
||||
}
|
||||
|
||||
/// Returns a sorted list of themes.
|
||||
pub fn sorted_themes(&self) -> Vec<&Rc<ThemeConfig>> {
|
||||
let mut themes = self.themes.values().collect::<Vec<_>>();
|
||||
// sort by is_default true first, then light first dark later, then by name case-insensitive
|
||||
themes.sort_by(|a, b| {
|
||||
b.is_default
|
||||
.cmp(&a.is_default)
|
||||
.then(a.name.to_lowercase().cmp(&b.name.to_lowercase()))
|
||||
});
|
||||
themes
|
||||
}
|
||||
|
||||
/// Returns a reference to the map of default themes.
|
||||
pub fn default_themes(&self) -> &HashMap<ThemeMode, Rc<ThemeConfig>> {
|
||||
&self.default_themes
|
||||
}
|
||||
|
||||
pub fn default_light_theme(&self) -> &Rc<ThemeConfig> {
|
||||
&self.default_themes[&ThemeMode::Light]
|
||||
}
|
||||
|
||||
pub fn default_dark_theme(&self) -> &Rc<ThemeConfig> {
|
||||
&self.default_themes[&ThemeMode::Dark]
|
||||
}
|
||||
|
||||
fn init_default_themes(&mut self) {
|
||||
let default_themes: Vec<ThemeConfig> = serde_json::from_str::<ThemeSet>(DEFAULT_THEME)
|
||||
.expect("failed to parse default theme.")
|
||||
.themes;
|
||||
for theme in default_themes.into_iter() {
|
||||
if theme.mode.is_dark() {
|
||||
self.default_themes.insert(ThemeMode::Dark, Rc::new(theme));
|
||||
} else {
|
||||
self.default_themes.insert(ThemeMode::Light, Rc::new(theme));
|
||||
}
|
||||
}
|
||||
self.themes = self
|
||||
.default_themes
|
||||
.values()
|
||||
.map(|theme| {
|
||||
let name = theme.name.clone();
|
||||
(name, Rc::clone(theme))
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
fn _watch_themes_dir(themes_dir: PathBuf, cx: &mut App) -> anyhow::Result<()> {
|
||||
if !themes_dir.exists() {
|
||||
fs::create_dir_all(&themes_dir)?;
|
||||
}
|
||||
|
||||
let (tx, rx) = smol::channel::bounded(100);
|
||||
let mut watcher =
|
||||
notify::recommended_watcher(move |res: notify::Result<notify::Event>| {
|
||||
if let Ok(event) = &res {
|
||||
match event.kind {
|
||||
notify::EventKind::Create(_)
|
||||
| notify::EventKind::Modify(_)
|
||||
| notify::EventKind::Remove(_) => {
|
||||
if let Err(err) = tx.send_blocking(res) {
|
||||
tracing::error!("Failed to send theme event: {:?}", err);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
if let Err(err) = watcher.watch(&themes_dir, notify::RecursiveMode::Recursive) {
|
||||
tracing::error!("Failed to watch themes directory: {:?}", err);
|
||||
}
|
||||
|
||||
while (rx.recv().await).is_ok() {
|
||||
tracing::info!("Reloading themes...");
|
||||
_ = cx.update(Self::reload_themes);
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reload_themes(cx: &mut App) {
|
||||
let registry = Self::global_mut(cx);
|
||||
match registry.reload() {
|
||||
Ok(_) => {
|
||||
tracing::info!("Themes reloaded successfully.");
|
||||
}
|
||||
Err(e) => tracing::error!("Failed to reload themes: {:?}", e),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reload themes from the `themes_dir`.
|
||||
fn reload(&mut self) -> Result<()> {
|
||||
let mut themes = vec![];
|
||||
|
||||
if self.themes_dir.exists() {
|
||||
for entry in fs::read_dir(&self.themes_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") {
|
||||
let file_content = fs::read_to_string(path.clone())?;
|
||||
|
||||
match serde_json::from_str::<ThemeSet>(&file_content) {
|
||||
Ok(theme_set) => {
|
||||
themes.extend(theme_set.themes);
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!(
|
||||
"ignored invalid theme file: {}, {}",
|
||||
path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.themes.clear();
|
||||
for theme in themes.iter() {
|
||||
if self.themes.contains_key(&theme.name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if theme.is_default {
|
||||
self.default_themes
|
||||
.insert(theme.mode, Rc::new(theme.clone()));
|
||||
}
|
||||
|
||||
self.has_custom_themes = true;
|
||||
self.themes
|
||||
.insert(theme.name.clone(), Rc::new(theme.clone()));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::Arc;
|
||||
use std::{rc::Rc, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use gpui::{Hsla, SharedString};
|
||||
|
|
@ -32,6 +32,8 @@ pub struct ThemeSet {
|
|||
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(default)]
|
||||
pub struct ThemeConfig {
|
||||
/// Whether this theme is the default theme.
|
||||
pub is_default: bool,
|
||||
/// The name of the theme.
|
||||
pub name: SharedString,
|
||||
/// The mode of the theme, default is light.
|
||||
|
|
@ -379,15 +381,10 @@ fn try_parse_color(color: &str) -> Result<Hsla> {
|
|||
Ok(rgba.into())
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
/// Apply the given theme configuration to the current theme.
|
||||
pub fn apply_config(&mut self, config: &ThemeConfig) {
|
||||
impl ThemeColor {
|
||||
/// Create a new `ThemeColor` from a `ThemeConfig`.
|
||||
pub(crate) fn apply_config(&mut self, config: &ThemeConfig, default_theme: &ThemeColor) {
|
||||
let colors = config.colors.clone();
|
||||
let default_theme = if config.mode.is_dark() {
|
||||
ThemeColor::dark()
|
||||
} else {
|
||||
ThemeColor::light()
|
||||
};
|
||||
|
||||
macro_rules! apply_color {
|
||||
($config_field:ident) => {
|
||||
|
|
@ -413,8 +410,6 @@ impl Theme {
|
|||
};
|
||||
}
|
||||
|
||||
self.mode = config.mode;
|
||||
|
||||
// Base colors for fallback
|
||||
apply_color!(red);
|
||||
apply_color!(red_light, fallback = self.red.opacity(0.8));
|
||||
|
|
@ -535,15 +530,19 @@ impl Theme {
|
|||
// TODO: Apply default fallback colors to highlight.
|
||||
|
||||
// Ensure opacity for list_active, table_active
|
||||
self.colors.list_active = self.colors.list_active.alpha(0.2);
|
||||
self.colors.table_active = self.colors.table_active.alpha(0.2);
|
||||
self.list_active = self.list_active.alpha(0.2);
|
||||
self.table_active = self.table_active.alpha(0.2);
|
||||
}
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
/// Apply the given theme configuration to the current theme.
|
||||
pub fn apply_config(&mut self, config: &Rc<ThemeConfig>) {
|
||||
if config.mode.is_dark() {
|
||||
self.dark_theme = self.colors;
|
||||
self.dark_theme = config.clone();
|
||||
} else {
|
||||
self.light_theme = self.colors;
|
||||
self.light_theme = config.clone();
|
||||
}
|
||||
|
||||
if let Some(style) = &config.highlight {
|
||||
let highlight_theme = Arc::new(HighlightTheme {
|
||||
name: config.name.to_string(),
|
||||
|
|
@ -551,12 +550,15 @@ impl Theme {
|
|||
style: style.clone(),
|
||||
});
|
||||
self.highlight_theme = highlight_theme.clone();
|
||||
if config.mode.is_dark() {
|
||||
self.dark_highlight_theme = highlight_theme;
|
||||
} else {
|
||||
self.light_highlight_theme = highlight_theme;
|
||||
}
|
||||
}
|
||||
|
||||
let default_theme = if config.mode.is_dark() {
|
||||
ThemeColor::dark()
|
||||
} else {
|
||||
ThemeColor::light()
|
||||
};
|
||||
|
||||
self.colors.apply_config(&config, &default_theme);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
use super::color::*;
|
||||
use gpui::{transparent_black, Hsla};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::{theme::DEFAULT_THEME_COLORS, ThemeMode};
|
||||
|
||||
use gpui::Hsla;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
|
|
@ -213,225 +216,11 @@ pub struct ThemeColor {
|
|||
}
|
||||
|
||||
impl ThemeColor {
|
||||
pub fn light() -> Self {
|
||||
Self {
|
||||
accent: neutral_100(),
|
||||
accent_foreground: neutral_900(),
|
||||
accordion: white(),
|
||||
accordion_active: neutral_200(),
|
||||
accordion_hover: neutral_100(),
|
||||
background: white(),
|
||||
border: neutral_200(),
|
||||
group_box: neutral_100(),
|
||||
group_box_foreground: neutral_900(),
|
||||
caret: neutral_950(),
|
||||
chart_1: blue_300(),
|
||||
chart_2: blue_500(),
|
||||
chart_3: blue_600(),
|
||||
chart_4: blue_700(),
|
||||
chart_5: blue_800(),
|
||||
danger: red_500(),
|
||||
danger_active: red_600(),
|
||||
danger_foreground: red_50(),
|
||||
danger_hover: red_500().opacity(0.9),
|
||||
description_list_label: neutral_100(),
|
||||
description_list_label_foreground: neutral_900(),
|
||||
drag_border: blue_500(),
|
||||
drop_target: blue_500().opacity(0.25),
|
||||
foreground: neutral_950(),
|
||||
info: sky_500(),
|
||||
info_active: sky_600(),
|
||||
info_hover: sky_500().opacity(0.9),
|
||||
info_foreground: sky_50(),
|
||||
input: neutral_200(),
|
||||
link: neutral_950(),
|
||||
link_active: neutral_950(),
|
||||
link_hover: neutral_800(),
|
||||
list: white(),
|
||||
list_active: blue_200().opacity(0.2),
|
||||
list_active_border: blue_400(),
|
||||
list_even: neutral_50(),
|
||||
list_head: neutral_50(),
|
||||
list_hover: neutral_100(),
|
||||
muted: neutral_100(),
|
||||
muted_foreground: neutral_500(),
|
||||
popover: white(),
|
||||
popover_foreground: neutral_950(),
|
||||
primary: neutral_900(),
|
||||
primary_active: neutral_950(),
|
||||
primary_foreground: neutral_50(),
|
||||
primary_hover: neutral_800(),
|
||||
progress_bar: neutral_900(),
|
||||
ring: neutral_950(),
|
||||
scrollbar: neutral_50().opacity(0.5),
|
||||
scrollbar_thumb: neutral_400().opacity(0.9),
|
||||
scrollbar_thumb_hover: neutral_400(),
|
||||
secondary: neutral_100(),
|
||||
secondary_active: neutral_200(),
|
||||
secondary_foreground: neutral_900(),
|
||||
secondary_hover: neutral_100().opacity(0.5),
|
||||
selection: blue_200(),
|
||||
sidebar: neutral_50(),
|
||||
sidebar_accent: neutral_200(),
|
||||
sidebar_accent_foreground: neutral_900(),
|
||||
sidebar_border: neutral_200(),
|
||||
sidebar_foreground: neutral_900(),
|
||||
sidebar_primary: neutral_900(),
|
||||
sidebar_primary_foreground: neutral_50(),
|
||||
skeleton: neutral_100(),
|
||||
slider_bar: neutral_900(),
|
||||
slider_thumb: white(),
|
||||
success: green_500(),
|
||||
success_active: green_600(),
|
||||
success_hover: green_500().opacity(0.9),
|
||||
success_foreground: gray_50(),
|
||||
switch: neutral_300(),
|
||||
tab: gpui::transparent_black(),
|
||||
tab_active: white(),
|
||||
tab_active_foreground: neutral_900(),
|
||||
tab_bar: neutral_100(),
|
||||
tab_bar_segmented: neutral_100(),
|
||||
tab_foreground: neutral_700(),
|
||||
table: white(),
|
||||
table_active: blue_200().opacity(0.2),
|
||||
table_active_border: blue_400(),
|
||||
table_even: neutral_50(),
|
||||
table_head: neutral_50(),
|
||||
table_head_foreground: neutral_500(),
|
||||
table_hover: neutral_100(),
|
||||
table_row_border: neutral_200().opacity(0.7),
|
||||
tiles: neutral_50(),
|
||||
title_bar: white(),
|
||||
title_bar_border: neutral_200(),
|
||||
warning: yellow_500(),
|
||||
warning_active: yellow_600(),
|
||||
warning_hover: yellow_500().opacity(0.9),
|
||||
warning_foreground: gray_50(),
|
||||
overlay: black().opacity(0.05),
|
||||
window_border: neutral_200(),
|
||||
red: red_500(),
|
||||
red_light: red_200(),
|
||||
green: green_500(),
|
||||
green_light: green_200(),
|
||||
blue: blue_500(),
|
||||
blue_light: blue_200(),
|
||||
yellow: yellow_500(),
|
||||
yellow_light: yellow_200(),
|
||||
magenta: purple_500(),
|
||||
magenta_light: purple_200(),
|
||||
cyan: cyan_500(),
|
||||
cyan_light: cyan_200(),
|
||||
}
|
||||
pub fn light() -> Arc<Self> {
|
||||
DEFAULT_THEME_COLORS[&ThemeMode::Light].0.clone()
|
||||
}
|
||||
|
||||
pub fn dark() -> Self {
|
||||
Self {
|
||||
accent: neutral_900(),
|
||||
accent_foreground: neutral_50(),
|
||||
accordion: neutral_950(),
|
||||
accordion_active: neutral_800(),
|
||||
accordion_hover: neutral_800().opacity(0.9),
|
||||
background: neutral_950(),
|
||||
border: neutral_800(),
|
||||
group_box: neutral_900(),
|
||||
group_box_foreground: neutral_50(),
|
||||
caret: neutral_50(),
|
||||
chart_1: blue_300(),
|
||||
chart_2: blue_500(),
|
||||
chart_3: blue_600(),
|
||||
chart_4: blue_700(),
|
||||
chart_5: blue_800(),
|
||||
danger: red_900(),
|
||||
danger_active: red_900().darken(0.2),
|
||||
danger_foreground: red_50(),
|
||||
danger_hover: red_900().lighten(0.1),
|
||||
description_list_label: neutral_900(),
|
||||
description_list_label_foreground: neutral_100(),
|
||||
drag_border: blue_500(),
|
||||
drop_target: blue_500().opacity(0.1),
|
||||
foreground: neutral_50(),
|
||||
info: sky_900(),
|
||||
info_active: sky_900().darken(0.2),
|
||||
info_foreground: sky_50(),
|
||||
info_hover: sky_900().lighten(0.1),
|
||||
input: neutral_800(),
|
||||
link: neutral_50(),
|
||||
link_active: neutral_50().darken(0.2),
|
||||
link_hover: neutral_50().lighten(0.2),
|
||||
list: neutral_950(),
|
||||
list_active: blue_800().opacity(0.2),
|
||||
list_active_border: blue_700(),
|
||||
list_even: neutral_900().opacity(0.8),
|
||||
list_head: neutral_900().opacity(0.8),
|
||||
list_hover: neutral_800(),
|
||||
muted: neutral_800(),
|
||||
muted_foreground: neutral_500(),
|
||||
popover: neutral_950(),
|
||||
popover_foreground: neutral_50(),
|
||||
primary: neutral_50(),
|
||||
primary_active: neutral_300(),
|
||||
primary_foreground: neutral_900(),
|
||||
primary_hover: neutral_200(),
|
||||
progress_bar: neutral_100(),
|
||||
ring: neutral_300(),
|
||||
scrollbar: neutral_900().opacity(0.5),
|
||||
scrollbar_thumb: neutral_700().opacity(0.9),
|
||||
scrollbar_thumb_hover: neutral_700(),
|
||||
secondary: neutral_900(),
|
||||
secondary_active: neutral_800(),
|
||||
secondary_foreground: neutral_50(),
|
||||
secondary_hover: neutral_900().lighten(0.1),
|
||||
selection: blue_700(),
|
||||
sidebar: neutral_950(),
|
||||
sidebar_accent: neutral_800(),
|
||||
sidebar_accent_foreground: neutral_100(),
|
||||
sidebar_border: neutral_800(),
|
||||
sidebar_foreground: neutral_100(),
|
||||
sidebar_primary: neutral_100(),
|
||||
sidebar_primary_foreground: neutral_950(),
|
||||
skeleton: neutral_900(),
|
||||
slider_bar: neutral_50(),
|
||||
slider_thumb: neutral_950(),
|
||||
success: green_900(),
|
||||
success_active: green_900().darken(0.2),
|
||||
success_foreground: green_50(),
|
||||
success_hover: green_900().lighten(0.1),
|
||||
switch: neutral_700(),
|
||||
tab: transparent_black(),
|
||||
tab_active: neutral_950(),
|
||||
tab_active_foreground: neutral_50(),
|
||||
tab_bar: neutral_900(),
|
||||
tab_bar_segmented: neutral_900(),
|
||||
tab_foreground: neutral_300(),
|
||||
table: neutral_950(),
|
||||
table_active: blue_800().opacity(0.2),
|
||||
table_active_border: blue_700(),
|
||||
table_even: neutral_900().opacity(0.8),
|
||||
table_head: neutral_900().opacity(0.8),
|
||||
table_head_foreground: neutral_600(),
|
||||
table_hover: neutral_800(),
|
||||
table_row_border: neutral_800().opacity(0.7),
|
||||
tiles: neutral_900(),
|
||||
title_bar: neutral_950(),
|
||||
title_bar_border: neutral_800(),
|
||||
warning: yellow_900(),
|
||||
warning_active: yellow_900().darken(0.2),
|
||||
warning_foreground: yellow_50(),
|
||||
warning_hover: yellow_900().lighten(0.1),
|
||||
overlay: white().opacity(0.03),
|
||||
window_border: neutral_800(),
|
||||
red: red_500(),
|
||||
red_light: red_200(),
|
||||
green: green_500(),
|
||||
green_light: green_200(),
|
||||
blue: blue_500(),
|
||||
blue_light: blue_200(),
|
||||
yellow: yellow_500(),
|
||||
yellow_light: yellow_200(),
|
||||
magenta: purple_500(),
|
||||
magenta_light: purple_200(),
|
||||
cyan: cyan_500(),
|
||||
cyan_light: cyan_200(),
|
||||
}
|
||||
pub fn dark() -> Arc<Self> {
|
||||
DEFAULT_THEME_COLORS[&ThemeMode::Dark].0.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
423
themes/default.json
Normal file
423
themes/default.json
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
{
|
||||
"$schema": "https://github.com/longbridge/gpui-component/raw/refs/heads/main/.theme-schema.json",
|
||||
"name": "Default",
|
||||
"author": "shadcn",
|
||||
"url": "https://ui.shadcn.com",
|
||||
"themes": [
|
||||
{
|
||||
"is_default": true,
|
||||
"name": "Default Light",
|
||||
"mode": "light",
|
||||
"colors": {
|
||||
"accent.background": "#f5f5f5",
|
||||
"accent.foreground": "#171717",
|
||||
"accordion.background": "#ffffff",
|
||||
"accordion.active.background": "#e5e5e5",
|
||||
"accordion.hover.background": "#f5f5f5",
|
||||
"background": "#ffffff",
|
||||
"border": "#e5e5e5",
|
||||
"group_box.background": "#f5f5f5",
|
||||
"group_box.foreground": "#171717",
|
||||
"caret": "#0a0a0a",
|
||||
"chart_1": "#93c5fd",
|
||||
"chart_2": "#3b82f6",
|
||||
"chart_3": "#2563eb",
|
||||
"chart_4": "#1d4ed8",
|
||||
"chart_5": "#1e40af",
|
||||
"danger.background": "#ef4444",
|
||||
"danger.active.background": "#dc2626",
|
||||
"danger.foreground": "#fef2f2",
|
||||
"danger.hover.background": "#ef444499",
|
||||
"description_list_label.background": "#f5f5f5",
|
||||
"description_list_label.foreground": "#171717",
|
||||
"drag_border": "#3b82f6",
|
||||
"drop_target.background": "#3b82f640",
|
||||
"foreground": "#0a0a0a",
|
||||
"info.background": "#0ea5e9",
|
||||
"info.active.background": "#0284c7",
|
||||
"info.foreground": "#f0f9ff",
|
||||
"info.hover.background": "#0ea5e999",
|
||||
"input.border": "#e5e5e5",
|
||||
"link.foreground": "#0a0a0a",
|
||||
"link.active.foreground": "#0a0a0a",
|
||||
"link.hover.foreground": "#404040",
|
||||
"list.background": "#ffffff",
|
||||
"list.active.background": "#bfdbfe33",
|
||||
"list.active.border": "#60a5fa",
|
||||
"list.even.background": "#fafafa",
|
||||
"list.head.background": "#fafafa",
|
||||
"list.hover.background": "#f5f5f5",
|
||||
"muted.background": "#f5f5f5",
|
||||
"muted.foreground": "#737373",
|
||||
"popover.background": "#ffffff",
|
||||
"popover.foreground": "#0a0a0a",
|
||||
"primary.background": "#171717",
|
||||
"primary.active.background": "#0a0a0a",
|
||||
"primary.foreground": "#fafafa",
|
||||
"primary.hover.background": "#404040",
|
||||
"progress_bar.background": "#171717",
|
||||
"ring": "#0a0a0a",
|
||||
"scrollbar.background": "#fafafa80",
|
||||
"scrollbar.thumb.background": "#a3a3a3e6",
|
||||
"scrollbar.thumb.hover.background": "#a3a3a3",
|
||||
"secondary.background": "#f5f5f5",
|
||||
"secondary.active.background": "#e5e5e5",
|
||||
"secondary.foreground": "#171717",
|
||||
"secondary.hover.background": "#f5f5f580",
|
||||
"selection.background": "#bfdbfe",
|
||||
"sidebar.background": "#fafafa",
|
||||
"sidebar.accent.background": "#e5e5e5",
|
||||
"sidebar.accent.foreground": "#171717",
|
||||
"sidebar.border": "#e5e5e5",
|
||||
"sidebar.foreground": "#171717",
|
||||
"sidebar.primary.background": "#171717",
|
||||
"sidebar.primary.foreground": "#fafafa",
|
||||
"skeleton.background": "#f5f5f5",
|
||||
"slider.bar.background": "#171717",
|
||||
"slider.thumb.background": "#ffffff",
|
||||
"success.background": "#22c55e",
|
||||
"success.active.background": "#16a34a",
|
||||
"success.foreground": "#f9fafb",
|
||||
"success.hover.background": "#22c55e99",
|
||||
"switch.background": "#d4d4d4",
|
||||
"tab.background": "#00000000",
|
||||
"tab.active.background": "#ffffff",
|
||||
"tab.active.foreground": "#171717",
|
||||
"tab_bar.background": "#f5f5f5",
|
||||
"tab_bar.segmented.background": "#f5f5f5",
|
||||
"tab.foreground": "#404040",
|
||||
"table.background": "#ffffff",
|
||||
"table.active.background": "#bfdbfe33",
|
||||
"table.active.border": "#60a5fa",
|
||||
"table.even.background": "#fafafa",
|
||||
"table.head.background": "#fafafa",
|
||||
"table.head.foreground": "#737373",
|
||||
"table.hover.background": "#f5f5f5",
|
||||
"table.row.border": "#e5e5e5b3",
|
||||
"tiles.background": "#fafafa",
|
||||
"title_bar.background": "#ffffff",
|
||||
"title_bar.border": "#e5e5e5",
|
||||
"warning.background": "#eab308",
|
||||
"warning.active.background": "#ca8a04",
|
||||
"warning.hover.background": "#eab30899",
|
||||
"warning.foreground": "#f9fafb",
|
||||
"overlay.background": "#0000000d",
|
||||
"window.border": "#e5e5e5",
|
||||
"base.red": "#ef4444",
|
||||
"base.red.light": "#fecaca",
|
||||
"base.green": "#22c55e",
|
||||
"base.green.light": "#bbf7d0",
|
||||
"base.blue": "#3b82f6",
|
||||
"base.blue.light": "#bfdbfe",
|
||||
"base.yellow": "#eab308",
|
||||
"base.yellow.light": "#fef08a",
|
||||
"base.magenta": "#9333ea",
|
||||
"base.magenta.light": "#e9d5ff",
|
||||
"base.cyan": "#06b6d4",
|
||||
"base.cyan.light": "#cffafe"
|
||||
},
|
||||
"highlight": {
|
||||
"editor.foreground": "#000000",
|
||||
"editor.background": "#ffffff",
|
||||
"editor.active_line.background": "#F5F5F5",
|
||||
"editor.line_number": "#929292",
|
||||
"editor.active_line_number": "#000000",
|
||||
"conflict": "#C5060B",
|
||||
"created": "#1642FF",
|
||||
"hidden": "#6D6D6D",
|
||||
"hint": "#9e5dff",
|
||||
"modified": "#9e7008",
|
||||
"predictive": "#A4ABB6",
|
||||
"warning": "#C99401",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#957931"
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#C5060B"
|
||||
},
|
||||
"comment": {
|
||||
"color": "#007fff"
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#007fff"
|
||||
},
|
||||
"constant": {
|
||||
"color": "#C5060B"
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"function": {
|
||||
"color": "#0000A2"
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#0000A2",
|
||||
"font_style": "normal"
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6A7293",
|
||||
"font_style": "italic"
|
||||
},
|
||||
"number": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"string": {
|
||||
"color": "#036A07"
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#036A07"
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#036A07"
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#d21f07"
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#d21f07"
|
||||
},
|
||||
"tag": {
|
||||
"color": "#0433ff"
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#6F42C1"
|
||||
},
|
||||
"title": {
|
||||
"color": "#0433FF"
|
||||
},
|
||||
"type": {
|
||||
"color": "#6f42c1"
|
||||
},
|
||||
"property": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"variable": {
|
||||
"color": "#333333"
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#C5060B"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"is_default": true,
|
||||
"name": "Default Dark",
|
||||
"mode": "dark",
|
||||
"colors": {
|
||||
"accent.background": "#171717",
|
||||
"accent.foreground": "#fafafa",
|
||||
"accordion.background": "#0a0a0a",
|
||||
"accordion.active.background": "#262626",
|
||||
"accordion.hover.background": "#262626e6",
|
||||
"background": "#0a0a0a",
|
||||
"border": "#262626",
|
||||
"group_box.background": "#171717",
|
||||
"group_box.foreground": "#fafafa",
|
||||
"caret": "#fafafa",
|
||||
"chart_1": "#93c5fd",
|
||||
"chart_2": "#3b82f6",
|
||||
"chart_3": "#2563eb",
|
||||
"chart_4": "#1d4ed8",
|
||||
"chart_5": "#1e40af",
|
||||
"danger.background": "#7f1d1d",
|
||||
"danger.active.background": "#991b1b",
|
||||
"danger.foreground": "#fef2f2",
|
||||
"danger.hover.background": "#b91c1c",
|
||||
"description_list_label.background": "#171717",
|
||||
"description_list_label.foreground": "#f5f5f5",
|
||||
"drag_border": "#3b82f6",
|
||||
"drop_target.background": "#3b82f619",
|
||||
"foreground": "#fafafa",
|
||||
"info.background": "#0c4a6e",
|
||||
"info.active.background": "#075985",
|
||||
"info.foreground": "#f0f9ff",
|
||||
"info.hover.background": "#0369a1",
|
||||
"input.border": "#262626",
|
||||
"link.foreground": "#fafafa",
|
||||
"link.active.foreground": "#d4d4d4",
|
||||
"link.hover.foreground": "#ffffff",
|
||||
"list.background": "#0a0a0a",
|
||||
"list.active.background": "#1e40af33",
|
||||
"list.active.border": "#1d4ed8",
|
||||
"list.even.background": "#171717cc",
|
||||
"list.head.background": "#171717cc",
|
||||
"list.hover.background": "#262626",
|
||||
"muted.background": "#262626",
|
||||
"muted.foreground": "#737373",
|
||||
"popover.background": "#0a0a0a",
|
||||
"popover.foreground": "#fafafa",
|
||||
"primary.background": "#fafafa",
|
||||
"primary.active.background": "#d4d4d4",
|
||||
"primary.foreground": "#171717",
|
||||
"primary.hover.background": "#e5e5e5",
|
||||
"progress_bar.background": "#f5f5f5",
|
||||
"ring": "#d4d4d4",
|
||||
"scrollbar.background": "#17171780",
|
||||
"scrollbar.thumb.background": "#525252e6",
|
||||
"scrollbar.thumb.hover.background": "#525252",
|
||||
"secondary.background": "#171717",
|
||||
"secondary.active.background": "#262626",
|
||||
"secondary.foreground": "#fafafa",
|
||||
"secondary.hover.background": "#1a1a1a",
|
||||
"selection.background": "#1d4ed8",
|
||||
"sidebar.background": "#0a0a0a",
|
||||
"sidebar.accent.background": "#262626",
|
||||
"sidebar.accent.foreground": "#f5f5f5",
|
||||
"sidebar.border": "#262626",
|
||||
"sidebar.foreground": "#f5f5f5",
|
||||
"sidebar.primary.background": "#f5f5f5",
|
||||
"sidebar.primary.foreground": "#0a0a0a",
|
||||
"skeleton.background": "#171717",
|
||||
"slider.bar.background": "#fafafa",
|
||||
"slider.thumb.background": "#0a0a0a",
|
||||
"success.background": "#14532d",
|
||||
"success.active.background": "#166534",
|
||||
"success.foreground": "#f0fdf4",
|
||||
"success.hover.background": "#15803d",
|
||||
"switch.background": "#404040",
|
||||
"tab.background": "#00000000",
|
||||
"tab.active.background": "#0a0a0a",
|
||||
"tab.active.foreground": "#fafafa",
|
||||
"tab_bar.background": "#171717",
|
||||
"tab_bar.segmented.background": "#171717",
|
||||
"tab.foreground": "#d4d4d4",
|
||||
"table.background": "#0a0a0a",
|
||||
"table.active.background": "#1e40af33",
|
||||
"table.active.border": "#1d4ed8",
|
||||
"table.even.background": "#171717cc",
|
||||
"table.head.background": "#171717cc",
|
||||
"table.head.foreground": "#525252",
|
||||
"table.hover.background": "#262626",
|
||||
"table.row.border": "#262626b3",
|
||||
"tiles.background": "#171717",
|
||||
"title_bar.background": "#0a0a0a",
|
||||
"title_bar.border": "#262626",
|
||||
"warning.background": "#713f12",
|
||||
"warning.active.background": "#854d0e",
|
||||
"warning.foreground": "#fefce8",
|
||||
"warning.hover.background": "#a16207",
|
||||
"overlay.background": "#ffffff08",
|
||||
"window.border": "#262626",
|
||||
"base.red": "#ef4444",
|
||||
"base.red.light": "#fecaca",
|
||||
"base.green": "#22c55e",
|
||||
"base.green.light": "#bbf7d0",
|
||||
"base.blue": "#3b82f6",
|
||||
"base.blue.light": "#bfdbfe",
|
||||
"base.yellow": "#eab308",
|
||||
"base.yellow.light": "#fef08a",
|
||||
"base.magenta": "#9333ea",
|
||||
"base.magenta.light": "#e9d5ff",
|
||||
"base.cyan": "#06b6d4",
|
||||
"base.cyan.light": "#cffafe"
|
||||
},
|
||||
"highlight": {
|
||||
"editor.foreground": "#CACCCA",
|
||||
"editor.background": "#131313",
|
||||
"editor.active_line.background": "#353436",
|
||||
"editor.line_number": "#8F8F8F",
|
||||
"editor.active_line_number": "#DDDDDD",
|
||||
"conflict": "#D2602D",
|
||||
"created": "#3f72e2",
|
||||
"created.background": "#0C4619",
|
||||
"deleted.background": "#46190C",
|
||||
"error.background": "#46190C",
|
||||
"error.border": "#802207",
|
||||
"hidden": "#9E9E9E",
|
||||
"hint": "#b283f8",
|
||||
"hint.background": "#250c4b",
|
||||
"hint.border": "#3f0891",
|
||||
"info.background": "#0C194D",
|
||||
"info.border": "#082190",
|
||||
"modified": "#B0A878",
|
||||
"modified.background": "#3A310E",
|
||||
"predictive": "#5D5945",
|
||||
"success.background": "#0C4619",
|
||||
"warning.background": "#3A310E",
|
||||
"warning.border": "#7B6508",
|
||||
"syntax": {
|
||||
"attribute": {
|
||||
"color": "#be9a52"
|
||||
},
|
||||
"boolean": {
|
||||
"color": "#E1D797"
|
||||
},
|
||||
"comment": {
|
||||
"color": "#9E9E9E"
|
||||
},
|
||||
"comment.doc": {
|
||||
"color": "#9E9E9E"
|
||||
},
|
||||
"constant": {
|
||||
"color": "#E1D797"
|
||||
},
|
||||
"constructor": {
|
||||
"color": "#b5af9a"
|
||||
},
|
||||
"embedded": {
|
||||
"color": "#CACCCA"
|
||||
},
|
||||
"function": {
|
||||
"color": "#E1D797"
|
||||
},
|
||||
"keyword": {
|
||||
"color": "#E19773"
|
||||
},
|
||||
"link_text": {
|
||||
"color": "#A86D3B",
|
||||
"font_style": "normal"
|
||||
},
|
||||
"link_uri": {
|
||||
"color": "#6F6D66",
|
||||
"font_style": "italic"
|
||||
},
|
||||
"number": {
|
||||
"color": "#E19773"
|
||||
},
|
||||
"string": {
|
||||
"color": "#76BA53"
|
||||
},
|
||||
"string.escape": {
|
||||
"color": "#76BA53"
|
||||
},
|
||||
"string.regex": {
|
||||
"color": "#76BA53"
|
||||
},
|
||||
"string.special": {
|
||||
"color": "#E1D797"
|
||||
},
|
||||
"string.special.symbol": {
|
||||
"color": "#E1D797"
|
||||
},
|
||||
"tag": {
|
||||
"color": "#b5af9a"
|
||||
},
|
||||
"text.literal": {
|
||||
"color": "#E1D797"
|
||||
},
|
||||
"title": {
|
||||
"color": "#A76D3B",
|
||||
"font_weight": 600
|
||||
},
|
||||
"type": {
|
||||
"color": "#A86D3B"
|
||||
},
|
||||
"property": {
|
||||
"color": "#CACCCA"
|
||||
},
|
||||
"variable.special": {
|
||||
"color": "#E19773"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue