theme: Split out theme_settings crate (#52569)

Piotr Osiewicz created

Self-Review Checklist:

- [ ] I've reviewed my own diff for quality, security, and reliability
- [ ] Unsafe blocks (if any) have justifying comments
- [ ] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [ ] Tests cover the new/changed behavior
- [ ] Performance impact has been considered and is acceptable

Closes #ISSUE

Release Notes:

- N/A

Change summary

Cargo.lock                                                                   |  89 
Cargo.toml                                                                   |   2 
crates/acp_tools/Cargo.toml                                                  |   2 
crates/acp_tools/src/acp_tools.rs                                            |   2 
crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs |   6 
crates/agent/src/tools/evals/fixtures/disable_cursor_blinking/before.rs      |   6 
crates/agent_ui/Cargo.toml                                                   |   1 
crates/agent_ui/src/agent_configuration/add_llm_provider_modal.rs            |   2 
crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs    |   2 
crates/agent_ui/src/agent_diff.rs                                            |   4 
crates/agent_ui/src/agent_panel.rs                                           |  24 
crates/agent_ui/src/agent_registry_ui.rs                                     |   2 
crates/agent_ui/src/completion_provider.rs                                   |   2 
crates/agent_ui/src/conversation_view.rs                                     |   4 
crates/agent_ui/src/entry_view_state.rs                                      |   4 
crates/agent_ui/src/inline_prompt_editor.rs                                  |   2 
crates/agent_ui/src/mention_set.rs                                           |   2 
crates/agent_ui/src/message_editor.rs                                        |   2 
crates/agent_ui/src/test_support.rs                                          |   2 
crates/agent_ui/src/text_thread_editor.rs                                    |   8 
crates/agent_ui/src/thread_history.rs                                        |   2 
crates/agent_ui/src/ui/agent_notification.rs                                 |   3 
crates/agent_ui/src/ui/mention_crease.rs                                     |   2 
crates/collab/Cargo.toml                                                     |   1 
crates/collab/tests/integration/randomized_test_helpers.rs                   |   2 
crates/collab/tests/integration/test_server.rs                               |   4 
crates/collab_ui/Cargo.toml                                                  |   1 
crates/collab_ui/src/collab_panel.rs                                         |   3 
crates/collab_ui/src/notifications/incoming_call_notification.rs             |   2 
crates/collab_ui/src/notifications/project_shared_notification.rs            |   2 
crates/command_palette/Cargo.toml                                            |   1 
crates/command_palette/src/command_palette.rs                                |   2 
crates/component_preview/Cargo.toml                                          |   1 
crates/component_preview/examples/component_preview.rs                       |   4 
crates/copilot/Cargo.toml                                                    |   1 
crates/copilot/src/copilot_edit_prediction_delegate.rs                       |   2 
crates/debugger_ui/Cargo.toml                                                |   1 
crates/debugger_ui/src/session/running/console.rs                            |   3 
crates/debugger_ui/src/session/running/memory_view.rs                        |   2 
crates/debugger_ui/src/tests.rs                                              |   2 
crates/diagnostics/Cargo.toml                                                |   1 
crates/diagnostics/src/diagnostic_renderer.rs                                |   2 
crates/diagnostics/src/diagnostics_tests.rs                                  |   2 
crates/edit_prediction_ui/Cargo.toml                                         |   2 
crates/edit_prediction_ui/src/rate_prediction_modal.rs                       |   2 
crates/editor/Cargo.toml                                                     |   1 
crates/editor/benches/editor_render.rs                                       |   2 
crates/editor/src/bracket_colorization.rs                                    |   2 
crates/editor/src/display_map.rs                                             |   2 
crates/editor/src/display_map/block_map.rs                                   |   2 
crates/editor/src/display_map/fold_map.rs                                    |   3 
crates/editor/src/display_map/inlay_map.rs                                   |   2 
crates/editor/src/display_map/wrap_map.rs                                    |   2 
crates/editor/src/editor.rs                                                  |  22 
crates/editor/src/editor_tests.rs                                            |   2 
crates/editor/src/element.rs                                                 |   5 
crates/editor/src/git/blame.rs                                               |   2 
crates/editor/src/hover_popover.rs                                           |   2 
crates/editor/src/inlays/inlay_hints.rs                                      |   2 
crates/editor/src/items.rs                                                   |   4 
crates/editor/src/movement.rs                                                |   2 
crates/editor/src/semantic_tokens.rs                                         |   4 
crates/editor/src/signature_help.rs                                          |   2 
crates/editor/src/split.rs                                                   |   2 
crates/extension_cli/Cargo.toml                                              |   2 
crates/extension_cli/src/main.rs                                             |   3 
crates/extension_host/Cargo.toml                                             |   1 
crates/extension_host/src/extension_store_test.rs                            |   2 
crates/extensions_ui/Cargo.toml                                              |   2 
crates/extensions_ui/src/extensions_ui.rs                                    |   2 
crates/file_finder/Cargo.toml                                                |   1 
crates/file_finder/src/file_finder_tests.rs                                  |   2 
crates/git_graph/Cargo.toml                                                  |   1 
crates/git_graph/src/git_graph.rs                                            |   5 
crates/git_ui/Cargo.toml                                                     |   1 
crates/git_ui/src/blame_ui.rs                                                |   2 
crates/git_ui/src/branch_picker.rs                                           |   2 
crates/git_ui/src/commit_tooltip.rs                                          |   2 
crates/git_ui/src/file_diff_view.rs                                          |   2 
crates/git_ui/src/git_panel.rs                                               |   4 
crates/git_ui/src/project_diff.rs                                            |   2 
crates/git_ui/src/stash_picker.rs                                            |   2 
crates/git_ui/src/text_diff_view.rs                                          |   2 
crates/image_viewer/Cargo.toml                                               |   2 
crates/image_viewer/src/image_viewer.rs                                      |   2 
crates/inspector_ui/Cargo.toml                                               |   2 
crates/inspector_ui/src/inspector.rs                                         |   2 
crates/keymap_editor/Cargo.toml                                              |   1 
crates/keymap_editor/src/keymap_editor.rs                                    |   2 
crates/keymap_editor/src/ui_components/keystroke_input.rs                    |   2 
crates/language/Cargo.toml                                                   |   1 
crates/language/src/buffer_tests.rs                                          |   2 
crates/language_tools/Cargo.toml                                             |   3 
crates/language_tools/src/lsp_log_view_tests.rs                              |   2 
crates/markdown/Cargo.toml                                                   |   1 
crates/markdown/examples/markdown.rs                                         |   2 
crates/markdown/examples/markdown_as_child.rs                                |   2 
crates/markdown/src/html/html_rendering.rs                                   |   2 
crates/markdown/src/markdown.rs                                              |   4 
crates/markdown/src/mermaid.rs                                               |   2 
crates/markdown_preview/Cargo.toml                                           |   2 
crates/markdown_preview/src/markdown_preview_view.rs                         |   2 
crates/miniprofiler_ui/Cargo.toml                                            |   2 
crates/miniprofiler_ui/src/miniprofiler_ui.rs                                |   2 
crates/onboarding/Cargo.toml                                                 |   1 
crates/onboarding/src/basics_page.rs                                         |  12 
crates/open_path_prompt/Cargo.toml                                           |   1 
crates/open_path_prompt/src/open_path_prompt_tests.rs                        |   2 
crates/outline/Cargo.toml                                                    |   1 
crates/outline/src/outline.rs                                                |   3 
crates/outline_panel/Cargo.toml                                              |   1 
crates/outline_panel/src/outline_panel.rs                                    |   5 
crates/picker/Cargo.toml                                                     |   1 
crates/picker/src/picker.rs                                                  |   4 
crates/platform_title_bar/Cargo.toml                                         |   1 
crates/platform_title_bar/src/system_window_tabs.rs                          |   2 
crates/project_panel/Cargo.toml                                              |   1 
crates/project_panel/src/project_panel.rs                                    |   2 
crates/project_panel/src/project_panel_tests.rs                              |   4 
crates/project_symbols/Cargo.toml                                            |   1 
crates/project_symbols/src/project_symbols.rs                                |   5 
crates/remote_connection/Cargo.toml                                          |   2 
crates/remote_connection/src/remote_connection.rs                            |   2 
crates/remote_server/Cargo.toml                                              |   1 
crates/remote_server/src/remote_editing_tests.rs                             |   2 
crates/repl/Cargo.toml                                                       |   1 
crates/repl/src/notebook/cell.rs                                             |   2 
crates/repl/src/outputs.rs                                                   |   2 
crates/repl/src/outputs/plain.rs                                             |   4 
crates/repl/src/outputs/table.rs                                             |   2 
crates/rules_library/Cargo.toml                                              |   2 
crates/rules_library/src/rules_library.rs                                    |   4 
crates/schema_generator/Cargo.toml                                           |   1 
crates/schema_generator/src/main.rs                                          |   3 
crates/search/Cargo.toml                                                     |   1 
crates/search/src/buffer_search.rs                                           |   2 
crates/search/src/project_search.rs                                          |   2 
crates/search/src/search_bar.rs                                              |   2 
crates/settings_profile_selector/Cargo.toml                                  |   1 
crates/settings_profile_selector/src/settings_profile_selector.rs            |   4 
crates/settings_ui/Cargo.toml                                                |   1 
crates/settings_ui/src/components/input_field.rs                             |   2 
crates/settings_ui/src/page_data.rs                                          |  15 
crates/settings_ui/src/pages/tool_permissions_setup.rs                       |   2 
crates/settings_ui/src/settings_ui.rs                                        |  12 
crates/sidebar/Cargo.toml                                                    |   1 
crates/sidebar/src/project_group_builder.rs                                  |   2 
crates/sidebar/src/sidebar.rs                                                |   4 
crates/storybook/Cargo.toml                                                  |   1 
crates/storybook/src/storybook.rs                                            |  10 
crates/tab_switcher/Cargo.toml                                               |   1 
crates/tab_switcher/src/tab_switcher_tests.rs                                |   2 
crates/terminal/Cargo.toml                                                   |   1 
crates/terminal/src/terminal.rs                                              |   2 
crates/terminal/src/terminal_settings.rs                                     |   2 
crates/terminal_view/Cargo.toml                                              |   1 
crates/terminal_view/src/terminal_element.rs                                 |   7 
crates/terminal_view/src/terminal_panel.rs                                   |   2 
crates/terminal_view/src/terminal_path_like_target.rs                        |   2 
crates/terminal_view/src/terminal_view.rs                                    |   2 
crates/theme/Cargo.toml                                                      |   6 
crates/theme/src/fallback_themes.rs                                          |   6 
crates/theme/src/icon_theme.rs                                               |   2 
crates/theme/src/registry.rs                                                 |  60 
crates/theme/src/schema.rs                                                   | 838 
crates/theme/src/styles/accents.rs                                           |  22 
crates/theme/src/styles/players.rs                                           |  38 
crates/theme/src/theme.rs                                                    | 350 
crates/theme_extension/Cargo.toml                                            |   1 
crates/theme_extension/src/theme_extension.rs                                |  12 
crates/theme_importer/Cargo.toml                                             |   1 
crates/theme_importer/src/vscode/converter.rs                                |   2 
crates/theme_selector/Cargo.toml                                             |   1 
crates/theme_selector/src/icon_theme_selector.rs                             |   8 
crates/theme_selector/src/theme_selector.rs                                  |  12 
crates/theme_settings/Cargo.toml                                             |  37 
crates/theme_settings/LICENSE-GPL                                            |   1 
crates/theme_settings/src/schema.rs                                          | 850 
crates/theme_settings/src/settings.rs                                        |  73 
crates/theme_settings/src/theme_settings.rs                                  | 386 
crates/ui/Cargo.toml                                                         |   1 
crates/ui/src/components/context_menu.rs                                     |   2 
crates/ui/src/components/label/label_like.rs                                 |  12 
crates/ui/src/components/list/list_header.rs                                 |   4 
crates/ui/src/components/tooltip.rs                                          |   2 
crates/ui/src/styles/spacing.rs                                              |   2 
crates/ui/src/styles/typography.rs                                           |   3 
crates/ui_macros/src/dynamic_spacing.rs                                      |  12 
crates/ui_prompt/Cargo.toml                                                  |   2 
crates/ui_prompt/src/ui_prompt.rs                                            |   2 
crates/vim/Cargo.toml                                                        |   1 
crates/vim/src/state.rs                                                      |   2 
crates/vim/src/test/vim_test_context.rs                                      |   2 
crates/vim/src/vim.rs                                                        |   2 
crates/which_key/Cargo.toml                                                  |   2 
crates/which_key/src/which_key_modal.rs                                      |   2 
crates/workspace/Cargo.toml                                                  |   1 
crates/workspace/src/multi_workspace.rs                                      |   4 
crates/workspace/src/notifications.rs                                        |   2 
crates/workspace/src/pane.rs                                                 |   4 
crates/workspace/src/tasks.rs                                                |   2 
crates/workspace/src/workspace.rs                                            |  33 
crates/zed/Cargo.toml                                                        |   1 
crates/zed/src/main.rs                                                       |  11 
crates/zed/src/visual_test_runner.rs                                         |   4 
crates/zed/src/zed.rs                                                        |  46 
crates/zed/src/zed/migrate.rs                                                |   2 
crates/zed/src/zed/telemetry_log.rs                                          |   2 
crates/zed/src/zed/visual_tests.rs                                           |   2 
209 files changed, 1,820 insertions(+), 1,577 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -59,7 +59,7 @@ dependencies = [
  "serde",
  "serde_json",
  "settings",
- "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -406,6 +406,7 @@ dependencies = [
  "terminal_view",
  "text",
  "theme",
+ "theme_settings",
  "time",
  "time_format",
  "tree-sitter-md",
@@ -3258,6 +3259,7 @@ dependencies = [
  "telemetry_events",
  "text",
  "theme",
+ "theme_settings",
  "time",
  "tokio",
  "toml 0.8.23",
@@ -3303,6 +3305,7 @@ dependencies = [
  "smallvec",
  "telemetry",
  "theme",
+ "theme_settings",
  "time",
  "time_format",
  "title_bar",
@@ -3375,6 +3378,7 @@ dependencies = [
  "settings",
  "telemetry",
  "theme",
+ "theme_settings",
  "time",
  "ui",
  "util",
@@ -3427,6 +3431,7 @@ dependencies = [
  "session",
  "settings",
  "theme",
+ "theme_settings",
  "ui",
  "ui_input",
  "uuid",
@@ -3624,6 +3629,7 @@ dependencies = [
  "settings",
  "sum_tree",
  "theme",
+ "theme_settings",
  "util",
  "workspace",
  "zlog",
@@ -4694,6 +4700,7 @@ dependencies = [
  "terminal_view",
  "text",
  "theme",
+ "theme_settings",
  "tree-sitter",
  "tree-sitter-go",
  "tree-sitter-json",
@@ -4881,6 +4888,7 @@ dependencies = [
  "settings",
  "text",
  "theme",
+ "theme_settings",
  "ui",
  "unindent",
  "util",
@@ -5397,6 +5405,7 @@ dependencies = [
  "telemetry",
  "text",
  "theme",
+ "theme_settings",
  "time",
  "ui",
  "util",
@@ -5465,6 +5474,7 @@ dependencies = [
  "telemetry",
  "text",
  "theme",
+ "theme_settings",
  "time",
  "tracing",
  "tree-sitter-bash",
@@ -6044,7 +6054,7 @@ dependencies = [
  "settings_content",
  "snippet_provider",
  "task",
- "theme",
+ "theme_settings",
  "tokio",
  "toml 0.8.23",
  "tree-sitter",
@@ -6093,6 +6103,7 @@ dependencies = [
  "tempfile",
  "theme",
  "theme_extension",
+ "theme_settings",
  "toml 0.8.23",
  "tracing",
  "url",
@@ -6131,7 +6142,7 @@ dependencies = [
  "smallvec",
  "strum 0.27.2",
  "telemetry",
- "theme",
+ "theme_settings",
  "ui",
  "util",
  "vim_mode_setting",
@@ -6288,6 +6299,7 @@ dependencies = [
  "serde_json",
  "settings",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -7303,6 +7315,7 @@ dependencies = [
  "settings",
  "smallvec",
  "theme",
+ "theme_settings",
  "time",
  "ui",
  "workspace",
@@ -7378,6 +7391,7 @@ dependencies = [
  "strum 0.27.2",
  "telemetry",
  "theme",
+ "theme_settings",
  "time",
  "time_format",
  "tracing",
@@ -8729,7 +8743,7 @@ dependencies = [
  "project",
  "serde",
  "settings",
- "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -8845,7 +8859,7 @@ dependencies = [
  "project",
  "serde_json",
  "serde_json_lenient",
- "theme",
+ "theme_settings",
  "ui",
  "util",
  "util_macros",
@@ -9288,6 +9302,7 @@ dependencies = [
  "telemetry",
  "tempfile",
  "theme",
+ "theme_settings",
  "tree-sitter-json",
  "tree-sitter-rust",
  "ui",
@@ -9398,6 +9413,7 @@ dependencies = [
  "task",
  "text",
  "theme",
+ "theme_settings",
  "toml 0.8.23",
  "tracing",
  "tree-sitter",
@@ -9611,6 +9627,7 @@ dependencies = [
  "sysinfo 0.37.2",
  "telemetry",
  "theme",
+ "theme_settings",
  "tree-sitter",
  "ui",
  "util",
@@ -10289,6 +10306,7 @@ dependencies = [
  "stacksafe",
  "sum_tree",
  "theme",
+ "theme_settings",
  "ui",
  "util",
 ]
@@ -10305,7 +10323,7 @@ dependencies = [
  "markdown",
  "settings",
  "tempfile",
- "theme",
+ "theme_settings",
  "ui",
  "urlencoding",
  "util",
@@ -10665,7 +10683,7 @@ dependencies = [
  "rpc",
  "serde_json",
  "smol",
- "theme",
+ "theme_settings",
  "util",
  "workspace",
  "zed_actions",
@@ -11556,6 +11574,7 @@ dependencies = [
  "settings",
  "telemetry",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "vim_mode_setting",
@@ -11661,6 +11680,7 @@ dependencies = [
  "serde_json",
  "settings",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -11839,6 +11859,7 @@ dependencies = [
  "settings",
  "smol",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -11871,6 +11892,7 @@ dependencies = [
  "smallvec",
  "smol",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -12736,6 +12758,7 @@ dependencies = [
  "serde",
  "settings",
  "theme",
+ "theme_settings",
  "ui",
  "ui_input",
  "workspace",
@@ -12844,6 +12867,7 @@ dependencies = [
  "settings",
  "smallvec",
  "theme",
+ "theme_settings",
  "ui",
  "windows 0.61.3",
  "workspace",
@@ -13342,6 +13366,7 @@ dependencies = [
  "telemetry",
  "tempfile",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -13368,6 +13393,7 @@ dependencies = [
  "serde_json",
  "settings",
  "theme",
+ "theme_settings",
  "util",
  "workspace",
 ]
@@ -14360,7 +14386,7 @@ dependencies = [
  "remote",
  "semver",
  "settings",
- "theme",
+ "theme_settings",
  "ui",
  "ui_input",
  "workspace",
@@ -14428,6 +14454,7 @@ dependencies = [
  "sysinfo 0.37.2",
  "task",
  "theme",
+ "theme_settings",
  "thiserror 2.0.17",
  "toml 0.8.23",
  "unindent",
@@ -14498,6 +14525,7 @@ dependencies = [
  "terminal",
  "terminal_view",
  "theme",
+ "theme_settings",
  "tree-sitter-md",
  "tree-sitter-python",
  "tree-sitter-typescript",
@@ -14837,7 +14865,7 @@ dependencies = [
  "rope",
  "serde",
  "settings",
- "theme",
+ "theme_settings",
  "ui",
  "ui_input",
  "util",
@@ -15251,6 +15279,7 @@ dependencies = [
  "serde_json",
  "settings",
  "theme",
+ "theme_settings",
 ]
 
 [[package]]
@@ -15474,6 +15503,7 @@ dependencies = [
  "settings",
  "smol",
  "theme",
+ "theme_settings",
  "tracing",
  "ui",
  "unindent",
@@ -15818,6 +15848,7 @@ dependencies = [
  "serde_json",
  "settings",
  "theme",
+ "theme_settings",
  "ui",
  "workspace",
  "zed_actions",
@@ -15866,6 +15897,7 @@ dependencies = [
  "strum 0.27.2",
  "telemetry",
  "theme",
+ "theme_settings",
  "title_bar",
  "ui",
  "util",
@@ -16001,6 +16033,7 @@ dependencies = [
  "serde_json",
  "settings",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "vim_mode_setting",
@@ -16656,6 +16689,7 @@ dependencies = [
  "story",
  "strum 0.27.2",
  "theme",
+ "theme_settings",
  "title_bar",
  "ui",
 ]
@@ -17306,6 +17340,7 @@ dependencies = [
  "settings",
  "smol",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -17490,6 +17525,7 @@ dependencies = [
  "sysinfo 0.37.2",
  "task",
  "theme",
+ "theme_settings",
  "thiserror 2.0.17",
  "url",
  "urlencoding",
@@ -17537,6 +17573,7 @@ dependencies = [
  "task",
  "terminal",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -17572,8 +17609,6 @@ dependencies = [
  "collections",
  "derive_more",
  "gpui",
- "gpui_util",
- "log",
  "palette",
  "parking_lot",
  "refineable",
@@ -17581,7 +17616,6 @@ dependencies = [
  "serde",
  "serde_json",
  "serde_json_lenient",
- "settings",
  "strum 0.27.2",
  "thiserror 2.0.17",
  "uuid",
@@ -17596,6 +17630,7 @@ dependencies = [
  "fs",
  "gpui",
  "theme",
+ "theme_settings",
 ]
 
 [[package]]
@@ -17615,6 +17650,7 @@ dependencies = [
  "simplelog",
  "strum 0.27.2",
  "theme",
+ "theme_settings",
  "vscode_theme",
 ]
 
@@ -17631,12 +17667,33 @@ dependencies = [
  "settings",
  "telemetry",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
  "zed_actions",
 ]
 
+[[package]]
+name = "theme_settings"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "gpui",
+ "gpui_util",
+ "log",
+ "palette",
+ "refineable",
+ "schemars",
+ "serde",
+ "serde_json",
+ "serde_json_lenient",
+ "settings",
+ "theme",
+ "uuid",
+]
+
 [[package]]
 name = "thiserror"
 version = "1.0.69"
@@ -18780,6 +18837,7 @@ dependencies = [
  "story",
  "strum 0.27.2",
  "theme",
+ "theme_settings",
  "ui_macros",
  "windows 0.61.3",
 ]
@@ -18811,7 +18869,7 @@ dependencies = [
  "markdown",
  "menu",
  "settings",
- "theme",
+ "theme_settings",
  "ui",
  "workspace",
 ]
@@ -19210,6 +19268,7 @@ dependencies = [
  "task",
  "text",
  "theme",
+ "theme_settings",
  "tokio",
  "ui",
  "util",
@@ -20337,7 +20396,7 @@ dependencies = [
  "gpui",
  "serde",
  "settings",
- "theme",
+ "theme_settings",
  "ui",
  "util",
  "workspace",
@@ -21556,6 +21615,7 @@ dependencies = [
  "telemetry",
  "tempfile",
  "theme",
+ "theme_settings",
  "ui",
  "util",
  "uuid",
@@ -22117,6 +22177,7 @@ dependencies = [
  "theme",
  "theme_extension",
  "theme_selector",
+ "theme_settings",
  "time",
  "time_format",
  "title_bar",

Cargo.toml 🔗

@@ -198,6 +198,7 @@ members = [
     "crates/text",
     "crates/theme",
     "crates/theme_extension",
+    "crates/theme_settings",
     "crates/theme_importer",
     "crates/theme_selector",
     "crates/time_format",
@@ -445,6 +446,7 @@ terminal_view = { path = "crates/terminal_view" }
 text = { path = "crates/text" }
 theme = { path = "crates/theme" }
 theme_extension = { path = "crates/theme_extension" }
+theme_settings = { path = "crates/theme_settings" }
 theme_selector = { path = "crates/theme_selector" }
 time_format = { path = "crates/time_format" }
 platform_title_bar = { path = "crates/platform_title_bar" }

crates/acp_tools/Cargo.toml 🔗

@@ -23,7 +23,7 @@ project.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/acp_tools/src/acp_tools.rs 🔗

@@ -16,7 +16,7 @@ use language::LanguageRegistry;
 use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
 use project::{AgentId, Project};
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{CopyButton, Tooltip, WithScrollbar, prelude::*};
 use util::ResultExt as _;
 use workspace::{

crates/agent/src/edit_agent/evals/fixtures/disable_cursor_blinking/before.rs 🔗

@@ -7837,7 +7837,7 @@ impl Editor {
         h_flex()
             .px_0p5()
             .when(is_platform_style_mac, |parent| parent.gap_0p5())
-            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+            .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
             .text_size(TextSize::XSmall.rems(cx))
             .child(h_flex().children(ui::render_modifiers(
                 &accept_keystroke.modifiers,
@@ -8149,7 +8149,7 @@ impl Editor {
                             .px_2()
                             .child(
                                 h_flex()
-                                    .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+                                    .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
                                     .when(is_platform_style_mac, |parent| parent.gap_1())
                                     .child(h_flex().children(ui::render_modifiers(
                                         &accept_keystroke.modifiers,
@@ -8258,7 +8258,7 @@ impl Editor {
                         .gap_2()
                         .pr_1()
                         .overflow_x_hidden()
-                        .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+                        .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
                         .child(left)
                         .child(preview),
                 )

crates/agent/src/tools/evals/fixtures/disable_cursor_blinking/before.rs 🔗

@@ -7837,7 +7837,7 @@ impl Editor {
         h_flex()
             .px_0p5()
             .when(is_platform_style_mac, |parent| parent.gap_0p5())
-            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+            .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
             .text_size(TextSize::XSmall.rems(cx))
             .child(h_flex().children(ui::render_modifiers(
                 &accept_keystroke.modifiers,
@@ -8149,7 +8149,7 @@ impl Editor {
                             .px_2()
                             .child(
                                 h_flex()
-                                    .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+                                    .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
                                     .when(is_platform_style_mac, |parent| parent.gap_1())
                                     .child(h_flex().children(ui::render_modifiers(
                                         &accept_keystroke.modifiers,
@@ -8258,7 +8258,7 @@ impl Editor {
                         .gap_2()
                         .pr_1()
                         .overflow_x_hidden()
-                        .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+                        .font(theme_settings::ThemeSettings::get_global(cx).buffer_font.clone())
                         .child(left)
                         .child(preview),
                 )

crates/agent_ui/Cargo.toml 🔗

@@ -101,6 +101,7 @@ terminal.workspace = true
 terminal_view.workspace = true
 text.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 time.workspace = true
 time_format.workspace = true
 ui.workspace = true

crates/agent_ui/src/agent_configuration/configure_context_server_modal.rs 🔗

@@ -22,7 +22,7 @@ use project::{
 use serde::Deserialize;
 use settings::{Settings as _, update_settings_file};
 use std::sync::Arc;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     CommonAnimationExt, KeyBinding, Modal, ModalFooter, ModalHeader, Section, Tooltip,
     WithScrollbar, prelude::*,

crates/agent_ui/src/agent_diff.rs 🔗

@@ -1806,7 +1806,7 @@ mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             prompt_store::init(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             language_model::init_settings(cx);
         });
 
@@ -1963,7 +1963,7 @@ mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             prompt_store::init(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             language_model::init_settings(cx);
             workspace::register_project_item::<Editor>(cx);
         });

crates/agent_ui/src/agent_panel.rs 🔗

@@ -76,7 +76,7 @@ use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
 use rules_library::{RulesLibrary, open_rules_library};
 use search::{BufferSearchBar, buffer_search};
 use settings::{Settings, update_settings_file};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Button, Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, DocumentationSide,
     KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*, utils::WithRemSize,
@@ -1624,17 +1624,17 @@ impl AgentPanel {
                         let agent_buffer_font_size =
                             ThemeSettings::get_global(cx).agent_buffer_font_size(cx) + delta;
 
-                        let _ = settings
-                            .theme
-                            .agent_ui_font_size
-                            .insert(f32::from(theme::clamp_font_size(agent_ui_font_size)).into());
+                        let _ = settings.theme.agent_ui_font_size.insert(
+                            f32::from(theme_settings::clamp_font_size(agent_ui_font_size)).into(),
+                        );
                         let _ = settings.theme.agent_buffer_font_size.insert(
-                            f32::from(theme::clamp_font_size(agent_buffer_font_size)).into(),
+                            f32::from(theme_settings::clamp_font_size(agent_buffer_font_size))
+                                .into(),
                         );
                     });
                 } else {
-                    theme::adjust_agent_ui_font_size(cx, |size| size + delta);
-                    theme::adjust_agent_buffer_font_size(cx, |size| size + delta);
+                    theme_settings::adjust_agent_ui_font_size(cx, |size| size + delta);
+                    theme_settings::adjust_agent_buffer_font_size(cx, |size| size + delta);
                 }
             }
             WhichFontSize::BufferFont => {
@@ -1658,14 +1658,14 @@ impl AgentPanel {
                 settings.theme.agent_buffer_font_size = None;
             });
         } else {
-            theme::reset_agent_ui_font_size(cx);
-            theme::reset_agent_buffer_font_size(cx);
+            theme_settings::reset_agent_ui_font_size(cx);
+            theme_settings::reset_agent_buffer_font_size(cx);
         }
     }
 
     pub fn reset_agent_zoom(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
-        theme::reset_agent_ui_font_size(cx);
-        theme::reset_agent_buffer_font_size(cx);
+        theme_settings::reset_agent_ui_font_size(cx);
+        theme_settings::reset_agent_buffer_font_size(cx);
     }
 
     pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {

crates/agent_ui/src/agent_registry_ui.rs 🔗

@@ -12,7 +12,7 @@ use gpui::{
 use project::agent_server_store::{AllAgentServersSettings, CustomAgentServerSettings};
 use project::{AgentRegistryStore, RegistryAgent};
 use settings::{Settings, SettingsStore, update_settings_file};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     ButtonStyle, ScrollableHandle, ToggleButtonGroup, ToggleButtonGroupSize,
     ToggleButtonGroupStyle, ToggleButtonSimple, Tooltip, WithScrollbar, prelude::*,

crates/agent_ui/src/completion_provider.rs 🔗

@@ -2577,7 +2577,7 @@ mod tests {
 
         let app_state = cx.update(|cx| {
             let state = AppState::test(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
             state
         });

crates/agent_ui/src/conversation_view.rs 🔗

@@ -49,7 +49,7 @@ use std::time::Instant;
 use std::{collections::BTreeMap, rc::Rc, time::Duration};
 use terminal_view::terminal_panel::TerminalPanel;
 use text::Anchor;
-use theme::AgentFontSize;
+use theme_settings::AgentFontSize;
 use ui::{
     Callout, CircularProgress, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton,
     DecoratedIcon, DiffStat, Disclosure, Divider, DividerColor, IconDecoration, IconDecorationKind,
@@ -4257,7 +4257,7 @@ pub(crate) mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             SidebarThreadMetadataStore::init_global(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
             agent_panel::init(cx);
             release_channel::init(semver::Version::new(0, 0, 0), cx);

crates/agent_ui/src/entry_view_state.rs 🔗

@@ -16,7 +16,7 @@ use prompt_store::PromptStore;
 use rope::Point;
 use settings::Settings as _;
 use terminal_view::TerminalView;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{Context, TextSize};
 use workspace::Workspace;
 
@@ -594,7 +594,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             release_channel::init(semver::Version::new(0, 0, 0), cx);
         });
     }

crates/agent_ui/src/inline_prompt_editor.rs 🔗

@@ -24,7 +24,7 @@ use std::cmp;
 use std::ops::Range;
 use std::rc::Rc;
 use std::sync::Arc;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::utils::WithRemSize;
 use ui::{IconButtonShape, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
 use uuid::Uuid;

crates/agent_ui/src/mention_set.rs 🔗

@@ -667,7 +667,7 @@ mod tests {
         let settings_store = cx.update(SettingsStore::test);
         cx.set_global(settings_store);
         cx.update(|cx| {
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             release_channel::init(Version::new(0, 0, 0), cx);
             prompt_store::init(cx);
         });

crates/agent_ui/src/message_editor.rs 🔗

@@ -33,7 +33,7 @@ use prompt_store::PromptStore;
 use rope::Point;
 use settings::Settings;
 use std::{fmt::Write, ops::Range, rc::Rc, sync::Arc};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{ContextMenu, Disclosure, ElevationIndex, prelude::*};
 use util::paths::PathStyle;
 use util::{ResultExt, debug_panic};

crates/agent_ui/src/test_support.rs 🔗

@@ -73,7 +73,7 @@ pub fn init_test(cx: &mut TestAppContext) {
     cx.update(|cx| {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         editor::init(cx);
         release_channel::init("0.0.0".parse().unwrap(), cx);
         agent_panel::init(cx);

crates/agent_ui/src/text_thread_editor.rs 🔗

@@ -1030,7 +1030,11 @@ impl TextThreadEditor {
         h_flex()
             .items_center()
             .gap_1()
-            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+            .font(
+                theme_settings::ThemeSettings::get_global(cx)
+                    .buffer_font
+                    .clone(),
+            )
             .text_size(TextSize::XSmall.rems(cx))
             .text_color(colors.text_muted)
             .child("Press")
@@ -3440,7 +3444,7 @@ mod tests {
         LanguageModelRegistry::test(cx);
         cx.set_global(settings_store);
 
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
     }
 
     #[gpui::test]

crates/agent_ui/src/thread_history.rs 🔗

@@ -232,7 +232,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = settings::SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
     }
 

crates/agent_ui/src/ui/agent_notification.rs 🔗

@@ -5,7 +5,6 @@ use gpui::{
 };
 use release_channel::ReleaseChannel;
 use std::rc::Rc;
-use theme;
 use ui::{Render, prelude::*};
 
 pub struct AgentNotification {
@@ -87,7 +86,7 @@ impl AgentNotification {
 
 impl Render for AgentNotification {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
         let line_height = window.line_height();
 
         let bg = cx.theme().colors().elevated_surface_background;

crates/agent_ui/src/ui/mention_crease.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
 use prompt_store::PromptId;
 use rope::Point;
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{ButtonLike, TintColor, Tooltip, prelude::*};
 use workspace::{OpenOptions, Workspace};
 

crates/collab/Cargo.toml 🔗

@@ -130,6 +130,7 @@ settings = { workspace = true, features = ["test-support"] }
 smol.workspace = true
 sqlx = { version = "0.8", features = ["sqlite"] }
 task.workspace = true
+theme_settings = { workspace = true, features = ["test-support"] }
 theme.workspace = true
 
 unindent.workspace = true

crates/collab/tests/integration/randomized_test_helpers.rs 🔗

@@ -191,7 +191,7 @@ pub async fn run_randomized_test<T: RandomizedTest>(
             let settings = cx.remove_global::<SettingsStore>();
             cx.clear_globals();
             cx.set_global(settings);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             drop(client);
         });
         executor.run_until_parked();

crates/collab/tests/integration/test_server.rs 🔗

@@ -173,7 +173,7 @@ impl TestServer {
             }
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             release_channel::init(semver::Version::new(0, 0, 0), cx);
         });
 
@@ -341,7 +341,7 @@ impl TestServer {
         let os_keymap = "keymaps/default-macos.json";
 
         cx.update(|cx| {
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             Project::init(&client, cx);
             client::init(&client, cx);
             editor::init(cx);

crates/collab_ui/Cargo.toml 🔗

@@ -54,6 +54,7 @@ settings.workspace = true
 smallvec.workspace = true
 telemetry.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 time.workspace = true
 time_format.workspace = true
 title_bar.workspace = true

crates/collab_ui/src/collab_panel.rs 🔗

@@ -29,7 +29,8 @@ use serde::{Deserialize, Serialize};
 use settings::Settings;
 use smallvec::SmallVec;
 use std::{mem, sync::Arc};
-use theme::{ActiveTheme, ThemeSettings};
+use theme::ActiveTheme;
+use theme_settings::ThemeSettings;
 use ui::{
     Avatar, AvatarAvailabilityIndicator, ContextMenu, CopyButton, Facepile, HighlightedLabel,
     IconButtonShape, Indicator, ListHeader, ListItem, Tab, Tooltip, prelude::*, tooltip_container,

crates/collab_ui/src/notifications/incoming_call_notification.rs 🔗

@@ -111,7 +111,7 @@ impl IncomingCallNotification {
 
 impl Render for IncomingCallNotification {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
 
         div().size_full().font(ui_font).child(
             CollabNotification::new(

crates/collab_ui/src/notifications/project_shared_notification.rs 🔗

@@ -120,7 +120,7 @@ impl ProjectSharedNotification {
 
 impl Render for ProjectSharedNotification {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
         let no_worktree_root_names = self.worktree_root_names.is_empty();
 
         let punctuation = if no_worktree_root_names { "" } else { ":" };

crates/command_palette/Cargo.toml 🔗

@@ -49,3 +49,4 @@ menu.workspace = true
 project = { workspace = true, features = ["test-support"] }
 
 workspace = { workspace = true, features = ["test-support"] }
+theme_settings.workspace = true

crates/command_palette/src/command_palette.rs 🔗

@@ -931,7 +931,7 @@ mod tests {
     fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
         cx.update(|cx| {
             let app_state = AppState::test(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
             menu::init();
             go_to_line::init(cx);

crates/component_preview/Cargo.toml 🔗

@@ -33,6 +33,7 @@ reqwest_client.workspace = true
 session.workspace = true
 settings.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 ui_input.workspace = true
 uuid.workspace = true

crates/component_preview/examples/component_preview.rs 🔗

@@ -39,7 +39,7 @@ fn main() {
         <dyn fs::Fs>::set_global(fs.clone(), cx);
 
         settings::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
 
         let languages = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
         let client = Client::production(cx);
@@ -81,7 +81,7 @@ fn main() {
             {
                 move |window, cx| {
                     let app_state = app_state;
-                    theme::setup_ui_font(window, cx);
+                    theme_settings::setup_ui_font(window, cx);
 
                     let project = Project::local(
                         app_state.client.clone(),

crates/copilot/Cargo.toml 🔗

@@ -68,3 +68,4 @@ settings = { workspace = true, features = ["test-support"] }
 theme = { workspace = true, features = ["test-support"] }
 util = { workspace = true, features = ["test-support"] }
 zlog.workspace = true
+theme_settings.workspace = true

crates/copilot/src/copilot_edit_prediction_delegate.rs 🔗

@@ -1120,7 +1120,7 @@ mod tests {
         cx.update(|cx| {
             let store = SettingsStore::test(cx);
             cx.set_global(store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             SettingsStore::update_global(cx, |store: &mut SettingsStore, cx| {
                 store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
             });

crates/debugger_ui/Cargo.toml 🔗

@@ -67,6 +67,7 @@ tasks_ui.workspace = true
 terminal_view.workspace = true
 text.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 tree-sitter-json.workspace = true
 tree-sitter.workspace = true
 ui.workspace = true

crates/debugger_ui/src/session/running/console.rs 🔗

@@ -26,7 +26,8 @@ use project::{
 use settings::Settings;
 use std::fmt::Write;
 use std::{ops::Range, rc::Rc, usize};
-use theme::{Theme, ThemeSettings};
+use theme::Theme;
+use theme_settings::ThemeSettings;
 use ui::{ContextMenu, Divider, PopoverMenu, SplitButton, Tooltip, prelude::*};
 use util::ResultExt;
 

crates/debugger_ui/src/session/running/memory_view.rs 🔗

@@ -17,7 +17,7 @@ use gpui::{
 use notifications::status_toast::{StatusToast, ToastIcon};
 use project::debugger::{MemoryCell, dap_command::DataBreakpointContext, session::Session};
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     ContextMenu, Divider, DropdownMenu, FluentBuilder, IntoElement, PopoverMenuHandle, Render,
     ScrollableHandle, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,

crates/debugger_ui/src/tests.rs 🔗

@@ -41,7 +41,7 @@ pub fn init_test(cx: &mut gpui::TestAppContext) {
         let settings = SettingsStore::test(cx);
         cx.set_global(settings);
         terminal_view::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         command_palette_hooks::init(cx);
         editor::init(cx);
         crate::init(cx);

crates/diagnostics/Cargo.toml 🔗

@@ -32,6 +32,7 @@ serde_json.workspace = true
 settings.workspace = true
 text.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/diagnostics/src/diagnostic_renderer.rs 🔗

@@ -11,7 +11,7 @@ use lsp::DiagnosticSeverity;
 use markdown::{Markdown, MarkdownElement};
 use settings::Settings;
 use text::{AnchorRangeExt, Point};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{CopyButton, prelude::*};
 use util::maybe;
 

crates/diagnostics/src/diagnostics_tests.rs 🔗

@@ -2034,7 +2034,7 @@ fn init_test(cx: &mut TestAppContext) {
         zlog::init_test();
         let settings = SettingsStore::test(cx);
         cx.set_global(settings);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         crate::init(cx);
         editor::init(cx);
     });

crates/edit_prediction_ui/Cargo.toml 🔗

@@ -42,7 +42,7 @@ regex.workspace = true
 settings.workspace = true
 telemetry.workspace = true
 text.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/edit_prediction_ui/src/rate_prediction_modal.rs 🔗

@@ -14,7 +14,7 @@ use project::{
 use settings::Settings as _;
 use std::rc::Rc;
 use std::{fmt::Write, sync::Arc};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     ContextMenu, DropdownMenu, KeyBinding, List, ListItem, ListItemSpacing, PopoverMenuHandle,
     Tooltip, prelude::*,

crates/editor/Cargo.toml 🔗

@@ -83,6 +83,7 @@ telemetry.workspace = true
 text.workspace = true
 time.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 tree-sitter-c = { workspace = true, optional = true }
 tree-sitter-html = { workspace = true, optional = true }
 tree-sitter-rust = { workspace = true, optional = true }

crates/editor/benches/editor_render.rs 🔗

@@ -122,7 +122,7 @@ pub fn benches() {
         let store = SettingsStore::test(cx);
         cx.set_global(store);
         assets::Assets.load_test_fonts(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         // release_channel::init(semver::Version::new(0,0,0), cx);
         editor::init(cx);
     });

crates/editor/src/bracket_colorization.rs 🔗

@@ -226,7 +226,7 @@ mod tests {
     use serde_json::json;
     use settings::{AccentContent, SettingsStore};
     use text::{Bias, OffsetRangeExt, ToOffset};
-    use theme::ThemeStyleContent;
+    use theme_settings::ThemeStyleContent;
 
     use util::{path, post_inc};
 

crates/editor/src/display_map.rs 🔗

@@ -4036,7 +4036,7 @@ pub mod tests {
         let settings = SettingsStore::test(cx);
         cx.set_global(settings);
         crate::init(cx);
-        theme::init(LoadThemes::JustBase, cx);
+        theme_settings::init(LoadThemes::JustBase, cx);
         cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings(cx, f);
         });

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

@@ -4830,7 +4830,7 @@ mod tests {
     fn init_test(cx: &mut gpui::App) {
         let settings = SettingsStore::test(cx);
         cx.set_global(settings);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         assets::Assets.load_test_fonts(cx);
     }
 

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

@@ -57,7 +57,8 @@ impl FoldPlaceholder {
     pub fn fold_element(fold_id: FoldId, cx: &App) -> Stateful<gpui::Div> {
         use gpui::{InteractiveElement as _, StatefulInteractiveElement as _, Styled as _};
         use settings::Settings as _;
-        use theme::{ActiveTheme as _, ThemeSettings};
+        use theme::ActiveTheme as _;
+        use theme_settings::ThemeSettings;
         let settings = ThemeSettings::get_global(cx);
         gpui::div()
             .id(fold_id)

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

@@ -2227,7 +2227,7 @@ mod tests {
     fn init_test(cx: &mut App) {
         let store = SettingsStore::test(cx);
         cx.set_global(store);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
     }
 
     /// Helper to create test highlights for an inlay

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

@@ -1664,7 +1664,7 @@ mod tests {
         cx.update(|cx| {
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
-            theme::init(LoadThemes::JustBase, cx);
+            theme_settings::init(LoadThemes::JustBase, cx);
         });
     }
 

crates/editor/src/editor.rs 🔗

@@ -204,8 +204,8 @@ use task::TaskVariables;
 use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _, ToPoint as _};
 use theme::{
     AccentColors, ActiveTheme, GlobalTheme, PlayerColor, StatusColors, SyntaxTheme, Theme,
-    ThemeSettings, observe_buffer_font_size_adjustment,
 };
+use theme_settings::{ThemeSettings, observe_buffer_font_size_adjustment};
 use ui::{
     Avatar, ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape,
     IconName, IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
@@ -9936,7 +9936,11 @@ impl Editor {
         h_flex()
             .px_0p5()
             .when(is_platform_style_mac, |parent| parent.gap_0p5())
-            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+            .font(
+                theme_settings::ThemeSettings::get_global(cx)
+                    .buffer_font
+                    .clone(),
+            )
             .text_size(TextSize::XSmall.rems(cx))
             .child(h_flex().children(ui::render_modifiers(
                 keystroke.modifiers(),
@@ -9967,7 +9971,11 @@ impl Editor {
 
         if keystroke.modifiers().modified() {
             h_flex()
-                .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+                .font(
+                    theme_settings::ThemeSettings::get_global(cx)
+                        .buffer_font
+                        .clone(),
+                )
                 .when(is_platform_style_mac, |parent| parent.gap_1())
                 .child(h_flex().children(ui::render_modifiers(
                     keystroke.modifiers(),
@@ -10473,7 +10481,11 @@ impl Editor {
                         .gap_2()
                         .pr_1()
                         .overflow_x_hidden()
-                        .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+                        .font(
+                            theme_settings::ThemeSettings::get_global(cx)
+                                .buffer_font
+                                .clone(),
+                        )
                         .child(left)
                         .child(preview),
                 )
@@ -24446,7 +24458,7 @@ impl Editor {
             return None;
         }
 
-        let theme_settings = theme::ThemeSettings::get_global(cx);
+        let theme_settings = theme_settings::ThemeSettings::get_global(cx);
         let theme = cx.theme();
         let accent_colors = theme.accents().clone();
 

crates/editor/src/editor_tests.rs 🔗

@@ -29721,7 +29721,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
         assets::Assets.load_test_fonts(cx);
         let store = SettingsStore::test(cx);
         cx.set_global(store);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         release_channel::init(semver::Version::new(0, 0, 0), cx);
         crate::init(cx);
     });

crates/editor/src/element.rs 🔗

@@ -83,7 +83,8 @@ use std::{
 };
 use sum_tree::Bias;
 use text::{BufferId, SelectionGoal};
-use theme::{ActiveTheme, Appearance, BufferLineHeight, PlayerColor};
+use theme::{ActiveTheme, Appearance, PlayerColor};
+use theme_settings::BufferLineHeight;
 use ui::utils::ensure_minimum_contrast;
 use ui::{
     ButtonLike, ContextMenu, Indicator, KeyBinding, POPOVER_Y_PADDING, Tooltip, prelude::*,
@@ -8448,7 +8449,7 @@ pub(crate) fn render_buffer_header(
                                         el.child(Icon::new(IconName::FileLock).color(Color::Muted))
                                     })
                                     .when_some(breadcrumbs, |then, breadcrumbs| {
-                                        let font = theme::ThemeSettings::get_global(cx)
+                                        let font = theme_settings::ThemeSettings::get_global(cx)
                                             .buffer_font
                                             .clone();
                                         then.child(render_breadcrumb_text(

crates/editor/src/git/blame.rs 🔗

@@ -746,7 +746,7 @@ mod tests {
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
 
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
 
             crate::init(cx);
         });

crates/editor/src/hover_popover.rs 🔗

@@ -26,7 +26,7 @@ use std::{
 };
 use std::{ops::Range, sync::Arc, time::Duration};
 use std::{path::PathBuf, rc::Rc};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{CopyButton, Scrollbars, WithScrollbar, prelude::*, theme_is_transparent};
 use url::Url;
 use util::TryFutureExt;

crates/editor/src/inlays/inlay_hints.rs 🔗

@@ -4798,7 +4798,7 @@ let c = 3;"#
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             release_channel::init(semver::Version::new(0, 0, 0), cx);
             crate::init(cx);
         });

crates/editor/src/items.rs 🔗

@@ -980,7 +980,9 @@ impl Item for Editor {
     // In a non-singleton case, the breadcrumbs are actually shown on sticky file headers of the multibuffer.
     fn breadcrumbs(&self, cx: &App) -> Option<(Vec<HighlightedText>, Option<Font>)> {
         if self.buffer.read(cx).is_singleton() {
-            let font = theme::ThemeSettings::get_global(cx).buffer_font.clone();
+            let font = theme_settings::ThemeSettings::get_global(cx)
+                .buffer_font
+                .clone();
             Some((self.breadcrumbs_inner(cx)?, Some(font)))
         } else {
             None

crates/editor/src/movement.rs 🔗

@@ -1393,7 +1393,7 @@ mod tests {
     fn init_test(cx: &mut gpui::App) {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         crate::init(cx);
     }
 }

crates/editor/src/semantic_tokens.rs 🔗

@@ -1383,7 +1383,7 @@ mod tests {
     async fn test_theme_override_changes_restyle_semantic_tokens(cx: &mut TestAppContext) {
         use collections::IndexMap;
         use gpui::{Hsla, Rgba, UpdateGlobal as _};
-        use theme::{HighlightStyleContent, ThemeStyleContent};
+        use theme_settings::{HighlightStyleContent, ThemeStyleContent};
 
         init_test(cx, |_| {});
 
@@ -1548,7 +1548,7 @@ mod tests {
     async fn test_per_theme_overrides_restyle_semantic_tokens(cx: &mut TestAppContext) {
         use collections::IndexMap;
         use gpui::{Hsla, Rgba, UpdateGlobal as _};
-        use theme::{HighlightStyleContent, ThemeStyleContent};
+        use theme_settings::{HighlightStyleContent, ThemeStyleContent};
         use ui::ActiveTheme as _;
 
         init_test(cx, |_| {});

crates/editor/src/signature_help.rs 🔗

@@ -13,7 +13,7 @@ use settings::Settings;
 use std::ops::Range;
 use std::time::Duration;
 use text::Rope;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     ActiveTheme, AnyElement, ButtonCommon, ButtonStyle, Clickable, FluentBuilder, IconButton,
     IconButtonShape, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon,

crates/editor/src/split.rs 🔗

@@ -2118,7 +2118,7 @@ mod tests {
         cx.update(|cx| {
             let store = SettingsStore::test(cx);
             cx.set_global(store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             crate::init(cx);
         });
         let project = Project::test(FakeFs::new(cx.executor()), [], cx).await;

crates/extension_cli/Cargo.toml 🔗

@@ -29,7 +29,7 @@ serde_json_lenient.workspace = true
 settings_content.workspace = true
 snippet_provider.workspace = true
 task.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 tokio = { workspace = true, features = ["full"] }
 toml.workspace = true
 tree-sitter.workspace = true

crates/extension_cli/src/main.rs 🔗

@@ -413,7 +413,8 @@ async fn test_themes(
 ) -> Result<()> {
     for relative_theme_path in &manifest.themes {
         let theme_path = extension_path.join(relative_theme_path);
-        let theme_family = theme::deserialize_user_theme(&fs.load_bytes(&theme_path).await?)?;
+        let theme_family =
+            theme_settings::deserialize_user_theme(&fs.load_bytes(&theme_path).await?)?;
         log::info!("loaded theme family {}", theme_family.name);
 
         for theme in &theme_family.themes {

crates/extension_host/Cargo.toml 🔗

@@ -68,6 +68,7 @@ project = { workspace = true, features = ["test-support"] }
 
 reqwest_client.workspace = true
 theme = { workspace = true, features = ["test-support"] }
+theme_settings.workspace = true
 theme_extension.workspace = true
 zlog.workspace = true
 

crates/extension_host/src/extension_store_test.rs 🔗

@@ -1007,7 +1007,7 @@ fn init_test(cx: &mut TestAppContext) {
         cx.set_global(store);
         release_channel::init(semver::Version::new(0, 0, 0), cx);
         extension::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         gpui_tokio::init(cx);
     });
 }

crates/extensions_ui/Cargo.toml 🔗

@@ -35,7 +35,7 @@ settings.workspace = true
 smallvec.workspace = true
 strum.workspace = true
 telemetry.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 vim_mode_setting.workspace = true

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -23,7 +23,7 @@ use project::DirectoryLister;
 use release_channel::ReleaseChannel;
 use settings::{Settings, SettingsContent};
 use strum::IntoEnumIterator as _;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Banner, Chip, ContextMenu, Divider, PopoverMenu, ScrollableHandle, Switch, ToggleButtonGroup,
     ToggleButtonGroupSize, ToggleButtonGroupStyle, ToggleButtonSimple, Tooltip, WithScrollbar,

crates/file_finder/Cargo.toml 🔗

@@ -47,3 +47,4 @@ theme = { workspace = true, features = ["test-support"] }
 workspace = { workspace = true, features = ["test-support"] }
 zlog.workspace = true
 remote_connection = { workspace = true, features = ["test-support"] }
+theme_settings = { workspace = true, features = ["test-support"] }

crates/file_finder/src/file_finder_tests.rs 🔗

@@ -3789,7 +3789,7 @@ async fn open_queried_buffer(
 fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
     cx.update(|cx| {
         let state = AppState::test(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         super::init(cx);
         editor::init(cx);
         state

crates/git_graph/Cargo.toml 🔗

@@ -32,6 +32,7 @@ project.workspace = true
 settings.workspace = true
 smallvec.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 time.workspace = true
 ui.workspace = true
 workspace.workspace = true

crates/git_graph/src/git_graph.rs 🔗

@@ -33,7 +33,8 @@ use std::{
     sync::OnceLock,
     time::{Duration, Instant},
 };
-use theme::{AccentColors, ThemeSettings};
+use theme::AccentColors;
+use theme_settings::ThemeSettings;
 use time::{OffsetDateTime, UtcOffset, format_description::BorrowedFormatItem};
 use ui::{
     ButtonLike, Chip, CommonAnimationExt as _, ContextMenu, DiffStat, Divider, ScrollableHandle,
@@ -2489,7 +2490,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
     }
 

crates/git_ui/Cargo.toml 🔗

@@ -56,6 +56,7 @@ smol.workspace = true
 strum.workspace = true
 telemetry.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 time.workspace = true
 time_format.workspace = true
 ui.workspace = true

crates/git_ui/src/blame_ui.rs 🔗

@@ -11,7 +11,7 @@ use gpui::{
 use markdown::{Markdown, MarkdownElement};
 use project::{git_store::Repository, project_settings::ProjectSettings};
 use settings::Settings as _;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use time::OffsetDateTime;
 use ui::{ContextMenu, CopyButton, Divider, prelude::*, tooltip_container};
 use workspace::Workspace;

crates/git_ui/src/branch_picker.rs 🔗

@@ -1325,7 +1325,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
         });
     }

crates/git_ui/src/commit_tooltip.rs 🔗

@@ -12,7 +12,7 @@ use markdown::{Markdown, MarkdownElement};
 use project::git_store::Repository;
 use settings::Settings;
 use std::hash::Hash;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use time::{OffsetDateTime, UtcOffset};
 use ui::{Avatar, CopyButton, Divider, prelude::*, tooltip_container};
 use workspace::Workspace;

crates/git_ui/src/file_diff_view.rs 🔗

@@ -379,7 +379,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
     }
 

crates/git_ui/src/git_panel.rs 🔗

@@ -66,7 +66,7 @@ use std::ops::Range;
 use std::path::Path;
 use std::{sync::Arc, time::Duration, usize};
 use strum::{IntoEnumIterator, VariantNames};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use time::OffsetDateTime;
 use ui::{
     ButtonLike, Checkbox, CommonAnimationExt, ContextMenu, ElevationIndex, IndentGuideColors,
@@ -6491,7 +6491,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(LoadThemes::JustBase, cx);
+            theme_settings::init(LoadThemes::JustBase, cx);
             editor::init(cx);
             crate::init(cx);
         });

crates/git_ui/src/project_diff.rs 🔗

@@ -1777,7 +1777,7 @@ mod tests {
                     settings.editor.diff_view_style = Some(DiffViewStyle::Unified);
                 });
             });
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
             crate::init(cx);
         });

crates/git_ui/src/stash_picker.rs 🔗

@@ -632,7 +632,7 @@ mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
 
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
         })
     }

crates/git_ui/src/text_diff_view.rs 🔗

@@ -499,7 +499,7 @@ mod tests {
                     settings.editor.diff_view_style = Some(DiffViewStyle::Unified);
                 });
             });
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
     }
 

crates/image_viewer/Cargo.toml 🔗

@@ -26,7 +26,7 @@ log.workspace = true
 project.workspace = true
 serde.workspace = true
 settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/image_viewer/src/image_viewer.rs 🔗

@@ -19,7 +19,7 @@ use language::File as _;
 use persistence::ImageViewerDb;
 use project::{ImageItem, Project, ProjectPath, image_store::ImageItemEvent};
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{Tooltip, prelude::*};
 use util::paths::PathExt;
 use workspace::{

crates/inspector_ui/Cargo.toml 🔗

@@ -21,7 +21,7 @@ language.workspace = true
 project.workspace = true
 serde_json.workspace = true
 serde_json_lenient.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 util_macros.workspace = true

crates/inspector_ui/src/inspector.rs 🔗

@@ -57,7 +57,7 @@ fn render_inspector(
     window: &mut Window,
     cx: &mut Context<Inspector>,
 ) -> AnyElement {
-    let ui_font = theme::setup_ui_font(window, cx);
+    let ui_font = theme_settings::setup_ui_font(window, cx);
     let colors = cx.theme().colors();
     let inspector_id = inspector.active_element_id();
     let toolbar_height = platform_title_bar_height(window);

crates/keymap_editor/Cargo.toml 🔗

@@ -36,6 +36,7 @@ settings.workspace = true
 telemetry.workspace = true
 tempfile.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 tree-sitter-json.workspace = true
 tree-sitter-rust.workspace = true
 ui_input.workspace = true

crates/keymap_editor/src/keymap_editor.rs 🔗

@@ -3431,7 +3431,7 @@ impl ActionArgumentsEditor {
 
 impl Render for ActionArgumentsEditor {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let settings = theme::ThemeSettings::get_global(cx);
+        let settings = theme_settings::ThemeSettings::get_global(cx);
         let colors = cx.theme().colors();
 
         let border_color = if self.is_loading {

crates/keymap_editor/src/ui_components/keystroke_input.rs 🔗

@@ -1115,7 +1115,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
 
         let fs = FakeFs::new(cx.executor());

crates/language/Cargo.toml 🔗

@@ -102,6 +102,7 @@ unindent.workspace = true
 util = { workspace = true, features = ["test-support"] }
 zlog.workspace = true
 criterion.workspace = true
+theme_settings.workspace = true
 
 [[bench]]
 name = "highlight_map"

crates/language/src/buffer_tests.rs 🔗

@@ -3247,7 +3247,7 @@ fn test_undo_after_merge_into_base(cx: &mut TestAppContext) {
 async fn test_preview_edits(cx: &mut TestAppContext) {
     cx.update(|cx| {
         init_settings(cx, |_| {});
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
     });
 
     let insertion_style = HighlightStyle {

crates/language_tools/Cargo.toml 🔗

@@ -44,4 +44,5 @@ release_channel.workspace = true
 gpui = { workspace = true, features = ["test-support"] }
 semver.workspace = true
 util = { workspace = true, features = ["test-support"] }
-zlog.workspace = true
+zlog.workspace = true
+theme_settings.workspace = true

crates/language_tools/src/lsp_log_view_tests.rs 🔗

@@ -109,7 +109,7 @@ fn init_test(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
 }

crates/markdown/Cargo.toml 🔗

@@ -35,6 +35,7 @@ settings.workspace = true
 stacksafe.workspace = true
 sum_tree.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 

crates/markdown/examples/markdown.rs 🔗

@@ -41,7 +41,7 @@ pub fn main() {
         cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);
 
         let node_runtime = NodeRuntime::unavailable();
-        theme::init(LoadThemes::JustBase, cx);
+        theme_settings::init(LoadThemes::JustBase, cx);
 
         let fs = fs::FakeFs::new(cx.background_executor().clone());
         let language_registry = LanguageRegistry::new(cx.background_executor().clone());

crates/markdown/examples/markdown_as_child.rs 🔗

@@ -28,7 +28,7 @@ pub fn main() {
         let language_registry = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
         let fs = fs::FakeFs::new(cx.background_executor().clone());
         languages::init(language_registry, fs, node_runtime, cx);
-        theme::init(LoadThemes::JustBase, cx);
+        theme_settings::init(LoadThemes::JustBase, cx);
         Assets.load_fonts(cx).unwrap();
 
         cx.activate(true);

crates/markdown/src/html/html_rendering.rs 🔗

@@ -505,7 +505,7 @@ mod tests {
                 settings::init(cx);
             }
             if !cx.has_global::<theme::GlobalTheme>() {
-                theme::init(theme::LoadThemes::JustBase, cx);
+                theme_settings::init(theme::LoadThemes::JustBase, cx);
             }
         });
     }

crates/markdown/src/markdown.rs 🔗

@@ -16,7 +16,7 @@ use mermaid::{
 };
 pub use path_range::{LineCol, PathWithRange};
 use settings::Settings as _;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::Checkbox;
 use ui::CopyButton;
 
@@ -2677,7 +2677,7 @@ mod tests {
                 settings::init(cx);
             }
             if !cx.has_global::<theme::GlobalTheme>() {
-                theme::init(theme::LoadThemes::JustBase, cx);
+                theme_settings::init(theme::LoadThemes::JustBase, cx);
             }
         });
     }

crates/markdown/src/mermaid.rs 🔗

@@ -278,7 +278,7 @@ mod tests {
                 settings::init(cx);
             }
             if !cx.has_global::<theme::GlobalTheme>() {
-                theme::init(theme::LoadThemes::JustBase, cx);
+                theme_settings::init(theme::LoadThemes::JustBase, cx);
             }
         });
     }

crates/markdown_preview/Cargo.toml 🔗

@@ -22,7 +22,7 @@ language.workspace = true
 log.workspace = true
 markdown.workspace = true
 settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 urlencoding.workspace = true
 util.workspace = true

crates/markdown_preview/src/markdown_preview_view.rs 🔗

@@ -16,7 +16,7 @@ use markdown::{
     CodeBlockRenderer, Markdown, MarkdownElement, MarkdownFont, MarkdownOptions, MarkdownStyle,
 };
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{WithScrollbar, prelude::*};
 use util::normalize_path;
 use workspace::item::{Item, ItemHandle};

crates/miniprofiler_ui/Cargo.toml 🔗

@@ -14,7 +14,7 @@ path = "src/miniprofiler_ui.rs"
 [dependencies]
 gpui.workspace = true
 rpc.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 zed_actions.workspace = true
 workspace.workspace = true
 util.workspace = true

crates/miniprofiler_ui/src/miniprofiler_ui.rs 🔗

@@ -456,7 +456,7 @@ impl Render for ProfilerWindow {
         window: &mut gpui::Window,
         cx: &mut gpui::Context<Self>,
     ) -> impl gpui::IntoElement {
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
         if !self.paused {
             self.poll_timings(cx);
             window.request_animation_frame();

crates/onboarding/Cargo.toml 🔗

@@ -32,6 +32,7 @@ serde.workspace = true
 settings.workspace = true
 telemetry.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 vim_mode_setting.workspace = true

crates/onboarding/src/basics_page.rs 🔗

@@ -5,10 +5,8 @@ use fs::Fs;
 use gpui::{Action, App, IntoElement};
 use project::project_settings::ProjectSettings;
 use settings::{BaseKeymap, Settings, update_settings_file};
-use theme::{
-    Appearance, SystemAppearance, ThemeAppearanceMode, ThemeName, ThemeRegistry, ThemeSelection,
-    ThemeSettings,
-};
+use theme::{Appearance, SystemAppearance, ThemeRegistry};
+use theme_settings::{ThemeAppearanceMode, ThemeName, ThemeSelection, ThemeSettings};
 use ui::{
     Divider, StatefulInteractiveElement, SwitchField, TintColor, ToggleButtonGroup,
     ToggleButtonGroupSize, ToggleButtonSimple, ToggleButtonWithIcon, Tooltip, prelude::*,
@@ -197,7 +195,7 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement
     fn write_mode_change(mode: ThemeAppearanceMode, cx: &mut App) {
         let fs = <dyn Fs>::global(cx);
         update_settings_file(fs, cx, move |settings, _cx| {
-            theme::set_mode(settings, mode);
+            theme_settings::set_mode(settings, mode);
         });
     }
 
@@ -219,13 +217,13 @@ fn render_theme_section(tab_index: &mut isize, cx: &mut App) -> impl IntoElement
                     dark: ThemeName(dark_theme.into()),
                 });
             }
-            ThemeAppearanceMode::Light => theme::set_theme(
+            ThemeAppearanceMode::Light => theme_settings::set_theme(
                 settings,
                 theme,
                 Appearance::Light,
                 *SystemAppearance::global(cx),
             ),
-            ThemeAppearanceMode::Dark => theme::set_theme(
+            ThemeAppearanceMode::Dark => theme_settings::set_theme(
                 settings,
                 theme,
                 Appearance::Dark,

crates/open_path_prompt/Cargo.toml 🔗

@@ -24,6 +24,7 @@ editor = {workspace = true, features = ["test-support"]}
 gpui = {workspace = true, features = ["test-support"]}
 serde_json.workspace = true
 theme = {workspace = true, features = ["test-support"]}
+theme_settings.workspace = true
 workspace = {workspace = true, features = ["test-support"]}
 
 [lints]

crates/open_path_prompt/src/open_path_prompt_tests.rs 🔗

@@ -410,7 +410,7 @@ async fn test_open_path_prompt_with_show_hidden(cx: &mut TestAppContext) {
 fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
     cx.update(|cx| {
         let state = AppState::test(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
 
         editor::init(cx);
         state

crates/outline/Cargo.toml 🔗

@@ -22,6 +22,7 @@ picker.workspace = true
 settings.workspace = true
 smol.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/outline/src/outline.rs 🔗

@@ -14,7 +14,8 @@ use language::{Outline, OutlineItem};
 use ordered_float::OrderedFloat;
 use picker::{Picker, PickerDelegate};
 use settings::Settings;
-use theme::{ActiveTheme, ThemeSettings};
+use theme::ActiveTheme;
+use theme_settings::ThemeSettings;
 use ui::{ListItem, ListItemSpacing, prelude::*};
 use util::ResultExt;
 use workspace::{DismissDecision, ModalView};

crates/outline_panel/Cargo.toml 🔗

@@ -33,6 +33,7 @@ settings.workspace = true
 smallvec.workspace = true
 smol.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/outline_panel/src/outline_panel.rs 🔗

@@ -47,7 +47,8 @@ use search::{BufferSearchBar, ProjectSearchView};
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsStore};
 use smol::channel;
-use theme::{SyntaxTheme, ThemeSettings};
+use theme::SyntaxTheme;
+use theme_settings::ThemeSettings;
 use ui::{
     ContextMenu, FluentBuilder, HighlightedLabel, IconButton, IconButtonShape, IndentGuideColors,
     IndentGuideLayout, ListItem, ScrollAxes, Scrollbars, Tab, Tooltip, WithScrollbar, prelude::*,
@@ -6899,7 +6900,7 @@ outline: struct OutlineEntryExcerpt
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
 
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
 
             editor::init(cx);
             project_search::init(cx);

crates/picker/Cargo.toml 🔗

@@ -22,6 +22,7 @@ menu.workspace = true
 schemars.workspace = true
 serde.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 ui_input.workspace = true
 workspace.workspace = true

crates/picker/src/picker.rs 🔗

@@ -16,7 +16,7 @@ use serde::Deserialize;
 use std::{
     cell::Cell, cell::RefCell, collections::HashMap, ops::Range, rc::Rc, sync::Arc, time::Duration,
 };
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Color, Divider, DocumentationAside, DocumentationSide, Label, ListItem, ListItemSpacing,
     ScrollAxes, Scrollbars, WithScrollbar, prelude::*, utils::WithRemSize, v_flex,
@@ -955,7 +955,7 @@ mod tests {
         cx.update(|cx| {
             let store = settings::SettingsStore::test(cx);
             cx.set_global(store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
         });
     }

crates/platform_title_bar/Cargo.toml 🔗

@@ -19,6 +19,7 @@ project.workspace = true
 settings.workspace = true
 smallvec.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true

crates/platform_title_bar/src/system_window_tabs.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{
     Styled, SystemWindowTab, SystemWindowTabController, Window, WindowId, actions, canvas, div,
 };
 
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Color, ContextMenu, DynamicSpacing, IconButton, IconButtonShape, IconName, IconSize, Label,
     LabelSize, Tab, h_flex, prelude::*, right_click_menu,

crates/project_panel/Cargo.toml 🔗

@@ -36,6 +36,7 @@ serde_json.workspace = true
 settings.workspace = true
 smallvec.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 rayon.workspace = true
 ui.workspace = true
 util.workspace = true

crates/project_panel/src/project_panel.rs 🔗

@@ -58,7 +58,7 @@ use std::{
     sync::Arc,
     time::Duration,
 };
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Color, ContextMenu, ContextMenuEntry, DecoratedIcon, Divider, Icon, IconDecoration,
     IconDecorationKind, IndentGuideColors, IndentGuideLayout, Indicator, KeyBinding, Label,

crates/project_panel/src/project_panel_tests.rs 🔗

@@ -10428,7 +10428,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext) {
     cx.update(|cx| {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         crate::init(cx);
 
         cx.update_global::<SettingsStore, _>(|store, cx| {
@@ -10446,7 +10446,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext) {
 fn init_test_with_editor(cx: &mut TestAppContext) {
     cx.update(|cx| {
         let app_state = AppState::test(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         editor::init(cx);
         crate::init(cx);
         workspace::init(app_state, cx);

crates/project_symbols/Cargo.toml 🔗

@@ -23,6 +23,7 @@ project.workspace = true
 serde_json.workspace = true
 settings.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 util.workspace = true
 workspace.workspace = true
 

crates/project_symbols/src/project_symbols.rs 🔗

@@ -9,7 +9,8 @@ use picker::{Picker, PickerDelegate};
 use project::{Project, Symbol, lsp_store::SymbolLocation};
 use settings::Settings;
 use std::{cmp::Reverse, sync::Arc};
-use theme::{ActiveTheme, ThemeSettings};
+use theme::ActiveTheme;
+use theme_settings::ThemeSettings;
 use util::ResultExt;
 use workspace::{
     Workspace,
@@ -477,7 +478,7 @@ mod tests {
         cx.update(|cx| {
             let store = SettingsStore::test(cx);
             cx.set_global(store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             release_channel::init(semver::Version::new(0, 0, 0), cx);
             editor::init(cx);
         });

crates/remote_connection/Cargo.toml 🔗

@@ -28,7 +28,7 @@ release_channel.workspace = true
 remote.workspace = true
 semver.workspace = true
 settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 ui_input.workspace = true
 workspace.workspace = true

crates/remote_connection/src/remote_connection.rs 🔗

@@ -13,7 +13,7 @@ use release_channel::ReleaseChannel;
 use remote::{ConnectionIdentifier, RemoteClient, RemoteConnectionOptions, RemotePlatform};
 use semver::Version;
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     ActiveTheme, CommonAnimationExt, Context, InteractiveElement, KeyBinding, ListItem, Tooltip,
     prelude::*,

crates/remote_server/Cargo.toml 🔗

@@ -98,6 +98,7 @@ node_runtime = { workspace = true, features = ["test-support"] }
 pretty_assertions.workspace = true
 project = { workspace = true, features = ["test-support"] }
 remote = { workspace = true, features = ["test-support"] }
+theme_settings.workspace = true
 theme = { workspace = true, features = ["test-support"] }
 language_model = { workspace = true, features = ["test-support"] }
 lsp = { workspace = true, features = ["test-support"] }

crates/remote_server/src/remote_editing_tests.rs 🔗

@@ -1661,7 +1661,7 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
     cx.update(|cx| {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         release_channel::init(semver::Version::new(0, 0, 0), cx);
         editor::init(cx);
     });

crates/repl/Cargo.toml 🔗

@@ -53,6 +53,7 @@ telemetry.workspace = true
 terminal.workspace = true
 terminal_view.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 uuid.workspace = true

crates/repl/src/notebook/cell.rs 🔗

@@ -12,7 +12,7 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle};
 use nbformat::v4::{CellId, CellMetadata, CellType};
 use runtimelib::{JupyterMessage, JupyterMessageContent};
 use settings::Settings as _;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{CommonAnimationExt, IconButtonShape, prelude::*};
 use util::ResultExt;
 

crates/repl/src/outputs.rs 🔗

@@ -895,7 +895,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
         let fs = project::FakeFs::new(cx.background_executor.clone());
         let project = project::Project::test(fs, [] as [&Path; 0], cx).await;

crates/repl/src/outputs/plain.rs 🔗

@@ -27,7 +27,7 @@ use language::Buffer;
 use settings::Settings as _;
 use terminal::terminal_settings::TerminalSettings;
 use terminal_view::terminal_element::TerminalElement;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{IntoElement, prelude::*};
 
 use crate::outputs::OutputContent;
@@ -275,7 +275,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
         cx.add_empty_window()
     }

crates/repl/src/outputs/table.rs 🔗

@@ -59,7 +59,7 @@ use runtimelib::datatable::TableSchema;
 use runtimelib::media::datatable::TabularDataResource;
 use serde_json::Value;
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{IntoElement, Styled, div, prelude::*, v_flex};
 use util::markdown::MarkdownEscaped;
 

crates/rules_library/Cargo.toml 🔗

@@ -28,7 +28,7 @@ release_channel.workspace = true
 rope.workspace = true
 serde.workspace = true
 settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 ui_input.workspace = true
 util.workspace = true

crates/rules_library/src/rules_library.rs 🔗

@@ -20,7 +20,7 @@ use std::rc::Rc;
 use std::sync::Arc;
 use std::sync::atomic::AtomicBool;
 use std::time::Duration;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{Divider, ListItem, ListItemSpacing, ListSubHeader, Tooltip, prelude::*};
 use ui_input::ErasedEditor;
 use util::{ResultExt, TryFutureExt};
@@ -1392,7 +1392,7 @@ impl RulesLibrary {
 
 impl Render for RulesLibrary {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
         let theme = cx.theme().clone();
 
         client_side_decorations(

crates/schema_generator/Cargo.toml 🔗

@@ -17,3 +17,4 @@ serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true
 theme.workspace = true
+theme_settings.workspace = true

crates/schema_generator/src/main.rs 🔗

@@ -2,7 +2,8 @@ use anyhow::Result;
 use clap::{Parser, ValueEnum};
 use schemars::schema_for;
 use settings::ProjectSettingsContent;
-use theme::{IconThemeFamilyContent, ThemeFamilyContent};
+use theme::IconThemeFamilyContent;
+use theme_settings::ThemeFamilyContent;
 
 #[derive(Parser, Debug)]
 pub struct Args {

crates/search/Cargo.toml 🔗

@@ -38,6 +38,7 @@ serde_json.workspace = true
 settings.workspace = true
 smol.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 util_macros.workspace = true

crates/search/src/buffer_search.rs 🔗

@@ -1906,7 +1906,7 @@ mod tests {
             cx.set_global(store);
             editor::init(cx);
 
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             crate::init(cx);
         });
     }

crates/search/src/project_search.rs 🔗

@@ -5143,7 +5143,7 @@ pub mod tests {
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
 
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
 
             editor::init(cx);
             crate::init(cx);

crates/search/src/search_bar.rs 🔗

@@ -1,7 +1,7 @@
 use editor::{Editor, EditorElement, EditorStyle, MultiBufferOffset, ToOffset};
 use gpui::{Action, App, Entity, FocusHandle, Hsla, IntoElement, TextStyle};
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{IconButton, IconButtonShape};
 use ui::{Tooltip, prelude::*};
 

crates/settings_profile_selector/Cargo.toml 🔗

@@ -29,4 +29,5 @@ project = { workspace = true, features = ["test-support"] }
 serde_json.workspace = true
 settings = { workspace = true, features = ["test-support"] }
 theme = { workspace = true, features = ["test-support"] }
+theme_settings.workspace = true
 workspace = { workspace = true, features = ["test-support"] }

crates/settings_profile_selector/src/settings_profile_selector.rs 🔗

@@ -286,7 +286,7 @@ mod tests {
     use project::{FakeFs, Project};
     use serde_json::json;
     use settings::Settings;
-    use theme::{self, ThemeSettings};
+    use theme_settings::ThemeSettings;
     use workspace::{self, AppState, MultiWorkspace};
     use zed_actions::settings_profile_selector;
 
@@ -299,7 +299,7 @@ mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             settings::init(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             super::init(cx);
             editor::init(cx);
             state

crates/settings_ui/Cargo.toml 🔗

@@ -54,6 +54,7 @@ shell_command_parser.workspace = true
 strum.workspace = true
 telemetry.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/settings_ui/src/components/input_field.rs 🔗

@@ -3,7 +3,7 @@ use std::rc::Rc;
 use editor::Editor;
 use gpui::{AnyElement, ElementId, Focusable, TextStyleRefinement};
 use settings::Settings as _;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{Tooltip, prelude::*, rems};
 
 #[derive(IntoElement)]

crates/settings_ui/src/page_data.rs 🔗

@@ -411,9 +411,9 @@ fn appearance_page() -> SettingsPage {
                                         settings::ThemeSelection::Static(_) => return,
                                         settings::ThemeSelection::Dynamic { mode, light, dark } => {
                                             match mode {
-                                                theme::ThemeAppearanceMode::Light => light.clone(),
-                                                theme::ThemeAppearanceMode::Dark => dark.clone(),
-                                                theme::ThemeAppearanceMode::System => dark.clone(), // no cx, can't determine correct choice
+                                                theme_settings::ThemeAppearanceMode::Light => light.clone(),
+                                                theme_settings::ThemeAppearanceMode::Dark => dark.clone(),
+                                                theme_settings::ThemeAppearanceMode::System => dark.clone(), // no cx, can't determine correct choice
                                             }
                                         },
                                     };
@@ -581,9 +581,9 @@ fn appearance_page() -> SettingsPage {
                                         settings::IconThemeSelection::Static(_) => return,
                                         settings::IconThemeSelection::Dynamic { mode, light, dark } => {
                                             match mode {
-                                                theme::ThemeAppearanceMode::Light => light.clone(),
-                                                theme::ThemeAppearanceMode::Dark => dark.clone(),
-                                                theme::ThemeAppearanceMode::System => dark.clone(), // no cx, can't determine correct choice
+                                                theme_settings::ThemeAppearanceMode::Light => light.clone(),
+                                                theme_settings::ThemeAppearanceMode::Dark => dark.clone(),
+                                                theme_settings::ThemeAppearanceMode::System => dark.clone(), // no cx, can't determine correct choice
                                             }
                                         },
                                     };
@@ -802,7 +802,8 @@ fn appearance_page() -> SettingsPage {
                                 }
                                 settings::BufferLineHeightDiscriminants::Custom => {
                                     let custom_value =
-                                        theme::BufferLineHeight::from(*settings_value).value();
+                                        theme_settings::BufferLineHeight::from(*settings_value)
+                                            .value();
                                     settings::BufferLineHeight::Custom(custom_value)
                                 }
                             };

crates/settings_ui/src/pages/tool_permissions_setup.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
 use settings::{Settings as _, SettingsStore, ToolPermissionMode};
 use shell_command_parser::extract_commands;
 use std::sync::Arc;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{Banner, ContextMenu, Divider, PopoverMenu, Severity, Tooltip, prelude::*};
 use util::ResultExt as _;
 use util::shell::ShellKind;

crates/settings_ui/src/settings_ui.rs 🔗

@@ -33,7 +33,7 @@ use std::{
     sync::{Arc, LazyLock, RwLock},
     time::Duration,
 };
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Banner, ContextMenu, Divider, DropdownMenu, DropdownStyle, IconButtonShape, KeyBinding,
     KeybindingHint, PopoverMenu, Scrollbars, Switch, Tooltip, TreeViewItem, WithScrollbar,
@@ -639,7 +639,9 @@ pub fn open_settings_editor(
     // We have to defer this to get the workspace off the stack.
     let path = path.map(ToOwned::to_owned);
     cx.defer(move |cx| {
-        let current_rem_size: f32 = theme::ThemeSettings::get_global(cx).ui_font_size(cx).into();
+        let current_rem_size: f32 = theme_settings::ThemeSettings::get_global(cx)
+            .ui_font_size(cx)
+            .into();
 
         let default_bounds = DEFAULT_ADDITIONAL_WINDOW_SIZE;
         let default_rem_size = 16.0;
@@ -3650,7 +3652,7 @@ impl SettingsWindow {
 
 impl Render for SettingsWindow {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
 
         client_side_decorations(
             v_flex()
@@ -4410,7 +4412,7 @@ pub mod test {
 
     pub fn register_settings(cx: &mut App) {
         settings::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         editor::init(cx);
         menu::init();
     }
@@ -5075,7 +5077,7 @@ mod project_settings_update_tests {
         cx.update(|cx| {
             let store = settings::SettingsStore::test(cx);
             cx.set_global(store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
             menu::init();
             let queue = ProjectSettingsUpdateQueue::new(cx);

crates/sidebar/Cargo.toml 🔗

@@ -33,6 +33,7 @@ project.workspace = true
 recent_projects.workspace = true
 settings.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 vim_mode_setting.workspace = true

crates/sidebar/src/project_group_builder.rs 🔗

@@ -223,7 +223,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
     }
 

crates/sidebar/src/sidebar.rs 🔗

@@ -3069,7 +3069,7 @@ impl Focusable for Sidebar {
 impl Render for Sidebar {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let _titlebar_height = ui::utils::platform_title_bar_height(window);
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
         let sticky_header = self.render_sticky_header(window, cx);
 
         let color = cx.theme().colors();
@@ -3219,7 +3219,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
             cx.update_flags(false, vec!["agent-v2".into()]);
             ThreadStore::init_global(cx);

crates/storybook/Cargo.toml 🔗

@@ -29,6 +29,7 @@ picker.workspace = true
 reqwest_client.workspace = true
 rust-embed.workspace = true
 settings.workspace = true
+theme_settings.workspace = true
 simplelog.workspace = true
 story.workspace = true
 strum = { workspace = true, features = ["derive"] }

crates/storybook/src/storybook.rs 🔗

@@ -15,10 +15,10 @@ use gpui::{
 };
 use log::LevelFilter;
 use reqwest_client::ReqwestClient;
-use settings::{KeymapFile, Settings};
+use settings::{KeymapFile, Settings as _};
 use simplelog::SimpleLogger;
 use strum::IntoEnumIterator;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::prelude::*;
 
 use crate::app_menus::app_menus;
@@ -76,13 +76,13 @@ fn main() {
             cx.set_http_client(Arc::new(http_client));
 
             settings::init(cx);
-            theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
+            theme_settings::init(theme::LoadThemes::All(Box::new(Assets)), cx);
 
             let selector = story_selector;
 
             let mut theme_settings = ThemeSettings::get_global(cx).clone();
             theme_settings.theme =
-                theme::ThemeSelection::Static(settings::ThemeName(theme_name.into()));
+                theme_settings::ThemeSelection::Static(settings::ThemeName(theme_name.into()));
             ThemeSettings::override_global(theme_settings, cx);
 
             editor::init(cx);
@@ -98,7 +98,7 @@ fn main() {
                     ..Default::default()
                 },
                 move |window, cx| {
-                    theme::setup_ui_font(window, cx);
+                    theme_settings::setup_ui_font(window, cx);
 
                     cx.new(|cx| StoryWrapper::new(selector.story(window, cx)))
                 },

crates/tab_switcher/Cargo.toml 🔗

@@ -33,5 +33,6 @@ ctor.workspace = true
 gpui = { workspace = true, features = ["test-support"] }
 serde_json.workspace = true
 theme = { workspace = true, features = ["test-support"] }
+theme_settings.workspace = true
 workspace = { workspace = true, features = ["test-support"] }
 zlog.workspace = true

crates/tab_switcher/src/tab_switcher_tests.rs 🔗

@@ -258,7 +258,7 @@ async fn test_close_selected_item(cx: &mut gpui::TestAppContext) {
 fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
     cx.update(|cx| {
         let state = AppState::test(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         super::init(cx);
         editor::init(cx);
         state

crates/terminal/Cargo.toml 🔗

@@ -37,6 +37,7 @@ sysinfo.workspace = true
 smol.workspace = true
 task.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 thiserror.workspace = true
 url.workspace = true
 util.workspace = true

crates/terminal/src/terminal.rs 🔗

@@ -2579,7 +2579,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = settings::SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
     }
 

crates/terminal/src/terminal_settings.rs 🔗

@@ -14,7 +14,7 @@ use settings::{
     merge_from::MergeFrom,
 };
 use task::Shell;
-use theme::FontFamilyName;
+use theme_settings::FontFamilyName;
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 pub struct Toolbar {

crates/terminal_view/Cargo.toml 🔗

@@ -42,6 +42,7 @@ settings.workspace = true
 shellexpand.workspace = true
 terminal.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/terminal_view/src/terminal_element.rs 🔗

@@ -25,7 +25,8 @@ use terminal::{
     },
     terminal_settings::TerminalSettings,
 };
-use theme::{ActiveTheme, Theme, ThemeSettings};
+use theme::{ActiveTheme, Theme};
+use theme_settings::ThemeSettings;
 use ui::utils::ensure_minimum_contrast;
 use ui::{ParentElement, Tooltip};
 use util::ResultExt;
@@ -913,7 +914,9 @@ impl Element for TerminalElement {
                     }
                     TerminalMode::Standalone => terminal_settings
                         .font_size
-                        .map_or(buffer_font_size, |size| theme::adjusted_font_size(size, cx)),
+                        .map_or(buffer_font_size, |size| {
+                            theme_settings::adjusted_font_size(size, cx)
+                        }),
                 };
 
                 let theme = cx.theme().clone();

crates/terminal_view/src/terminal_panel.rs 🔗

@@ -2346,7 +2346,7 @@ mod tests {
         cx.update(|cx| {
             let store = SettingsStore::test(cx);
             cx.set_global(store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
             crate::init(cx);
         });

crates/terminal_view/src/terminal_path_like_target.rs 🔗

@@ -554,7 +554,7 @@ mod tests {
         let fs = app_cx.update(AppState::test).fs.as_fake().clone();
 
         app_cx.update(|cx| {
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             editor::init(cx);
         });
 

crates/terminal_view/src/terminal_view.rs 🔗

@@ -2208,7 +2208,7 @@ mod tests {
     ) {
         let params = cx.update(AppState::test);
         cx.update(|cx| {
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
 
         let project = Project::test(params.fs.clone(), [], cx).await;

crates/theme/Cargo.toml 🔗

@@ -10,7 +10,7 @@ workspace = true
 
 [features]
 default = []
-test-support = ["gpui/test-support", "settings/test-support"]
+test-support = ["gpui/test-support"]
 
 [lib]
 path = "src/theme.rs"
@@ -21,8 +21,6 @@ anyhow.workspace = true
 collections.workspace = true
 derive_more.workspace = true
 gpui.workspace = true
-gpui_util.workspace = true
-log.workspace = true
 palette = { workspace = true, default-features = false, features = ["std"] }
 parking_lot.workspace = true
 refineable.workspace = true
@@ -30,11 +28,9 @@ schemars = { workspace = true, features = ["indexmap2"] }
 serde.workspace = true
 serde_json.workspace = true
 serde_json_lenient.workspace = true
-settings.workspace = true
 strum.workspace = true
 thiserror.workspace = true
 uuid.workspace = true
 
 [dev-dependencies]
 gpui = { workspace = true, features = ["test-support"] }
-settings = { workspace = true, features = ["test-support"] }

crates/theme/src/fallback_themes.rs 🔗

@@ -25,7 +25,8 @@ pub fn zed_default_themes() -> ThemeFamily {
 // If a theme customizes a foreground version of a status color, but does not
 // customize the background color, then use a partly-transparent version of the
 // foreground color for the background color.
-pub(crate) fn apply_status_color_defaults(status: &mut StatusColorsRefinement) {
+/// Applies default status color backgrounds from their foreground counterparts.
+pub fn apply_status_color_defaults(status: &mut StatusColorsRefinement) {
     for (fg_color, bg_color) in [
         (&status.deleted, &mut status.deleted_background),
         (&status.created, &mut status.created_background),
@@ -42,7 +43,8 @@ pub(crate) fn apply_status_color_defaults(status: &mut StatusColorsRefinement) {
     }
 }
 
-pub(crate) fn apply_theme_color_defaults(
+/// Applies default theme color values derived from player colors.
+pub fn apply_theme_color_defaults(
     theme_colors: &mut ThemeColorsRefinement,
     player_colors: &PlayerColors,
 ) {

crates/theme/src/icon_theme.rs 🔗

@@ -416,7 +416,7 @@ fn icon_keys_by_association(
 }
 
 /// The name of the default icon theme.
-pub(crate) const DEFAULT_ICON_THEME_NAME: &str = "Zed (Default)";
+pub const DEFAULT_ICON_THEME_NAME: &str = "Zed (Default)";
 
 static DEFAULT_ICON_THEME: LazyLock<Arc<IconTheme>> = LazyLock::new(|| {
     Arc::new(IconTheme {

crates/theme/src/registry.rs 🔗

@@ -1,18 +1,16 @@
 use std::sync::Arc;
 use std::{fmt::Debug, path::Path};
 
-use anyhow::{Context as _, Result};
+use anyhow::Result;
 use collections::HashMap;
 use derive_more::{Deref, DerefMut};
 use gpui::{App, AssetSource, Global, SharedString};
-use gpui_util::ResultExt;
 use parking_lot::RwLock;
 use thiserror::Error;
 
 use crate::{
     Appearance, AppearanceContent, ChevronIcons, DEFAULT_ICON_THEME_NAME, DirectoryIcons,
-    IconDefinition, IconTheme, IconThemeFamilyContent, Theme, ThemeFamily, ThemeFamilyContent,
-    default_icon_theme, deserialize_user_theme, refine_theme_family,
+    IconDefinition, IconTheme, IconThemeFamilyContent, Theme, ThemeFamily, default_icon_theme,
 };
 
 /// The metadata for a theme.
@@ -81,6 +79,11 @@ impl ThemeRegistry {
         cx.set_global(GlobalThemeRegistry(Arc::new(ThemeRegistry::new(assets))));
     }
 
+    /// Returns the asset source used by this registry.
+    pub fn assets(&self) -> &dyn AssetSource {
+        self.assets.as_ref()
+    }
+
     /// Creates a new [`ThemeRegistry`] with the given [`AssetSource`].
     pub fn new(assets: Box<dyn AssetSource>) -> Self {
         let registry = Self {
@@ -116,28 +119,21 @@ impl ThemeRegistry {
         self.state.write().extensions_loaded = true;
     }
 
-    fn insert_theme_families(&self, families: impl IntoIterator<Item = ThemeFamily>) {
+    /// Inserts the given theme families into the registry.
+    pub fn insert_theme_families(&self, families: impl IntoIterator<Item = ThemeFamily>) {
         for family in families.into_iter() {
             self.insert_themes(family.themes);
         }
     }
 
-    fn insert_themes(&self, themes: impl IntoIterator<Item = Theme>) {
+    /// Inserts the given themes into the registry.
+    pub fn insert_themes(&self, themes: impl IntoIterator<Item = Theme>) {
         let mut state = self.state.write();
         for theme in themes.into_iter() {
             state.themes.insert(theme.name.clone(), Arc::new(theme));
         }
     }
 
-    #[allow(unused)]
-    fn insert_user_theme_families(&self, families: impl IntoIterator<Item = ThemeFamilyContent>) {
-        for family in families.into_iter() {
-            let refined_family = refine_theme_family(family);
-
-            self.insert_themes(refined_family.themes);
-        }
-    }
-
     /// Removes the themes with the given names from the registry.
     pub fn remove_user_themes(&self, themes_to_remove: &[SharedString]) {
         self.state
@@ -181,40 +177,6 @@ impl ThemeRegistry {
             .cloned()
     }
 
-    /// Loads the themes bundled with the Zed binary and adds them to the registry.
-    pub fn load_bundled_themes(&self) {
-        let theme_paths = self
-            .assets
-            .list("themes/")
-            .expect("failed to list theme assets")
-            .into_iter()
-            .filter(|path| path.ends_with(".json"));
-
-        for path in theme_paths {
-            let Some(theme) = self.assets.load(&path).log_err().flatten() else {
-                continue;
-            };
-
-            let Some(theme_family) = serde_json::from_slice(&theme)
-                .with_context(|| format!("failed to parse theme at path \"{path}\""))
-                .log_err()
-            else {
-                continue;
-            };
-
-            self.insert_user_theme_families([theme_family]);
-        }
-    }
-
-    /// Loads the user theme from the specified data and adds it to the registry.
-    pub fn load_user_theme(&self, bytes: &[u8]) -> Result<()> {
-        let theme = deserialize_user_theme(bytes)?;
-
-        self.insert_user_theme_families([theme]);
-
-        Ok(())
-    }
-
     /// Returns the default icon theme.
     pub fn default_icon_theme(&self) -> Result<Arc<IconTheme>, IconThemeNotFoundError> {
         self.get_icon_theme(DEFAULT_ICON_THEME_NAME)

crates/theme/src/schema.rs 🔗

@@ -1,30 +1,11 @@
 #![allow(missing_docs)]
 
-use gpui::{HighlightStyle, Hsla};
+use gpui::Hsla;
 use palette::FromColor;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::IntoGpui;
-pub use settings::{FontWeightContent, WindowBackgroundContent};
-
-use crate::{StatusColorsRefinement, ThemeColorsRefinement};
-
-fn ensure_non_opaque(color: Hsla) -> Hsla {
-    const MAXIMUM_OPACITY: f32 = 0.7;
-    if color.a <= MAXIMUM_OPACITY {
-        color
-    } else {
-        Hsla {
-            a: MAXIMUM_OPACITY,
-            ..color
-        }
-    }
-}
-
-fn ensure_opaque(color: Hsla) -> Hsla {
-    Hsla { a: 1.0, ..color }
-}
 
+/// The appearance of a theme in serialized content.
 #[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum AppearanceContent {
@@ -32,819 +13,8 @@ pub enum AppearanceContent {
     Dark,
 }
 
-/// The content of a serialized theme family.
-#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct ThemeFamilyContent {
-    pub name: String,
-    pub author: String,
-    pub themes: Vec<ThemeContent>,
-}
-
-/// The content of a serialized theme.
-#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct ThemeContent {
-    pub name: String,
-    pub appearance: AppearanceContent,
-    pub style: settings::ThemeStyleContent,
-}
-
-/// Returns the syntax style overrides in the [`ThemeContent`].
-pub fn syntax_overrides(this: &settings::ThemeStyleContent) -> Vec<(String, HighlightStyle)> {
-    this.syntax
-        .iter()
-        .map(|(key, style)| {
-            (
-                key.clone(),
-                HighlightStyle {
-                    color: style
-                        .color
-                        .as_ref()
-                        .and_then(|color| try_parse_color(color).ok()),
-                    background_color: style
-                        .background_color
-                        .as_ref()
-                        .and_then(|color| try_parse_color(color).ok()),
-                    font_style: style.font_style.map(|s| s.into_gpui()),
-                    font_weight: style.font_weight.map(|w| w.into_gpui()),
-                    ..Default::default()
-                },
-            )
-        })
-        .collect()
-}
-
-pub fn status_colors_refinement(colors: &settings::StatusColorsContent) -> StatusColorsRefinement {
-    StatusColorsRefinement {
-        conflict: colors
-            .conflict
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        conflict_background: colors
-            .conflict_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        conflict_border: colors
-            .conflict_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        created: colors
-            .created
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        created_background: colors
-            .created_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        created_border: colors
-            .created_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        deleted: colors
-            .deleted
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        deleted_background: colors
-            .deleted_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        deleted_border: colors
-            .deleted_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        error: colors
-            .error
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        error_background: colors
-            .error_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        error_border: colors
-            .error_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        hidden: colors
-            .hidden
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        hidden_background: colors
-            .hidden_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        hidden_border: colors
-            .hidden_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        hint: colors
-            .hint
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        hint_background: colors
-            .hint_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        hint_border: colors
-            .hint_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ignored: colors
-            .ignored
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ignored_background: colors
-            .ignored_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ignored_border: colors
-            .ignored_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        info: colors
-            .info
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        info_background: colors
-            .info_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        info_border: colors
-            .info_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        modified: colors
-            .modified
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        modified_background: colors
-            .modified_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        modified_border: colors
-            .modified_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        predictive: colors
-            .predictive
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        predictive_background: colors
-            .predictive_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        predictive_border: colors
-            .predictive_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        renamed: colors
-            .renamed
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        renamed_background: colors
-            .renamed_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        renamed_border: colors
-            .renamed_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        success: colors
-            .success
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        success_background: colors
-            .success_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        success_border: colors
-            .success_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        unreachable: colors
-            .unreachable
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        unreachable_background: colors
-            .unreachable_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        unreachable_border: colors
-            .unreachable_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        warning: colors
-            .warning
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        warning_background: colors
-            .warning_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        warning_border: colors
-            .warning_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-    }
-}
-
-pub fn theme_colors_refinement(
-    this: &settings::ThemeColorsContent,
-    status_colors: &StatusColorsRefinement,
-) -> ThemeColorsRefinement {
-    let border = this
-        .border
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok());
-    let editor_document_highlight_read_background = this
-        .editor_document_highlight_read_background
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok());
-    let scrollbar_thumb_background = this
-        .scrollbar_thumb_background
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok())
-        .or_else(|| {
-            this.deprecated_scrollbar_thumb_background
-                .as_ref()
-                .and_then(|color| try_parse_color(color).ok())
-        });
-    let scrollbar_thumb_hover_background = this
-        .scrollbar_thumb_hover_background
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok());
-    let scrollbar_thumb_active_background = this
-        .scrollbar_thumb_active_background
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok())
-        .or(scrollbar_thumb_background);
-    let scrollbar_thumb_border = this
-        .scrollbar_thumb_border
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok());
-    let element_hover = this
-        .element_hover
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok());
-    let panel_background = this
-        .panel_background
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok());
-    let search_match_background = this
-        .search_match_background
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok());
-    let search_active_match_background = this
-        .search_active_match_background
-        .as_ref()
-        .and_then(|color| try_parse_color(color).ok())
-        .or(search_match_background);
-    ThemeColorsRefinement {
-        border,
-        border_variant: this
-            .border_variant
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        border_focused: this
-            .border_focused
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        border_selected: this
-            .border_selected
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        border_transparent: this
-            .border_transparent
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        border_disabled: this
-            .border_disabled
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        elevated_surface_background: this
-            .elevated_surface_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        surface_background: this
-            .surface_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        background: this
-            .background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        element_background: this
-            .element_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        element_hover,
-        element_active: this
-            .element_active
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        element_selected: this
-            .element_selected
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        element_disabled: this
-            .element_disabled
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        element_selection_background: this
-            .element_selection_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        drop_target_background: this
-            .drop_target_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        drop_target_border: this
-            .drop_target_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ghost_element_background: this
-            .ghost_element_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ghost_element_hover: this
-            .ghost_element_hover
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ghost_element_active: this
-            .ghost_element_active
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ghost_element_selected: this
-            .ghost_element_selected
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        ghost_element_disabled: this
-            .ghost_element_disabled
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        text: this
-            .text
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        text_muted: this
-            .text_muted
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        text_placeholder: this
-            .text_placeholder
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        text_disabled: this
-            .text_disabled
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        text_accent: this
-            .text_accent
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        icon: this
-            .icon
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        icon_muted: this
-            .icon_muted
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        icon_disabled: this
-            .icon_disabled
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        icon_placeholder: this
-            .icon_placeholder
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        icon_accent: this
-            .icon_accent
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        debugger_accent: this
-            .debugger_accent
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        status_bar_background: this
-            .status_bar_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        title_bar_background: this
-            .title_bar_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        title_bar_inactive_background: this
-            .title_bar_inactive_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        toolbar_background: this
-            .toolbar_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        tab_bar_background: this
-            .tab_bar_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        tab_inactive_background: this
-            .tab_inactive_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        tab_active_background: this
-            .tab_active_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        search_match_background: search_match_background,
-        search_active_match_background: search_active_match_background,
-        panel_background,
-        panel_focused_border: this
-            .panel_focused_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        panel_indent_guide: this
-            .panel_indent_guide
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        panel_indent_guide_hover: this
-            .panel_indent_guide_hover
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        panel_indent_guide_active: this
-            .panel_indent_guide_active
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        panel_overlay_background: this
-            .panel_overlay_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(panel_background.map(ensure_opaque)),
-        panel_overlay_hover: this
-            .panel_overlay_hover
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(panel_background
-                .zip(element_hover)
-                .map(|(panel_bg, hover_bg)| panel_bg.blend(hover_bg))
-                .map(ensure_opaque)),
-        pane_focused_border: this
-            .pane_focused_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        pane_group_border: this
-            .pane_group_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(border),
-        scrollbar_thumb_background,
-        scrollbar_thumb_hover_background,
-        scrollbar_thumb_active_background,
-        scrollbar_thumb_border,
-        scrollbar_track_background: this
-            .scrollbar_track_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        scrollbar_track_border: this
-            .scrollbar_track_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        minimap_thumb_background: this
-            .minimap_thumb_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(scrollbar_thumb_background.map(ensure_non_opaque)),
-        minimap_thumb_hover_background: this
-            .minimap_thumb_hover_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(scrollbar_thumb_hover_background.map(ensure_non_opaque)),
-        minimap_thumb_active_background: this
-            .minimap_thumb_active_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(scrollbar_thumb_active_background.map(ensure_non_opaque)),
-        minimap_thumb_border: this
-            .minimap_thumb_border
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(scrollbar_thumb_border),
-        editor_foreground: this
-            .editor_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_background: this
-            .editor_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_gutter_background: this
-            .editor_gutter_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_subheader_background: this
-            .editor_subheader_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_active_line_background: this
-            .editor_active_line_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_highlighted_line_background: this
-            .editor_highlighted_line_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_debugger_active_line_background: this
-            .editor_debugger_active_line_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_line_number: this
-            .editor_line_number
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_hover_line_number: this
-            .editor_hover_line_number
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_active_line_number: this
-            .editor_active_line_number
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_invisible: this
-            .editor_invisible
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_wrap_guide: this
-            .editor_wrap_guide
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_active_wrap_guide: this
-            .editor_active_wrap_guide
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_indent_guide: this
-            .editor_indent_guide
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_indent_guide_active: this
-            .editor_indent_guide_active
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_document_highlight_read_background,
-        editor_document_highlight_write_background: this
-            .editor_document_highlight_write_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        editor_document_highlight_bracket_background: this
-            .editor_document_highlight_bracket_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            // Fall back to `editor.document_highlight.read_background`, for backwards compatibility.
-            .or(editor_document_highlight_read_background),
-        terminal_background: this
-            .terminal_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_background: this
-            .terminal_ansi_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_foreground: this
-            .terminal_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_bright_foreground: this
-            .terminal_bright_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_dim_foreground: this
-            .terminal_dim_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_black: this
-            .terminal_ansi_black
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_black: this
-            .terminal_ansi_bright_black
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_black: this
-            .terminal_ansi_dim_black
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_red: this
-            .terminal_ansi_red
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_red: this
-            .terminal_ansi_bright_red
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_red: this
-            .terminal_ansi_dim_red
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_green: this
-            .terminal_ansi_green
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_green: this
-            .terminal_ansi_bright_green
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_green: this
-            .terminal_ansi_dim_green
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_yellow: this
-            .terminal_ansi_yellow
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_yellow: this
-            .terminal_ansi_bright_yellow
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_yellow: this
-            .terminal_ansi_dim_yellow
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_blue: this
-            .terminal_ansi_blue
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_blue: this
-            .terminal_ansi_bright_blue
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_blue: this
-            .terminal_ansi_dim_blue
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_magenta: this
-            .terminal_ansi_magenta
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_magenta: this
-            .terminal_ansi_bright_magenta
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_magenta: this
-            .terminal_ansi_dim_magenta
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_cyan: this
-            .terminal_ansi_cyan
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_cyan: this
-            .terminal_ansi_bright_cyan
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_cyan: this
-            .terminal_ansi_dim_cyan
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_white: this
-            .terminal_ansi_white
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_bright_white: this
-            .terminal_ansi_bright_white
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        terminal_ansi_dim_white: this
-            .terminal_ansi_dim_white
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        link_text_hover: this
-            .link_text_hover
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        version_control_added: this
-            .version_control_added
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            // Fall back to `created`, for backwards compatibility.
-            .or(status_colors.created),
-        version_control_deleted: this
-            .version_control_deleted
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            // Fall back to `deleted`, for backwards compatibility.
-            .or(status_colors.deleted),
-        version_control_modified: this
-            .version_control_modified
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            // Fall back to `modified`, for backwards compatibility.
-            .or(status_colors.modified),
-        version_control_renamed: this
-            .version_control_renamed
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            // Fall back to `modified`, for backwards compatibility.
-            .or(status_colors.modified),
-        version_control_conflict: this
-            .version_control_conflict
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            // Fall back to `ignored`, for backwards compatibility.
-            .or(status_colors.ignored),
-        version_control_ignored: this
-            .version_control_ignored
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            // Fall back to `conflict`, for backwards compatibility.
-            .or(status_colors.ignored),
-        version_control_word_added: this
-            .version_control_word_added
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        version_control_word_deleted: this
-            .version_control_word_deleted
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        #[allow(deprecated)]
-        version_control_conflict_marker_ours: this
-            .version_control_conflict_marker_ours
-            .as_ref()
-            .or(this.version_control_conflict_ours_background.as_ref())
-            .and_then(|color| try_parse_color(color).ok()),
-        #[allow(deprecated)]
-        version_control_conflict_marker_theirs: this
-            .version_control_conflict_marker_theirs
-            .as_ref()
-            .or(this.version_control_conflict_theirs_background.as_ref())
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_normal_background: this
-            .vim_normal_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_insert_background: this
-            .vim_insert_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_replace_background: this
-            .vim_replace_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_visual_background: this
-            .vim_visual_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_visual_line_background: this
-            .vim_visual_line_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_visual_block_background: this
-            .vim_visual_block_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_yank_background: this
-            .vim_yank_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok())
-            .or(editor_document_highlight_read_background),
-        vim_helix_normal_background: this
-            .vim_helix_normal_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_helix_select_background: this
-            .vim_helix_select_background
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_normal_foreground: this
-            .vim_normal_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_insert_foreground: this
-            .vim_insert_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_replace_foreground: this
-            .vim_replace_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_visual_foreground: this
-            .vim_visual_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_visual_line_foreground: this
-            .vim_visual_line_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_visual_block_foreground: this
-            .vim_visual_block_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_helix_normal_foreground: this
-            .vim_helix_normal_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-        vim_helix_select_foreground: this
-            .vim_helix_select_foreground
-            .as_ref()
-            .and_then(|color| try_parse_color(color).ok()),
-    }
-}
-
-pub(crate) fn try_parse_color(color: &str) -> anyhow::Result<Hsla> {
+/// Parses a color string into an [`Hsla`] value.
+pub fn try_parse_color(color: &str) -> anyhow::Result<Hsla> {
     let rgba = gpui::Rgba::try_from(color)?;
     let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
     let hsla = palette::Hsla::from_color(rgba);

crates/theme/src/styles/accents.rs 🔗

@@ -5,7 +5,6 @@ use serde::Deserialize;
 
 use crate::{
     amber, blue, cyan, gold, grass, indigo, iris, jade, lime, orange, pink, purple, tomato,
-    try_parse_color,
 };
 
 /// A collection of colors that are used to color indent aware lines in the editor.
@@ -66,25 +65,4 @@ impl AccentColors {
     pub fn color_for_index(&self, index: u32) -> Hsla {
         self.0[index as usize % self.0.len()]
     }
-
-    /// Merges the given accent colors into this [`AccentColors`] instance.
-    pub fn merge(&mut self, accent_colors: &[settings::AccentContent]) {
-        if accent_colors.is_empty() {
-            return;
-        }
-
-        let colors = accent_colors
-            .iter()
-            .filter_map(|accent_color| {
-                accent_color
-                    .0
-                    .as_ref()
-                    .and_then(|color| try_parse_color(color).ok())
-            })
-            .collect::<Vec<_>>();
-
-        if !colors.is_empty() {
-            self.0 = Arc::from(colors);
-        }
-    }
 }

crates/theme/src/styles/players.rs 🔗

@@ -3,7 +3,7 @@
 use gpui::Hsla;
 use serde::Deserialize;
 
-use crate::{amber, blue, jade, lime, orange, pink, purple, red, try_parse_color};
+use crate::{amber, blue, jade, lime, orange, pink, purple, red};
 
 #[derive(Debug, Clone, Copy, Deserialize, Default, PartialEq)]
 pub struct PlayerColor {
@@ -148,40 +148,4 @@ impl PlayerColors {
         let len = self.0.len() - 1;
         self.0[(participant_index as usize % len) + 1]
     }
-
-    /// Merges the given player colors into this [`PlayerColors`] instance.
-    pub fn merge(&mut self, user_player_colors: &[settings::PlayerColorContent]) {
-        if user_player_colors.is_empty() {
-            return;
-        }
-
-        for (idx, player) in user_player_colors.iter().enumerate() {
-            let cursor = player
-                .cursor
-                .as_ref()
-                .and_then(|color| try_parse_color(color).ok());
-            let background = player
-                .background
-                .as_ref()
-                .and_then(|color| try_parse_color(color).ok());
-            let selection = player
-                .selection
-                .as_ref()
-                .and_then(|color| try_parse_color(color).ok());
-
-            if let Some(player_color) = self.0.get_mut(idx) {
-                *player_color = PlayerColor {
-                    cursor: cursor.unwrap_or(player_color.cursor),
-                    background: background.unwrap_or(player_color.background),
-                    selection: selection.unwrap_or(player_color.selection),
-                };
-            } else {
-                self.0.push(PlayerColor {
-                    cursor: cursor.unwrap_or_default(),
-                    background: background.unwrap_or_default(),
-                    selection: selection.unwrap_or_default(),
-                });
-            }
-        }
-    }
 }

crates/theme/src/theme.rs 🔗

@@ -16,40 +16,30 @@ mod icon_theme_schema;
 mod registry;
 mod scale;
 mod schema;
-mod settings;
 mod styles;
 
 use std::sync::Arc;
 
-use ::settings::DEFAULT_DARK_THEME;
-use ::settings::IntoGpui;
-use ::settings::Settings;
-use ::settings::SettingsStore;
-use anyhow::Result;
-use fallback_themes::apply_status_color_defaults;
+use derive_more::{Deref, DerefMut};
 use gpui::BorrowAppContext;
 use gpui::Global;
 use gpui::{
-    App, AssetSource, HighlightStyle, Hsla, Pixels, Refineable, SharedString, WindowAppearance,
-    WindowBackgroundAppearance, px,
+    App, AssetSource, Hsla, Pixels, SharedString, WindowAppearance, WindowBackgroundAppearance, px,
 };
 use serde::Deserialize;
-use uuid::Uuid;
 
 pub use crate::default_colors::*;
-use crate::fallback_themes::apply_theme_color_defaults;
+pub use crate::fallback_themes::{apply_status_color_defaults, apply_theme_color_defaults};
 pub use crate::font_family_cache::*;
 pub use crate::icon_theme::*;
 pub use crate::icon_theme_schema::*;
 pub use crate::registry::*;
 pub use crate::scale::*;
 pub use crate::schema::*;
-pub use crate::settings::*;
 pub use crate::styles::*;
-pub use ::settings::{
-    FontStyleContent, HighlightStyleContent, StatusColorsContent, ThemeColorsContent,
-    ThemeStyleContent,
-};
+
+/// The name of the default dark theme.
+pub const DEFAULT_DARK_THEME: &str = "One Dark";
 
 /// Defines window border radius for platforms that use client side decorations.
 pub const CLIENT_SIDE_DECORATION_ROUNDING: Pixels = px(10.0);
@@ -84,15 +74,6 @@ impl From<WindowAppearance> for Appearance {
     }
 }
 
-impl From<Appearance> for ThemeAppearanceMode {
-    fn from(value: Appearance) -> Self {
-        match value {
-            Appearance::Light => Self::Light,
-            Appearance::Dark => Self::Dark,
-        }
-    }
-}
-
 /// Which themes should be loaded. This is used primarily for testing.
 pub enum LoadThemes {
     /// Only load the base theme.
@@ -104,84 +85,31 @@ pub enum LoadThemes {
     All(Box<dyn AssetSource>),
 }
 
-/// Initialize the theme system.
+/// Initialize the theme system with default themes.
+///
+/// This sets up the [`ThemeRegistry`], [`FontFamilyCache`], [`SystemAppearance`],
+/// and [`GlobalTheme`] with the default dark theme. It does NOT load bundled
+/// themes from JSON or integrate with settings — use `theme_settings::init` for that.
 pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
     SystemAppearance::init(cx);
-    let (assets, load_user_themes) = match themes_to_load {
-        LoadThemes::JustBase => (Box::new(()) as Box<dyn AssetSource>, false),
-        LoadThemes::All(assets) => (assets, true),
+    let assets = match themes_to_load {
+        LoadThemes::JustBase => Box::new(()) as Box<dyn AssetSource>,
+        LoadThemes::All(assets) => assets,
     };
     ThemeRegistry::set_global(assets, cx);
-
-    if load_user_themes {
-        ThemeRegistry::global(cx).load_bundled_themes();
-    }
-
     FontFamilyCache::init_global(cx);
 
-    let theme = GlobalTheme::configured_theme(cx);
-    let icon_theme = GlobalTheme::configured_icon_theme(cx);
+    let themes = ThemeRegistry::default_global(cx);
+    let theme = themes.get(DEFAULT_DARK_THEME).unwrap_or_else(|_| {
+        themes
+            .list()
+            .into_iter()
+            .next()
+            .map(|m| themes.get(&m.name).unwrap())
+            .unwrap()
+    });
+    let icon_theme = themes.default_icon_theme().unwrap();
     cx.set_global(GlobalTheme { theme, icon_theme });
-
-    let settings = ThemeSettings::get_global(cx);
-
-    let mut prev_buffer_font_size_settings = settings.buffer_font_size_settings();
-    let mut prev_ui_font_size_settings = settings.ui_font_size_settings();
-    let mut prev_agent_ui_font_size_settings = settings.agent_ui_font_size_settings();
-    let mut prev_agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();
-    let mut prev_theme_name = settings.theme.name(SystemAppearance::global(cx).0);
-    let mut prev_icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);
-    let mut prev_theme_overrides = (
-        settings.experimental_theme_overrides.clone(),
-        settings.theme_overrides.clone(),
-    );
-
-    cx.observe_global::<SettingsStore>(move |cx| {
-        let settings = ThemeSettings::get_global(cx);
-
-        let buffer_font_size_settings = settings.buffer_font_size_settings();
-        let ui_font_size_settings = settings.ui_font_size_settings();
-        let agent_ui_font_size_settings = settings.agent_ui_font_size_settings();
-        let agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();
-        let theme_name = settings.theme.name(SystemAppearance::global(cx).0);
-        let icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);
-        let theme_overrides = (
-            settings.experimental_theme_overrides.clone(),
-            settings.theme_overrides.clone(),
-        );
-
-        if buffer_font_size_settings != prev_buffer_font_size_settings {
-            prev_buffer_font_size_settings = buffer_font_size_settings;
-            reset_buffer_font_size(cx);
-        }
-
-        if ui_font_size_settings != prev_ui_font_size_settings {
-            prev_ui_font_size_settings = ui_font_size_settings;
-            reset_ui_font_size(cx);
-        }
-
-        if agent_ui_font_size_settings != prev_agent_ui_font_size_settings {
-            prev_agent_ui_font_size_settings = agent_ui_font_size_settings;
-            reset_agent_ui_font_size(cx);
-        }
-
-        if agent_buffer_font_size_settings != prev_agent_buffer_font_size_settings {
-            prev_agent_buffer_font_size_settings = agent_buffer_font_size_settings;
-            reset_agent_buffer_font_size(cx);
-        }
-
-        if theme_name != prev_theme_name || theme_overrides != prev_theme_overrides {
-            prev_theme_name = theme_name;
-            prev_theme_overrides = theme_overrides;
-            GlobalTheme::reload_theme(cx);
-        }
-
-        if icon_theme_name != prev_icon_theme_name {
-            prev_icon_theme_name = icon_theme_name;
-            GlobalTheme::reload_icon_theme(cx);
-        }
-    })
-    .detach();
 }
 
 /// Implementing this trait allows accessing the active theme.
@@ -196,6 +124,39 @@ impl ActiveTheme for App {
     }
 }
 
+/// The appearance of the system.
+#[derive(Debug, Clone, Copy, Deref)]
+pub struct SystemAppearance(pub Appearance);
+
+impl Default for SystemAppearance {
+    fn default() -> Self {
+        Self(Appearance::Dark)
+    }
+}
+
+#[derive(Deref, DerefMut, Default)]
+struct GlobalSystemAppearance(SystemAppearance);
+
+impl Global for GlobalSystemAppearance {}
+
+impl SystemAppearance {
+    /// Initializes the [`SystemAppearance`] for the application.
+    pub fn init(cx: &mut App) {
+        *cx.default_global::<GlobalSystemAppearance>() =
+            GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
+    }
+
+    /// Returns the global [`SystemAppearance`].
+    pub fn global(cx: &App) -> Self {
+        cx.global::<GlobalSystemAppearance>().0
+    }
+
+    /// Returns a mutable reference to the global [`SystemAppearance`].
+    pub fn global_mut(cx: &mut App) -> &mut Self {
+        cx.global_mut::<GlobalSystemAppearance>()
+    }
+}
+
 /// A theme family is a grouping of themes under a single name.
 ///
 /// For example, the "One" theme family contains the "One Light" and "One Dark" themes.
@@ -217,113 +178,6 @@ pub struct ThemeFamily {
     pub scales: ColorScales,
 }
 
-impl ThemeFamily {
-    // This is on ThemeFamily because we will have variables here we will need
-    // in the future to resolve @references.
-    /// Refines ThemeContent into a theme, merging it's contents with the base theme.
-    pub fn refine_theme(&self, theme: &ThemeContent) -> Theme {
-        let appearance = match theme.appearance {
-            AppearanceContent::Light => Appearance::Light,
-            AppearanceContent::Dark => Appearance::Dark,
-        };
-
-        let mut refined_status_colors = match theme.appearance {
-            AppearanceContent::Light => StatusColors::light(),
-            AppearanceContent::Dark => StatusColors::dark(),
-        };
-        let mut status_colors_refinement = status_colors_refinement(&theme.style.status);
-        apply_status_color_defaults(&mut status_colors_refinement);
-        refined_status_colors.refine(&status_colors_refinement);
-
-        let mut refined_player_colors = match theme.appearance {
-            AppearanceContent::Light => PlayerColors::light(),
-            AppearanceContent::Dark => PlayerColors::dark(),
-        };
-        refined_player_colors.merge(&theme.style.players);
-
-        let mut refined_theme_colors = match theme.appearance {
-            AppearanceContent::Light => ThemeColors::light(),
-            AppearanceContent::Dark => ThemeColors::dark(),
-        };
-        let mut theme_colors_refinement =
-            theme_colors_refinement(&theme.style.colors, &status_colors_refinement);
-        apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors);
-        refined_theme_colors.refine(&theme_colors_refinement);
-
-        let mut refined_accent_colors = match theme.appearance {
-            AppearanceContent::Light => AccentColors::light(),
-            AppearanceContent::Dark => AccentColors::dark(),
-        };
-        refined_accent_colors.merge(&theme.style.accents);
-
-        let syntax_highlights = theme.style.syntax.iter().map(|(syntax_token, highlight)| {
-            (
-                syntax_token.clone(),
-                HighlightStyle {
-                    color: highlight
-                        .color
-                        .as_ref()
-                        .and_then(|color| try_parse_color(color).ok()),
-                    background_color: highlight
-                        .background_color
-                        .as_ref()
-                        .and_then(|color| try_parse_color(color).ok()),
-                    font_style: highlight.font_style.map(|s| s.into_gpui()),
-                    font_weight: highlight.font_weight.map(|w| w.into_gpui()),
-                    ..Default::default()
-                },
-            )
-        });
-        let syntax_theme = Arc::new(SyntaxTheme::new(syntax_highlights));
-
-        let window_background_appearance = theme
-            .style
-            .window_background_appearance
-            .map(|w| w.into_gpui())
-            .unwrap_or_default();
-
-        Theme {
-            id: uuid::Uuid::new_v4().to_string(),
-            name: theme.name.clone().into(),
-            appearance,
-            styles: ThemeStyles {
-                system: SystemColors::default(),
-                window_background_appearance,
-                accents: refined_accent_colors,
-                colors: refined_theme_colors,
-                status: refined_status_colors,
-                player: refined_player_colors,
-                syntax: syntax_theme,
-            },
-        }
-    }
-}
-
-/// Refines a [ThemeFamilyContent] and it's [ThemeContent]s into a [ThemeFamily].
-pub fn refine_theme_family(theme_family_content: ThemeFamilyContent) -> ThemeFamily {
-    let id = Uuid::new_v4().to_string();
-    let name = theme_family_content.name.clone();
-    let author = theme_family_content.author.clone();
-
-    let mut theme_family = ThemeFamily {
-        id,
-        name: name.into(),
-        author: author.into(),
-        themes: vec![],
-        scales: default_color_scales(),
-    };
-
-    let refined_themes = theme_family_content
-        .themes
-        .iter()
-        .map(|theme_content| theme_family.refine_theme(theme_content))
-        .collect();
-
-    theme_family.themes = refined_themes;
-
-    theme_family
-}
-
 /// A theme is the primary mechanism for defining the appearance of the UI.
 #[derive(Clone, Debug, PartialEq)]
 pub struct Theme {
@@ -403,35 +257,14 @@ impl Theme {
     }
 }
 
-/// Deserializes a user theme from the given bytes.
-pub fn deserialize_user_theme(bytes: &[u8]) -> Result<ThemeFamilyContent> {
-    let theme_family: ThemeFamilyContent = serde_json_lenient::from_slice(bytes)?;
-
-    for theme in &theme_family.themes {
-        if theme
-            .style
-            .colors
-            .deprecated_scrollbar_thumb_background
-            .is_some()
-        {
-            log::warn!(
-                r#"Theme "{theme_name}" is using a deprecated style property: scrollbar_thumb.background. Use `scrollbar.thumb.background` instead."#,
-                theme_name = theme.name
-            )
-        }
-    }
-
-    Ok(theme_family)
-}
-
-/// Deserializes a icon theme from the given bytes.
-pub fn deserialize_icon_theme(bytes: &[u8]) -> Result<IconThemeFamilyContent> {
+/// Deserializes an icon theme from the given bytes.
+pub fn deserialize_icon_theme(bytes: &[u8]) -> anyhow::Result<IconThemeFamilyContent> {
     let icon_theme_family: IconThemeFamilyContent = serde_json_lenient::from_slice(bytes)?;
 
     Ok(icon_theme_family)
 }
 
-/// The active theme
+/// The active theme.
 pub struct GlobalTheme {
     theme: Arc<Theme>,
     icon_theme: Arc<IconTheme>,
@@ -439,72 +272,27 @@ pub struct GlobalTheme {
 impl Global for GlobalTheme {}
 
 impl GlobalTheme {
-    fn configured_theme(cx: &mut App) -> Arc<Theme> {
-        let themes = ThemeRegistry::default_global(cx);
-        let theme_settings = ThemeSettings::get_global(cx);
-        let system_appearance = SystemAppearance::global(cx);
-
-        let theme_name = theme_settings.theme.name(*system_appearance);
-
-        let theme = match themes.get(&theme_name.0) {
-            Ok(theme) => theme,
-            Err(err) => {
-                if themes.extensions_loaded() {
-                    log::error!("{err}");
-                }
-                themes
-                    .get(default_theme(*system_appearance))
-                    // fallback for tests.
-                    .unwrap_or_else(|_| themes.get(DEFAULT_DARK_THEME).unwrap())
-            }
-        };
-        theme_settings.apply_theme_overrides(theme)
+    /// Creates a new [`GlobalTheme`] with the given theme and icon theme.
+    pub fn new(theme: Arc<Theme>, icon_theme: Arc<IconTheme>) -> Self {
+        Self { theme, icon_theme }
     }
 
-    /// Reloads the current theme.
-    ///
-    /// Reads the [`ThemeSettings`] to know which theme should be loaded,
-    /// taking into account the current [`SystemAppearance`].
-    pub fn reload_theme(cx: &mut App) {
-        let theme = Self::configured_theme(cx);
+    /// Updates the active theme.
+    pub fn update_theme(cx: &mut App, theme: Arc<Theme>) {
         cx.update_global::<Self, _>(|this, _| this.theme = theme);
-        cx.refresh_windows();
-    }
-
-    fn configured_icon_theme(cx: &mut App) -> Arc<IconTheme> {
-        let themes = ThemeRegistry::default_global(cx);
-        let theme_settings = ThemeSettings::get_global(cx);
-        let system_appearance = SystemAppearance::global(cx);
-
-        let icon_theme_name = theme_settings.icon_theme.name(*system_appearance);
-
-        match themes.get_icon_theme(&icon_theme_name.0) {
-            Ok(theme) => theme,
-            Err(err) => {
-                if themes.extensions_loaded() {
-                    log::error!("{err}");
-                }
-                themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()
-            }
-        }
     }
 
-    /// Reloads the current icon theme.
-    ///
-    /// Reads the [`ThemeSettings`] to know which icon theme should be loaded,
-    /// taking into account the current [`SystemAppearance`].
-    pub fn reload_icon_theme(cx: &mut App) {
-        let icon_theme = Self::configured_icon_theme(cx);
+    /// Updates the active icon theme.
+    pub fn update_icon_theme(cx: &mut App, icon_theme: Arc<IconTheme>) {
         cx.update_global::<Self, _>(|this, _| this.icon_theme = icon_theme);
-        cx.refresh_windows();
     }
 
-    /// the active theme
+    /// Returns the active theme.
     pub fn theme(cx: &App) -> &Arc<Theme> {
         &cx.global::<Self>().theme
     }
 
-    /// the active icon theme
+    /// Returns the active icon theme.
     pub fn icon_theme(cx: &App) -> &Arc<IconTheme> {
         &cx.global::<Self>().icon_theme
     }

crates/theme_extension/src/theme_extension.rs 🔗

@@ -5,7 +5,8 @@ use anyhow::Result;
 use extension::{ExtensionHostProxy, ExtensionThemeProxy};
 use fs::Fs;
 use gpui::{App, BackgroundExecutor, SharedString, Task};
-use theme::{GlobalTheme, ThemeRegistry, deserialize_icon_theme};
+use theme::{ThemeRegistry, deserialize_icon_theme};
+use theme_settings;
 
 pub fn init(
     extension_host_proxy: Arc<ExtensionHostProxy>,
@@ -30,7 +31,8 @@ impl ExtensionThemeProxy for ThemeRegistryProxy {
 
     fn list_theme_names(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<Vec<String>>> {
         self.executor.spawn(async move {
-            let themes = theme::deserialize_user_theme(&fs.load_bytes(&theme_path).await?)?;
+            let themes =
+                theme_settings::deserialize_user_theme(&fs.load_bytes(&theme_path).await?)?;
             Ok(themes.themes.into_iter().map(|theme| theme.name).collect())
         })
     }
@@ -42,12 +44,12 @@ impl ExtensionThemeProxy for ThemeRegistryProxy {
     fn load_user_theme(&self, theme_path: PathBuf, fs: Arc<dyn Fs>) -> Task<Result<()>> {
         let theme_registry = self.theme_registry.clone();
         self.executor.spawn(async move {
-            theme_registry.load_user_theme(&fs.load_bytes(&theme_path).await?)
+            theme_settings::load_user_theme(&theme_registry, &fs.load_bytes(&theme_path).await?)
         })
     }
 
     fn reload_current_theme(&self, cx: &mut App) {
-        GlobalTheme::reload_theme(cx)
+        theme_settings::reload_theme(cx)
     }
 
     fn list_icon_theme_names(
@@ -85,6 +87,6 @@ impl ExtensionThemeProxy for ThemeRegistryProxy {
     }
 
     fn reload_current_icon_theme(&self, cx: &mut App) {
-        GlobalTheme::reload_icon_theme(cx)
+        theme_settings::reload_icon_theme(cx)
     }
 }

crates/theme_importer/Cargo.toml 🔗

@@ -22,4 +22,5 @@ serde_json_lenient.workspace = true
 simplelog.workspace= true
 strum = { workspace = true, features = ["derive"] }
 theme.workspace = true
+theme_settings.workspace = true
 vscode_theme = "0.2.0"

crates/theme_importer/src/vscode/converter.rs 🔗

@@ -1,7 +1,7 @@
 use anyhow::Result;
 use collections::IndexMap;
 use strum::IntoEnumIterator;
-use theme::{
+use theme_settings::{
     FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent,
     ThemeColorsContent, ThemeContent, ThemeStyleContent, WindowBackgroundContent,
 };

crates/theme_selector/Cargo.toml 🔗

@@ -22,6 +22,7 @@ serde.workspace = true
 settings.workspace = true
 telemetry.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/theme_selector/src/icon_theme_selector.rs 🔗

@@ -7,10 +7,8 @@ use gpui::{
 use picker::{Picker, PickerDelegate};
 use settings::{Settings as _, SettingsStore, update_settings_file};
 use std::sync::Arc;
-use theme::{
-    Appearance, IconThemeName, IconThemeSelection, SystemAppearance, ThemeMeta, ThemeRegistry,
-    ThemeSettings,
-};
+use theme::{Appearance, SystemAppearance, ThemeMeta, ThemeRegistry};
+use theme_settings::{IconThemeName, IconThemeSelection, ThemeSettings};
 use ui::{ListItem, ListItemSpacing, prelude::*, v_flex};
 use util::ResultExt;
 use workspace::{ModalView, ui::HighlightedLabel};
@@ -176,7 +174,7 @@ impl PickerDelegate for IconThemeSelectorDelegate {
         let appearance = Appearance::from(window.appearance());
 
         update_settings_file(self.fs.clone(), cx, move |settings, _| {
-            theme::set_icon_theme(settings, theme_name, appearance);
+            theme_settings::set_icon_theme(settings, theme_name, appearance);
         });
 
         self.selector

crates/theme_selector/src/theme_selector.rs 🔗

@@ -9,9 +9,9 @@ use gpui::{
 use picker::{Picker, PickerDelegate};
 use settings::{Settings, SettingsStore, update_settings_file};
 use std::sync::Arc;
-use theme::{
-    Appearance, SystemAppearance, Theme, ThemeAppearanceMode, ThemeMeta, ThemeName, ThemeRegistry,
-    ThemeSelection, ThemeSettings,
+use theme::{Appearance, SystemAppearance, Theme, ThemeMeta, ThemeRegistry};
+use theme_settings::{
+    ThemeAppearanceMode, ThemeName, ThemeSelection, ThemeSettings, appearance_to_mode,
 };
 use ui::{ListItem, ListItemSpacing, prelude::*, v_flex};
 use util::ResultExt;
@@ -233,7 +233,7 @@ impl ThemeSelectorDelegate {
 /// Overrides the global (in-memory) theme settings.
 ///
 /// Note that this does **not** update the user's `settings.json` file (see the
-/// [`ThemeSelectorDelegate::confirm`] method and [`theme::set_theme`] function).
+/// [`ThemeSelectorDelegate::confirm`] method and [`theme_settings::set_theme`] function).
 fn override_global_theme(
     store: &mut SettingsStore,
     new_theme: &Theme,
@@ -303,7 +303,7 @@ fn update_mode_if_new_appearance_is_different_from_system(
     if original_mode == &ThemeAppearanceMode::System && system_appearance == new_appearance {
         ThemeAppearanceMode::System
     } else {
-        ThemeAppearanceMode::from(new_appearance)
+        appearance_to_mode(new_appearance)
     }
 }
 
@@ -360,7 +360,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
         telemetry::event!("Settings Changed", setting = "theme", value = theme_name);
 
         update_settings_file(self.fs.clone(), cx, move |settings, _| {
-            theme::set_theme(settings, theme_name, theme_appearance, system_appearance);
+            theme_settings::set_theme(settings, theme_name, theme_appearance, system_appearance);
         });
 
         self.selector

crates/theme_settings/Cargo.toml 🔗

@@ -0,0 +1,37 @@
+[package]
+name = "theme_settings"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[features]
+default = []
+test-support = ["gpui/test-support", "settings/test-support", "theme/test-support"]
+
+[lib]
+path = "src/theme_settings.rs"
+doctest = false
+
+[dependencies]
+anyhow.workspace = true
+collections.workspace = true
+gpui.workspace = true
+gpui_util.workspace = true
+log.workspace = true
+palette = { workspace = true, default-features = false, features = ["std"] }
+refineable.workspace = true
+schemars.workspace = true
+serde.workspace = true
+serde_json.workspace = true
+serde_json_lenient.workspace = true
+settings.workspace = true
+theme.workspace = true
+uuid.workspace = true
+
+[dev-dependencies]
+gpui = { workspace = true, features = ["test-support"] }
+settings = { workspace = true, features = ["test-support"] }

crates/theme_settings/src/schema.rs 🔗

@@ -0,0 +1,850 @@
+#![allow(missing_docs)]
+
+use gpui::{HighlightStyle, Hsla};
+use palette::FromColor;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::IntoGpui;
+pub use settings::{
+    FontStyleContent, HighlightStyleContent, StatusColorsContent, ThemeColorsContent,
+    ThemeStyleContent,
+};
+pub use settings::{FontWeightContent, WindowBackgroundContent};
+
+use theme::{StatusColorsRefinement, ThemeColorsRefinement};
+
+/// The content of a serialized theme family.
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct ThemeFamilyContent {
+    pub name: String,
+    pub author: String,
+    pub themes: Vec<ThemeContent>,
+}
+
+/// The content of a serialized theme.
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct ThemeContent {
+    pub name: String,
+    pub appearance: theme::AppearanceContent,
+    pub style: settings::ThemeStyleContent,
+}
+
+/// Returns the syntax style overrides in the [`ThemeContent`].
+pub fn syntax_overrides(this: &settings::ThemeStyleContent) -> Vec<(String, HighlightStyle)> {
+    this.syntax
+        .iter()
+        .map(|(key, style)| {
+            (
+                key.clone(),
+                HighlightStyle {
+                    color: style
+                        .color
+                        .as_ref()
+                        .and_then(|color| theme::try_parse_color(color).ok()),
+                    background_color: style
+                        .background_color
+                        .as_ref()
+                        .and_then(|color| theme::try_parse_color(color).ok()),
+                    font_style: style.font_style.map(|s| s.into_gpui()),
+                    font_weight: style.font_weight.map(|w| w.into_gpui()),
+                    ..Default::default()
+                },
+            )
+        })
+        .collect()
+}
+
+pub fn status_colors_refinement(colors: &settings::StatusColorsContent) -> StatusColorsRefinement {
+    StatusColorsRefinement {
+        conflict: colors
+            .conflict
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        conflict_background: colors
+            .conflict_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        conflict_border: colors
+            .conflict_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        created: colors
+            .created
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        created_background: colors
+            .created_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        created_border: colors
+            .created_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        deleted: colors
+            .deleted
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        deleted_background: colors
+            .deleted_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        deleted_border: colors
+            .deleted_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        error: colors
+            .error
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        error_background: colors
+            .error_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        error_border: colors
+            .error_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        hidden: colors
+            .hidden
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        hidden_background: colors
+            .hidden_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        hidden_border: colors
+            .hidden_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        hint: colors
+            .hint
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        hint_background: colors
+            .hint_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        hint_border: colors
+            .hint_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ignored: colors
+            .ignored
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ignored_background: colors
+            .ignored_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ignored_border: colors
+            .ignored_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        info: colors
+            .info
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        info_background: colors
+            .info_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        info_border: colors
+            .info_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        modified: colors
+            .modified
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        modified_background: colors
+            .modified_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        modified_border: colors
+            .modified_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        predictive: colors
+            .predictive
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        predictive_background: colors
+            .predictive_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        predictive_border: colors
+            .predictive_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        renamed: colors
+            .renamed
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        renamed_background: colors
+            .renamed_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        renamed_border: colors
+            .renamed_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        success: colors
+            .success
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        success_background: colors
+            .success_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        success_border: colors
+            .success_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        unreachable: colors
+            .unreachable
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        unreachable_background: colors
+            .unreachable_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        unreachable_border: colors
+            .unreachable_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        warning: colors
+            .warning
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        warning_background: colors
+            .warning_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        warning_border: colors
+            .warning_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+    }
+}
+
+pub fn theme_colors_refinement(
+    this: &settings::ThemeColorsContent,
+    status_colors: &StatusColorsRefinement,
+) -> ThemeColorsRefinement {
+    let border = this
+        .border
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok());
+    let editor_document_highlight_read_background = this
+        .editor_document_highlight_read_background
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok());
+    let scrollbar_thumb_background = this
+        .scrollbar_thumb_background
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok())
+        .or_else(|| {
+            this.deprecated_scrollbar_thumb_background
+                .as_ref()
+                .and_then(|color| try_parse_color(color).ok())
+        });
+    let scrollbar_thumb_hover_background = this
+        .scrollbar_thumb_hover_background
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok());
+    let scrollbar_thumb_active_background = this
+        .scrollbar_thumb_active_background
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok())
+        .or(scrollbar_thumb_background);
+    let scrollbar_thumb_border = this
+        .scrollbar_thumb_border
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok());
+    let element_hover = this
+        .element_hover
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok());
+    let panel_background = this
+        .panel_background
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok());
+    let search_match_background = this
+        .search_match_background
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok());
+    let search_active_match_background = this
+        .search_active_match_background
+        .as_ref()
+        .and_then(|color| try_parse_color(color).ok())
+        .or(search_match_background);
+    ThemeColorsRefinement {
+        border,
+        border_variant: this
+            .border_variant
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        border_focused: this
+            .border_focused
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        border_selected: this
+            .border_selected
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        border_transparent: this
+            .border_transparent
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        border_disabled: this
+            .border_disabled
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        elevated_surface_background: this
+            .elevated_surface_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        surface_background: this
+            .surface_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        background: this
+            .background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        element_background: this
+            .element_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        element_hover,
+        element_active: this
+            .element_active
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        element_selected: this
+            .element_selected
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        element_disabled: this
+            .element_disabled
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        element_selection_background: this
+            .element_selection_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        drop_target_background: this
+            .drop_target_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        drop_target_border: this
+            .drop_target_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ghost_element_background: this
+            .ghost_element_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ghost_element_hover: this
+            .ghost_element_hover
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ghost_element_active: this
+            .ghost_element_active
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ghost_element_selected: this
+            .ghost_element_selected
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        ghost_element_disabled: this
+            .ghost_element_disabled
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        text: this
+            .text
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        text_muted: this
+            .text_muted
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        text_placeholder: this
+            .text_placeholder
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        text_disabled: this
+            .text_disabled
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        text_accent: this
+            .text_accent
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        icon: this
+            .icon
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        icon_muted: this
+            .icon_muted
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        icon_disabled: this
+            .icon_disabled
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        icon_placeholder: this
+            .icon_placeholder
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        icon_accent: this
+            .icon_accent
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        debugger_accent: this
+            .debugger_accent
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        status_bar_background: this
+            .status_bar_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        title_bar_background: this
+            .title_bar_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        title_bar_inactive_background: this
+            .title_bar_inactive_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        toolbar_background: this
+            .toolbar_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        tab_bar_background: this
+            .tab_bar_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        tab_inactive_background: this
+            .tab_inactive_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        tab_active_background: this
+            .tab_active_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        search_match_background,
+        search_active_match_background,
+        panel_background,
+        panel_focused_border: this
+            .panel_focused_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        panel_indent_guide: this
+            .panel_indent_guide
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        panel_indent_guide_hover: this
+            .panel_indent_guide_hover
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        panel_indent_guide_active: this
+            .panel_indent_guide_active
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        panel_overlay_background: this
+            .panel_overlay_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(panel_background.map(ensure_opaque)),
+        panel_overlay_hover: this
+            .panel_overlay_hover
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(panel_background
+                .zip(element_hover)
+                .map(|(panel_bg, hover_bg)| panel_bg.blend(hover_bg))
+                .map(ensure_opaque)),
+        pane_focused_border: this
+            .pane_focused_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        pane_group_border: this
+            .pane_group_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(border),
+        scrollbar_thumb_background,
+        scrollbar_thumb_hover_background,
+        scrollbar_thumb_active_background,
+        scrollbar_thumb_border,
+        scrollbar_track_background: this
+            .scrollbar_track_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        scrollbar_track_border: this
+            .scrollbar_track_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        minimap_thumb_background: this
+            .minimap_thumb_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(scrollbar_thumb_background.map(ensure_non_opaque)),
+        minimap_thumb_hover_background: this
+            .minimap_thumb_hover_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(scrollbar_thumb_hover_background.map(ensure_non_opaque)),
+        minimap_thumb_active_background: this
+            .minimap_thumb_active_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(scrollbar_thumb_active_background.map(ensure_non_opaque)),
+        minimap_thumb_border: this
+            .minimap_thumb_border
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(scrollbar_thumb_border),
+        editor_foreground: this
+            .editor_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_background: this
+            .editor_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_gutter_background: this
+            .editor_gutter_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_subheader_background: this
+            .editor_subheader_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_active_line_background: this
+            .editor_active_line_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_highlighted_line_background: this
+            .editor_highlighted_line_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_debugger_active_line_background: this
+            .editor_debugger_active_line_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_line_number: this
+            .editor_line_number
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_hover_line_number: this
+            .editor_hover_line_number
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_active_line_number: this
+            .editor_active_line_number
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_invisible: this
+            .editor_invisible
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_wrap_guide: this
+            .editor_wrap_guide
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_active_wrap_guide: this
+            .editor_active_wrap_guide
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_indent_guide: this
+            .editor_indent_guide
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_indent_guide_active: this
+            .editor_indent_guide_active
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_document_highlight_read_background,
+        editor_document_highlight_write_background: this
+            .editor_document_highlight_write_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        editor_document_highlight_bracket_background: this
+            .editor_document_highlight_bracket_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(editor_document_highlight_read_background),
+        terminal_background: this
+            .terminal_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_background: this
+            .terminal_ansi_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_foreground: this
+            .terminal_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_bright_foreground: this
+            .terminal_bright_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_dim_foreground: this
+            .terminal_dim_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_black: this
+            .terminal_ansi_black
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_black: this
+            .terminal_ansi_bright_black
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_black: this
+            .terminal_ansi_dim_black
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_red: this
+            .terminal_ansi_red
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_red: this
+            .terminal_ansi_bright_red
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_red: this
+            .terminal_ansi_dim_red
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_green: this
+            .terminal_ansi_green
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_green: this
+            .terminal_ansi_bright_green
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_green: this
+            .terminal_ansi_dim_green
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_yellow: this
+            .terminal_ansi_yellow
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_yellow: this
+            .terminal_ansi_bright_yellow
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_yellow: this
+            .terminal_ansi_dim_yellow
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_blue: this
+            .terminal_ansi_blue
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_blue: this
+            .terminal_ansi_bright_blue
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_blue: this
+            .terminal_ansi_dim_blue
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_magenta: this
+            .terminal_ansi_magenta
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_magenta: this
+            .terminal_ansi_bright_magenta
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_magenta: this
+            .terminal_ansi_dim_magenta
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_cyan: this
+            .terminal_ansi_cyan
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_cyan: this
+            .terminal_ansi_bright_cyan
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_cyan: this
+            .terminal_ansi_dim_cyan
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_white: this
+            .terminal_ansi_white
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_bright_white: this
+            .terminal_ansi_bright_white
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        terminal_ansi_dim_white: this
+            .terminal_ansi_dim_white
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        link_text_hover: this
+            .link_text_hover
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        version_control_added: this
+            .version_control_added
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(status_colors.created),
+        version_control_deleted: this
+            .version_control_deleted
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(status_colors.deleted),
+        version_control_modified: this
+            .version_control_modified
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(status_colors.modified),
+        version_control_renamed: this
+            .version_control_renamed
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(status_colors.modified),
+        version_control_conflict: this
+            .version_control_conflict
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(status_colors.ignored),
+        version_control_ignored: this
+            .version_control_ignored
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(status_colors.ignored),
+        version_control_word_added: this
+            .version_control_word_added
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        version_control_word_deleted: this
+            .version_control_word_deleted
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        #[allow(deprecated)]
+        version_control_conflict_marker_ours: this
+            .version_control_conflict_marker_ours
+            .as_ref()
+            .or(this.version_control_conflict_ours_background.as_ref())
+            .and_then(|color| try_parse_color(color).ok()),
+        #[allow(deprecated)]
+        version_control_conflict_marker_theirs: this
+            .version_control_conflict_marker_theirs
+            .as_ref()
+            .or(this.version_control_conflict_theirs_background.as_ref())
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_normal_background: this
+            .vim_normal_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_insert_background: this
+            .vim_insert_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_replace_background: this
+            .vim_replace_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_visual_background: this
+            .vim_visual_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_visual_line_background: this
+            .vim_visual_line_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_visual_block_background: this
+            .vim_visual_block_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_yank_background: this
+            .vim_yank_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok())
+            .or(editor_document_highlight_read_background),
+        vim_helix_normal_background: this
+            .vim_helix_normal_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_helix_select_background: this
+            .vim_helix_select_background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_normal_foreground: this
+            .vim_normal_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_insert_foreground: this
+            .vim_insert_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_replace_foreground: this
+            .vim_replace_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_visual_foreground: this
+            .vim_visual_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_visual_line_foreground: this
+            .vim_visual_line_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_visual_block_foreground: this
+            .vim_visual_block_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_helix_normal_foreground: this
+            .vim_helix_normal_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+        vim_helix_select_foreground: this
+            .vim_helix_select_foreground
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok()),
+    }
+}
+
+fn ensure_non_opaque(color: Hsla) -> Hsla {
+    const MAXIMUM_OPACITY: f32 = 0.7;
+    if color.a <= MAXIMUM_OPACITY {
+        color
+    } else {
+        Hsla {
+            a: MAXIMUM_OPACITY,
+            ..color
+        }
+    }
+}
+
+fn ensure_opaque(color: Hsla) -> Hsla {
+    Hsla { a: 1.0, ..color }
+}
+
+fn try_parse_color(color: &str) -> anyhow::Result<Hsla> {
+    let rgba = gpui::Rgba::try_from(color)?;
+    let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
+    let hsla = palette::Hsla::from_color(rgba);
+
+    let hsla = gpui::hsla(
+        hsla.hue.into_positive_degrees() / 360.,
+        hsla.saturation,
+        hsla.lightness,
+        hsla.alpha,
+    );
+
+    Ok(hsla)
+}

crates/theme/src/settings.rs → crates/theme_settings/src/settings.rs 🔗

@@ -1,9 +1,8 @@
-use crate::{
-    Appearance, DEFAULT_ICON_THEME_NAME, SyntaxTheme, Theme, status_colors_refinement,
-    syntax_overrides, theme_colors_refinement,
-};
+#![allow(missing_docs)]
+
+use crate::schema::{status_colors_refinement, syntax_overrides, theme_colors_refinement};
+use crate::{merge_accent_colors, merge_player_colors};
 use collections::HashMap;
-use derive_more::{Deref, DerefMut};
 use gpui::{
     App, Context, Font, FontFallbacks, FontStyle, Global, Pixels, Subscription, Window, px,
 };
@@ -13,6 +12,7 @@ use serde::{Deserialize, Serialize};
 pub use settings::{FontFamilyName, IconThemeName, ThemeAppearanceMode, ThemeName};
 use settings::{IntoGpui, RegisterSetting, Settings, SettingsContent};
 use std::sync::Arc;
+use theme::{Appearance, DEFAULT_ICON_THEME_NAME, SyntaxTheme, Theme};
 
 const MIN_FONT_SIZE: Pixels = px(6.0);
 const MAX_FONT_SIZE: Pixels = px(100.0);
@@ -92,6 +92,13 @@ impl From<settings::UiDensity> for UiDensity {
     }
 }
 
+pub fn appearance_to_mode(appearance: Appearance) -> ThemeAppearanceMode {
+    match appearance {
+        Appearance::Light => ThemeAppearanceMode::Light,
+        Appearance::Dark => ThemeAppearanceMode::Dark,
+    }
+}
+
 /// Customizable settings for the UI and theme system.
 #[derive(Clone, PartialEq, RegisterSetting)]
 pub struct ThemeSettings {
@@ -145,39 +152,6 @@ pub fn default_theme(appearance: Appearance) -> &'static str {
     }
 }
 
-/// The appearance of the system.
-#[derive(Debug, Clone, Copy, Deref)]
-pub struct SystemAppearance(pub Appearance);
-
-impl Default for SystemAppearance {
-    fn default() -> Self {
-        Self(Appearance::Dark)
-    }
-}
-
-#[derive(Deref, DerefMut, Default)]
-struct GlobalSystemAppearance(SystemAppearance);
-
-impl Global for GlobalSystemAppearance {}
-
-impl SystemAppearance {
-    /// Initializes the [`SystemAppearance`] for the application.
-    pub fn init(cx: &mut App) {
-        *cx.default_global::<GlobalSystemAppearance>() =
-            GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
-    }
-
-    /// Returns the global [`SystemAppearance`].
-    pub fn global(cx: &App) -> Self {
-        cx.global::<GlobalSystemAppearance>().0
-    }
-
-    /// Returns a mutable reference to the global [`SystemAppearance`].
-    pub fn global_mut(cx: &mut App) -> &mut Self {
-        cx.global_mut::<GlobalSystemAppearance>()
-    }
-}
-
 #[derive(Default)]
 struct BufferFontSize(Pixels);
 
@@ -327,21 +301,16 @@ pub fn set_theme(
             *theme = theme_name;
         }
         settings::ThemeSelection::Dynamic { mode, light, dark } => {
-            // Update the appropriate theme slot based on appearance.
             match theme_appearance {
                 Appearance::Light => *light = theme_name,
                 Appearance::Dark => *dark = theme_name,
             }
 
-            // Don't update the theme mode if it is set to system and the new theme has the same
-            // appearance.
             let should_update_mode =
                 !(mode == &ThemeAppearanceMode::System && theme_appearance == system_appearance);
 
             if should_update_mode {
-                // Update the mode to the specified appearance (otherwise we might set the theme and
-                // nothing gets updated because the system specified the other mode appearance).
-                *mode = ThemeAppearanceMode::from(theme_appearance);
+                *mode = appearance_to_mode(theme_appearance);
             }
         }
     }
@@ -379,9 +348,6 @@ pub fn set_mode(content: &mut SettingsContent, mode: ThemeAppearanceMode) {
     if let Some(selection) = theme.theme.as_mut() {
         match selection {
             settings::ThemeSelection::Static(_) => {
-                // If the theme was previously set to a single static theme,
-                // reset to the default dynamic light/dark pair and let users
-                // customize light/dark themes explicitly afterward.
                 *selection = settings::ThemeSelection::Dynamic {
                     mode: ThemeAppearanceMode::System,
                     light: ThemeName(settings::DEFAULT_LIGHT_THEME.into()),
@@ -404,9 +370,6 @@ pub fn set_mode(content: &mut SettingsContent, mode: ThemeAppearanceMode) {
     if let Some(selection) = theme.icon_theme.as_mut() {
         match selection {
             settings::IconThemeSelection::Static(icon_theme) => {
-                // If the icon theme was previously set to a single static
-                // theme, we don't know whether it was a light or dark
-                // theme, so we just use it for both.
                 *selection = settings::IconThemeSelection::Dynamic {
                     mode,
                     light: icon_theme.clone(),
@@ -424,7 +387,6 @@ pub fn set_mode(content: &mut SettingsContent, mode: ThemeAppearanceMode) {
         )));
     }
 }
-// }
 
 /// The buffer's line height.
 #[derive(Clone, Copy, Debug, PartialEq, Default)]
@@ -530,7 +492,6 @@ impl ThemeSettings {
         self.agent_buffer_font_size
     }
 
-    // TODO: Rename: `line_height` -> `buffer_line_height`
     /// Returns the buffer's line height.
     pub fn line_height(&self) -> f32 {
         f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
@@ -538,7 +499,6 @@ impl ThemeSettings {
 
     /// Applies the theme overrides, if there are any, to the current theme.
     pub fn apply_theme_overrides(&self, mut arc_theme: Arc<Theme>) -> Arc<Theme> {
-        // Apply the old overrides setting first, so that the new setting can override those.
         if let Some(experimental_theme_overrides) = &self.experimental_theme_overrides {
             let mut theme = (*arc_theme).clone();
             ThemeSettings::modify_theme(&mut theme, experimental_theme_overrides);
@@ -566,11 +526,11 @@ impl ThemeSettings {
             &status_color_refinement,
         ));
         base_theme.styles.status.refine(&status_color_refinement);
-        base_theme.styles.player.merge(&theme_overrides.players);
-        base_theme.styles.accents.merge(&theme_overrides.accents);
+        merge_player_colors(&mut base_theme.styles.player, &theme_overrides.players);
+        merge_accent_colors(&mut base_theme.styles.accents, &theme_overrides.accents);
         base_theme.styles.syntax = SyntaxTheme::merge(
             base_theme.styles.syntax.clone(),
-            syntax_overrides(&theme_overrides),
+            syntax_overrides(theme_overrides),
         );
     }
 }
@@ -614,7 +574,6 @@ pub fn reset_buffer_font_size(cx: &mut App) {
     }
 }
 
-// TODO: Make private, change usages to use `get_ui_font_size` instead.
 #[allow(missing_docs)]
 pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
     let (ui_font, ui_font_size) = {

crates/theme_settings/src/theme_settings.rs 🔗

@@ -0,0 +1,386 @@
+#![deny(missing_docs)]
+
+//! # Theme Settings
+//!
+//! This crate provides theme settings integration for Zed,
+//! bridging the theme system with the settings infrastructure.
+
+mod schema;
+mod settings;
+
+use std::sync::Arc;
+
+use ::settings::{IntoGpui, Settings, SettingsStore};
+use anyhow::{Context as _, Result};
+use gpui::{App, HighlightStyle, Refineable};
+use gpui_util::ResultExt;
+use theme::{
+    AccentColors, Appearance, AppearanceContent, DEFAULT_DARK_THEME, DEFAULT_ICON_THEME_NAME,
+    GlobalTheme, LoadThemes, PlayerColor, PlayerColors, StatusColors, SyntaxTheme,
+    SystemAppearance, SystemColors, Theme, ThemeColors, ThemeFamily, ThemeRegistry, ThemeStyles,
+    default_color_scales, try_parse_color,
+};
+
+pub use crate::schema::{
+    FontStyleContent, FontWeightContent, HighlightStyleContent, StatusColorsContent,
+    ThemeColorsContent, ThemeContent, ThemeFamilyContent, ThemeStyleContent,
+    WindowBackgroundContent, status_colors_refinement, syntax_overrides, theme_colors_refinement,
+};
+pub use crate::settings::{
+    AgentFontSize, BufferLineHeight, FontFamilyName, IconThemeName, IconThemeSelection,
+    ThemeAppearanceMode, ThemeName, ThemeSelection, ThemeSettings, UiDensity,
+    adjust_agent_buffer_font_size, adjust_agent_ui_font_size, adjust_buffer_font_size,
+    adjust_ui_font_size, adjusted_font_size, appearance_to_mode, clamp_font_size, default_theme,
+    observe_buffer_font_size_adjustment, reset_agent_buffer_font_size, reset_agent_ui_font_size,
+    reset_buffer_font_size, reset_ui_font_size, set_icon_theme, set_mode, set_theme, setup_ui_font,
+};
+
+/// Initialize the theme system with settings integration.
+///
+/// This is the full initialization for the application. It calls [`theme::init`]
+/// and then wires up settings observation for theme/font changes.
+pub fn init(themes_to_load: LoadThemes, cx: &mut App) {
+    let load_user_themes = matches!(&themes_to_load, LoadThemes::All(_));
+
+    theme::init(themes_to_load, cx);
+
+    if load_user_themes {
+        let registry = ThemeRegistry::global(cx);
+        load_bundled_themes(&registry);
+    }
+
+    let theme = configured_theme(cx);
+    let icon_theme = configured_icon_theme(cx);
+    GlobalTheme::update_theme(cx, theme);
+    GlobalTheme::update_icon_theme(cx, icon_theme);
+
+    let settings = ThemeSettings::get_global(cx);
+
+    let mut prev_buffer_font_size_settings = settings.buffer_font_size_settings();
+    let mut prev_ui_font_size_settings = settings.ui_font_size_settings();
+    let mut prev_agent_ui_font_size_settings = settings.agent_ui_font_size_settings();
+    let mut prev_agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();
+    let mut prev_theme_name = settings.theme.name(SystemAppearance::global(cx).0);
+    let mut prev_icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);
+    let mut prev_theme_overrides = (
+        settings.experimental_theme_overrides.clone(),
+        settings.theme_overrides.clone(),
+    );
+
+    cx.observe_global::<SettingsStore>(move |cx| {
+        let settings = ThemeSettings::get_global(cx);
+
+        let buffer_font_size_settings = settings.buffer_font_size_settings();
+        let ui_font_size_settings = settings.ui_font_size_settings();
+        let agent_ui_font_size_settings = settings.agent_ui_font_size_settings();
+        let agent_buffer_font_size_settings = settings.agent_buffer_font_size_settings();
+        let theme_name = settings.theme.name(SystemAppearance::global(cx).0);
+        let icon_theme_name = settings.icon_theme.name(SystemAppearance::global(cx).0);
+        let theme_overrides = (
+            settings.experimental_theme_overrides.clone(),
+            settings.theme_overrides.clone(),
+        );
+
+        if buffer_font_size_settings != prev_buffer_font_size_settings {
+            prev_buffer_font_size_settings = buffer_font_size_settings;
+            reset_buffer_font_size(cx);
+        }
+
+        if ui_font_size_settings != prev_ui_font_size_settings {
+            prev_ui_font_size_settings = ui_font_size_settings;
+            reset_ui_font_size(cx);
+        }
+
+        if agent_ui_font_size_settings != prev_agent_ui_font_size_settings {
+            prev_agent_ui_font_size_settings = agent_ui_font_size_settings;
+            reset_agent_ui_font_size(cx);
+        }
+
+        if agent_buffer_font_size_settings != prev_agent_buffer_font_size_settings {
+            prev_agent_buffer_font_size_settings = agent_buffer_font_size_settings;
+            reset_agent_buffer_font_size(cx);
+        }
+
+        if theme_name != prev_theme_name || theme_overrides != prev_theme_overrides {
+            prev_theme_name = theme_name;
+            prev_theme_overrides = theme_overrides;
+            reload_theme(cx);
+        }
+
+        if icon_theme_name != prev_icon_theme_name {
+            prev_icon_theme_name = icon_theme_name;
+            reload_icon_theme(cx);
+        }
+    })
+    .detach();
+}
+
+fn configured_theme(cx: &mut App) -> Arc<Theme> {
+    let themes = ThemeRegistry::default_global(cx);
+    let theme_settings = ThemeSettings::get_global(cx);
+    let system_appearance = SystemAppearance::global(cx);
+
+    let theme_name = theme_settings.theme.name(*system_appearance);
+
+    let theme = match themes.get(&theme_name.0) {
+        Ok(theme) => theme,
+        Err(err) => {
+            if themes.extensions_loaded() {
+                log::error!("{err}");
+            }
+            themes
+                .get(default_theme(*system_appearance))
+                .unwrap_or_else(|_| themes.get(DEFAULT_DARK_THEME).unwrap())
+        }
+    };
+    theme_settings.apply_theme_overrides(theme)
+}
+
+fn configured_icon_theme(cx: &mut App) -> Arc<theme::IconTheme> {
+    let themes = ThemeRegistry::default_global(cx);
+    let theme_settings = ThemeSettings::get_global(cx);
+    let system_appearance = SystemAppearance::global(cx);
+
+    let icon_theme_name = theme_settings.icon_theme.name(*system_appearance);
+
+    match themes.get_icon_theme(&icon_theme_name.0) {
+        Ok(theme) => theme,
+        Err(err) => {
+            if themes.extensions_loaded() {
+                log::error!("{err}");
+            }
+            themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()
+        }
+    }
+}
+
+/// Reloads the current theme from settings.
+pub fn reload_theme(cx: &mut App) {
+    let theme = configured_theme(cx);
+    GlobalTheme::update_theme(cx, theme);
+    cx.refresh_windows();
+}
+
+/// Reloads the current icon theme from settings.
+pub fn reload_icon_theme(cx: &mut App) {
+    let icon_theme = configured_icon_theme(cx);
+    GlobalTheme::update_icon_theme(cx, icon_theme);
+    cx.refresh_windows();
+}
+
+/// Loads the themes bundled with the Zed binary into the registry.
+pub fn load_bundled_themes(registry: &ThemeRegistry) {
+    let theme_paths = registry
+        .assets()
+        .list("themes/")
+        .expect("failed to list theme assets")
+        .into_iter()
+        .filter(|path| path.ends_with(".json"));
+
+    for path in theme_paths {
+        let Some(theme) = registry.assets().load(&path).log_err().flatten() else {
+            continue;
+        };
+
+        let Some(theme_family) = serde_json::from_slice(&theme)
+            .with_context(|| format!("failed to parse theme at path \"{path}\""))
+            .log_err()
+        else {
+            continue;
+        };
+
+        let refined = refine_theme_family(theme_family);
+        registry.insert_theme_families([refined]);
+    }
+}
+
+/// Loads a user theme from the given bytes into the registry.
+pub fn load_user_theme(registry: &ThemeRegistry, bytes: &[u8]) -> Result<()> {
+    let theme = deserialize_user_theme(bytes)?;
+    let refined = refine_theme_family(theme);
+    registry.insert_theme_families([refined]);
+    Ok(())
+}
+
+/// Deserializes a user theme from the given bytes.
+pub fn deserialize_user_theme(bytes: &[u8]) -> Result<ThemeFamilyContent> {
+    let theme_family: ThemeFamilyContent = serde_json_lenient::from_slice(bytes)?;
+
+    for theme in &theme_family.themes {
+        if theme
+            .style
+            .colors
+            .deprecated_scrollbar_thumb_background
+            .is_some()
+        {
+            log::warn!(
+                r#"Theme "{theme_name}" is using a deprecated style property: scrollbar_thumb.background. Use `scrollbar.thumb.background` instead."#,
+                theme_name = theme.name
+            )
+        }
+    }
+
+    Ok(theme_family)
+}
+
+/// Refines a [`ThemeFamilyContent`] and its [`ThemeContent`]s into a [`ThemeFamily`].
+pub fn refine_theme_family(theme_family_content: ThemeFamilyContent) -> ThemeFamily {
+    let id = uuid::Uuid::new_v4().to_string();
+    let name = theme_family_content.name.clone();
+    let author = theme_family_content.author.clone();
+
+    let themes: Vec<Theme> = theme_family_content
+        .themes
+        .iter()
+        .map(|theme_content| refine_theme(theme_content))
+        .collect();
+
+    ThemeFamily {
+        id,
+        name: name.into(),
+        author: author.into(),
+        themes,
+        scales: default_color_scales(),
+    }
+}
+
+/// Refines a [`ThemeContent`] into a [`Theme`].
+pub fn refine_theme(theme: &ThemeContent) -> Theme {
+    let appearance = match theme.appearance {
+        AppearanceContent::Light => Appearance::Light,
+        AppearanceContent::Dark => Appearance::Dark,
+    };
+
+    let mut refined_status_colors = match theme.appearance {
+        AppearanceContent::Light => StatusColors::light(),
+        AppearanceContent::Dark => StatusColors::dark(),
+    };
+    let mut status_colors_refinement = status_colors_refinement(&theme.style.status);
+    theme::apply_status_color_defaults(&mut status_colors_refinement);
+    refined_status_colors.refine(&status_colors_refinement);
+
+    let mut refined_player_colors = match theme.appearance {
+        AppearanceContent::Light => PlayerColors::light(),
+        AppearanceContent::Dark => PlayerColors::dark(),
+    };
+    merge_player_colors(&mut refined_player_colors, &theme.style.players);
+
+    let mut refined_theme_colors = match theme.appearance {
+        AppearanceContent::Light => ThemeColors::light(),
+        AppearanceContent::Dark => ThemeColors::dark(),
+    };
+    let mut theme_colors_refinement =
+        theme_colors_refinement(&theme.style.colors, &status_colors_refinement);
+    theme::apply_theme_color_defaults(&mut theme_colors_refinement, &refined_player_colors);
+    refined_theme_colors.refine(&theme_colors_refinement);
+
+    let mut refined_accent_colors = match theme.appearance {
+        AppearanceContent::Light => AccentColors::light(),
+        AppearanceContent::Dark => AccentColors::dark(),
+    };
+    merge_accent_colors(&mut refined_accent_colors, &theme.style.accents);
+
+    let syntax_highlights = theme.style.syntax.iter().map(|(syntax_token, highlight)| {
+        (
+            syntax_token.clone(),
+            HighlightStyle {
+                color: highlight
+                    .color
+                    .as_ref()
+                    .and_then(|color| try_parse_color(color).ok()),
+                background_color: highlight
+                    .background_color
+                    .as_ref()
+                    .and_then(|color| try_parse_color(color).ok()),
+                font_style: highlight.font_style.map(|s| s.into_gpui()),
+                font_weight: highlight.font_weight.map(|w| w.into_gpui()),
+                ..Default::default()
+            },
+        )
+    });
+    let syntax_theme = Arc::new(SyntaxTheme::new(syntax_highlights));
+
+    let window_background_appearance = theme
+        .style
+        .window_background_appearance
+        .map(|w| w.into_gpui())
+        .unwrap_or_default();
+
+    Theme {
+        id: uuid::Uuid::new_v4().to_string(),
+        name: theme.name.clone().into(),
+        appearance,
+        styles: ThemeStyles {
+            system: SystemColors::default(),
+            window_background_appearance,
+            accents: refined_accent_colors,
+            colors: refined_theme_colors,
+            status: refined_status_colors,
+            player: refined_player_colors,
+            syntax: syntax_theme,
+        },
+    }
+}
+
+/// Merges player color overrides into the given [`PlayerColors`].
+pub fn merge_player_colors(
+    player_colors: &mut PlayerColors,
+    user_player_colors: &[::settings::PlayerColorContent],
+) {
+    if user_player_colors.is_empty() {
+        return;
+    }
+
+    for (idx, player) in user_player_colors.iter().enumerate() {
+        let cursor = player
+            .cursor
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok());
+        let background = player
+            .background
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok());
+        let selection = player
+            .selection
+            .as_ref()
+            .and_then(|color| try_parse_color(color).ok());
+
+        if let Some(player_color) = player_colors.0.get_mut(idx) {
+            *player_color = PlayerColor {
+                cursor: cursor.unwrap_or(player_color.cursor),
+                background: background.unwrap_or(player_color.background),
+                selection: selection.unwrap_or(player_color.selection),
+            };
+        } else {
+            player_colors.0.push(PlayerColor {
+                cursor: cursor.unwrap_or_default(),
+                background: background.unwrap_or_default(),
+                selection: selection.unwrap_or_default(),
+            });
+        }
+    }
+}
+
+/// Merges accent color overrides into the given [`AccentColors`].
+pub fn merge_accent_colors(
+    accent_colors: &mut AccentColors,
+    user_accent_colors: &[::settings::AccentContent],
+) {
+    if user_accent_colors.is_empty() {
+        return;
+    }
+
+    let colors = user_accent_colors
+        .iter()
+        .filter_map(|accent_color| {
+            accent_color
+                .0
+                .as_ref()
+                .and_then(|color| try_parse_color(color).ok())
+        })
+        .collect::<Vec<_>>();
+
+    if !colors.is_empty() {
+        accent_colors.0 = Arc::from(colors);
+    }
+}

crates/ui/Cargo.toml 🔗

@@ -28,6 +28,7 @@ smallvec.workspace = true
 story = { workspace = true, optional = true }
 strum.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui_macros.workspace = true
 gpui_util.workspace = true
 

crates/ui/src/components/context_menu.rs 🔗

@@ -15,7 +15,7 @@ use std::{
     rc::Rc,
     time::{Duration, Instant},
 };
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq)]
 enum SubmenuOpenTrigger {

crates/ui/src/components/label/label_like.rs 🔗

@@ -2,7 +2,7 @@ use crate::prelude::*;
 use gpui::{FontWeight, Rems, StyleRefinement, UnderlineStyle};
 use settings::Settings;
 use smallvec::SmallVec;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 
 /// Sets the size of a label
 #[derive(Debug, PartialEq, Clone, Copy, Default)]
@@ -191,7 +191,9 @@ impl LabelCommon for LabelLike {
     }
 
     fn buffer_font(mut self, cx: &App) -> Self {
-        let font = theme::ThemeSettings::get_global(cx).buffer_font.clone();
+        let font = theme_settings::ThemeSettings::get_global(cx)
+            .buffer_font
+            .clone();
         self.weight = Some(font.weight);
         self.base = self.base.font(font);
         self
@@ -200,7 +202,11 @@ impl LabelCommon for LabelLike {
     fn inline_code(mut self, cx: &App) -> Self {
         self.base = self
             .base
-            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
+            .font(
+                theme_settings::ThemeSettings::get_global(cx)
+                    .buffer_font
+                    .clone(),
+            )
             .bg(cx.theme().colors().element_background)
             .rounded_sm()
             .px_0p5();

crates/ui/src/components/list/list_header.rs 🔗

@@ -4,7 +4,7 @@ use crate::{Disclosure, prelude::*};
 use component::{Component, ComponentScope, example_group_with_title, single_example};
 use gpui::{AnyElement, ClickEvent};
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 
 #[derive(IntoElement, RegisterComponent)]
 pub struct ListHeader {
@@ -91,7 +91,7 @@ impl RenderOnce for ListHeader {
             .child(
                 div()
                     .map(|this| match ui_density {
-                        theme::UiDensity::Comfortable => this.h_5(),
+                        theme_settings::UiDensity::Comfortable => this.h_5(),
                         _ => this.h_7(),
                     })
                     .when(self.inset, |this| this.px_2())

crates/ui/src/components/tooltip.rs 🔗

@@ -3,7 +3,7 @@ use std::rc::Rc;
 
 use gpui::{Action, AnyElement, AnyView, AppContext, FocusHandle, IntoElement, Render};
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 
 use crate::prelude::*;
 use crate::{Color, KeyBinding, Label, LabelSize, StyledExt, h_flex, v_flex};

crates/ui/src/styles/spacing.rs 🔗

@@ -1,6 +1,6 @@
 use gpui::{App, Pixels, Rems, px, rems};
 use settings::Settings;
-use theme::{ThemeSettings, UiDensity};
+use theme_settings::{ThemeSettings, UiDensity};
 use ui_macros::derive_dynamic_spacing;
 
 // Derives [DynamicSpacing]. See [ui_macros::derive_dynamic_spacing].

crates/ui/src/styles/typography.rs 🔗

@@ -4,7 +4,8 @@ use gpui::{
     div, rems,
 };
 use settings::Settings;
-use theme::{ActiveTheme, ThemeSettings};
+use theme::ActiveTheme;
+use theme_settings::ThemeSettings;
 
 use crate::{Color, rems_from_px};
 

crates/ui_macros/src/dynamic_spacing.rs 🔗

@@ -66,9 +66,9 @@ pub fn derive_spacing(input: TokenStream) -> TokenStream {
                     let n = n.base10_parse::<f32>().unwrap();
                     quote! {
                         DynamicSpacing::#variant => match ThemeSettings::get_global(cx).ui_density {
-                            ::theme::UiDensity::Compact => (#n - 4.0).max(0.0) / BASE_REM_SIZE_IN_PX,
-                            ::theme::UiDensity::Default => #n / BASE_REM_SIZE_IN_PX,
-                            ::theme::UiDensity::Comfortable => (#n + 4.0) / BASE_REM_SIZE_IN_PX,
+                            ::theme_settings::UiDensity::Compact => (#n - 4.0).max(0.0) / BASE_REM_SIZE_IN_PX,
+                            ::theme_settings::UiDensity::Default => #n / BASE_REM_SIZE_IN_PX,
+                            ::theme_settings::UiDensity::Comfortable => (#n + 4.0) / BASE_REM_SIZE_IN_PX,
                         }
                     }
                 }
@@ -78,9 +78,9 @@ pub fn derive_spacing(input: TokenStream) -> TokenStream {
                     let c = c.base10_parse::<f32>().unwrap();
                     quote! {
                         DynamicSpacing::#variant => match ThemeSettings::get_global(cx).ui_density {
-                            ::theme::UiDensity::Compact => #a / BASE_REM_SIZE_IN_PX,
-                            ::theme::UiDensity::Default => #b / BASE_REM_SIZE_IN_PX,
-                            ::theme::UiDensity::Comfortable => #c / BASE_REM_SIZE_IN_PX,
+                            ::theme_settings::UiDensity::Compact => #a / BASE_REM_SIZE_IN_PX,
+                            ::theme_settings::UiDensity::Default => #b / BASE_REM_SIZE_IN_PX,
+                            ::theme_settings::UiDensity::Comfortable => #c / BASE_REM_SIZE_IN_PX,
                         }
                     }
                 }

crates/ui_prompt/Cargo.toml 🔗

@@ -19,6 +19,6 @@ gpui.workspace = true
 markdown.workspace = true
 menu.workspace = true
 settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 workspace.workspace = true

crates/ui_prompt/src/ui_prompt.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{
 };
 use markdown::{Markdown, MarkdownElement, MarkdownStyle};
 use settings::{Settings, SettingsStore};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{FluentBuilder, TintColor, prelude::*};
 use workspace::WorkspaceSettings;
 

crates/vim/Cargo.toml 🔗

@@ -44,6 +44,7 @@ settings.workspace = true
 task.workspace = true
 text.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 menu.workspace = true
 tokio = { version = "1.15", features = ["full"], optional = true }
 ui.workspace = true

crates/vim/src/state.rs 🔗

@@ -29,7 +29,7 @@ use std::collections::HashSet;
 use std::path::Path;
 use std::{fmt::Display, ops::Range, sync::Arc};
 use text::{Bias, ToPoint};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     ActiveTheme, Context, Div, FluentBuilder, KeyBinding, ParentElement, SharedString, Styled,
     StyledTypography, Window, h_flex, rems,

crates/vim/src/test/vim_test_context.rs 🔗

@@ -27,7 +27,7 @@ impl VimTestContext {
             git_ui::init(cx);
             crate::init(cx);
             search::init(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             settings_ui::init(cx);
             markdown_preview::init(cx);
             zed_actions::init();

crates/vim/src/vim.rs 🔗

@@ -51,7 +51,7 @@ pub use settings::{
 use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
 use std::{mem, ops::Range, sync::Arc};
 use surrounds::SurroundsType;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{IntoElement, SharedString, px};
 use vim_mode_setting::HelixModeSetting;
 use vim_mode_setting::VimModeSetting;

crates/which_key/Cargo.toml 🔗

@@ -17,7 +17,7 @@ command_palette.workspace = true
 gpui.workspace = true
 serde.workspace = true
 settings.workspace = true
-theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 workspace.workspace = true

crates/which_key/src/which_key_modal.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
 };
 use settings::Settings;
 use std::collections::HashMap;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Divider, DividerColor, DynamicSpacing, LabelSize, WithScrollbar, prelude::*,
     text_for_keystrokes,

crates/workspace/Cargo.toml 🔗

@@ -63,6 +63,7 @@ strum.workspace = true
 task.workspace = true
 telemetry.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 ui.workspace = true
 util.workspace = true
 uuid.workspace = true

crates/workspace/src/multi_workspace.rs 🔗

@@ -883,7 +883,7 @@ impl Render for MultiWorkspace {
             (sidebar, None)
         };
 
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
         let text_color = cx.theme().colors().text;
 
         let workspace = self.workspace().clone();
@@ -970,7 +970,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             DisableAiSettings::register(cx);
             cx.update_flags(false, vec!["agent-v2".into()]);
         });

crates/workspace/src/notifications.rs 🔗

@@ -9,7 +9,7 @@ use markdown::{Markdown, MarkdownElement, MarkdownStyle};
 use parking_lot::Mutex;
 use project::project_settings::ProjectSettings;
 use settings::Settings;
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 
 use std::ops::Deref;
 use std::sync::{Arc, LazyLock};

crates/workspace/src/pane.rs 🔗

@@ -42,7 +42,7 @@ use std::{
     },
     time::Duration,
 };
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     ContextMenu, ContextMenuEntry, ContextMenuItem, DecoratedIcon, IconButtonShape, IconDecoration,
     IconDecorationKind, Indicator, PopoverMenu, PopoverMenuHandle, Tab, TabBar, TabPosition,
@@ -8515,7 +8515,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(LoadThemes::JustBase, cx);
+            theme_settings::init(LoadThemes::JustBase, cx);
         });
     }
 

crates/workspace/src/tasks.rs 🔗

@@ -254,7 +254,7 @@ mod tests {
         cx.update(|cx| {
             let settings_store = settings::SettingsStore::test(cx);
             cx.set_global(settings_store);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             register_serializable_item::<TestItem>(cx);
         });
         let fs = FakeFs::new(cx.executor());

crates/workspace/src/workspace.rs 🔗

@@ -130,7 +130,8 @@ use std::{
     time::Duration,
 };
 use task::{DebugScenario, SharedTaskContext, SpawnInTerminal};
-use theme::{ActiveTheme, GlobalTheme, SystemAppearance, ThemeSettings};
+use theme::{ActiveTheme, SystemAppearance};
+use theme_settings::ThemeSettings;
 pub use toolbar::{
     PaneSearchBarCallbacks, Toolbar, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView,
 };
@@ -1142,7 +1143,7 @@ impl AppState {
         let user_store = cx.new(|cx| UserStore::new(client.clone(), cx));
         let workspace_store = cx.new(|cx| WorkspaceStore::new(client.clone(), cx));
 
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         client::init(&client, cx);
 
         Arc::new(Self {
@@ -1682,8 +1683,8 @@ impl Workspace {
 
                 *SystemAppearance::global_mut(cx) = SystemAppearance(window_appearance.into());
 
-                GlobalTheme::reload_theme(cx);
-                GlobalTheme::reload_icon_theme(cx);
+                theme_settings::reload_theme(cx);
+                theme_settings::reload_icon_theme(cx);
             }),
             cx.on_release({
                 let weak_handle = weak_handle.clone();
@@ -7473,17 +7474,23 @@ impl Workspace {
     fn toggle_theme_mode(&mut self, _: &ToggleMode, _window: &mut Window, cx: &mut Context<Self>) {
         let current_mode = ThemeSettings::get_global(cx).theme.mode();
         let next_mode = match current_mode {
-            Some(theme::ThemeAppearanceMode::Light) => theme::ThemeAppearanceMode::Dark,
-            Some(theme::ThemeAppearanceMode::Dark) => theme::ThemeAppearanceMode::Light,
-            Some(theme::ThemeAppearanceMode::System) | None => match cx.theme().appearance() {
-                theme::Appearance::Light => theme::ThemeAppearanceMode::Dark,
-                theme::Appearance::Dark => theme::ThemeAppearanceMode::Light,
-            },
+            Some(theme_settings::ThemeAppearanceMode::Light) => {
+                theme_settings::ThemeAppearanceMode::Dark
+            }
+            Some(theme_settings::ThemeAppearanceMode::Dark) => {
+                theme_settings::ThemeAppearanceMode::Light
+            }
+            Some(theme_settings::ThemeAppearanceMode::System) | None => {
+                match cx.theme().appearance() {
+                    theme::Appearance::Light => theme_settings::ThemeAppearanceMode::Dark,
+                    theme::Appearance::Dark => theme_settings::ThemeAppearanceMode::Light,
+                }
+            }
         };
 
         let fs = self.project().read(cx).fs().clone();
         settings::update_settings_file(fs, cx, move |settings, _cx| {
-            theme::set_mode(settings, next_mode);
+            theme_settings::set_mode(settings, next_mode);
         });
     }
 
@@ -7912,7 +7919,7 @@ impl Render for Workspace {
         } else {
             (None, None)
         };
-        let ui_font = theme::setup_ui_font(window, cx);
+        let ui_font = theme_settings::setup_ui_font(window, cx);
 
         let theme = cx.theme().clone();
         let colors = theme.colors();
@@ -14409,7 +14416,7 @@ mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             cx.set_global(db::AppDatabase::test_new());
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
         });
     }
 

crates/zed/Cargo.toml 🔗

@@ -197,6 +197,7 @@ telemetry.workspace = true
 telemetry_events.workspace = true
 terminal_view.workspace = true
 theme.workspace = true
+theme_settings.workspace = true
 theme_extension.workspace = true
 theme_selector.workspace = true
 time.workspace = true

crates/zed/src/main.rs 🔗

@@ -52,6 +52,7 @@ use std::{
     time::Instant,
 };
 use theme::{ActiveTheme, GlobalTheme, ThemeRegistry};
+use theme_settings::load_user_theme;
 use util::{ResultExt, TryFutureExt, maybe};
 use uuid::Uuid;
 use workspace::{
@@ -639,7 +640,7 @@ fn main() {
             cx,
         );
 
-        theme::init(theme::LoadThemes::All(Box::new(Assets)), cx);
+        theme_settings::init(theme::LoadThemes::All(Box::new(Assets)), cx);
         eager_load_active_theme_and_icon_theme(fs.clone(), cx);
         theme_extension::init(
             extension_host_proxy,
@@ -1796,10 +1797,10 @@ fn load_user_themes_in_background(fs: Arc<dyn fs::Fs>, cx: &mut App) {
                     continue;
                 };
 
-                theme_registry.load_user_theme(&bytes).log_err();
+                load_user_theme(&theme_registry, &bytes).log_err();
             }
 
-            cx.update(GlobalTheme::reload_theme);
+            cx.update(theme_settings::reload_theme);
             anyhow::Ok(())
         }
     })
@@ -1819,9 +1820,9 @@ fn watch_themes(fs: Arc<dyn fs::Fs>, cx: &mut App) {
                 if fs.metadata(&event.path).await.ok().flatten().is_some() {
                     let theme_registry = cx.update(|cx| ThemeRegistry::global(cx));
                     if let Some(bytes) = fs.load_bytes(&event.path).await.log_err()
-                        && theme_registry.load_user_theme(&bytes).log_err().is_some()
+                        && load_user_theme(&theme_registry, &bytes).log_err().is_some()
                     {
-                        cx.update(GlobalTheme::reload_theme);
+                        cx.update(theme_settings::reload_theme);
                     }
                 }
             }

crates/zed/src/visual_test_runner.rs 🔗

@@ -176,7 +176,7 @@ fn run_visual_tests(project_path: PathBuf, update_baseline: bool) -> Result<()>
     // Initialize all Zed subsystems
     cx.update(|cx| {
         gpui_tokio::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         client::init(&app_state.client, cx);
         audio::init(cx);
         workspace::init(app_state.clone(), cx);
@@ -965,7 +965,7 @@ fn init_app_state(cx: &mut App) -> Arc<AppState> {
     let user_store = cx.new(|cx| client::UserStore::new(client.clone(), cx));
     let workspace_store = cx.new(|cx| workspace::WorkspaceStore::new(client.clone(), cx));
 
-    theme::init(theme::LoadThemes::JustBase, cx);
+    theme_settings::init(theme::LoadThemes::JustBase, cx);
     client::init(&client, cx);
 
     let app_state = Arc::new(AppState {

crates/zed/src/zed.rs 🔗

@@ -77,10 +77,8 @@ use std::{
     sync::atomic::{self, AtomicBool},
 };
 use terminal_view::terminal_panel::{self, TerminalPanel};
-use theme::{
-    ActiveTheme, GlobalTheme, SystemAppearance, ThemeRegistry, ThemeSettings,
-    deserialize_icon_theme,
-};
+use theme::{ActiveTheme, SystemAppearance, ThemeRegistry, deserialize_icon_theme};
+use theme_settings::{ThemeSettings, load_user_theme};
 use ui::{PopoverMenuHandle, prelude::*};
 use util::markdown::MarkdownString;
 use util::rel_path::RelPath;
@@ -903,10 +901,10 @@ fn register_actions(
                         let _ = settings
                             .theme
                             .ui_font_size
-                            .insert(f32::from(theme::clamp_font_size(ui_font_size)).into());
+                            .insert(f32::from(theme_settings::clamp_font_size(ui_font_size)).into());
                     });
                 } else {
-                    theme::adjust_ui_font_size(cx, |size| size + px(1.0));
+                    theme_settings::adjust_ui_font_size(cx, |size| size + px(1.0));
                 }
             }
         })
@@ -919,10 +917,10 @@ fn register_actions(
                         let _ = settings
                             .theme
                             .ui_font_size
-                            .insert(f32::from(theme::clamp_font_size(ui_font_size)).into());
+                            .insert(f32::from(theme_settings::clamp_font_size(ui_font_size)).into());
                     });
                 } else {
-                    theme::adjust_ui_font_size(cx, |size| size - px(1.0));
+                    theme_settings::adjust_ui_font_size(cx, |size| size - px(1.0));
                 }
             }
         })
@@ -934,7 +932,7 @@ fn register_actions(
                         settings.theme.ui_font_size = None;
                     });
                 } else {
-                    theme::reset_ui_font_size(cx);
+                    theme_settings::reset_ui_font_size(cx);
                 }
             }
         })
@@ -948,10 +946,10 @@ fn register_actions(
                         let _ = settings
                             .theme
                             .buffer_font_size
-                            .insert(f32::from(theme::clamp_font_size(buffer_font_size)).into());
+                            .insert(f32::from(theme_settings::clamp_font_size(buffer_font_size)).into());
                     });
                 } else {
-                    theme::adjust_buffer_font_size(cx, |size| size + px(1.0));
+                    theme_settings::adjust_buffer_font_size(cx, |size| size + px(1.0));
                 }
             }
         })
@@ -965,10 +963,10 @@ fn register_actions(
                         let _ = settings
                             .theme
                             .buffer_font_size
-                            .insert(f32::from(theme::clamp_font_size(buffer_font_size)).into());
+                            .insert(f32::from(theme_settings::clamp_font_size(buffer_font_size)).into());
                     });
                 } else {
-                    theme::adjust_buffer_font_size(cx, |size| size - px(1.0));
+                    theme_settings::adjust_buffer_font_size(cx, |size| size - px(1.0));
                 }
             }
         })
@@ -980,7 +978,7 @@ fn register_actions(
                         settings.theme.buffer_font_size = None;
                     });
                 } else {
-                    theme::reset_buffer_font_size(cx);
+                    theme_settings::reset_buffer_font_size(cx);
                 }
             }
         })
@@ -995,10 +993,10 @@ fn register_actions(
                         settings.theme.agent_buffer_font_size = None;
                     });
                 } else {
-                    theme::reset_ui_font_size(cx);
-                    theme::reset_buffer_font_size(cx);
-                    theme::reset_agent_ui_font_size(cx);
-                    theme::reset_agent_buffer_font_size(cx);
+                    theme_settings::reset_ui_font_size(cx);
+                    theme_settings::reset_buffer_font_size(cx);
+                    theme_settings::reset_agent_ui_font_size(cx);
+                    theme_settings::reset_agent_buffer_font_size(cx);
                 }
             }
         })
@@ -2228,7 +2226,7 @@ pub(crate) fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &mut A
                 match load_target {
                     LoadTarget::Theme(theme_path) => {
                         if let Some(bytes) = fs.load_bytes(&theme_path).await.log_err()
-                            && theme_registry.load_user_theme(&bytes).log_err().is_some()
+                            && load_user_theme(theme_registry, &bytes).log_err().is_some()
                         {
                             reload_tasks.lock().push(ReloadTarget::Theme);
                         }
@@ -2252,8 +2250,8 @@ pub(crate) fn eager_load_active_theme_and_icon_theme(fs: Arc<dyn Fs>, cx: &mut A
 
     for reload_target in reload_tasks.into_inner() {
         match reload_target {
-            ReloadTarget::Theme => GlobalTheme::reload_theme(cx),
-            ReloadTarget::IconTheme => GlobalTheme::reload_icon_theme(cx),
+            ReloadTarget::Theme => theme_settings::reload_theme(cx),
+            ReloadTarget::IconTheme => theme_settings::reload_icon_theme(cx),
         };
     }
 }
@@ -4457,7 +4455,7 @@ mod tests {
         cx.update(|cx| {
             let app_state = AppState::test(cx);
 
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             client::init(&app_state.client, cx);
             workspace::init(app_state.clone(), cx);
             onboarding::init(cx);
@@ -4875,7 +4873,7 @@ mod tests {
             .unwrap();
         let themes = ThemeRegistry::default();
         settings::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
 
         let mut has_default_theme = false;
         for theme_name in themes.list().into_iter().map(|meta| meta.name) {
@@ -5013,7 +5011,7 @@ mod tests {
             app_state.languages.add(markdown_lang());
 
             gpui_tokio::init(cx);
-            theme::init(theme::LoadThemes::JustBase, cx);
+            theme_settings::init(theme::LoadThemes::JustBase, cx);
             audio::init(cx);
             channel::init(&app_state.client, app_state.user_store.clone(), cx);
             call::init(app_state.client.clone(), app_state.user_store.clone(), cx);

crates/zed/src/zed/migrate.rs 🔗

@@ -11,7 +11,7 @@ use std::sync::Arc;
 
 use gpui::{Entity, EventEmitter, Global, Task, TextStyle, TextStyleRefinement};
 use markdown::{Markdown, MarkdownElement, MarkdownStyle};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::prelude::*;
 use workspace::item::ItemHandle;
 use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace};

crates/zed/src/zed/telemetry_log.rs 🔗

@@ -16,7 +16,7 @@ use markdown::{CodeBlockRenderer, Markdown, MarkdownElement, MarkdownStyle};
 use project::Project;
 use settings::Settings;
 use telemetry_events::{Event, EventWrapper};
-use theme::ThemeSettings;
+use theme_settings::ThemeSettings;
 use ui::{
     Icon, IconButton, IconName, IconSize, Label, TextSize, Tooltip, WithScrollbar, prelude::*,
 };

crates/zed/src/zed/visual_tests.rs 🔗

@@ -51,7 +51,7 @@ pub fn init_visual_test(cx: &mut VisualTestAppContext) -> Arc<AppState> {
         let app_state = AppState::test(cx);
 
         gpui_tokio::init(cx);
-        theme::init(theme::LoadThemes::JustBase, cx);
+        theme_settings::init(theme::LoadThemes::JustBase, cx);
         audio::init(cx);
         workspace::init(app_state.clone(), cx);
         release_channel::init(semver::Version::new(0, 0, 0), cx);