mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-25 01:34:02 +00:00
Merge pull request #1619 from zed-industries/experimental-themes
Internal themes
This commit is contained in:
commit
c7df17b9af
18 changed files with 165 additions and 74 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -7,3 +7,5 @@
|
|||
/crates/collab/static/styles.css
|
||||
/vendor/bin
|
||||
/assets/themes/*.json
|
||||
/assets/themes/internal/*.json
|
||||
/assets/themes/experiments/*.json
|
0
assets/keymaps/experiments/.gitkeep
Normal file
0
assets/keymaps/experiments/.gitkeep
Normal file
1
assets/keymaps/internal.json
Normal file
1
assets/keymaps/internal.json
Normal file
|
@ -0,0 +1 @@
|
|||
[]
|
0
assets/themes/experiments/.gitkeep
Normal file
0
assets/themes/experiments/.gitkeep
Normal file
0
assets/themes/internal/.gitkeep
Normal file
0
assets/themes/internal/.gitkeep
Normal file
|
@ -42,8 +42,15 @@ struct ActionWithData(Box<str>, Box<RawValue>);
|
|||
|
||||
impl KeymapFileContent {
|
||||
pub fn load_defaults(cx: &mut MutableAppContext) {
|
||||
let settings = cx.global::<Settings>();
|
||||
let mut paths = vec!["keymaps/default.json", "keymaps/vim.json"];
|
||||
paths.extend(cx.global::<Settings>().experiments.keymap_files());
|
||||
|
||||
if settings.staff_mode {
|
||||
paths.push("keymaps/internal.json")
|
||||
}
|
||||
|
||||
paths.extend(settings.experiments.keymap_files());
|
||||
|
||||
for path in paths {
|
||||
Self::load(path, cx).unwrap();
|
||||
}
|
||||
|
|
|
@ -37,10 +37,13 @@ pub struct Settings {
|
|||
pub language_overrides: HashMap<Arc<str>, EditorSettings>,
|
||||
pub lsp: HashMap<Arc<str>, LspSettings>,
|
||||
pub theme: Arc<Theme>,
|
||||
pub staff_mode: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, Deserialize, JsonSchema)]
|
||||
pub struct FeatureFlags {}
|
||||
pub struct FeatureFlags {
|
||||
pub experimental_themes: bool,
|
||||
}
|
||||
|
||||
impl FeatureFlags {
|
||||
pub fn keymap_files(&self) -> Vec<&'static str> {
|
||||
|
@ -175,6 +178,8 @@ pub struct SettingsFileContent {
|
|||
pub lsp: HashMap<Arc<str>, LspSettings>,
|
||||
#[serde(default)]
|
||||
pub theme: Option<String>,
|
||||
#[serde(default)]
|
||||
pub staff_mode: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
|
@ -226,6 +231,8 @@ impl Settings {
|
|||
language_overrides: Default::default(),
|
||||
lsp: defaults.lsp.clone(),
|
||||
theme: themes.get(&defaults.theme.unwrap()).unwrap(),
|
||||
|
||||
staff_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,7 +267,7 @@ impl Settings {
|
|||
merge(&mut self.vim_mode, data.vim_mode);
|
||||
merge(&mut self.autosave, data.autosave);
|
||||
merge(&mut self.experiments, data.experiments);
|
||||
|
||||
merge(&mut self.staff_mode, data.staff_mode);
|
||||
// Ensure terminal font is loaded, so we can request it in terminal_element layout
|
||||
if let Some(terminal_font) = &data.terminal.font_family {
|
||||
font_cache.load_family(&[terminal_font]).log_err();
|
||||
|
@ -345,6 +352,7 @@ impl Settings {
|
|||
lsp: Default::default(),
|
||||
projects_online_by_default: true,
|
||||
theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
|
||||
staff_mode: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -400,27 +408,25 @@ pub fn settings_file_json_schema(
|
|||
("ThemeName".into(), theme_name_schema.into()),
|
||||
("Languages".into(), languages_object_schema.into()),
|
||||
]);
|
||||
root_schema
|
||||
.schema
|
||||
.object
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.properties
|
||||
.extend([
|
||||
(
|
||||
"theme".to_owned(),
|
||||
Schema::new_ref("#/definitions/ThemeName".into()),
|
||||
),
|
||||
(
|
||||
"languages".to_owned(),
|
||||
Schema::new_ref("#/definitions/Languages".into()),
|
||||
),
|
||||
// For backward compatibility
|
||||
(
|
||||
"language_overrides".to_owned(),
|
||||
Schema::new_ref("#/definitions/Languages".into()),
|
||||
),
|
||||
]);
|
||||
let root_schema_object = &mut root_schema.schema.object.as_mut().unwrap();
|
||||
|
||||
// Avoid automcomplete for non-user facing settings
|
||||
root_schema_object.properties.remove("staff_mode");
|
||||
root_schema_object.properties.extend([
|
||||
(
|
||||
"theme".to_owned(),
|
||||
Schema::new_ref("#/definitions/ThemeName".into()),
|
||||
),
|
||||
(
|
||||
"languages".to_owned(),
|
||||
Schema::new_ref("#/definitions/Languages".into()),
|
||||
),
|
||||
// For backward compatibility
|
||||
(
|
||||
"language_overrides".to_owned(),
|
||||
Schema::new_ref("#/definitions/Languages".into()),
|
||||
),
|
||||
]);
|
||||
|
||||
serde_json::to_value(root_schema).unwrap()
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ pub use theme_registry::*;
|
|||
#[derive(Deserialize, Default)]
|
||||
pub struct Theme {
|
||||
#[serde(default)]
|
||||
pub name: String,
|
||||
pub meta: ThemeMeta,
|
||||
pub workspace: Workspace,
|
||||
pub context_menu: ContextMenu,
|
||||
pub chat_panel: ChatPanel,
|
||||
|
@ -34,6 +34,12 @@ pub struct Theme {
|
|||
pub terminal: TerminalStyle,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Clone)]
|
||||
pub struct ThemeMeta {
|
||||
pub name: String,
|
||||
pub is_light: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct Workspace {
|
||||
pub background: Color,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::Theme;
|
||||
use crate::{Theme, ThemeMeta};
|
||||
use anyhow::{Context, Result};
|
||||
use gpui::{fonts, AssetSource, FontCache};
|
||||
use parking_lot::Mutex;
|
||||
|
@ -22,11 +22,27 @@ impl ThemeRegistry {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn list(&self) -> impl Iterator<Item = String> {
|
||||
self.assets.list("themes/").into_iter().filter_map(|path| {
|
||||
pub fn list(&self, internal: bool, experiments: bool) -> impl Iterator<Item = ThemeMeta> + '_ {
|
||||
let mut dirs = self.assets.list("themes/");
|
||||
|
||||
if !internal {
|
||||
dirs = dirs
|
||||
.into_iter()
|
||||
.filter(|path| !path.starts_with("themes/internal"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
if !experiments {
|
||||
dirs = dirs
|
||||
.into_iter()
|
||||
.filter(|path| !path.starts_with("themes/experiments"))
|
||||
.collect()
|
||||
}
|
||||
|
||||
dirs.into_iter().filter_map(|path| {
|
||||
let filename = path.strip_prefix("themes/")?;
|
||||
let theme_name = filename.strip_suffix(".json")?;
|
||||
Some(theme_name.to_string())
|
||||
self.get(theme_name).ok().map(|theme| theme.meta.clone())
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -50,7 +66,8 @@ impl ThemeRegistry {
|
|||
serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(&theme_json))
|
||||
})?;
|
||||
|
||||
theme.name = name.into();
|
||||
// Reset name to be the file path, so that we can use it to access the stored themes
|
||||
theme.meta.name = name.into();
|
||||
let theme = Arc::new(theme);
|
||||
self.themes.lock().insert(name.to_string(), theme.clone());
|
||||
Ok(theme)
|
||||
|
|
|
@ -6,12 +6,12 @@ use gpui::{
|
|||
use picker::{Picker, PickerDelegate};
|
||||
use settings::Settings;
|
||||
use std::sync::Arc;
|
||||
use theme::{Theme, ThemeRegistry};
|
||||
use theme::{Theme, ThemeMeta, ThemeRegistry};
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
pub struct ThemeSelector {
|
||||
registry: Arc<ThemeRegistry>,
|
||||
theme_names: Vec<String>,
|
||||
theme_data: Vec<ThemeMeta>,
|
||||
matches: Vec<StringMatch>,
|
||||
original_theme: Arc<Theme>,
|
||||
picker: ViewHandle<Picker<Self>>,
|
||||
|
@ -39,32 +39,41 @@ impl ThemeSelector {
|
|||
fn new(registry: Arc<ThemeRegistry>, cx: &mut ViewContext<Self>) -> Self {
|
||||
let handle = cx.weak_handle();
|
||||
let picker = cx.add_view(|cx| Picker::new(handle, cx));
|
||||
let original_theme = cx.global::<Settings>().theme.clone();
|
||||
let mut theme_names = registry.list().collect::<Vec<_>>();
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
let original_theme = settings.theme.clone();
|
||||
|
||||
let mut theme_names = registry
|
||||
.list(
|
||||
settings.staff_mode,
|
||||
settings.experiments.experimental_themes,
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
theme_names.sort_unstable_by(|a, b| {
|
||||
a.ends_with("dark")
|
||||
.cmp(&b.ends_with("dark"))
|
||||
.then_with(|| a.cmp(b))
|
||||
a.is_light
|
||||
.cmp(&b.is_light)
|
||||
.reverse()
|
||||
.then(a.name.cmp(&b.name))
|
||||
});
|
||||
let matches = theme_names
|
||||
.iter()
|
||||
.map(|name| StringMatch {
|
||||
.map(|meta| StringMatch {
|
||||
candidate_id: 0,
|
||||
score: 0.0,
|
||||
positions: Default::default(),
|
||||
string: name.clone(),
|
||||
string: meta.name.clone(),
|
||||
})
|
||||
.collect();
|
||||
let mut this = Self {
|
||||
registry,
|
||||
theme_names,
|
||||
theme_data: theme_names,
|
||||
matches,
|
||||
picker,
|
||||
original_theme: original_theme.clone(),
|
||||
selected_index: 0,
|
||||
selection_completed: false,
|
||||
};
|
||||
this.select_if_matching(&original_theme.name);
|
||||
this.select_if_matching(&original_theme.meta.name);
|
||||
this
|
||||
}
|
||||
|
||||
|
@ -82,7 +91,7 @@ impl ThemeSelector {
|
|||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut MutableAppContext) {
|
||||
let current_theme_name = cx.global::<Settings>().theme.name.clone();
|
||||
let current_theme_name = cx.global::<Settings>().theme.meta.name.clone();
|
||||
themes.clear();
|
||||
match themes.get(¤t_theme_name) {
|
||||
Ok(theme) => {
|
||||
|
@ -165,13 +174,13 @@ impl PickerDelegate for ThemeSelector {
|
|||
fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) -> gpui::Task<()> {
|
||||
let background = cx.background().clone();
|
||||
let candidates = self
|
||||
.theme_names
|
||||
.theme_data
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, name)| StringMatchCandidate {
|
||||
.map(|(id, meta)| StringMatchCandidate {
|
||||
id,
|
||||
char_bag: name.as_str().into(),
|
||||
string: name.clone(),
|
||||
char_bag: meta.name.as_str().into(),
|
||||
string: meta.name.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ fn main() {
|
|||
load_embedded_fonts(&app);
|
||||
|
||||
let fs = Arc::new(RealFs);
|
||||
|
||||
let themes = ThemeRegistry::new(Assets, app.font_cache());
|
||||
let default_settings = Settings::defaults(Assets, &app.font_cache(), &themes);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ lazy_static::lazy_static! {
|
|||
pub static ref DB: PathBuf = DB_DIR.join("zed.db");
|
||||
pub static ref SETTINGS: PathBuf = CONFIG_DIR.join("settings.json");
|
||||
pub static ref KEYMAP: PathBuf = CONFIG_DIR.join("keymap.json");
|
||||
pub static ref LAST_USERNAME: PathBuf = CONFIG_DIR.join("last-username.txt");
|
||||
pub static ref LOG: PathBuf = LOGS_DIR.join("Zed.log");
|
||||
pub static ref OLD_LOG: PathBuf = LOGS_DIR.join("Zed.log.old");
|
||||
}
|
||||
|
|
|
@ -244,7 +244,16 @@ pub fn initialize_workspace(
|
|||
|
||||
cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone()));
|
||||
|
||||
let theme_names = app_state.themes.list().collect();
|
||||
let settings = cx.global::<Settings>();
|
||||
|
||||
let theme_names = app_state
|
||||
.themes
|
||||
.list(
|
||||
settings.staff_mode,
|
||||
settings.experiments.experimental_themes,
|
||||
)
|
||||
.map(|meta| meta.name)
|
||||
.collect();
|
||||
let language_names = &languages::LANGUAGE_NAMES;
|
||||
|
||||
workspace.project().update(cx, |project, cx| {
|
||||
|
@ -1668,12 +1677,12 @@ mod tests {
|
|||
let settings = Settings::defaults(Assets, cx.font_cache(), &themes);
|
||||
|
||||
let mut has_default_theme = false;
|
||||
for theme_name in themes.list() {
|
||||
for theme_name in themes.list(false, false).map(|meta| meta.name) {
|
||||
let theme = themes.get(&theme_name).unwrap();
|
||||
if theme.name == settings.theme.name {
|
||||
if theme.meta.name == settings.theme.meta.name {
|
||||
has_default_theme = true;
|
||||
}
|
||||
assert_eq!(theme.name, theme_name);
|
||||
assert_eq!(theme.meta.name, theme_name);
|
||||
}
|
||||
assert!(has_default_theme);
|
||||
}
|
||||
|
|
|
@ -2,29 +2,44 @@ import * as fs from "fs";
|
|||
import * as path from "path";
|
||||
import { tmpdir } from "os";
|
||||
import app from "./styleTree/app";
|
||||
import themes from "./themes";
|
||||
import themes, { internalThemes, experimentalThemes } from "./themes";
|
||||
import snakeCase from "./utils/snakeCase";
|
||||
import Theme from "./themes/common/theme";
|
||||
|
||||
const themeDirectory = `${__dirname}/../../assets/themes/`;
|
||||
const themeDirectory = `${__dirname}/../../assets/themes`;
|
||||
const internalDirectory = `${themeDirectory}/internal`;
|
||||
const experimentsDirectory = `${themeDirectory}/experiments`;
|
||||
const tempDirectory = fs.mkdtempSync(path.join(tmpdir(), "build-themes"));
|
||||
|
||||
// Clear existing themes
|
||||
for (const file of fs.readdirSync(themeDirectory)) {
|
||||
if (file.endsWith(".json")) {
|
||||
const name = file.replace(/\.json$/, "");
|
||||
if (!themes.find((theme) => theme.name === name)) {
|
||||
fs.unlinkSync(path.join(themeDirectory, file));
|
||||
function clearThemes(themeDirectory: string) {
|
||||
for (const file of fs.readdirSync(themeDirectory)) {
|
||||
if (file.endsWith(".json")) {
|
||||
const name = file.replace(/\.json$/, "");
|
||||
if (!themes.find((theme) => theme.name === name)) {
|
||||
fs.unlinkSync(path.join(themeDirectory, file));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write new themes to theme directory
|
||||
for (let theme of themes) {
|
||||
let styleTree = snakeCase(app(theme));
|
||||
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
|
||||
let tempPath = path.join(tempDirectory, `${theme.name}.json`);
|
||||
let outPath = path.join(themeDirectory, `${theme.name}.json`);
|
||||
fs.writeFileSync(tempPath, styleTreeJSON);
|
||||
fs.renameSync(tempPath, outPath);
|
||||
console.log(`- ${outPath} created`);
|
||||
clearThemes(themeDirectory);
|
||||
clearThemes(internalDirectory);
|
||||
clearThemes(experimentsDirectory);
|
||||
|
||||
function writeThemes(themes: Theme[], outputDirectory: string) {
|
||||
for (let theme of themes) {
|
||||
let styleTree = snakeCase(app(theme));
|
||||
let styleTreeJSON = JSON.stringify(styleTree, null, 2);
|
||||
let tempPath = path.join(tempDirectory, `${theme.name}.json`);
|
||||
let outPath = path.join(outputDirectory, `${theme.name}.json`);
|
||||
fs.writeFileSync(tempPath, styleTreeJSON);
|
||||
fs.renameSync(tempPath, outPath);
|
||||
console.log(`- ${outPath} created`);
|
||||
}
|
||||
}
|
||||
|
||||
// Write new themes to theme directory
|
||||
writeThemes(themes, themeDirectory);
|
||||
writeThemes(internalThemes, internalDirectory);
|
||||
writeThemes(experimentalThemes, experimentsDirectory);
|
||||
|
|
|
@ -22,6 +22,10 @@ export const panel = {
|
|||
|
||||
export default function app(theme: Theme): Object {
|
||||
return {
|
||||
meta: {
|
||||
name: theme.name,
|
||||
isLight: theme.isLight
|
||||
},
|
||||
picker: picker(theme),
|
||||
workspace: workspace(theme),
|
||||
contextMenu: contextMenu(theme),
|
||||
|
|
|
@ -5,14 +5,27 @@ import Theme from "./themes/common/theme";
|
|||
const themes: Theme[] = [];
|
||||
export default themes;
|
||||
|
||||
const themesPath = path.resolve(`${__dirname}/themes`);
|
||||
for (const fileName of fs.readdirSync(themesPath)) {
|
||||
if (fileName == "template.ts") continue;
|
||||
const filePath = path.join(themesPath, fileName);
|
||||
const internalThemes: Theme[] = [];
|
||||
export { internalThemes }
|
||||
|
||||
if (fs.statSync(filePath).isFile()) {
|
||||
const theme = require(filePath);
|
||||
if (theme.dark) themes.push(theme.dark);
|
||||
if (theme.light) themes.push(theme.light);
|
||||
const experimentalThemes: Theme[] = [];
|
||||
export { experimentalThemes }
|
||||
|
||||
|
||||
function fillThemes(themesPath: string, themes: Theme[]) {
|
||||
for (const fileName of fs.readdirSync(themesPath)) {
|
||||
if (fileName == "template.ts") continue;
|
||||
const filePath = path.join(themesPath, fileName);
|
||||
|
||||
if (fs.statSync(filePath).isFile()) {
|
||||
const theme = require(filePath);
|
||||
if (theme.dark) themes.push(theme.dark);
|
||||
if (theme.light) themes.push(theme.light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fillThemes(path.resolve(`${__dirname}/themes`), themes)
|
||||
fillThemes(path.resolve(`${__dirname}/themes/internal`), internalThemes)
|
||||
fillThemes(path.resolve(`${__dirname}/themes/experiments`), experimentalThemes)
|
||||
|
||||
|
|
0
styles/src/themes/experiments/.gitkeep
Normal file
0
styles/src/themes/experiments/.gitkeep
Normal file
0
styles/src/themes/internal/.gitkeep
Normal file
0
styles/src/themes/internal/.gitkeep
Normal file
Loading…
Reference in a new issue