Define theme/ui text style settings in theme crate

Max Brunsfeld created

Change summary

Cargo.lock                                              |   9 
crates/activity_indicator/Cargo.toml                    |   2 
crates/activity_indicator/src/activity_indicator.rs     |   8 
crates/auto_update/src/update_notification.rs           |   3 
crates/breadcrumbs/src/breadcrumbs.rs                   |   3 
crates/collab/src/tests.rs                              |   6 
crates/collab/src/tests/randomized_integration_tests.rs |   4 
crates/collab_ui/src/collab_titlebar_item.rs            |   7 
crates/collab_ui/src/contact_finder.rs                  |   3 
crates/collab_ui/src/contact_list.rs                    |   5 
crates/collab_ui/src/contacts_popover.rs                |   3 
crates/collab_ui/src/incoming_call_notification.rs      |  28 -
crates/collab_ui/src/notifications.rs                   |   3 
crates/collab_ui/src/project_shared_notification.rs     |  27 
crates/command_palette/src/command_palette.rs           |   5 
crates/context_menu/src/context_menu.rs                 |   5 
crates/copilot/src/sign_in.rs                           |   5 
crates/copilot_button/src/copilot_button.rs             |  11 
crates/diagnostics/Cargo.toml                           |   1 
crates/diagnostics/src/diagnostics.rs                   |  38 +-
crates/diagnostics/src/items.rs                         |  10 
crates/editor/src/blink_manager.rs                      |   6 
crates/editor/src/display_map/block_map.rs              |  13 
crates/editor/src/display_map/fold_map.rs               |  14 
crates/editor/src/display_map/suggestion_map.rs         |  10 
crates/editor/src/display_map/wrap_map.rs               |  14 
crates/editor/src/editor.rs                             |  22 
crates/editor/src/editor_tests.rs                       |   6 
crates/editor/src/element.rs                            |   9 
crates/editor/src/hover_popover.rs                      |   3 
crates/editor/src/items.rs                              |   3 
crates/editor/src/link_go_to_definition.rs              |   3 
crates/editor/src/movement.rs                           |   5 
crates/editor/src/multi_buffer.rs                       |   6 
crates/editor/src/test/editor_lsp_test_context.rs       |   1 
crates/feedback/src/deploy_feedback_button.rs           |   3 
crates/feedback/src/feedback_info_text.rs               |   3 
crates/feedback/src/submit_feedback_button.rs           |   3 
crates/file_finder/src/file_finder.rs                   |   6 
crates/go_to_line/Cargo.toml                            |   1 
crates/go_to_line/src/go_to_line.rs                     |   3 
crates/language/src/language_settings.rs                |   1 
crates/language_selector/src/active_buffer_language.rs  |   3 
crates/language_selector/src/language_selector.rs       |   4 
crates/lsp_log/src/lsp_log.rs                           |   3 
crates/outline/Cargo.toml                               |   2 
crates/outline/src/outline.rs                           |   7 
crates/picker/src/picker.rs                             |   2 
crates/project/src/project.rs                           |  10 
crates/project_panel/src/project_panel.rs               |  13 
crates/project_symbols/Cargo.toml                       |   3 
crates/project_symbols/src/project_symbols.rs           |  12 
crates/recent_projects/Cargo.toml                       |   1 
crates/recent_projects/src/recent_projects.rs           |   6 
crates/search/src/buffer_search.rs                      |  25 
crates/search/src/project_search.rs                     |  35 -
crates/settings/Cargo.toml                              |   8 
crates/settings/src/settings.rs                         | 209 ----------
crates/settings/src/settings_file.rs                    |  25 
crates/settings/src/settings_store.rs                   |  30 +
crates/terminal/src/terminal.rs                         |   3 
crates/terminal_view/src/terminal_button.rs             |   3 
crates/terminal_view/src/terminal_element.rs            |   6 
crates/terminal_view/src/terminal_view.rs               |   1 
crates/theme/Cargo.toml                                 |  15 
crates/theme/src/theme.rs                               |  18 
crates/theme/src/theme_registry.rs                      |  12 
crates/theme/src/theme_settings.rs                      | 136 +++++++
crates/theme_selector/src/theme_selector.rs             |  54 +-
crates/theme_testbench/src/theme_testbench.rs           |   7 
crates/welcome/src/base_keymap_picker.rs                |   4 
crates/welcome/src/welcome.rs                           |  10 
crates/workspace/src/dock.rs                            |   3 
crates/workspace/src/dock/toggle_dock_button.rs         |   3 
crates/workspace/src/notifications.rs                   |   9 
crates/workspace/src/pane.rs                            |  17 
crates/workspace/src/pane/dragged_item_receiver.rs      |  12 
crates/workspace/src/shared_screen.rs                   |   3 
crates/workspace/src/sidebar.rs                         |   5 
crates/workspace/src/status_bar.rs                      |   3 
crates/workspace/src/toolbar.rs                         |   5 
crates/workspace/src/workspace.rs                       |  17 
crates/zed/src/languages.rs                             |   8 
crates/zed/src/languages/json.rs                        |  31 -
crates/zed/src/main.rs                                  |  33 -
crates/zed/src/zed.rs                                   |  30 +
86 files changed, 530 insertions(+), 637 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -20,6 +20,7 @@ dependencies = [
  "project",
  "settings",
  "smallvec",
+ "theme",
  "util",
  "workspace",
 ]
@@ -2750,6 +2751,7 @@ dependencies = [
  "postage",
  "settings",
  "text",
+ "theme",
  "util",
  "workspace",
 ]
@@ -4398,6 +4400,7 @@ dependencies = [
  "settings",
  "smol",
  "text",
+ "theme",
  "workspace",
 ]
 
@@ -4915,6 +4918,7 @@ dependencies = [
  "settings",
  "smol",
  "text",
+ "theme",
  "util",
  "workspace",
 ]
@@ -5235,6 +5239,7 @@ dependencies = [
  "settings",
  "smol",
  "text",
+ "theme",
  "util",
  "workspace",
 ]
