diff --git a/.gitignore b/.gitignore index fcebdc84a2126b9d974ed64942896caf576434f0..5e6963ba8b899780ec067d64b42eac6d934c4d36 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ /crates/collab/static/styles.css /vendor/bin /assets/themes/*.json +/assets/themes/internal/*.json +/assets/themes/experiments/*.json \ No newline at end of file diff --git a/assets/keymaps/experiments/.gitkeep b/assets/keymaps/experiments/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/assets/keymaps/internal.json b/assets/keymaps/internal.json new file mode 100644 index 0000000000000000000000000000000000000000..0637a088a01e8ddab3bf3fa98dbe804cbde1a0dc --- /dev/null +++ b/assets/keymaps/internal.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/assets/themes/experiments/.gitkeep b/assets/themes/experiments/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/assets/themes/internal/.gitkeep b/assets/themes/internal/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/crates/settings/src/keymap_file.rs b/crates/settings/src/keymap_file.rs index c7cff927219ce8531e2779d1ec192bd2eb30af37..4dcb5a6fb0c27943ec49d94ddb6ff88b3beea75f 100644 --- a/crates/settings/src/keymap_file.rs +++ b/crates/settings/src/keymap_file.rs @@ -42,8 +42,15 @@ struct ActionWithData(Box, Box); impl KeymapFileContent { pub fn load_defaults(cx: &mut MutableAppContext) { + let settings = cx.global::(); let mut paths = vec!["keymaps/default.json", "keymaps/vim.json"]; - paths.extend(cx.global::().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(); } diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 895a0bc36341d31204a8dbb0eb0ea56bd8daf7b9..1095f289ebb94382449ea346e6af827331fc0e32 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -37,10 +37,13 @@ pub struct Settings { pub language_overrides: HashMap, EditorSettings>, pub lsp: HashMap, LspSettings>, pub theme: Arc, + 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, LspSettings>, #[serde(default)] pub theme: Option, + #[serde(default)] + pub staff_mode: Option, } #[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() } diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index ff13a233144c762d692e38cb2f5e08fb85810b7a..9d90b44402c41d0128af41669c4eb19ad9e827c9 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -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, diff --git a/crates/theme/src/theme_registry.rs b/crates/theme/src/theme_registry.rs index 219828b65083b08f1d7d0a105831acfc27bdf50a..5735f13b14fda153c0a9580d1d786cfbb4069a35 100644 --- a/crates/theme/src/theme_registry.rs +++ b/crates/theme/src/theme_registry.rs @@ -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 { - self.assets.list("themes/").into_iter().filter_map(|path| { + pub fn list(&self, internal: bool, experiments: bool) -> impl Iterator + '_ { + 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) diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index d09e3b298dc0fa2b15982d056edafe7e790d4aca..59b0bc7e6a8c4e3cd205da3f1f55c2fa6a116f11 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -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, - theme_names: Vec, + theme_data: Vec, matches: Vec, original_theme: Arc, picker: ViewHandle>, @@ -39,32 +39,41 @@ impl ThemeSelector { fn new(registry: Arc, cx: &mut ViewContext) -> Self { let handle = cx.weak_handle(); let picker = cx.add_view(|cx| Picker::new(handle, cx)); - let original_theme = cx.global::().theme.clone(); - let mut theme_names = registry.list().collect::>(); + let settings = cx.global::(); + + let original_theme = settings.theme.clone(); + + let mut theme_names = registry + .list( + settings.staff_mode, + settings.experiments.experimental_themes, + ) + .collect::>(); 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, cx: &mut MutableAppContext) { - let current_theme_name = cx.global::().theme.name.clone(); + let current_theme_name = cx.global::().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) -> 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::>(); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 141355742a5c8c960ef9371fd409f51e6f2fca7c..fc3616d59287f184d79c4895fda4bd6ce7368ea7 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -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); diff --git a/crates/zed/src/paths.rs b/crates/zed/src/paths.rs index 6643cc0fe6d40c7d91038e0634eab8b8f27de0cf..d6d99288c771f5260d5cd452fc97a04f15c87969 100644 --- a/crates/zed/src/paths.rs +++ b/crates/zed/src/paths.rs @@ -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"); } diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index f64da9c1c89d340147ffa4f6d2196447e74dfe3f..a8f10a1579f09ecdaaced6763d2305307ff8fc88 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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::(); + + 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); } diff --git a/styles/src/buildThemes.ts b/styles/src/buildThemes.ts index 22178163b6bd6642c255049a978def6dd840518a..10fb62bdd31948316934b6291cdc17f556382de9 100644 --- a/styles/src/buildThemes.ts +++ b/styles/src/buildThemes.ts @@ -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); diff --git a/styles/src/styleTree/app.ts b/styles/src/styleTree/app.ts index fe67cf701d2071206d8faed0bfe74a8a3412e909..1345d4e8554658ea9fe1686884f7b9e98b271db1 100644 --- a/styles/src/styleTree/app.ts +++ b/styles/src/styleTree/app.ts @@ -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), diff --git a/styles/src/themes.ts b/styles/src/themes.ts index 8d1782fd8472937f2ae97c3536139c35db1971de..fb9b05d786d16e2f36bd42c7a5008f3a3855ff7a 100644 --- a/styles/src/themes.ts +++ b/styles/src/themes.ts @@ -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) + diff --git a/styles/src/themes/experiments/.gitkeep b/styles/src/themes/experiments/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/styles/src/themes/internal/.gitkeep b/styles/src/themes/internal/.gitkeep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391