.gitignore 🔗
@@ -7,3 +7,5 @@
/crates/collab/static/styles.css
/vendor/bin
/assets/themes/*.json
+/assets/themes/internal/*.json
+/assets/themes/experiments/*.json
Mikayla Maki created
Internal themes
.gitignore | 2
assets/keymaps/experiments/.gitkeep | 0
assets/keymaps/internal.json | 1
assets/themes/experiments/.gitkeep | 0
assets/themes/internal/.gitkeep | 0
crates/settings/src/keymap_file.rs | 9 +++
crates/settings/src/settings.rs | 52 ++++++++++++----------
crates/theme/src/theme.rs | 8 +++
crates/theme/src/theme_registry.rs | 27 +++++++++--
crates/theme_selector/src/theme_selector.rs | 41 +++++++++++-------
crates/zed/src/main.rs | 1
crates/zed/src/paths.rs | 1
crates/zed/src/zed.rs | 17 +++++-
styles/src/buildThemes.ts | 47 +++++++++++++-------
styles/src/styleTree/app.ts | 4 +
styles/src/themes.ts | 31 +++++++++---
styles/src/themes/experiments/.gitkeep | 0
styles/src/themes/internal/.gitkeep | 0
18 files changed, 166 insertions(+), 75 deletions(-)
@@ -7,3 +7,5 @@
/crates/collab/static/styles.css
/vendor/bin
/assets/themes/*.json
+/assets/themes/internal/*.json
+/assets/themes/experiments/*.json
@@ -0,0 +1 @@
+[]
@@ -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);
-
- if (fs.statSync(filePath).isFile()) {
- const theme = require(filePath);
- if (theme.dark) themes.push(theme.dark);
- if (theme.light) themes.push(theme.light);
+const internalThemes: Theme[] = [];
+export { internalThemes }
+
+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)
+