@@ -6135,7 +6140,6 @@ dependencies = [
  "smallvec",
  "sqlez",
  "staff_mode",
- "theme",
  "toml",
  "tree-sitter",
  "tree-sitter-json 0.19.0",
@@ -6863,10 +6867,13 @@ dependencies = [
  "gpui",
  "indexmap",
  "parking_lot 0.11.2",
+ "schemars",
  "serde",
  "serde_derive",
  "serde_json",
+ "settings",
  "toml",
+ "util",
 ]
 
 [[package]]

crates/activity_indicator/Cargo.toml 🔗

@@ -16,6 +16,8 @@ gpui = { path = "../gpui" }
 project = { path = "../project" }
 settings = { path = "../settings" }
 util = { path = "../util" }
+theme = { path = "../theme" }
 workspace = { path = "../workspace" }
+
 futures.workspace = true
 smallvec.workspace = true

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -9,7 +9,6 @@ use gpui::{
 };
 use language::{LanguageRegistry, LanguageServerBinaryStatus};
 use project::{LanguageServerProgress, Project};
-use settings::Settings;
 use smallvec::SmallVec;
 use std::{cmp::Reverse, fmt::Write, sync::Arc};
 use util::ResultExt;
@@ -325,12 +324,7 @@ impl View for ActivityIndicator {
         } = self.content_to_render(cx);
 
         let mut element = MouseEventHandler::<Self, _>::new(0, cx, |state, cx| {
-            let theme = &cx
-                .global::<Settings>()
-                .theme
-                .workspace
-                .status_bar
-                .lsp_status;
+            let theme = &theme::current(cx).workspace.status_bar.lsp_status;
             let style = if state.hovered() && on_click.is_some() {
                 theme.hover.as_ref().unwrap_or(&theme.default)
             } else {

crates/auto_update/src/update_notification.rs 🔗

@@ -5,7 +5,6 @@ use gpui::{
     Element, Entity, View, ViewContext,
 };
 use menu::Cancel;
-use settings::Settings;
 use util::channel::ReleaseChannel;
 use workspace::notifications::Notification;
 
@@ -27,7 +26,7 @@ impl View for UpdateNotification {
     }
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         let theme = &theme.update_notification;
 
         let app_name = cx.global::<ReleaseChannel>().display_name();

crates/breadcrumbs/src/breadcrumbs.rs 🔗

@@ -4,7 +4,6 @@ use gpui::{
 };
 use itertools::Itertools;
 use search::ProjectSearchView;
-use settings::Settings;
 use workspace::{
     item::{ItemEvent, ItemHandle},
     ToolbarItemLocation, ToolbarItemView, Workspace,
@@ -50,7 +49,7 @@ impl View for Breadcrumbs {
         };
         let not_editor = active_item.downcast::<editor::Editor>().is_none();
 
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         let style = &theme.workspace.breadcrumbs;
 
         let breadcrumbs = match active_item.breadcrumbs(&theme, cx) {

crates/collab/src/tests.rs 🔗

@@ -19,7 +19,7 @@ use gpui::{
 use language::LanguageRegistry;
 use parking_lot::Mutex;
 use project::{Project, WorktreeId};
-use settings::{Settings, SettingsStore};
+use settings::SettingsStore;
 use std::{
     cell::{Ref, RefCell, RefMut},
     env,
@@ -30,7 +30,6 @@ use std::{
         Arc,
     },
 };
-use theme::ThemeRegistry;
 use util::http::FakeHttpClient;
 use workspace::Workspace;
 
@@ -103,7 +102,6 @@ impl TestServer {
     async fn create_client(&mut self, cx: &mut TestAppContext, name: &str) -> TestClient {
         cx.update(|cx| {
             cx.set_global(SettingsStore::test(cx));
-            cx.set_global(Settings::test(cx));
         });
 
         let http = FakeHttpClient::with_404_response();
@@ -192,7 +190,6 @@ impl TestServer {
             client: client.clone(),
             user_store: user_store.clone(),
             languages: Arc::new(LanguageRegistry::test()),
-            themes: ThemeRegistry::new((), cx.font_cache()),
             fs: fs.clone(),
             build_window_options: |_, _, _| Default::default(),
             initialize_workspace: |_, _, _| unimplemented!(),
@@ -201,6 +198,7 @@ impl TestServer {
         });
 
         cx.update(|cx| {
+            theme::init((), cx);
             Project::init(&client, cx);
             client::init(&client, cx);
             language::init(cx);

crates/collab/src/tests/randomized_integration_tests.rs 🔗

@@ -21,7 +21,7 @@ use rand::{
     prelude::*,
 };
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
+use settings::SettingsStore;
 use std::{
     env,
     ops::Range,
@@ -150,10 +150,8 @@ async fn test_random_collaboration(
     for (client, mut cx) in clients {
         cx.update(|cx| {
             let store = cx.remove_global::<SettingsStore>();
-            let settings = cx.remove_global::<Settings>();
             cx.clear_globals();
             cx.set_global(store);
-            cx.set_global(settings);
             drop(client);
         });
     }

crates/collab_ui/src/collab_titlebar_item.rs 🔗

@@ -18,7 +18,6 @@ use gpui::{
     ViewContext, ViewHandle, WeakViewHandle,
 };
 use project::Project;
-use settings::Settings;
 use std::{ops::Range, sync::Arc};
 use theme::{AvatarStyle, Theme};
 use util::ResultExt;
@@ -70,7 +69,7 @@ impl View for CollabTitlebarItem {
         };
 
         let project = self.project.read(cx);
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         let mut left_container = Flex::row();
         let mut right_container = Flex::row().align_children_center();
 
@@ -298,7 +297,7 @@ impl CollabTitlebarItem {
     }
 
     pub fn toggle_user_menu(&mut self, _: &ToggleUserMenu, cx: &mut ViewContext<Self>) {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         let avatar_style = theme.workspace.titlebar.leader_avatar.clone();
         let item_style = theme.context_menu.item.disabled_style().clone();
         self.user_menu.update(cx, |user_menu, cx| {
@@ -866,7 +865,7 @@ impl CollabTitlebarItem {
     ) -> Option<AnyElement<Self>> {
         enum ConnectionStatusButton {}
 
-        let theme = &cx.global::<Settings>().theme.clone();
+        let theme = &theme::current(cx).clone();
         match status {
             client::Status::ConnectionError
             | client::Status::ConnectionLost

crates/collab_ui/src/contact_finder.rs 🔗

@@ -1,7 +1,6 @@
 use client::{ContactRequestStatus, User, UserStore};
 use gpui::{elements::*, AppContext, ModelHandle, MouseState, Task, ViewContext};
 use picker::{Picker, PickerDelegate, PickerEvent};
-use settings::Settings;
 use std::sync::Arc;
 use util::TryFutureExt;
 
@@ -98,7 +97,7 @@ impl PickerDelegate for ContactFinderDelegate {
         selected: bool,
         cx: &gpui::AppContext,
     ) -> AnyElement<Picker<Self>> {
-        let theme = &cx.global::<Settings>().theme;
+        let theme = &theme::current(cx);
         let user = &self.potential_contacts[ix];
         let request_status = self.user_store.read(cx).contact_request_status(user);
 

crates/collab_ui/src/contact_list.rs 🔗

@@ -14,7 +14,6 @@ use gpui::{
 use menu::{Confirm, SelectNext, SelectPrev};
 use project::Project;
 use serde::Deserialize;
-use settings::Settings;
 use std::{mem, sync::Arc};
 use theme::IconButton;
 use workspace::Workspace;
@@ -192,7 +191,7 @@ impl ContactList {
         .detach();
 
         let list_state = ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
-            let theme = cx.global::<Settings>().theme.clone();
+            let theme = theme::current(cx).clone();
             let is_selected = this.selection == Some(ix);
             let current_project_id = this.project.read(cx).remote_id();
 
@@ -1313,7 +1312,7 @@ impl View for ContactList {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         enum AddContact {}
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
 
         Flex::column()
             .with_child(

crates/collab_ui/src/contacts_popover.rs 🔗

@@ -9,7 +9,6 @@ use gpui::{
 };
 use picker::PickerEvent;
 use project::Project;
-use settings::Settings;
 use workspace::Workspace;
 
 actions!(contacts_popover, [ToggleContactFinder]);
@@ -108,7 +107,7 @@ impl View for ContactsPopover {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         let child = match &self.child {
             Child::ContactList(child) => ChildView::new(child, cx),
             Child::ContactFinder(child) => ChildView::new(child, cx),

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -9,7 +9,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
     AnyElement, AppContext, Entity, View, ViewContext,
 };
-use settings::Settings;
 use util::ResultExt;
 use workspace::AppState;
 
@@ -26,7 +25,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
             if let Some(incoming_call) = incoming_call {
                 const PADDING: f32 = 16.;
                 let window_size = cx.read(|cx| {
-                    let theme = &cx.global::<Settings>().theme.incoming_call_notification;
+                    let theme = &theme::current(cx).incoming_call_notification;
                     vec2f(theme.window_width, theme.window_height)
                 });
 
@@ -106,7 +105,7 @@ impl IncomingCallNotification {
     }
 
     fn render_caller(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &cx.global::<Settings>().theme.incoming_call_notification;
+        let theme = &theme::current(cx).incoming_call_notification;
         let default_project = proto::ParticipantProject::default();
         let initial_project = self
             .call
@@ -170,10 +169,11 @@ impl IncomingCallNotification {
         enum Accept {}
         enum Decline {}
 
+        let theme = theme::current(cx);
         Flex::column()
             .with_child(
-                MouseEventHandler::<Accept, Self>::new(0, cx, |_, cx| {
-                    let theme = &cx.global::<Settings>().theme.incoming_call_notification;
+                MouseEventHandler::<Accept, Self>::new(0, cx, |_, _| {
+                    let theme = &theme.incoming_call_notification;
                     Label::new("Accept", theme.accept_button.text.clone())
                         .aligned()
                         .contained()
@@ -186,8 +186,8 @@ impl IncomingCallNotification {
                 .flex(1., true),
             )
             .with_child(
-                MouseEventHandler::<Decline, Self>::new(0, cx, |_, cx| {
-                    let theme = &cx.global::<Settings>().theme.incoming_call_notification;
+                MouseEventHandler::<Decline, Self>::new(0, cx, |_, _| {
+                    let theme = &theme.incoming_call_notification;
                     Label::new("Decline", theme.decline_button.text.clone())
                         .aligned()
                         .contained()
@@ -200,12 +200,7 @@ impl IncomingCallNotification {
                 .flex(1., true),
             )
             .constrained()
-            .with_width(
-                cx.global::<Settings>()
-                    .theme
-                    .incoming_call_notification
-                    .button_width,
-            )
+            .with_width(theme.incoming_call_notification.button_width)
             .into_any()
     }
 }
@@ -220,12 +215,7 @@ impl View for IncomingCallNotification {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let background = cx
-            .global::<Settings>()
-            .theme
-            .incoming_call_notification
-            .background;
-
+        let background = theme::current(cx).incoming_call_notification.background;
         Flex::row()
             .with_child(self.render_caller(cx))
             .with_child(self.render_buttons(cx))

crates/collab_ui/src/notifications.rs 🔗

@@ -4,7 +4,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     AnyElement, Element, View, ViewContext,
 };
-use settings::Settings;
 use std::sync::Arc;
 
 enum Dismiss {}
@@ -22,7 +21,7 @@ where
     F: 'static + Fn(&mut V, &mut ViewContext<V>),
     V: View,
 {
-    let theme = cx.global::<Settings>().theme.clone();
+    let theme = theme::current(cx).clone();
     let theme = &theme.contact_notification;
 
     Flex::column()

crates/collab_ui/src/project_shared_notification.rs 🔗

@@ -7,7 +7,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton, WindowBounds, WindowKind, WindowOptions},
     AppContext, Entity, View, ViewContext,
 };
-use settings::Settings;
 use std::sync::{Arc, Weak};
 use workspace::AppState;
 
@@ -22,7 +21,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut AppContext) {
             worktree_root_names,
         } => {
             const PADDING: f32 = 16.;
-            let theme = &cx.global::<Settings>().theme.project_shared_notification;
+            let theme = &theme::current(cx).project_shared_notification;
             let window_size = vec2f(theme.window_width, theme.window_height);
 
             for screen in cx.platform().screens() {
@@ -109,7 +108,7 @@ impl ProjectSharedNotification {
     }
 
     fn render_owner(&self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &cx.global::<Settings>().theme.project_shared_notification;
+        let theme = &theme::current(cx).project_shared_notification;
         Flex::row()
             .with_children(self.owner.avatar.clone().map(|avatar| {
                 Image::from_data(avatar)
@@ -167,10 +166,11 @@ impl ProjectSharedNotification {
         enum Open {}
         enum Dismiss {}
 
+        let theme = theme::current(cx);
         Flex::column()
             .with_child(
-                MouseEventHandler::<Open, Self>::new(0, cx, |_, cx| {
-                    let theme = &cx.global::<Settings>().theme.project_shared_notification;
+                MouseEventHandler::<Open, Self>::new(0, cx, |_, _| {
+                    let theme = &theme.project_shared_notification;
                     Label::new("Open", theme.open_button.text.clone())
                         .aligned()
                         .contained()
@@ -181,8 +181,8 @@ impl ProjectSharedNotification {
                 .flex(1., true),
             )
             .with_child(
-                MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, cx| {
-                    let theme = &cx.global::<Settings>().theme.project_shared_notification;
+                MouseEventHandler::<Dismiss, Self>::new(0, cx, |_, _| {
+                    let theme = &theme.project_shared_notification;
                     Label::new("Dismiss", theme.dismiss_button.text.clone())
                         .aligned()
                         .contained()
@@ -195,12 +195,7 @@ impl ProjectSharedNotification {
                 .flex(1., true),
             )
             .constrained()
-            .with_width(
-                cx.global::<Settings>()
-                    .theme
-                    .project_shared_notification
-                    .button_width,
-            )
+            .with_width(theme.project_shared_notification.button_width)
             .into_any()
     }
 }
@@ -215,11 +210,7 @@ impl View for ProjectSharedNotification {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> gpui::AnyElement<Self> {
-        let background = cx
-            .global::<Settings>()
-            .theme
-            .project_shared_notification
-            .background;
+        let background = theme::current(cx).project_shared_notification.background;
         Flex::row()
             .with_child(self.render_owner(cx))
             .with_child(self.render_buttons(cx))

crates/command_palette/src/command_palette.rs 🔗

@@ -5,7 +5,6 @@ use gpui::{
     ViewContext,
 };
 use picker::{Picker, PickerDelegate, PickerEvent};
-use settings::Settings;
 use std::cmp;
 use util::ResultExt;
 use workspace::Workspace;
@@ -185,8 +184,7 @@ impl PickerDelegate for CommandPaletteDelegate {
     ) -> AnyElement<Picker<Self>> {
         let mat = &self.matches[ix];
         let command = &self.actions[mat.candidate_id];
-        let settings = cx.global::<Settings>();
-        let theme = &settings.theme;
+        let theme = theme::current(cx);
         let style = theme.picker.item.style_for(mouse_state, selected);
         let key_style = &theme.command_palette.key.style_for(mouse_state, selected);
         let keystroke_spacing = theme.command_palette.keystroke_spacing;
@@ -366,6 +364,7 @@ mod tests {
     fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
         cx.update(|cx| {
             let app_state = AppState::test(cx);
+            theme::init((), cx);
             language::init(cx);
             editor::init(cx);
             workspace::init(app_state.clone(), cx);

crates/context_menu/src/context_menu.rs 🔗

@@ -8,7 +8,6 @@ use gpui::{
     View, ViewContext,
 };
 use menu::*;
-use settings::Settings;
 use std::{any::TypeId, borrow::Cow, sync::Arc, time::Duration};
 
 pub fn init(cx: &mut AppContext) {
@@ -323,7 +322,7 @@ impl ContextMenu {
     }
 
     fn render_menu_for_measurement(&self, cx: &mut ViewContext<Self>) -> impl Element<ContextMenu> {
-        let style = cx.global::<Settings>().theme.context_menu.clone();
+        let style = theme::current(cx).context_menu.clone();
         Flex::row()
             .with_child(
                 Flex::column().with_children(self.items.iter().enumerate().map(|(ix, item)| {
@@ -403,7 +402,7 @@ impl ContextMenu {
         enum Menu {}
         enum MenuItem {}
 
-        let style = cx.global::<Settings>().theme.context_menu.clone();
+        let style = theme::current(cx).context_menu.clone();
 
         MouseEventHandler::<Menu, ContextMenu>::new(0, cx, |_, cx| {
             Flex::column()

crates/copilot/src/sign_in.rs 🔗

@@ -6,7 +6,6 @@ use gpui::{
     AnyElement, AnyViewHandle, AppContext, ClipboardItem, Element, Entity, View, ViewContext,
     ViewHandle,
 };
-use settings::Settings;
 use theme::ui::modal;
 
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -68,7 +67,7 @@ fn create_copilot_auth_window(
     cx: &mut AppContext,
     status: &Status,
 ) -> ViewHandle<CopilotCodeVerification> {
-    let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
+    let window_size = theme::current(cx).copilot.modal.dimensions();
     let window_options = WindowOptions {
         bounds: WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
         titlebar: None,
@@ -338,7 +337,7 @@ impl View for CopilotCodeVerification {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         enum ConnectModal {}
 
-        let style = cx.global::<Settings>().theme.clone();
+        let style = theme::current(cx).clone();
 
         modal::<ConnectModal, _, _, _, _>(
             "Connect Copilot to Zed",

crates/copilot_button/src/copilot_button.rs 🔗

@@ -10,7 +10,7 @@ use gpui::{
     ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
 use language::language_settings::{self, all_language_settings, AllLanguageSettings};
-use settings::{update_settings_file, Settings, SettingsStore};
+use settings::{update_settings_file, SettingsStore};
 use std::{path::Path, sync::Arc};
 use util::{paths, ResultExt};
 use workspace::{
@@ -46,8 +46,7 @@ impl View for CopilotButton {
             return Empty::new().into_any();
         }
 
-        let settings = cx.global::<Settings>();
-        let theme = settings.theme.clone();
+        let theme = theme::current(cx).clone();
         let active = self.popup_menu.read(cx).visible();
         let Some(copilot) = Copilot::global(cx) else {
             return Empty::new().into_any();
@@ -158,7 +157,7 @@ impl CopilotButton {
 
         Copilot::global(cx).map(|copilot| cx.observe(&copilot, |_, _, cx| cx.notify()).detach());
 
-        cx.observe_global::<Settings, _>(move |_, cx| cx.notify())
+        cx.observe_global::<SettingsStore, _>(move |_, cx| cx.notify())
             .detach();
 
         Self {
@@ -249,7 +248,7 @@ impl CopilotButton {
 
         menu_options.push(ContextMenuItem::Separator);
 
-        let icon_style = cx.global::<Settings>().theme.copilot.out_link_icon.clone();
+        let icon_style = theme::current(cx).copilot.out_link_icon.clone();
         menu_options.push(ContextMenuItem::action(
             move |state: &mut MouseState, style: &theme::ContextMenuItem| {
                 Flex::row()
@@ -316,7 +315,7 @@ async fn configure_disabled_globs(
     let settings_editor = workspace
         .update(&mut cx, |_, cx| {
             create_and_open_local_file(&paths::SETTINGS, cx, || {
-                Settings::initial_user_settings_content(&assets::Assets)
+                settings::initial_user_settings_content(&assets::Assets)
                     .as_ref()
                     .into()
             })

crates/diagnostics/Cargo.toml 🔗

@@ -31,6 +31,7 @@ language = { path = "../language", features = ["test-support"] }
 lsp = { path = "../lsp", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
+theme = { path = "../theme", features = ["test-support"] }
 
 serde_json.workspace = true
 unindent.workspace = true

crates/diagnostics/src/diagnostics.rs 🔗

@@ -20,7 +20,6 @@ use language::{
 use lsp::LanguageServerId;
 use project::{DiagnosticSummary, Project, ProjectPath};
 use serde_json::json;
-use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -30,6 +29,7 @@ use std::{
     path::PathBuf,
     sync::Arc,
 };
+use theme::ThemeSettings;
 use util::TryFutureExt;
 use workspace::{
     item::{BreadcrumbText, Item, ItemEvent, ItemHandle},
@@ -89,7 +89,7 @@ impl View for ProjectDiagnosticsEditor {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         if self.path_states.is_empty() {
-            let theme = &cx.global::<Settings>().theme.project_diagnostics;
+            let theme = &theme::current(cx).project_diagnostics;
             Label::new("No problems in workspace", theme.empty_message.clone())
                 .aligned()
                 .contained()
@@ -537,7 +537,7 @@ impl Item for ProjectDiagnosticsEditor {
         render_summary(
             &self.summary,
             &style.label.text,
-            &cx.global::<Settings>().theme.project_diagnostics,
+            &theme::current(cx).project_diagnostics,
         )
     }
 
@@ -679,7 +679,7 @@ impl Item for ProjectDiagnosticsEditor {
 fn diagnostic_header_renderer(diagnostic: Diagnostic) -> RenderBlock {
     let (message, highlights) = highlight_diagnostic_message(Vec::new(), &diagnostic.message);
     Arc::new(move |cx| {
-        let settings = cx.global::<Settings>();
+        let settings = settings::get_setting::<ThemeSettings>(None, cx);
         let theme = &settings.theme.editor;
         let style = theme.diagnostic_header.clone();
         let font_size = (style.text_scale_factor
@@ -832,23 +832,23 @@ mod tests {
             "/test",
             json!({
                 "consts.rs": "
-                        const a: i32 = 'a';
-                        const b: i32 = c;
-                    "
+                    const a: i32 = 'a';
+                    const b: i32 = c;
+                "
                 .unindent(),
 
                 "main.rs": "
-                        fn main() {
-                            let x = vec![];
-                            let y = vec![];
-                            a(x);
-                            b(y);
-                            // comment 1
-                            // comment 2
-                            c(y);
-                            d(x);
-                        }
-                    "
+                    fn main() {
+                        let x = vec![];
+                        let y = vec![];
+                        a(x);
+                        b(y);
+                        // comment 1
+                        // comment 2
+                        c(y);
+                        d(x);
+                    }
+                "
                 .unindent(),
             }),
         )
@@ -1496,8 +1496,8 @@ mod tests {
 
     fn init_test(cx: &mut TestAppContext) {
         cx.update(|cx| {
-            cx.set_global(Settings::test(cx));
             cx.set_global(SettingsStore::test(cx));
+            theme::init((), cx);
             language::init(cx);
             client::init_settings(cx);
             workspace::init_settings(cx);

crates/diagnostics/src/items.rs 🔗

@@ -7,7 +7,6 @@ use gpui::{
 };
 use language::Diagnostic;
 use lsp::LanguageServerId;
-use settings::Settings;
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
 use crate::ProjectDiagnosticsEditor;
@@ -92,13 +91,12 @@ impl View for DiagnosticIndicator {
         enum Summary {}
         enum Message {}
 
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
         let in_progress = !self.in_progress_checks.is_empty();
         let mut element = Flex::row().with_child(
             MouseEventHandler::<Summary, _>::new(0, cx, |state, cx| {
-                let style = cx
-                    .global::<Settings>()
-                    .theme
+                let theme = theme::current(cx);
+                let style = theme
                     .workspace
                     .status_bar
                     .diagnostic_summary
@@ -184,7 +182,7 @@ impl View for DiagnosticIndicator {
             .into_any(),
         );
 
-        let style = &cx.global::<Settings>().theme.workspace.status_bar;
+        let style = &theme::current(cx).workspace.status_bar;
         let item_spacing = style.item_spacing;
 
         if in_progress {
@@ -1,6 +1,6 @@
 use crate::EditorSettings;
 use gpui::{Entity, ModelContext};
-use settings::Settings;
+use settings::SettingsStore;
 use smol::Timer;
 use std::time::Duration;
 
@@ -15,8 +15,8 @@ pub struct BlinkManager {
 
 impl BlinkManager {
     pub fn new(blink_interval: Duration, cx: &mut ModelContext<Self>) -> Self {
-        cx.observe_global::<Settings, _>(move |this, cx| {
-            // Make sure we blink the cursors if the setting is re-enabled
+        // Make sure we blink the cursors if the setting is re-enabled
+        cx.observe_global::<SettingsStore, _>(move |this, cx| {
             this.blink_cursors(this.blink_epoch, cx)
         })
         .detach();

crates/editor/src/display_map/block_map.rs 🔗

@@ -993,7 +993,7 @@ mod tests {
     use crate::multi_buffer::MultiBuffer;
     use gpui::{elements::Empty, Element};
     use rand::prelude::*;
-    use settings::Settings;
+    use settings::SettingsStore;
     use std::env;
     use util::RandomCharIter;
 
@@ -1013,7 +1013,7 @@ mod tests {
 
     #[gpui::test]
     fn test_basic_blocks(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
 
         let family_id = cx
             .font_cache()
@@ -1189,7 +1189,7 @@ mod tests {
 
     #[gpui::test]
     fn test_blocks_on_wrapped_lines(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
 
         let family_id = cx
             .font_cache()
@@ -1239,7 +1239,7 @@ mod tests {
 
     #[gpui::test(iterations = 100)]
     fn test_random_blocks(cx: &mut gpui::AppContext, mut rng: StdRng) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
 
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
@@ -1647,6 +1647,11 @@ mod tests {
         }
     }
 
+    fn init_test(cx: &mut gpui::AppContext) {
+        cx.set_global(SettingsStore::test(cx));
+        theme::init((), cx);
+    }
+
     impl TransformBlock {
         fn as_custom(&self) -> Option<&Block> {
             match self {

crates/editor/src/display_map/fold_map.rs 🔗

@@ -1204,7 +1204,7 @@ mod tests {
     use crate::{MultiBuffer, ToPoint};
     use collections::HashSet;
     use rand::prelude::*;
-    use settings::Settings;
+    use settings::SettingsStore;
     use std::{cmp::Reverse, env, mem, sync::Arc};
     use sum_tree::TreeMap;
     use util::test::sample_text;
@@ -1213,7 +1213,7 @@ mod tests {
 
     #[gpui::test]
     fn test_basic_folds(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
         let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
         let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
         let buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -1286,7 +1286,7 @@ mod tests {
 
     #[gpui::test]
     fn test_adjacent_folds(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
         let buffer = MultiBuffer::build_simple("abcdefghijkl", cx);
         let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
         let buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -1349,7 +1349,7 @@ mod tests {
 
     #[gpui::test]
     fn test_merging_folds_via_edit(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
         let buffer = MultiBuffer::build_simple(&sample_text(5, 6, 'a'), cx);
         let subscription = buffer.update(cx, |buffer, _| buffer.subscribe());
         let buffer_snapshot = buffer.read(cx).snapshot(cx);
@@ -1400,7 +1400,7 @@ mod tests {
 
     #[gpui::test(iterations = 100)]
     fn test_random_folds(cx: &mut gpui::AppContext, mut rng: StdRng) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
@@ -1676,6 +1676,10 @@ mod tests {
         assert_eq!(snapshot.buffer_rows(3).collect::<Vec<_>>(), [Some(6)]);
     }
 
+    fn init_test(cx: &mut gpui::AppContext) {
+        cx.set_global(SettingsStore::test(cx));
+    }
+
     impl FoldMap {
         fn merged_fold_ranges(&self) -> Vec<Range<usize>> {
             let buffer = self.buffer.lock().clone();

crates/editor/src/display_map/suggestion_map.rs 🔗

@@ -578,7 +578,7 @@ mod tests {
     use crate::{display_map::fold_map::FoldMap, MultiBuffer};
     use gpui::AppContext;
     use rand::{prelude::StdRng, Rng};
-    use settings::Settings;
+    use settings::SettingsStore;
     use std::{
         env,
         ops::{Bound, RangeBounds},
@@ -631,7 +631,8 @@ mod tests {
 
     #[gpui::test(iterations = 100)]
     fn test_random_suggestions(cx: &mut AppContext, mut rng: StdRng) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
@@ -834,6 +835,11 @@ mod tests {
         }
     }
 
+    fn init_test(cx: &mut AppContext) {
+        cx.set_global(SettingsStore::test(cx));
+        theme::init((), cx);
+    }
+
     impl SuggestionMap {
         pub fn randomly_mutate(
             &self,

crates/editor/src/display_map/wrap_map.rs 🔗

@@ -1043,16 +1043,16 @@ mod tests {
     };
     use gpui::test::observe;
     use rand::prelude::*;
-    use settings::Settings;
+    use settings::SettingsStore;
     use smol::stream::StreamExt;
     use std::{cmp, env, num::NonZeroU32};
     use text::Rope;
 
     #[gpui::test(iterations = 100)]
     async fn test_random_wraps(cx: &mut gpui::TestAppContext, mut rng: StdRng) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
+        init_test(cx);
+
         cx.foreground().set_block_on_ticks(0..=50);
-        cx.foreground().forbid_parking();
         let operations = env::var("OPERATIONS")
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
@@ -1287,6 +1287,14 @@ mod tests {
         wrap_map.read_with(cx, |map, _| assert!(map.pending_edits.is_empty()));
     }
 
+    fn init_test(cx: &mut gpui::TestAppContext) {
+        cx.foreground().forbid_parking();
+        cx.update(|cx| {
+            cx.set_global(SettingsStore::test(cx));
+            theme::init((), cx);
+        });
+    }
+
     fn wrap_text(
         unwrapped_text: &str,
         wrap_width: Option<f32>,

crates/editor/src/editor.rs 🔗

@@ -73,7 +73,7 @@ use scroll::{
 };
 use selections_collection::{resolve_multiple, MutableSelectionsCollection, SelectionsCollection};
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
+use settings::SettingsStore;
 use smallvec::SmallVec;
 use snippet::Snippet;
 use std::{
@@ -88,7 +88,7 @@ use std::{
     time::{Duration, Instant},
 };
 pub use sum_tree::Bias;
-use theme::{DiagnosticStyle, Theme};
+use theme::{DiagnosticStyle, Theme, ThemeSettings};
 use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
 use workspace::{ItemNavHistory, ViewId, Workspace};
 
@@ -1237,8 +1237,8 @@ impl Editor {
     ) -> Self {
         let editor_view_id = cx.view_id();
         let display_map = cx.add_model(|cx| {
-            let settings = cx.global::<Settings>();
-            let style = build_style(&*settings, get_field_editor_theme.as_deref(), None, cx);
+            let settings = settings::get_setting::<ThemeSettings>(None, cx);
+            let style = build_style(settings, get_field_editor_theme.as_deref(), None, cx);
             DisplayMap::new(
                 buffer.clone(),
                 style.text.font_id,
@@ -1319,7 +1319,7 @@ impl Editor {
                 cx.subscribe(&buffer, Self::on_buffer_event),
                 cx.observe(&display_map, Self::on_display_map_changed),
                 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
-                cx.observe_global::<Settings, _>(Self::settings_changed),
+                cx.observe_global::<SettingsStore, _>(Self::settings_changed),
             ],
         };
 
@@ -1418,7 +1418,7 @@ impl Editor {
 
     fn style(&self, cx: &AppContext) -> EditorStyle {
         build_style(
-            cx.global::<Settings>(),
+            settings::get_setting::<ThemeSettings>(None, cx),
             self.get_field_editor_theme.as_deref(),
             self.override_text_style.as_deref(),
             cx,
@@ -6561,8 +6561,8 @@ impl Editor {
         let buffer = &snapshot.buffer_snapshot;
         let start = buffer.anchor_before(0);
         let end = buffer.anchor_after(buffer.len());
-        let theme = cx.global::<Settings>().theme.as_ref();
-        self.background_highlights_in_range(start..end, &snapshot, theme)
+        let theme = theme::current(cx);
+        self.background_highlights_in_range(start..end, &snapshot, theme.as_ref())
     }
 
     fn document_highlights_for_position<'a>(
@@ -6985,7 +6985,7 @@ impl Editor {
         let mut lines = Vec::new();
         let mut line: VecDeque<Chunk> = VecDeque::new();
 
-        let theme = &cx.global::<Settings>().theme.editor.syntax;
+        let theme = &theme::current(cx).editor.syntax;
 
         for chunk in chunks {
             let highlight = chunk.syntax_highlight_id.and_then(|id| id.name(theme));
@@ -7407,7 +7407,7 @@ impl View for Editor {
 }
 
 fn build_style(
-    settings: &Settings,
+    settings: &ThemeSettings,
     get_field_editor_theme: Option<&GetFieldEditorTheme>,
     override_text_style: Option<&OverrideTextStyle>,
     cx: &AppContext,
@@ -7607,7 +7607,7 @@ pub fn diagnostic_block_renderer(diagnostic: Diagnostic, is_valid: bool) -> Rend
     }
 
     Arc::new(move |cx: &mut BlockContext| {
-        let settings = cx.global::<Settings>();
+        let settings = settings::get_setting::<ThemeSettings>(None, cx);
         let theme = &settings.theme.editor;
         let style = diagnostic_style(diagnostic.severity, is_valid, theme);
         let font_size = (style.text_scale_factor

crates/editor/src/editor_tests.rs 🔗

@@ -5452,7 +5452,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
         let mut highlighted_ranges = editor.background_highlights_in_range(
             anchor_range(Point::new(3, 4)..Point::new(7, 4)),
             &snapshot,
-            cx.global::<Settings>().theme.as_ref(),
+            theme::current(cx).as_ref(),
         );
         // Enforce a consistent ordering based on color without relying on the ordering of the
         // highlight's `TypeId` which is non-deterministic.
@@ -5482,7 +5482,7 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
             editor.background_highlights_in_range(
                 anchor_range(Point::new(5, 6)..Point::new(6, 4)),
                 &snapshot,
-                cx.global::<Settings>().theme.as_ref(),
+                theme::current(cx).as_ref(),
             ),
             &[(
                 DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5),
@@ -6681,7 +6681,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
 
     cx.update(|cx| {
         cx.set_global(SettingsStore::test(cx));
-        cx.set_global(Settings::test(cx));
+        theme::init((), cx);
         client::init_settings(cx);
         language::init(cx);
         Project::init_settings(cx);

crates/editor/src/element.rs 🔗

@@ -40,7 +40,6 @@ use language::{
     Selection,
 };
 use project::ProjectPath;
-use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     borrow::Cow,
@@ -611,7 +610,7 @@ impl EditorElement {
         layout: &mut LayoutState,
         cx: &mut ViewContext<Editor>,
     ) {
-        let diff_style = &cx.global::<Settings>().theme.editor.diff.clone();
+        let diff_style = &theme::current(cx).editor.diff.clone();
         let line_height = layout.position_map.line_height;
 
         let scroll_position = layout.position_map.snapshot.scroll_position();
@@ -1417,7 +1416,7 @@ impl EditorElement {
         editor: &mut Editor,
         cx: &mut LayoutContext<Editor>,
     ) -> (f32, Vec<BlockLayout>) {
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
         let scroll_x = snapshot.scroll_anchor.offset.x();
         let (fixed_blocks, non_fixed_blocks) = snapshot
             .blocks_in_range(rows.clone())
@@ -1936,11 +1935,11 @@ impl Element<Editor> for EditorElement {
         let is_singleton = editor.is_singleton(cx);
 
         let highlighted_rows = editor.highlighted_rows();
-        let theme = cx.global::<Settings>().theme.as_ref();
+        let theme = theme::current(cx);
         let highlighted_ranges = editor.background_highlights_in_range(
             start_anchor..end_anchor,
             &snapshot.display_snapshot,
-            theme,
+            theme.as_ref(),
         );
 
         fold_ranges.extend(

crates/editor/src/hover_popover.rs 🔗

@@ -12,7 +12,6 @@ use gpui::{
 };
 use language::{Bias, DiagnosticEntry, DiagnosticSeverity, Language, LanguageRegistry};
 use project::{HoverBlock, HoverBlockKind, Project};
-use settings::Settings;
 use std::{ops::Range, sync::Arc, time::Duration};
 use util::TryFutureExt;
 
@@ -654,7 +653,7 @@ impl DiagnosticPopover {
             _ => style.hover_popover.container,
         };
 
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
 
         MouseEventHandler::<DiagnosticPopover, _>::new(0, cx, |_, _| {
             text.with_soft_wrap(true)

crates/editor/src/items.rs 🔗

@@ -16,7 +16,6 @@ use language::{
 };
 use project::{FormatTrigger, Item as _, Project, ProjectPath};
 use rpc::proto::{self, update_view};
-use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     borrow::Cow,
@@ -1116,7 +1115,7 @@ impl View for CursorPosition {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         if let Some(position) = self.position {
-            let theme = &cx.global::<Settings>().theme.workspace.status_bar;
+            let theme = &theme::current(cx).workspace.status_bar;
             let mut text = format!(
                 "{}{FILE_ROW_COLUMN_DELIMITER}{}",
                 position.row + 1,
@@ -2,7 +2,6 @@ use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase};
 use gpui::{Task, ViewContext};
 use language::{Bias, ToOffset};
 use project::LocationLink;
-use settings::Settings;
 use std::ops::Range;
 use util::TryFutureExt;
 
@@ -210,7 +209,7 @@ pub fn show_link_definition(
                         });
 
                         // Highlight symbol using theme link definition highlight style
-                        let style = cx.global::<Settings>().theme.editor.link_definition;
+                        let style = theme::current(cx).editor.link_definition;
                         this.highlight_text::<LinkGoToDefinitionState>(
                             vec![highlight_range],
                             style,

crates/editor/src/movement.rs 🔗

@@ -367,10 +367,9 @@ pub fn split_display_range_by_lines(
 
 #[cfg(test)]
 mod tests {
-    use settings::{Settings, SettingsStore};
-
     use super::*;
     use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer};
+    use settings::SettingsStore;
 
     #[gpui::test]
     fn test_previous_word_start(cx: &mut gpui::AppContext) {
@@ -703,7 +702,7 @@ mod tests {
 
     fn init_test(cx: &mut gpui::AppContext) {
         cx.set_global(SettingsStore::test(cx));
-        cx.set_global(Settings::test(cx));
+        theme::init((), cx);
         language::init(cx);
         crate::init(cx);
     }

crates/editor/src/multi_buffer.rs 🔗

@@ -3806,10 +3806,9 @@ mod tests {
     use gpui::{AppContext, TestAppContext};
     use language::{Buffer, Rope};
     use rand::prelude::*;
-    use settings::Settings;
+    use settings::SettingsStore;
     use std::{env, rc::Rc};
     use unindent::Unindent;
-
     use util::test::sample_text;
 
     #[gpui::test]
@@ -5055,7 +5054,8 @@ mod tests {
 
     #[gpui::test]
     fn test_history(cx: &mut AppContext) {
-        cx.set_global(Settings::test(cx));
+        cx.set_global(SettingsStore::test(cx));
+
         let buffer_1 = cx.add_model(|cx| Buffer::new(0, "1234", cx));
         let buffer_2 = cx.add_model(|cx| Buffer::new(0, "5678", cx));
         let multibuffer = cx.add_model(|_| MultiBuffer::new(0));

crates/feedback/src/deploy_feedback_button.rs 🔗

@@ -3,7 +3,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     Entity, View, ViewContext, WeakViewHandle,
 };
-use settings::Settings;
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
 use crate::feedback_editor::{FeedbackEditor, GiveFeedback};
@@ -33,7 +32,7 @@ impl View for DeployFeedbackButton {
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         let active = self.active;
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         Stack::new()
             .with_child(
                 MouseEventHandler::<Self, Self>::new(0, cx, |state, _| {

crates/feedback/src/feedback_info_text.rs 🔗

@@ -3,7 +3,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     AnyElement, Element, Entity, View, ViewContext, ViewHandle,
 };
-use settings::Settings;
 use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
 
 use crate::{feedback_editor::FeedbackEditor, open_zed_community_repo, OpenZedCommunityRepo};
@@ -30,7 +29,7 @@ impl View for FeedbackInfoText {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
 
         Flex::row()
             .with_child(

crates/feedback/src/submit_feedback_button.rs 🔗

@@ -5,7 +5,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     AnyElement, AppContext, Element, Entity, Task, View, ViewContext, ViewHandle,
 };
-use settings::Settings;
 use workspace::{item::ItemHandle, ToolbarItemLocation, ToolbarItemView};
 
 pub fn init(cx: &mut AppContext) {
@@ -46,7 +45,7 @@ impl View for SubmitFeedbackButton {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         enum SubmitFeedbackButton {}
         MouseEventHandler::<SubmitFeedbackButton, Self>::new(0, cx, |state, _| {
             let style = theme.feedback.submit_button.style_for(state, false);

crates/file_finder/src/file_finder.rs 🔗

@@ -5,7 +5,6 @@ use gpui::{
 };
 use picker::{Picker, PickerDelegate};
 use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
-use settings::Settings;
 use std::{
     path::Path,
     sync::{
@@ -324,8 +323,8 @@ impl PickerDelegate for FileFinderDelegate {
         cx: &AppContext,
     ) -> AnyElement<Picker<Self>> {
         let path_match = &self.matches[ix];
-        let settings = cx.global::<Settings>();
-        let style = settings.theme.picker.item.style_for(mouse_state, selected);
+        let theme = theme::current(cx);
+        let style = theme.picker.item.style_for(mouse_state, selected);
         let (file_name, file_name_positions, full_path, full_path_positions) =
             self.labels_for_match(path_match);
         Flex::column()
@@ -909,6 +908,7 @@ mod tests {
         cx.foreground().forbid_parking();
         cx.update(|cx| {
             let state = AppState::test(cx);
+            theme::init((), cx);
             language::init(cx);
             super::init(cx);
             editor::init(cx);

crates/go_to_line/Cargo.toml 🔗

@@ -16,4 +16,5 @@ settings = { path = "../settings" }
 text = { path = "../text" }
 workspace = { path = "../workspace" }
 postage.workspace = true
+theme = { path = "../theme" }
 util = { path = "../util" }

crates/go_to_line/src/go_to_line.rs 🔗

@@ -6,7 +6,6 @@ use gpui::{
     View, ViewContext, ViewHandle,
 };
 use menu::{Cancel, Confirm};
-use settings::Settings;
 use text::{Bias, Point};
 use util::paths::FILE_ROW_COLUMN_DELIMITER;
 use workspace::{Modal, Workspace};
@@ -151,7 +150,7 @@ impl View for GoToLine {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &cx.global::<Settings>().theme.picker;
+        let theme = &theme::current(cx).picker;
 
         let label = format!(
             "{}{FILE_ROW_COLUMN_DELIMITER}{} of {} lines",

crates/language/src/language_settings.rs 🔗

@@ -254,6 +254,7 @@ impl settings::Setting for AllLanguageSettings {
     fn json_schema(
         generator: &mut schemars::gen::SchemaGenerator,
         params: &settings::SettingsJsonSchemaParams,
+        _: &AppContext,
     ) -> schemars::schema::RootSchema {
         let mut root_schema = generator.root_schema_for::<Self::FileContent>();
 

crates/language_selector/src/active_buffer_language.rs 🔗

@@ -4,7 +4,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle,
 };
-use settings::Settings;
 use std::sync::Arc;
 use workspace::{item::ItemHandle, StatusItemView, Workspace};
 
@@ -55,7 +54,7 @@ impl View for ActiveBufferLanguage {
             };
 
             MouseEventHandler::<Self, Self>::new(0, cx, |state, cx| {
-                let theme = &cx.global::<Settings>().theme.workspace.status_bar;
+                let theme = &theme::current(cx).workspace.status_bar;
                 let style = theme.active_language.style_for(state, false);
                 Label::new(active_language_text, style.text.clone())
                     .contained()

crates/language_selector/src/language_selector.rs 🔗

@@ -8,7 +8,6 @@ use gpui::{actions, elements::*, AppContext, ModelHandle, MouseState, ViewContex
 use language::{Buffer, LanguageRegistry};
 use picker::{Picker, PickerDelegate, PickerEvent};
 use project::Project;
-use settings::Settings;
 use std::sync::Arc;
 use util::ResultExt;
 use workspace::Workspace;
@@ -179,8 +178,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
         selected: bool,
         cx: &AppContext,
     ) -> AnyElement<Picker<Self>> {
-        let settings = cx.global::<Settings>();
-        let theme = &settings.theme;
+        let theme = theme::current(cx);
         let mat = &self.matches[ix];
         let style = theme.picker.item.style_for(mouse_state, selected);
         let buffer_language_name = self.buffer.read(cx).language().map(|l| l.name());

crates/lsp_log/src/lsp_log.rs 🔗

@@ -13,7 +13,6 @@ use gpui::{
 };
 use language::{Buffer, LanguageServerId, LanguageServerName};
 use project::{Project, WorktreeId};
-use settings::Settings;
 use std::{borrow::Cow, sync::Arc};
 use theme::{ui, Theme};
 use workspace::{
@@ -304,7 +303,7 @@ impl View for LspLogToolbarItemView {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         let Some(log_view) = self.log_view.as_ref() else { return Empty::new().into_any() };
         let project = self.project.read(cx);
         let log_view = log_view.read(cx);

crates/outline/Cargo.toml 🔗

@@ -16,7 +16,9 @@ language = { path = "../language" }
 picker = { path = "../picker" }
 settings = { path = "../settings" }
 text = { path = "../text" }
+theme = { path = "../theme" }
 workspace = { path = "../workspace" }
+
 ordered-float.workspace = true
 postage.workspace = true
 smol.workspace = true

crates/outline/src/outline.rs 🔗

@@ -10,7 +10,6 @@ use gpui::{
 use language::Outline;
 use ordered_float::OrderedFloat;
 use picker::{Picker, PickerDelegate, PickerEvent};
-use settings::Settings;
 use std::{
     cmp::{self, Reverse},
     sync::Arc,
@@ -34,7 +33,7 @@ pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Worksp
             .buffer()
             .read(cx)
             .snapshot(cx)
-            .outline(Some(cx.global::<Settings>().theme.editor.syntax.as_ref()));
+            .outline(Some(theme::current(cx).editor.syntax.as_ref()));
         if let Some(outline) = outline {
             workspace.toggle_modal(cx, |_, cx| {
                 cx.add_view(|cx| {
@@ -204,9 +203,9 @@ impl PickerDelegate for OutlineViewDelegate {
         selected: bool,
         cx: &AppContext,
     ) -> AnyElement<Picker<Self>> {
-        let settings = cx.global::<Settings>();
+        let theme = theme::current(cx);
+        let style = theme.picker.item.style_for(mouse_state, selected);
         let string_match = &self.matches[ix];
-        let style = settings.theme.picker.item.style_for(mouse_state, selected);
         let outline_item = &self.outline.items[string_match.candidate_id];
 
         Text::new(outline_item.text.clone(), style.label.text.clone())

crates/picker/src/picker.rs 🔗

@@ -57,7 +57,7 @@ impl<D: PickerDelegate> View for Picker<D> {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = (self.theme.lock())(&cx.global::<settings::Settings>().theme);
+        let theme = (self.theme.lock())(theme::current(cx).as_ref());
         let query = self.query(cx);
         let match_count = self.delegate.match_count();
 

crates/project/src/project.rs 🔗

@@ -47,7 +47,7 @@ use project_settings::ProjectSettings;
 use rand::prelude::*;
 use search::SearchQuery;
 use serde::Serialize;
-use settings::{Settings, SettingsStore};
+use settings::SettingsStore;
 use sha2::{Digest, Sha256};
 use similar::{ChangeTag, TextDiff};
 use std::{
@@ -610,12 +610,6 @@ impl Project {
         root_paths: impl IntoIterator<Item = &Path>,
         cx: &mut gpui::TestAppContext,
     ) -> ModelHandle<Project> {
-        if !cx.read(|cx| cx.has_global::<Settings>()) {
-            cx.update(|cx| {
-                cx.set_global(Settings::test(cx));
-            });
-        }
-
         let mut languages = LanguageRegistry::test();
         languages.set_executor(cx.background());
         let http_client = util::http::FakeHttpClient::with_404_response();
@@ -2137,7 +2131,7 @@ impl Project {
         let (mut settings_changed_tx, mut settings_changed_rx) = watch::channel();
         let _ = postage::stream::Stream::try_recv(&mut settings_changed_rx);
 
-        let settings_observation = cx.observe_global::<Settings, _>(move |_, _| {
+        let settings_observation = cx.observe_global::<SettingsStore, _>(move |_, _| {
             *settings_changed_tx.borrow_mut() = ();
         });
         cx.spawn_weak(|this, mut cx| async move {

crates/project_panel/src/project_panel.rs 🔗

@@ -20,7 +20,6 @@ use project::{
     repository::GitFileStatus, Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree,
     WorktreeId,
 };
-use settings::Settings;
 use std::{
     cmp::Ordering,
     collections::{hash_map, HashMap},
@@ -1113,7 +1112,7 @@ impl ProjectPanel {
                 ComponentHost::new(FileName::new(
                     details.filename.clone(),
                     details.git_status,
-                    FileName::style(style.text.clone(), &cx.global::<Settings>().theme),
+                    FileName::style(style.text.clone(), &theme::current(cx)),
                 ))
                 .contained()
                 .with_margin_left(style.icon_spacing)
@@ -1223,7 +1222,7 @@ impl ProjectPanel {
             let row_container_style = theme.dragged_entry.container;
 
             move |_, cx: &mut ViewContext<Workspace>| {
-                let theme = cx.global::<Settings>().theme.clone();
+                let theme = theme::current(cx).clone();
                 Self::render_entry_visual_element(
                     &details,
                     None,
@@ -1246,7 +1245,7 @@ impl View for ProjectPanel {
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
         enum ProjectPanel {}
-        let theme = &cx.global::<Settings>().theme.project_panel;
+        let theme = &theme::current(cx).project_panel;
         let mut container_style = theme.container;
         let padding = std::mem::take(&mut container_style.padding);
         let last_worktree_root_id = self.last_worktree_root_id;
@@ -1265,7 +1264,7 @@ impl View for ProjectPanel {
                                 .sum(),
                             cx,
                             move |this, range, items, cx| {
-                                let theme = cx.global::<Settings>().theme.clone();
+                                let theme = theme::current(cx).clone();
                                 let mut dragged_entry_destination =
                                     this.dragged_entry_destination.clone();
                                 this.for_each_visible_entry(range, cx, |id, details, cx| {
@@ -1302,8 +1301,7 @@ impl View for ProjectPanel {
                 .with_child(
                     MouseEventHandler::<Self, _>::new(2, cx, {
                         let button_style = theme.open_project_button.clone();
-                        let context_menu_item_style =
-                            cx.global::<Settings>().theme.context_menu.item.clone();
+                        let context_menu_item_style = theme::current(cx).context_menu.item.clone();
                         move |state, cx| {
                             let button_style = button_style.style_for(state, false).clone();
                             let context_menu_item =
@@ -1952,6 +1950,7 @@ mod tests {
         cx.foreground().forbid_parking();
         cx.update(|cx| {
             cx.set_global(SettingsStore::test(cx));
+            theme::init((), cx);
             language::init(cx);
             editor::init_settings(cx);
             workspace::init_settings(cx);

crates/project_symbols/Cargo.toml 🔗

@@ -17,7 +17,9 @@ project = { path = "../project" }
 text = { path = "../text" }
 settings = { path = "../settings" }
 workspace = { path = "../workspace" }
+theme = { path = "../theme" }
 util = { path = "../util" }
+
 anyhow.workspace = true
 ordered-float.workspace = true
 postage.workspace = true
@@ -30,4 +32,5 @@ gpui = { path = "../gpui", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }
 lsp = { path = "../lsp", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
+theme = { path = "../theme", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }

crates/project_symbols/src/project_symbols.rs 🔗

@@ -9,7 +9,6 @@ use gpui::{
 use ordered_float::OrderedFloat;
 use picker::{Picker, PickerDelegate, PickerEvent};
 use project::{Project, Symbol};
-use settings::Settings;
 use std::{borrow::Cow, cmp::Reverse, sync::Arc};
 use util::ResultExt;
 use workspace::Workspace;
@@ -195,12 +194,13 @@ impl PickerDelegate for ProjectSymbolsDelegate {
         selected: bool,
         cx: &AppContext,
     ) -> AnyElement<Picker<Self>> {
-        let string_match = &self.matches[ix];
-        let settings = cx.global::<Settings>();
-        let style = &settings.theme.picker.item;
+        let theme = theme::current(cx);
+        let style = &theme.picker.item;
         let current_style = style.style_for(mouse_state, selected);
+
+        let string_match = &self.matches[ix];
         let symbol = &self.symbols[string_match.candidate_id];
-        let syntax_runs = styled_runs_for_code_label(&symbol.label, &settings.theme.editor.syntax);
+        let syntax_runs = styled_runs_for_code_label(&symbol.label, &theme.editor.syntax);
 
         let mut path = symbol.path.path.to_string_lossy();
         if self.show_worktree_root_name {
@@ -371,8 +371,8 @@ mod tests {
     fn init_test(cx: &mut TestAppContext) {
         cx.foreground().forbid_parking();
         cx.update(|cx| {
-            cx.set_global(Settings::test(cx));
             cx.set_global(SettingsStore::test(cx));
+            theme::init((), cx);
             language::init(cx);
             Project::init_settings(cx);
             workspace::init_settings(cx);

crates/recent_projects/Cargo.toml 🔗

@@ -18,6 +18,7 @@ picker = { path = "../picker" }
 settings = { path = "../settings" }
 text = { path = "../text" }
 util = { path = "../util"}
+theme = { path = "../theme" }
 workspace = { path = "../workspace" }
 
 ordered-float.workspace = true

crates/recent_projects/src/recent_projects.rs 🔗

@@ -10,7 +10,6 @@ use gpui::{
 use highlighted_workspace_location::HighlightedWorkspaceLocation;
 use ordered_float::OrderedFloat;
 use picker::{Picker, PickerDelegate, PickerEvent};
-use settings::Settings;
 use std::sync::Arc;
 use workspace::{
     notifications::simple_message_notification::MessageNotification, Workspace, WorkspaceLocation,
@@ -173,9 +172,10 @@ impl PickerDelegate for RecentProjectsDelegate {
         selected: bool,
         cx: &gpui::AppContext,
     ) -> AnyElement<Picker<Self>> {
-        let settings = cx.global::<Settings>();
+        let theme = theme::current(cx);
+        let style = theme.picker.item.style_for(mouse_state, selected);
+
         let string_match = &self.matches[ix];
-        let style = settings.theme.picker.item.style_for(mouse_state, selected);
 
         let highlighted_location = HighlightedWorkspaceLocation::new(
             &string_match,

crates/search/src/buffer_search.rs 🔗

@@ -13,7 +13,6 @@ use gpui::{
 };
 use project::search::SearchQuery;
 use serde::Deserialize;
-use settings::Settings;
 use std::{any::Any, sync::Arc};
 use util::ResultExt;
 use workspace::{
@@ -93,7 +92,7 @@ impl View for BufferSearchBar {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         let editor_container = if self.query_contains_error {
             theme.search.invalid_editor
         } else {
@@ -324,16 +323,12 @@ impl BufferSearchBar {
             return None;
         }
 
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
         let is_active = self.is_search_option_enabled(option);
         Some(
             MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
-                let style = cx
-                    .global::<Settings>()
-                    .theme
-                    .search
-                    .option_button
-                    .style_for(state, is_active);
+                let theme = theme::current(cx);
+                let style = theme.search.option_button.style_for(state, is_active);
                 Label::new(icon, style.text.clone())
                     .contained()
                     .with_style(style.container)
@@ -371,16 +366,12 @@ impl BufferSearchBar {
                 tooltip = "Select Next Match";
             }
         };
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
 
         enum NavButton {}
         MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
-            let style = cx
-                .global::<Settings>()
-                .theme
-                .search
-                .option_button
-                .style_for(state, false);
+            let theme = theme::current(cx);
+            let style = theme.search.option_button.style_for(state, false);
             Label::new(icon, style.text.clone())
                 .contained()
                 .with_style(style.container)
@@ -408,7 +399,7 @@ impl BufferSearchBar {
         cx: &mut ViewContext<Self>,
     ) -> AnyElement<Self> {
         let tooltip = "Dismiss Buffer Search";
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
 
         enum CloseButton {}
         MouseEventHandler::<CloseButton, _>::new(0, cx, |state, _| {

crates/search/src/project_search.rs 🔗

@@ -17,7 +17,6 @@ use gpui::{
 };
 use menu::Confirm;
 use project::{search::SearchQuery, Project};
-use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId},
@@ -195,7 +194,7 @@ impl View for ProjectSearchView {
         if model.match_ranges.is_empty() {
             enum Status {}
 
-            let theme = cx.global::<Settings>().theme.clone();
+            let theme = theme::current(cx).clone();
             let text = if self.query_editor.read(cx).text(cx).is_empty() {
                 ""
             } else if model.pending_search.is_some() {
@@ -903,16 +902,12 @@ impl ProjectSearchBar {
                 tooltip = "Select Next Match";
             }
         };
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
 
         enum NavButton {}
         MouseEventHandler::<NavButton, _>::new(direction as usize, cx, |state, cx| {
-            let style = &cx
-                .global::<Settings>()
-                .theme
-                .search
-                .option_button
-                .style_for(state, false);
+            let theme = theme::current(cx);
+            let style = theme.search.option_button.style_for(state, false);
             Label::new(icon, style.text.clone())
                 .contained()
                 .with_style(style.container)
@@ -939,15 +934,11 @@ impl ProjectSearchBar {
         option: SearchOption,
         cx: &mut ViewContext<Self>,
     ) -> AnyElement<Self> {
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
         let is_active = self.is_option_enabled(option, cx);
         MouseEventHandler::<Self, _>::new(option as usize, cx, |state, cx| {
-            let style = &cx
-                .global::<Settings>()
-                .theme
-                .search
-                .option_button
-                .style_for(state, is_active);
+            let theme = theme::current(cx);
+            let style = theme.search.option_button.style_for(state, is_active);
             Label::new(icon, style.text.clone())
                 .contained()
                 .with_style(style.container)
@@ -992,7 +983,7 @@ impl View for ProjectSearchBar {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         if let Some(search) = self.active_project_search.as_ref() {
             let search = search.read(cx);
-            let theme = cx.global::<Settings>().theme.clone();
+            let theme = theme::current(cx).clone();
             let query_container_style = if search.panels_with_errors.contains(&InputPanel::Query) {
                 theme.search.invalid_editor
             } else {
@@ -1154,6 +1145,7 @@ pub mod tests {
     use serde_json::json;
     use settings::SettingsStore;
     use std::sync::Arc;
+    use theme::ThemeSettings;
 
     #[gpui::test]
     async fn test_project_search(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
@@ -1282,9 +1274,12 @@ pub mod tests {
             cx.set_global(SettingsStore::test(cx));
             cx.set_global(ActiveSearches::default());
 
-            let mut settings = Settings::test(cx);
-            settings.theme = Arc::new(theme);
-            cx.set_global(settings);
+            theme::init((), cx);
+            cx.update_global::<SettingsStore, _, _>(|store, _| {
+                let mut settings = store.get::<ThemeSettings>(None).clone();
+                settings.theme = Arc::new(theme);
+                store.override_global(settings)
+            });
 
             language::init(cx);
             client::init_settings(cx);

crates/settings/Cargo.toml 🔗

@@ -9,7 +9,7 @@ path = "src/settings.rs"
 doctest = false
 
 [features]
-test-support = ["theme/test-support", "gpui/test-support", "fs/test-support"]
+test-support = ["gpui/test-support", "fs/test-support"]
 
 [dependencies]
 assets = { path = "../assets" }
@@ -17,12 +17,11 @@ collections = { path = "../collections" }
 gpui = { path = "../gpui" }
 sqlez = { path = "../sqlez" }
 fs = { path = "../fs" }
-anyhow.workspace = true
-futures.workspace = true
-theme = { path = "../theme" }
 staff_mode = { path = "../staff_mode" }
 util = { path = "../util" }
 
+anyhow.workspace = true
+futures.workspace = true
 glob.workspace = true
 json_comments = "0.2"
 lazy_static.workspace = true
@@ -39,7 +38,6 @@ tree-sitter-json = "*"
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
 fs = { path = "../fs", features = ["test-support"] }
-theme = { path = "../theme", features = ["test-support"] }
 
 pretty_assertions = "1.3.0"
 unindent.workspace = true

crates/settings/src/settings.rs 🔗

@@ -3,23 +3,10 @@ mod keymap_file;
 mod settings_file;
 mod settings_store;
 
-use anyhow::Result;
-use gpui::{
-    font_cache::{FamilyId, FontCache},
-    fonts, AppContext, AssetSource,
-};
-use schemars::{
-    gen::SchemaGenerator,
-    schema::{InstanceType, Schema, SchemaObject},
-    JsonSchema,
-};
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-use std::{borrow::Cow, str, sync::Arc};
-use theme::{Theme, ThemeRegistry};
-use util::ResultExt as _;
+use std::{borrow::Cow, str};
 
 pub use font_size::{adjust_font_size_delta, font_size_for_setting};
+use gpui::AssetSource;
 pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
 pub use settings_file::*;
 pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
@@ -27,193 +14,9 @@ pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
 pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
 pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
 
-#[derive(Clone)]
-pub struct Settings {
-    pub buffer_font_family_name: String,
-    pub buffer_font_features: fonts::Features,
-    pub buffer_font_family: FamilyId,
-    pub buffer_font_size: f32,
-    pub theme: Arc<Theme>,
-}
-
-impl Setting for Settings {
-    const KEY: Option<&'static str> = None;
-
-    type FileContent = SettingsFileContent;
-
-    fn load(
-        defaults: &Self::FileContent,
-        user_values: &[&Self::FileContent],
-        cx: &AppContext,
-    ) -> Result<Self> {
-        let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
-        let themes = cx.global::<Arc<ThemeRegistry>>();
-
-        let mut this = Self {
-            buffer_font_family: cx
-                .font_cache()
-                .load_family(
-                    &[defaults.buffer_font_family.as_ref().unwrap()],
-                    &buffer_font_features,
-                )
-                .unwrap(),
-            buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
-            buffer_font_features,
-            buffer_font_size: defaults.buffer_font_size.unwrap(),
-            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
-        };
-
-        for value in user_values.into_iter().copied().cloned() {
-            this.set_user_settings(value, themes.as_ref(), cx.font_cache());
-        }
-
-        Ok(this)
-    }
-
-    fn json_schema(
-        generator: &mut SchemaGenerator,
-        params: &SettingsJsonSchemaParams,
-    ) -> schemars::schema::RootSchema {
-        let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
-
-        // Create a schema for a theme name.
-        let theme_name_schema = SchemaObject {
-            instance_type: Some(InstanceType::String.into()),
-            enum_values: Some(
-                params
-                    .theme_names
-                    .iter()
-                    .cloned()
-                    .map(Value::String)
-                    .collect(),
-            ),
-            ..Default::default()
-        };
-
-        root_schema
-            .definitions
-            .extend([("ThemeName".into(), theme_name_schema.into())]);
-
-        root_schema
-            .schema
-            .object
-            .as_mut()
-            .unwrap()
-            .properties
-            .extend([(
-                "theme".to_owned(),
-                Schema::new_ref("#/definitions/ThemeName".into()),
-            )]);
-
-        root_schema
-    }
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct SettingsFileContent {
-    #[serde(default)]
-    pub buffer_font_family: Option<String>,
-    #[serde(default)]
-    pub buffer_font_size: Option<f32>,
-    #[serde(default)]
-    pub buffer_font_features: Option<fonts::Features>,
-    #[serde(default)]
-    pub theme: Option<String>,
-}
-
-impl Settings {
-    pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
-        match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
-            Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
-            Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
-        }
-    }
-
-    /// Fill out the settings corresponding to the default.json file, overrides will be set later
-    pub fn defaults(
-        assets: impl AssetSource,
-        font_cache: &FontCache,
-        themes: &ThemeRegistry,
-    ) -> Self {
-        let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
-            str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
-        )
-        .unwrap();
-
-        let buffer_font_features = defaults.buffer_font_features.unwrap();
-        Self {
-            buffer_font_family: font_cache
-                .load_family(
-                    &[defaults.buffer_font_family.as_ref().unwrap()],
-                    &buffer_font_features,
-                )
-                .unwrap(),
-            buffer_font_family_name: defaults.buffer_font_family.unwrap(),
-            buffer_font_features,
-            buffer_font_size: defaults.buffer_font_size.unwrap(),
-            theme: themes.get(&defaults.theme.unwrap()).unwrap(),
-        }
-    }
-
-    // Fill out the overrride and etc. settings from the user's settings.json
-    fn set_user_settings(
-        &mut self,
-        data: SettingsFileContent,
-        theme_registry: &ThemeRegistry,
-        font_cache: &FontCache,
-    ) {
-        let mut family_changed = false;
-        if let Some(value) = data.buffer_font_family {
-            self.buffer_font_family_name = value;
-            family_changed = true;
-        }
-        if let Some(value) = data.buffer_font_features {
-            self.buffer_font_features = value;
-            family_changed = true;
-        }
-        if family_changed {
-            if let Some(id) = font_cache
-                .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
-                .log_err()
-            {
-                self.buffer_font_family = id;
-            }
-        }
-
-        if let Some(value) = &data.theme {
-            if let Some(theme) = theme_registry.get(value).log_err() {
-                self.theme = theme;
-            }
-        }
-
-        merge(&mut self.buffer_font_size, data.buffer_font_size);
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn test(cx: &gpui::AppContext) -> Settings {
-        Settings {
-            buffer_font_family_name: "Monaco".to_string(),
-            buffer_font_features: Default::default(),
-            buffer_font_family: cx
-                .font_cache()
-                .load_family(&["Monaco"], &Default::default())
-                .unwrap(),
-            buffer_font_size: 14.,
-            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
-        }
-    }
-
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn test_async(cx: &mut gpui::TestAppContext) {
-        cx.update(|cx| {
-            let settings = Self::test(cx);
-            cx.set_global(settings);
-        });
-    }
-}
-
-fn merge<T: Copy>(target: &mut T, value: Option<T>) {
-    if let Some(value) = value {
-        *target = value;
+pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
+    match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
+        Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
+        Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
     }
 }

crates/settings/src/settings_file.rs 🔗

@@ -1,7 +1,4 @@
-use crate::{
-    settings_store::parse_json_with_comments, settings_store::SettingsStore, Setting, Settings,
-    DEFAULT_SETTINGS_ASSET_PATH,
-};
+use crate::{settings_store::SettingsStore, Setting, DEFAULT_SETTINGS_ASSET_PATH};
 use anyhow::Result;
 use assets::Assets;
 use fs::Fs;
@@ -34,16 +31,21 @@ pub fn default_settings() -> Cow<'static, str> {
     }
 }
 
+#[cfg(any(test, feature = "test-support"))]
+pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
+
 #[cfg(any(test, feature = "test-support"))]
 pub fn test_settings() -> String {
-    let mut value =
-        parse_json_with_comments::<serde_json::Value>(default_settings().as_ref()).unwrap();
+    let mut value = crate::settings_store::parse_json_with_comments::<serde_json::Value>(
+        default_settings().as_ref(),
+    )
+    .unwrap();
     util::merge_non_null_json_value_into(
         serde_json::json!({
             "buffer_font_family": "Courier",
             "buffer_font_features": {},
             "buffer_font_size": 14,
-            "theme": theme::EMPTY_THEME_NAME,
+            "theme": EMPTY_THEME_NAME,
         }),
         &mut value,
     );
@@ -85,10 +87,6 @@ pub fn handle_settings_file_changes(
         store
             .set_user_settings(&user_settings_content, cx)
             .log_err();
-
-        // TODO - remove the Settings global, use the SettingsStore instead.
-        store.register_setting::<Settings>(cx);
-        cx.set_global(store.get::<Settings>(None).clone());
     });
     cx.spawn(move |mut cx| async move {
         while let Some(user_settings_content) = user_settings_file_rx.next().await {
@@ -97,9 +95,6 @@ pub fn handle_settings_file_changes(
                     store
                         .set_user_settings(&user_settings_content, cx)
                         .log_err();
-
-                    // TODO - remove the Settings global, use the SettingsStore instead.
-                    cx.set_global(store.get::<Settings>(None).clone());
                 });
             });
         }
@@ -113,7 +108,7 @@ async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
         Err(err) => {
             if let Some(e) = err.downcast_ref::<std::io::Error>() {
                 if e.kind() == ErrorKind::NotFound {
-                    return Ok(Settings::initial_user_settings_content(&Assets).to_string());
+                    return Ok(crate::initial_user_settings_content(&Assets).to_string());
                 }
             }
             return Err(err);

crates/settings/src/settings_store.rs 🔗

@@ -40,7 +40,11 @@ pub trait Setting: 'static {
     where
         Self: Sized;
 
-    fn json_schema(generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams) -> RootSchema {
+    fn json_schema(
+        generator: &mut SchemaGenerator,
+        _: &SettingsJsonSchemaParams,
+        _: &AppContext,
+    ) -> RootSchema {
         generator.root_schema_for::<Self::FileContent>()
     }
 
@@ -75,7 +79,7 @@ pub trait Setting: 'static {
 }
 
 pub struct SettingsJsonSchemaParams<'a> {
-    pub theme_names: &'a [String],
+    pub staff_mode: bool,
     pub language_names: &'a [String],
 }
 
@@ -112,6 +116,7 @@ trait AnySettingValue {
         &self,
         generator: &mut SchemaGenerator,
         _: &SettingsJsonSchemaParams,
+        cx: &AppContext,
     ) -> RootSchema;
 }
 
@@ -169,6 +174,16 @@ impl SettingsStore {
             .expect("no default value for setting type")
     }
 
+    /// Override the global value for a setting.
+    ///
+    /// The given value will be overwritten if the user settings file changes.
+    pub fn override_global<T: Setting>(&mut self, value: T) {
+        self.setting_values
+            .get_mut(&TypeId::of::<T>())
+            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
+            .set_global_value(Box::new(value))
+    }
+
     /// Get the user's settings as a raw JSON value.
     ///
     /// This is only for debugging and reporting. For user-facing functionality,
@@ -342,7 +357,11 @@ impl SettingsStore {
         Ok(())
     }
 
-    pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams) -> serde_json::Value {
+    pub fn json_schema(
+        &self,
+        schema_params: &SettingsJsonSchemaParams,
+        cx: &AppContext,
+    ) -> serde_json::Value {
         use schemars::{
             gen::SchemaSettings,
             schema::{Schema, SchemaObject},
@@ -355,7 +374,7 @@ impl SettingsStore {
         let mut combined_schema = RootSchema::default();
 
         for setting_value in self.setting_values.values() {
-            let setting_schema = setting_value.json_schema(&mut generator, schema_params);
+            let setting_schema = setting_value.json_schema(&mut generator, schema_params, cx);
             combined_schema
                 .definitions
                 .extend(setting_schema.definitions);
@@ -552,8 +571,9 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
         &self,
         generator: &mut SchemaGenerator,
         params: &SettingsJsonSchemaParams,
+        cx: &AppContext,
     ) -> RootSchema {
-        T::json_schema(generator, params)
+        T::json_schema(generator, params, cx)
     }
 }
 

crates/terminal/src/terminal.rs 🔗

@@ -33,7 +33,6 @@ use mappings::mouse::{
 use procinfo::LocalProcessInfo;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::Settings;
 use util::truncate_and_trailoff;
 
 use std::{
@@ -700,7 +699,7 @@ impl Terminal {
         match event {
             InternalEvent::ColorRequest(index, format) => {
                 let color = term.colors()[*index].unwrap_or_else(|| {
-                    let term_style = &cx.global::<Settings>().theme.terminal;
+                    let term_style = &theme::current(cx).terminal;
                     to_alac_rgb(get_color_at_index(index, &term_style))
                 });
                 self.write_to_pty(format(color))

crates/terminal_view/src/terminal_button.rs 🔗

@@ -5,7 +5,6 @@ use gpui::{
     platform::{CursorStyle, MouseButton},
     AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
 };
-use settings::Settings;
 use std::any::TypeId;
 use workspace::{
     dock::{Dock, FocusDock},
@@ -43,7 +42,7 @@ impl View for TerminalButton {
 
         let has_terminals = !project.local_terminal_handles().is_empty();
         let terminal_count = project.local_terminal_handles().len() as i32;
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
 
         Stack::new()
             .with_child(

crates/terminal_view/src/terminal_element.rs 🔗

@@ -16,7 +16,7 @@ use gpui::{
 use itertools::Itertools;
 use language::CursorShape;
 use ordered_float::OrderedFloat;
-use settings::{font_size_for_setting, Settings};
+use settings::font_size_for_setting;
 use terminal::{
     alacritty_terminal::{
         ansi::{Color as AnsiColor, Color::Named, CursorShape as AlacCursorShape, NamedColor},
@@ -27,7 +27,7 @@ use terminal::{
     mappings::colors::convert_color,
     IndexedCell, Terminal, TerminalContent, TerminalSettings, TerminalSize,
 };
-use theme::TerminalStyle;
+use theme::{TerminalStyle, ThemeSettings};
 use util::ResultExt;
 
 use std::{fmt::Debug, ops::RangeInclusive};
@@ -522,7 +522,7 @@ impl Element<TerminalView> for TerminalElement {
         view: &mut TerminalView,
         cx: &mut LayoutContext<TerminalView>,
     ) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
-        let settings = cx.global::<Settings>();
+        let settings = settings::get_setting::<ThemeSettings>(None, cx);
         let terminal_settings = settings::get_setting::<TerminalSettings>(None, cx);
 
         //Setup layout information

crates/terminal_view/src/terminal_view.rs 🔗

@@ -904,6 +904,7 @@ mod tests {
         cx: &mut TestAppContext,
     ) -> (ModelHandle<Project>, ViewHandle<Workspace>) {
         let params = cx.update(AppState::test);
+        cx.update(|cx| theme::init((), cx));
 
         let project = Project::test(params.fs.clone(), [], cx).await;
         let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));

crates/theme/Cargo.toml 🔗

@@ -5,7 +5,11 @@ edition = "2021"
 publish = false
 
 [features]
-test-support = ["gpui/test-support"]
+test-support = [
+    "gpui/test-support",
+    "fs/test-support",
+    "settings/test-support"
+]
 
 [lib]
 path = "src/theme.rs"
@@ -14,10 +18,19 @@ doctest = false
 [dependencies]
 gpui = { path = "../gpui" }
 fs = { path = "../fs" }
+settings = { path = "../settings" }
+util = { path = "../util" }
+
 anyhow.workspace = true
 indexmap = "1.6.2"
 parking_lot.workspace = true
+schemars.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 serde_json.workspace = true
 toml.workspace = true
+
+[dev-dependencies]
+gpui = { path = "../gpui", features = ["test-support"] }
+fs = { path = "../fs", features = ["test-support"] }
+settings = { path = "../settings", features = ["test-support"] }

crates/theme/src/theme.rs 🔗

@@ -1,19 +1,31 @@
 mod theme_registry;
+mod theme_settings;
+pub mod ui;
 
 use gpui::{
     color::Color,
     elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, TooltipStyle},
     fonts::{HighlightStyle, TextStyle},
-    platform, Border, MouseState,
+    platform, AppContext, AssetSource, Border, MouseState,
 };
 use serde::{de::DeserializeOwned, Deserialize};
 use serde_json::Value;
 use std::{collections::HashMap, sync::Arc};
 use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle};
 
-pub mod ui;
+pub use theme_registry::ThemeRegistry;
+pub use theme_settings::ThemeSettings;
 
-pub use theme_registry::*;
+pub fn current(cx: &AppContext) -> Arc<Theme> {
+    settings::get_setting::<ThemeSettings>(None, cx)
+        .theme
+        .clone()
+}
+
+pub fn init(source: impl AssetSource, cx: &mut AppContext) {
+    cx.set_global(ThemeRegistry::new(source, cx.font_cache().clone()));
+    settings::register_setting::<ThemeSettings>(cx);
+}
 
 #[derive(Deserialize, Default)]
 pub struct Theme {

crates/theme/src/theme_registry.rs 🔗

@@ -20,9 +20,6 @@ pub struct ThemeRegistry {
     next_theme_id: AtomicUsize,
 }
 
-#[cfg(any(test, feature = "test-support"))]
-pub const EMPTY_THEME_NAME: &'static str = "empty-theme";
-
 impl ThemeRegistry {
     pub fn new(source: impl AssetSource, font_cache: Arc<FontCache>) -> Arc<Self> {
         let this = Arc::new(Self {
@@ -35,8 +32,13 @@ impl ThemeRegistry {
 
         #[cfg(any(test, feature = "test-support"))]
         this.themes.lock().insert(
-            EMPTY_THEME_NAME.to_string(),
-            gpui::fonts::with_font_cache(this.font_cache.clone(), || Arc::new(Theme::default())),
+            settings::EMPTY_THEME_NAME.to_string(),
+            gpui::fonts::with_font_cache(this.font_cache.clone(), || {
+                let mut theme = Theme::default();
+                theme.meta.id = this.next_theme_id.fetch_add(1, SeqCst);
+                theme.meta.name = settings::EMPTY_THEME_NAME.into();
+                Arc::new(theme)
+            }),
         );
 
         this

crates/theme/src/theme_settings.rs 🔗

@@ -0,0 +1,136 @@
+use crate::{Theme, ThemeRegistry};
+use anyhow::Result;
+use gpui::{font_cache::FamilyId, fonts, AppContext};
+use schemars::{
+    gen::SchemaGenerator,
+    schema::{InstanceType, Schema, SchemaObject},
+    JsonSchema,
+};
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+use settings::SettingsJsonSchemaParams;
+use std::sync::Arc;
+use util::ResultExt as _;
+
+#[derive(Clone)]
+pub struct ThemeSettings {
+    pub buffer_font_family_name: String,
+    pub buffer_font_features: fonts::Features,
+    pub buffer_font_family: FamilyId,
+    pub buffer_font_size: f32,
+    pub theme: Arc<Theme>,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+pub struct ThemeSettingsContent {
+    #[serde(default)]
+    pub buffer_font_family: Option<String>,
+    #[serde(default)]
+    pub buffer_font_size: Option<f32>,
+    #[serde(default)]
+    pub buffer_font_features: Option<fonts::Features>,
+    #[serde(default)]
+    pub theme: Option<String>,
+}
+
+impl settings::Setting for ThemeSettings {
+    const KEY: Option<&'static str> = None;
+
+    type FileContent = ThemeSettingsContent;
+
+    fn load(
+        defaults: &Self::FileContent,
+        user_values: &[&Self::FileContent],
+        cx: &AppContext,
+    ) -> Result<Self> {
+        let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
+        let themes = cx.global::<Arc<ThemeRegistry>>();
+
+        let mut this = Self {
+            buffer_font_family: cx
+                .font_cache()
+                .load_family(
+                    &[defaults.buffer_font_family.as_ref().unwrap()],
+                    &buffer_font_features,
+                )
+                .unwrap(),
+            buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
+            buffer_font_features,
+            buffer_font_size: defaults.buffer_font_size.unwrap(),
+            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
+        };
+
+        for value in user_values.into_iter().copied().cloned() {
+            let font_cache = cx.font_cache();
+            let mut family_changed = false;
+            if let Some(value) = value.buffer_font_family {
+                this.buffer_font_family_name = value;
+                family_changed = true;
+            }
+            if let Some(value) = value.buffer_font_features {
+                this.buffer_font_features = value;
+                family_changed = true;
+            }
+            if family_changed {
+                if let Some(id) = font_cache
+                    .load_family(&[&this.buffer_font_family_name], &this.buffer_font_features)
+                    .log_err()
+                {
+                    this.buffer_font_family = id;
+                }
+            }
+
+            if let Some(value) = &value.theme {
+                if let Some(theme) = themes.get(value).log_err() {
+                    this.theme = theme;
+                }
+            }
+
+            merge(&mut this.buffer_font_size, value.buffer_font_size);
+        }
+
+        Ok(this)
+    }
+
+    fn json_schema(
+        generator: &mut SchemaGenerator,
+        params: &SettingsJsonSchemaParams,
+        cx: &AppContext,
+    ) -> schemars::schema::RootSchema {
+        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
+        let theme_names = cx
+            .global::<Arc<ThemeRegistry>>()
+            .list(params.staff_mode)
+            .map(|theme| Value::String(theme.name.clone()))
+            .collect();
+
+        let theme_name_schema = SchemaObject {
+            instance_type: Some(InstanceType::String.into()),
+            enum_values: Some(theme_names),
+            ..Default::default()
+        };
+
+        root_schema
+            .definitions
+            .extend([("ThemeName".into(), theme_name_schema.into())]);
+
+        root_schema
+            .schema
+            .object
+            .as_mut()
+            .unwrap()
+            .properties
+            .extend([(
+                "theme".to_owned(),
+                Schema::new_ref("#/definitions/ThemeName".into()),
+            )]);
+
+        root_schema
+    }
+}
+
+fn merge<T: Copy>(target: &mut T, value: Option<T>) {
+    if let Some(value) = value {
+        *target = value;
+    }
+}

crates/theme_selector/src/theme_selector.rs 🔗

@@ -2,10 +2,10 @@ use fs::Fs;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{actions, elements::*, AnyElement, AppContext, Element, MouseState, ViewContext};
 use picker::{Picker, PickerDelegate, PickerEvent};
-use settings::{update_settings_file, Settings};
+use settings::{update_settings_file, SettingsStore};
 use staff_mode::StaffMode;
 use std::sync::Arc;
-use theme::{Theme, ThemeMeta, ThemeRegistry};
+use theme::{Theme, ThemeMeta, ThemeRegistry, ThemeSettings};
 use util::ResultExt;
 use workspace::Workspace;
 
@@ -18,17 +18,17 @@ pub fn init(cx: &mut AppContext) {
 
 pub fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
     workspace.toggle_modal(cx, |workspace, cx| {
-        let themes = workspace.app_state().themes.clone();
         let fs = workspace.app_state().fs.clone();
-        cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, themes, cx), cx))
+        cx.add_view(|cx| ThemeSelector::new(ThemeSelectorDelegate::new(fs, cx), cx))
     });
 }
 
 #[cfg(debug_assertions)]
-pub fn reload(themes: Arc<ThemeRegistry>, cx: &mut AppContext) {
-    let current_theme_name = cx.global::<Settings>().theme.meta.name.clone();
-    themes.clear();
-    match themes.get(&current_theme_name) {
+pub fn reload(cx: &mut AppContext) {
+    let current_theme_name = theme::current(cx).meta.name.clone();
+    let registry = cx.global::<Arc<ThemeRegistry>>();
+    registry.clear();
+    match registry.get(&current_theme_name) {
         Ok(theme) => {
             ThemeSelectorDelegate::set_theme(theme, cx);
             log::info!("reloaded theme {}", current_theme_name);
@@ -43,7 +43,6 @@ pub type ThemeSelector = Picker<ThemeSelectorDelegate>;
 
 pub struct ThemeSelectorDelegate {
     fs: Arc<dyn Fs>,
-    registry: Arc<ThemeRegistry>,
     theme_data: Vec<ThemeMeta>,
     matches: Vec<StringMatch>,
     original_theme: Arc<Theme>,
@@ -52,18 +51,12 @@ pub struct ThemeSelectorDelegate {
 }
 
 impl ThemeSelectorDelegate {
-    fn new(
-        fs: Arc<dyn Fs>,
-        registry: Arc<ThemeRegistry>,
-        cx: &mut ViewContext<ThemeSelector>,
-    ) -> Self {
-        let settings = cx.global::<Settings>();
-
-        let original_theme = settings.theme.clone();
+    fn new(fs: Arc<dyn Fs>, cx: &mut ViewContext<ThemeSelector>) -> Self {
+        let original_theme = theme::current(cx).clone();
 
-        let mut theme_names = registry
-            .list(**cx.default_global::<StaffMode>())
-            .collect::<Vec<_>>();
+        let staff_mode = **cx.default_global::<StaffMode>();
+        let registry = cx.global::<Arc<ThemeRegistry>>();
+        let mut theme_names = registry.list(staff_mode).collect::<Vec<_>>();
         theme_names.sort_unstable_by(|a, b| a.is_light.cmp(&b.is_light).then(a.name.cmp(&b.name)));
         let matches = theme_names
             .iter()
@@ -76,7 +69,6 @@ impl ThemeSelectorDelegate {
             .collect();
         let mut this = Self {
             fs,
-            registry,
             theme_data: theme_names,
             matches,
             original_theme: original_theme.clone(),
@@ -89,7 +81,8 @@ impl ThemeSelectorDelegate {
 
     fn show_selected_theme(&mut self, cx: &mut ViewContext<ThemeSelector>) {
         if let Some(mat) = self.matches.get(self.selected_index) {
-            match self.registry.get(&mat.string) {
+            let registry = cx.global::<Arc<ThemeRegistry>>();
+            match registry.get(&mat.string) {
                 Ok(theme) => {
                     Self::set_theme(theme, cx);
                 }
@@ -109,8 +102,10 @@ impl ThemeSelectorDelegate {
     }
 
     fn set_theme(theme: Arc<Theme>, cx: &mut AppContext) {
-        cx.update_global::<Settings, _, _>(|settings, cx| {
-            settings.theme = theme;
+        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+            let mut theme_settings = store.get::<ThemeSettings>(None).clone();
+            theme_settings.theme = theme;
+            store.override_global(theme_settings);
             cx.refresh_windows();
         });
     }
@@ -128,9 +123,9 @@ impl PickerDelegate for ThemeSelectorDelegate {
     fn confirm(&mut self, cx: &mut ViewContext<ThemeSelector>) {
         self.selection_completed = true;
 
-        let theme_name = cx.global::<Settings>().theme.meta.name.clone();
-        update_settings_file::<Settings>(self.fs.clone(), cx, |settings_content| {
-            settings_content.theme = Some(theme_name);
+        let theme_name = theme::current(cx).meta.name.clone();
+        update_settings_file::<ThemeSettings>(self.fs.clone(), cx, |settings| {
+            settings.theme = Some(theme_name);
         });
 
         cx.emit(PickerEvent::Dismiss);
@@ -212,11 +207,10 @@ impl PickerDelegate for ThemeSelectorDelegate {
         selected: bool,
         cx: &AppContext,
     ) -> AnyElement<Picker<Self>> {
-        let settings = cx.global::<Settings>();
-        let theme = &settings.theme;
-        let theme_match = &self.matches[ix];
+        let theme = theme::current(cx);
         let style = theme.picker.item.style_for(mouse_state, selected);
 
+        let theme_match = &self.matches[ix];
         Label::new(theme_match.string.clone(), style.label.clone())
             .with_highlights(theme_match.positions.clone())
             .contained()

crates/theme_testbench/src/theme_testbench.rs 🔗

@@ -10,8 +10,7 @@ use gpui::{
     WeakViewHandle,
 };
 use project::Project;
-use settings::Settings;
-use theme::{ColorScheme, Layer, Style, StyleSet};
+use theme::{ColorScheme, Layer, Style, StyleSet, ThemeSettings};
 use workspace::{item::Item, register_deserializable_item, Pane, Workspace};
 
 actions!(theme, [DeployThemeTestbench]);
@@ -220,7 +219,7 @@ impl ThemeTestbench {
     }
 
     fn render_label(text: String, style: &Style, cx: &mut ViewContext<Self>) -> Label {
-        let settings = cx.global::<Settings>();
+        let settings = settings::get_setting::<ThemeSettings>(None, cx);
         let font_cache = cx.font_cache();
         let family_id = settings.buffer_font_family;
         let font_size = settings::font_size_for_setting(settings.buffer_font_size, cx);
@@ -252,7 +251,7 @@ impl View for ThemeTestbench {
     }
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
-        let color_scheme = &cx.global::<Settings>().theme.clone().color_scheme;
+        let color_scheme = &theme::current(cx).clone().color_scheme;
 
         Flex::row()
             .with_child(

crates/welcome/src/base_keymap_picker.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
 };
 use picker::{Picker, PickerDelegate, PickerEvent};
 use project::Fs;
-use settings::{update_settings_file, Settings};
+use settings::update_settings_file;
 use std::sync::Arc;
 use util::ResultExt;
 use workspace::Workspace;
@@ -139,7 +139,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
         selected: bool,
         cx: &gpui::AppContext,
     ) -> gpui::AnyElement<Picker<Self>> {
-        let theme = &cx.global::<Settings>().theme;
+        let theme = &theme::current(cx);
         let keymap_match = &self.matches[ix];
         let style = theme.picker.item.style_for(mouse_state, selected);
 

crates/welcome/src/welcome.rs 🔗

@@ -8,7 +8,7 @@ use gpui::{
     elements::{Flex, Label, ParentElement},
     AnyElement, AppContext, Element, Entity, Subscription, View, ViewContext, WeakViewHandle,
 };
-use settings::{update_settings_file, Settings};
+use settings::{update_settings_file, SettingsStore};
 use std::{borrow::Cow, sync::Arc};
 use workspace::{
     item::Item, open_new, sidebar::SidebarSide, AppState, PaneBackdrop, Welcome, Workspace,
@@ -61,9 +61,7 @@ impl View for WelcomePage {
 
     fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<Self> {
         let self_handle = cx.handle();
-        let settings = cx.global::<Settings>();
-        let theme = settings.theme.clone();
-
+        let theme = theme::current(cx);
         let width = theme.welcome.page_width;
 
         let telemetry_settings = *settings::get_setting::<TelemetrySettings>(None, cx);
@@ -224,7 +222,7 @@ impl WelcomePage {
     pub fn new(workspace: &Workspace, cx: &mut ViewContext<Self>) -> Self {
         WelcomePage {
             workspace: workspace.weak_handle(),
-            _settings_subscription: cx.observe_global::<Settings, _>(move |_, cx| cx.notify()),
+            _settings_subscription: cx.observe_global::<SettingsStore, _>(move |_, cx| cx.notify()),
         }
     }
 }
@@ -260,7 +258,7 @@ impl Item for WelcomePage {
     ) -> Option<Self> {
         Some(WelcomePage {
             workspace: self.workspace.clone(),
-            _settings_subscription: cx.observe_global::<Settings, _>(move |_, cx| cx.notify()),
+            _settings_subscription: cx.observe_global::<SettingsStore, _>(move |_, cx| cx.notify()),
         })
     }
 }

crates/workspace/src/dock.rs 🔗

@@ -408,7 +408,6 @@ mod tests {
 
     use gpui::{AppContext, BorrowWindowContext, TestAppContext, ViewContext, WindowContext};
     use project::{FakeFs, Project};
-    use theme::ThemeRegistry;
 
     use super::*;
     use crate::{
@@ -468,7 +467,6 @@ mod tests {
                 project.clone(),
                 Arc::new(AppState {
                     languages: project.read(cx).languages().clone(),
-                    themes: ThemeRegistry::new((), cx.font_cache().clone()),
                     client: project.read(cx).client(),
                     user_store: project.read(cx).user_store(),
                     fs: project.read(cx).fs().clone(),
@@ -615,7 +613,6 @@ mod tests {
                     project.clone(),
                     Arc::new(AppState {
                         languages: project.read(cx).languages().clone(),
-                        themes: ThemeRegistry::new((), cx.font_cache().clone()),
                         client: project.read(cx).client(),
                         user_store: project.read(cx).user_store(),
                         fs: project.read(cx).fs().clone(),

crates/workspace/src/dock/toggle_dock_button.rs 🔗

@@ -6,7 +6,6 @@ use gpui::{
     platform::MouseButton,
     AnyElement, Element, Entity, View, ViewContext, ViewHandle, WeakViewHandle,
 };
-use settings::Settings;
 
 pub struct ToggleDockButton {
     workspace: WeakViewHandle<Workspace>,
@@ -43,7 +42,7 @@ impl View for ToggleDockButton {
         let dock_position = workspace.read(cx).dock.position;
         let dock_pane = workspace.read(cx).dock_pane().clone();
 
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
 
         let button = MouseEventHandler::<Self, _>::new(0, cx, {
             let theme = theme.clone();

crates/workspace/src/notifications.rs 🔗

@@ -149,6 +149,8 @@ impl Workspace {
 }
 
 pub mod simple_message_notification {
+    use super::Notification;
+    use crate::Workspace;
     use gpui::{
         actions,
         elements::{Flex, MouseEventHandler, Padding, ParentElement, Svg, Text},
@@ -158,13 +160,8 @@ pub mod simple_message_notification {
     };
     use menu::Cancel;
     use serde::Deserialize;
-    use settings::Settings;
     use std::{borrow::Cow, sync::Arc};
 
-    use crate::Workspace;
-
-    use super::Notification;
-
     actions!(message_notifications, [CancelMessageNotification]);
 
     #[derive(Clone, Default, Deserialize, PartialEq)]
@@ -240,7 +237,7 @@ pub mod simple_message_notification {
         }
 
         fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> gpui::AnyElement<Self> {
-            let theme = cx.global::<Settings>().theme.clone();
+            let theme = theme::current(cx).clone();
             let theme = &theme.simple_message_notification;
 
             enum MessageNotificationTag {}

crates/workspace/src/pane.rs 🔗

@@ -30,7 +30,6 @@ use gpui::{
 };
 use project::{Project, ProjectEntryId, ProjectPath};
 use serde::Deserialize;
-use settings::Settings;
 use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
 use theme::Theme;
 use util::ResultExt;
@@ -1297,7 +1296,7 @@ impl Pane {
     }
 
     fn render_tabs(&mut self, cx: &mut ViewContext<Self>) -> impl Element<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
 
         let pane = cx.handle().downgrade();
         let autoscroll = if mem::take(&mut self.autoscroll) {
@@ -1328,7 +1327,7 @@ impl Pane {
                         let pane = pane.clone();
                         let detail = detail.clone();
 
-                        let theme = cx.global::<Settings>().theme.clone();
+                        let theme = theme::current(cx).clone();
                         let mut tooltip_theme = theme.tooltip.clone();
                         tooltip_theme.max_text_width = None;
                         let tab_tooltip_text = item.tab_tooltip_text(cx).map(|a| a.to_string());
@@ -1406,7 +1405,7 @@ impl Pane {
                         pane: pane.clone(),
                     },
                     {
-                        let theme = cx.global::<Settings>().theme.clone();
+                        let theme = theme::current(cx).clone();
 
                         let detail = detail.clone();
                         move |dragged_item: &DraggedItem, cx: &mut ViewContext<Workspace>| {
@@ -1699,7 +1698,7 @@ impl View for Pane {
             if let Some(active_item) = self.active_item() {
                 Flex::column()
                     .with_child({
-                        let theme = cx.global::<Settings>().theme.clone();
+                        let theme = theme::current(cx).clone();
 
                         let mut stack = Stack::new();
 
@@ -1765,7 +1764,7 @@ impl View for Pane {
                     .into_any()
             } else {
                 enum EmptyPane {}
-                let theme = cx.global::<Settings>().theme.clone();
+                let theme = theme::current(cx).clone();
 
                 dragged_item_receiver::<EmptyPane, _, _>(0, 0, false, None, cx, |_, cx| {
                     self.render_blank_pane(&theme, cx)
@@ -1862,7 +1861,7 @@ fn render_tab_bar_button<F: 'static + Fn(&mut Pane, &mut EventContext<Pane>)>(
     Stack::new()
         .with_child(
             MouseEventHandler::<TabBarButton, _>::new(index, cx, |mouse_state, cx| {
-                let theme = &cx.global::<Settings>().theme.workspace.tab_bar;
+                let theme = &theme::current(cx).workspace.tab_bar;
                 let style = theme.pane_button.style_for(mouse_state, false);
                 Svg::new(icon)
                     .with_color(style.color)
@@ -2024,7 +2023,7 @@ impl<V: View> Element<V> for PaneBackdrop<V> {
         view: &mut V,
         cx: &mut ViewContext<V>,
     ) -> Self::PaintState {
-        let background = cx.global::<Settings>().theme.editor.background;
+        let background = theme::current(cx).editor.background;
 
         let visible_bounds = bounds.intersection(visible_bounds).unwrap_or_default();
 
@@ -2536,7 +2535,7 @@ mod tests {
     fn init_test(cx: &mut TestAppContext) {
         cx.update(|cx| {
             cx.set_global(SettingsStore::test(cx));
-            cx.set_global(settings::Settings::test(cx));
+            theme::init((), cx);
             crate::init_settings(cx);
         });
     }

crates/workspace/src/pane/dragged_item_receiver.rs 🔗

@@ -1,3 +1,5 @@
+use super::DraggedItem;
+use crate::{Pane, SplitDirection, Workspace};
 use drag_and_drop::DragAndDrop;
 use gpui::{
     color::Color,
@@ -8,11 +10,6 @@ use gpui::{
     AppContext, Element, EventContext, MouseState, Quad, View, ViewContext, WeakViewHandle,
 };
 use project::ProjectEntryId;
-use settings::Settings;
-
-use crate::{Pane, SplitDirection, Workspace};
-
-use super::DraggedItem;
 
 pub fn dragged_item_receiver<Tag, D, F>(
     region_id: usize,
@@ -225,8 +222,5 @@ fn drop_split_direction(
 }
 
 fn overlay_color(cx: &AppContext) -> Color {
-    cx.global::<Settings>()
-        .theme
-        .workspace
-        .drop_target_overlay_color
+    theme::current(cx).workspace.drop_target_overlay_color
 }

crates/workspace/src/shared_screen.rs 🔗

@@ -12,7 +12,6 @@ use gpui::{
     platform::MouseButton,
     AppContext, Entity, Task, View, ViewContext,
 };
-use settings::Settings;
 use smallvec::SmallVec;
 use std::{
     borrow::Cow,
@@ -88,7 +87,7 @@ impl View for SharedScreen {
                 }
             })
             .contained()
-            .with_style(cx.global::<Settings>().theme.shared_screen)
+            .with_style(theme::current(cx).shared_screen)
         })
         .on_down(MouseButton::Left, |_, _, cx| cx.focus_parent())
         .into_any()

crates/workspace/src/sidebar.rs 🔗

@@ -4,7 +4,6 @@ use gpui::{
     AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
 use serde::Deserialize;
-use settings::Settings;
 use std::rc::Rc;
 
 pub trait SidebarItem: View {
@@ -192,7 +191,7 @@ impl View for Sidebar {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
         if let Some(active_item) = self.active_item() {
             enum ResizeHandleTag {}
-            let style = &cx.global::<Settings>().theme.workspace.sidebar;
+            let style = &theme::current(cx).workspace.sidebar;
             ChildView::new(active_item.as_any(), cx)
                 .contained()
                 .with_style(style.container)
@@ -231,7 +230,7 @@ impl View for SidebarButtons {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &cx.global::<Settings>().theme;
+        let theme = &theme::current(cx);
         let tooltip_style = theme.tooltip.clone();
         let theme = &theme.workspace.status_bar.sidebar_buttons;
         let sidebar = self.sidebar.read(cx);

crates/workspace/src/status_bar.rs 🔗

@@ -11,7 +11,6 @@ use gpui::{
     AnyElement, AnyViewHandle, Entity, LayoutContext, SceneBuilder, SizeConstraint, Subscription,
     View, ViewContext, ViewHandle, WindowContext,
 };
-use settings::Settings;
 
 pub trait StatusItemView: View {
     fn set_active_pane_item(
@@ -47,7 +46,7 @@ impl View for StatusBar {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &cx.global::<Settings>().theme.workspace.status_bar;
+        let theme = &theme::current(cx).workspace.status_bar;
 
         StatusBarElement {
             left: Flex::row()

crates/workspace/src/toolbar.rs 🔗

@@ -3,7 +3,6 @@ use gpui::{
     elements::*, platform::CursorStyle, platform::MouseButton, Action, AnyElement, AnyViewHandle,
     AppContext, Entity, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
-use settings::Settings;
 
 pub trait ToolbarItemView: View {
     fn set_active_pane_item(
@@ -68,7 +67,7 @@ impl View for Toolbar {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = &cx.global::<Settings>().theme.workspace.toolbar;
+        let theme = &theme::current(cx).workspace.toolbar;
 
         let mut primary_left_items = Vec::new();
         let mut primary_right_items = Vec::new();
@@ -131,7 +130,7 @@ impl View for Toolbar {
         let height = theme.height * primary_items_row_count as f32;
         let nav_button_height = theme.height;
         let button_style = theme.nav_button;
-        let tooltip_style = cx.global::<Settings>().theme.tooltip.clone();
+        let tooltip_style = theme::current(cx).tooltip.clone();
 
         Flex::column()
             .with_child(

crates/workspace/src/workspace.rs 🔗

@@ -76,12 +76,11 @@ pub use persistence::{
 use postage::prelude::Stream;
 use project::{Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
 use serde::Deserialize;
-use settings::Settings;
 use shared_screen::SharedScreen;
 use sidebar::{Sidebar, SidebarButtons, SidebarSide, ToggleSidebarItem};
 use status_bar::StatusBar;
 pub use status_bar::StatusItemView;
-use theme::{Theme, ThemeRegistry};
+use theme::Theme;
 pub use toolbar::{ToolbarItemLocation, ToolbarItemView};
 use util::{async_iife, paths, ResultExt};
 pub use workspace_settings::{AutosaveSetting, DockAnchor, GitGutterSetting, WorkspaceSettings};
@@ -276,7 +275,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
     cx.add_action(
         move |_: &mut Workspace, _: &OpenSettings, cx: &mut ViewContext<Workspace>| {
             create_and_open_local_file(&paths::SETTINGS, cx, || {
-                Settings::initial_user_settings_content(&Assets)
+                settings::initial_user_settings_content(&Assets)
                     .as_ref()
                     .into()
             })
@@ -361,7 +360,6 @@ pub fn register_deserializable_item<I: Item>(cx: &mut AppContext) {
 
 pub struct AppState {
     pub languages: Arc<LanguageRegistry>,
-    pub themes: Arc<ThemeRegistry>,
     pub client: Arc<client::Client>,
     pub user_store: ModelHandle<client::UserStore>,
     pub fs: Arc<dyn fs::Fs>,
@@ -379,7 +377,6 @@ impl AppState {
 
         if !cx.has_global::<SettingsStore>() {
             cx.set_global(SettingsStore::test(cx));
-            cx.set_global(Settings::test(cx));
         }
 
         let fs = fs::FakeFs::new(cx.background().clone());
@@ -387,14 +384,13 @@ impl AppState {
         let http_client = util::http::FakeHttpClient::with_404_response();
         let client = Client::new(http_client.clone(), cx);
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http_client, cx));
-        let themes = ThemeRegistry::new((), cx.font_cache().clone());
 
+        theme::init((), cx);
         client::init(&client, cx);
         crate::init_settings(cx);
 
         Arc::new(Self {
             client,
-            themes,
             fs,
             languages,
             user_store,
@@ -1992,7 +1988,7 @@ impl Workspace {
             enum DisconnectedOverlay {}
             Some(
                 MouseEventHandler::<DisconnectedOverlay, _>::new(0, cx, |_, cx| {
-                    let theme = &cx.global::<Settings>().theme;
+                    let theme = &theme::current(cx);
                     Label::new(
                         "Your connection to the remote project has been lost.",
                         theme.workspace.disconnected_overlay.text.clone(),
@@ -2630,7 +2626,6 @@ impl Workspace {
     pub fn test_new(project: ModelHandle<Project>, cx: &mut ViewContext<Self>) -> Self {
         let app_state = Arc::new(AppState {
             languages: project.read(cx).languages().clone(),
-            themes: ThemeRegistry::new((), cx.font_cache().clone()),
             client: project.read(cx).client(),
             user_store: project.read(cx).user_store(),
             fs: project.read(cx).fs().clone(),
@@ -2776,7 +2771,7 @@ impl View for Workspace {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let theme = cx.global::<Settings>().theme.clone();
+        let theme = theme::current(cx).clone();
         Stack::new()
             .with_child(
                 Flex::column()
@@ -3798,7 +3793,7 @@ mod tests {
         cx.foreground().forbid_parking();
         cx.update(|cx| {
             cx.set_global(SettingsStore::test(cx));
-            cx.set_global(Settings::test(cx));
+            theme::init((), cx);
             language::init(cx);
             crate::init_settings(cx);
         });

crates/zed/src/languages.rs 🔗

@@ -3,7 +3,6 @@ pub use language::*;
 use node_runtime::NodeRuntime;
 use rust_embed::RustEmbed;
 use std::{borrow::Cow, str, sync::Arc};
-use theme::ThemeRegistry;
 
 mod c;
 mod elixir;
@@ -32,11 +31,7 @@ mod yaml;
 #[exclude = "*.rs"]
 struct LanguageDir;
 
-pub fn init(
-    languages: Arc<LanguageRegistry>,
-    themes: Arc<ThemeRegistry>,
-    node_runtime: Arc<NodeRuntime>,
-) {
+pub fn init(languages: Arc<LanguageRegistry>, node_runtime: Arc<NodeRuntime>) {
     fn adapter_arc(adapter: impl LspAdapter) -> Arc<dyn LspAdapter> {
         Arc::new(adapter)
     }
@@ -69,7 +64,6 @@ pub fn init(
             vec![adapter_arc(json::JsonLspAdapter::new(
                 node_runtime.clone(),
                 languages.clone(),
-                themes.clone(),
             ))],
         ),
         ("markdown", tree_sitter_markdown::language(), vec![]),

crates/zed/src/languages/json.rs 🔗

@@ -16,7 +16,6 @@ use std::{
     path::{Path, PathBuf},
     sync::Arc,
 };
-use theme::ThemeRegistry;
 use util::http::HttpClient;
 use util::{paths, ResultExt};
 
@@ -30,20 +29,11 @@ fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
 pub struct JsonLspAdapter {
     node: Arc<NodeRuntime>,
     languages: Arc<LanguageRegistry>,
-    themes: Arc<ThemeRegistry>,
 }
 
 impl JsonLspAdapter {
-    pub fn new(
-        node: Arc<NodeRuntime>,
-        languages: Arc<LanguageRegistry>,
-        themes: Arc<ThemeRegistry>,
-    ) -> Self {
-        JsonLspAdapter {
-            node,
-            languages,
-            themes,
-        }
+    pub fn new(node: Arc<NodeRuntime>, languages: Arc<LanguageRegistry>) -> Self {
+        JsonLspAdapter { node, languages }
     }
 }
 
@@ -128,18 +118,15 @@ impl LspAdapter for JsonLspAdapter {
         cx: &mut AppContext,
     ) -> Option<BoxFuture<'static, serde_json::Value>> {
         let action_names = cx.all_action_names().collect::<Vec<_>>();
-        let theme_names = &self
-            .themes
-            .list(**cx.default_global::<StaffMode>())
-            .map(|meta| meta.name)
-            .collect::<Vec<_>>();
+        let staff_mode = cx.global::<StaffMode>().0;
         let language_names = &self.languages.language_names();
-        let settings_schema = cx
-            .global::<SettingsStore>()
-            .json_schema(&SettingsJsonSchemaParams {
-                theme_names,
+        let settings_schema = cx.global::<SettingsStore>().json_schema(
+            &SettingsJsonSchemaParams {
                 language_names,
-            });
+                staff_mode,
+            },
+            cx,
+        );
         Some(
             future::ready(serde_json::json!({
                 "json": {

crates/zed/src/main.rs 🔗

@@ -23,9 +23,7 @@ use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use project::Fs;
 use serde::{Deserialize, Serialize};
-use settings::{
-    default_settings, handle_settings_file_changes, watch_config_file, Settings, SettingsStore,
-};
+use settings::{default_settings, handle_settings_file_changes, watch_config_file, SettingsStore};
 use simplelog::ConfigBuilder;
 use smol::process::Command;
 use std::{
@@ -56,7 +54,6 @@ use welcome::{show_welcome_experience, FIRST_OPEN};
 use fs::RealFs;
 #[cfg(debug_assertions)]
 use staff_mode::StaffMode;
-use theme::ThemeRegistry;
 use util::{channel::RELEASE_CHANNEL, paths, ResultExt, TryFutureExt};
 use workspace::{
     dock::FocusDock, item::ItemHandle, notifications::NotifyResultExt, AppState, OpenSettings,
@@ -84,7 +81,6 @@ fn main() {
     load_embedded_fonts(&app);
 
     let fs = Arc::new(RealFs);
-    let themes = ThemeRegistry::new(Assets, app.font_cache());
     let user_settings_file_rx =
         watch_config_file(app.background(), fs.clone(), paths::SETTINGS.clone());
     let user_keymap_file_rx =
@@ -124,7 +120,6 @@ fn main() {
 
     app.run(move |cx| {
         cx.set_global(*RELEASE_CHANNEL);
-        cx.set_global(themes.clone());
 
         #[cfg(debug_assertions)]
         cx.set_global(StaffMode(true));
@@ -148,11 +143,12 @@ fn main() {
         let languages = Arc::new(languages);
         let node_runtime = NodeRuntime::new(http.clone(), cx.background().to_owned());
 
-        languages::init(languages.clone(), themes.clone(), node_runtime.clone());
+        languages::init(languages.clone(), node_runtime.clone());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http.clone(), cx));
 
         cx.set_global(client.clone());
 
+        theme::init(Assets, cx);
         context_menu::init(cx);
         project::Project::init(&client, cx);
         client::init(&client, cx);
@@ -171,13 +167,12 @@ fn main() {
         theme_testbench::init(cx);
         copilot::init(http.clone(), node_runtime, cx);
 
-        cx.spawn(|cx| watch_themes(fs.clone(), themes.clone(), cx))
-            .detach();
+        cx.spawn(|cx| watch_themes(fs.clone(), cx)).detach();
 
-        languages.set_theme(cx.global::<Settings>().theme.clone());
-        cx.observe_global::<Settings, _>({
+        languages.set_theme(theme::current(cx).clone());
+        cx.observe_global::<SettingsStore, _>({
             let languages = languages.clone();
-            move |cx| languages.set_theme(cx.global::<Settings>().theme.clone())
+            move |cx| languages.set_theme(theme::current(cx).clone())
         })
         .detach();
 
@@ -190,7 +185,6 @@ fn main() {
 
         let app_state = Arc::new(AppState {
             languages,
-            themes,
             client: client.clone(),
             user_store,
             fs,
@@ -208,10 +202,13 @@ fn main() {
         journal::init(app_state.clone(), cx);
         language_selector::init(cx);
         theme_selector::init(cx);
-        zed::init(&app_state, cx);
+        activity_indicator::init(cx);
+        lsp_log::init(cx);
+        call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
         collab_ui::init(&app_state, cx);
         feedback::init(cx);
         welcome::init(cx);
+        zed::init(&app_state, cx);
 
         cx.set_menus(menus::menus());
 
@@ -584,11 +581,7 @@ fn load_embedded_fonts(app: &App) {
 }
 
 #[cfg(debug_assertions)]
-async fn watch_themes(
-    fs: Arc<dyn Fs>,
-    themes: Arc<ThemeRegistry>,
-    mut cx: AsyncAppContext,
-) -> Option<()> {
+async fn watch_themes(fs: Arc<dyn Fs>, mut cx: AsyncAppContext) -> Option<()> {
     let mut events = fs
         .watch("styles/src".as_ref(), Duration::from_millis(100))
         .await;
@@ -600,7 +593,7 @@ async fn watch_themes(
             .await
             .log_err()?;
         if output.status.success() {
-            cx.update(|cx| theme_selector::reload(themes.clone(), cx))
+            cx.update(|cx| theme_selector::reload(cx))
         } else {
             eprintln!(
                 "build script failed {}",

crates/zed/src/zed.rs 🔗

@@ -30,10 +30,11 @@ use search::{BufferSearchBar, ProjectSearchBar};
 use serde::Deserialize;
 use serde_json::to_string_pretty;
 use settings::{
-    adjust_font_size_delta, KeymapFileContent, Settings, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH,
+    adjust_font_size_delta, KeymapFileContent, SettingsStore, DEFAULT_SETTINGS_ASSET_PATH,
 };
 use std::{borrow::Cow, str, sync::Arc};
 use terminal_view::terminal_button::TerminalButton;
+use theme::ThemeSettings;
 use util::{channel::ReleaseChannel, paths, ResultExt};
 use uuid::Uuid;
 use welcome::BaseKeymap;
@@ -124,7 +125,7 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
     });
     cx.add_global_action(move |_: &DecreaseBufferFontSize, cx| {
         adjust_font_size_delta(cx, |size, cx| {
-            if cx.global::<Settings>().buffer_font_size + *size > MIN_FONT_SIZE {
+            if cx.global::<ThemeSettings>().buffer_font_size + *size > MIN_FONT_SIZE {
                 *size -= 1.0;
             }
         })
@@ -258,9 +259,6 @@ pub fn init(app_state: &Arc<AppState>, cx: &mut gpui::AppContext) {
             }
         }
     });
-    activity_indicator::init(cx);
-    lsp_log::init(cx);
-    call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
     load_default_keymap(cx);
 }
 
@@ -1910,7 +1908,7 @@ mod tests {
 
         cx.update(|cx| {
             cx.set_global(SettingsStore::test(cx));
-            cx.set_global(ThemeRegistry::new(Assets, cx.font_cache().clone()));
+            theme::init(Assets, cx);
             welcome::init(cx);
 
             cx.add_global_action(|_: &A, _cx| {});
@@ -2038,15 +2036,25 @@ mod tests {
             ])
             .unwrap();
         let themes = ThemeRegistry::new(Assets, cx.font_cache().clone());
-        let settings = Settings::defaults(Assets, cx.font_cache(), &themes);
+        let mut settings = SettingsStore::default();
+        settings
+            .set_default_settings(&settings::default_settings(), cx)
+            .unwrap();
+        cx.set_global(settings);
+        theme::init(Assets, cx);
 
         let mut has_default_theme = false;
         for theme_name in themes.list(false).map(|meta| meta.name) {
             let theme = themes.get(&theme_name).unwrap();
-            if theme.meta.name == settings.theme.meta.name {
+            assert_eq!(theme.meta.name, theme_name);
+            if theme.meta.name
+                == settings::get_setting::<ThemeSettings>(None, cx)
+                    .theme
+                    .meta
+                    .name
+            {
                 has_default_theme = true;
             }
-            assert_eq!(theme.meta.name, theme_name);
         }
         assert!(has_default_theme);
     }
@@ -2056,10 +2064,9 @@ mod tests {
         let mut languages = LanguageRegistry::test();
         languages.set_executor(cx.background().clone());
         let languages = Arc::new(languages);
-        let themes = ThemeRegistry::new((), cx.font_cache().clone());
         let http = FakeHttpClient::with_404_response();
         let node_runtime = NodeRuntime::new(http, cx.background().to_owned());
-        languages::init(languages.clone(), themes, node_runtime);
+        languages::init(languages.clone(), node_runtime);
         for name in languages.language_names() {
             languages.language_for_name(&name);
         }
@@ -2073,6 +2080,7 @@ mod tests {
             let state = Arc::get_mut(&mut app_state).unwrap();
             state.initialize_workspace = initialize_workspace;
             state.build_window_options = build_window_options;
+            theme::init((), cx);
             call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
             workspace::init(app_state.clone(), cx);
             language::init(cx);