Define language settings in the language crate

Max Brunsfeld created

Change summary

Cargo.lock                                        |   8 
crates/auto_update/src/auto_update.rs             |  14 
crates/client/src/client.rs                       |  17 
crates/collab/src/tests.rs                        |   5 
crates/collab/src/tests/integration_tests.rs      |  13 
crates/command_palette/Cargo.toml                 |   1 
crates/command_palette/src/command_palette.rs     |  20 
crates/copilot/src/copilot.rs                     |  88 +--
crates/copilot_button/Cargo.toml                  |   1 
crates/copilot_button/src/copilot_button.rs       | 103 ++--
crates/diagnostics/src/diagnostics.rs             |  15 
crates/editor/Cargo.toml                          |   2 
crates/editor/src/display_map.rs                  |  65 +-
crates/editor/src/editor.rs                       |  91 +--
crates/editor/src/editor_tests.rs                 | 352 ++++++++++------
crates/editor/src/element.rs                      |  90 ++--
crates/editor/src/highlight_matching_bracket.rs   |   4 
crates/editor/src/hover_popover.rs                |  11 
crates/editor/src/link_go_to_definition.rs        |  13 
crates/editor/src/mouse_context_menu.rs           |   5 
crates/editor/src/movement.rs                     |  34 +
crates/editor/src/multi_buffer.rs                 |  23 +
crates/editor/src/test/editor_lsp_test_context.rs |   1 
crates/editor/src/test/editor_test_context.rs     |  24 
crates/file_finder/Cargo.toml                     |   4 
crates/file_finder/src/file_finder.rs             |  35 
crates/gpui/src/executor.rs                       |  16 
crates/journal/src/journal.rs                     |  18 
crates/language/Cargo.toml                        |   3 
crates/language/src/buffer.rs                     |  17 
crates/language/src/buffer_tests.rs               |  73 ++-
crates/language/src/language.rs                   |   5 
crates/language/src/language_settings.rs          | 285 +++++++++++++
crates/project/src/project.rs                     |  50 +-
crates/project/src/project_tests.rs               | 143 ++++--
crates/project_panel/Cargo.toml                   |   1 
crates/project_panel/src/project_panel.rs         |  27 
crates/project_symbols/Cargo.toml                 |   1 
crates/project_symbols/src/project_symbols.rs     |  13 
crates/search/src/buffer_search.rs                |  10 
crates/search/src/project_search.rs               |  29 
crates/settings/src/settings.rs                   | 302 -------------
crates/settings/src/settings_file.rs              |   4 
crates/settings/src/settings_store.rs             | 362 +++++++---------
crates/terminal/src/terminal.rs                   |   2 
crates/vim/Cargo.toml                             |   1 
crates/vim/src/test/vim_test_context.rs           |   4 
crates/vim/src/vim.rs                             |  16 
crates/workspace/src/workspace.rs                 |   8 
crates/zed/src/languages/c.rs                     |  15 
crates/zed/src/languages/python.rs                |  15 
crates/zed/src/languages/rust.rs                  |  15 
crates/zed/src/languages/yaml.rs                  |   8 
crates/zed/src/zed.rs                             |  25 
54 files changed, 1,347 insertions(+), 1,160 deletions(-)

Detailed changes

Cargo.lock šŸ”—

@@ -1341,6 +1341,7 @@ dependencies = [
  "env_logger 0.9.3",
  "fuzzy",
  "gpui",
+ "language",
  "picker",
  "project",
  "serde_json",
@@ -1408,6 +1409,7 @@ dependencies = [
  "fs",
  "futures 0.3.28",
  "gpui",
+ "language",
  "settings",
  "smol",
  "theme",
@@ -2034,6 +2036,7 @@ dependencies = [
  "pulldown-cmark",
  "rand 0.8.5",
  "rpc",
+ "schemars",
  "serde",
  "serde_derive",
  "settings",
@@ -2243,6 +2246,7 @@ dependencies = [
  "env_logger 0.9.3",
  "fuzzy",
  "gpui",
+ "language",
  "menu",
  "picker",
  "postage",
@@ -3427,6 +3431,7 @@ dependencies = [
  "futures 0.3.28",
  "fuzzy",
  "git",
+ "glob",
  "gpui",
  "indoc",
  "lazy_static",
@@ -3437,6 +3442,7 @@ dependencies = [
  "rand 0.8.5",
  "regex",
  "rpc",
+ "schemars",
  "serde",
  "serde_derive",
  "serde_json",
@@ -4872,6 +4878,7 @@ dependencies = [
  "editor",
  "futures 0.3.28",
  "gpui",
+ "language",
  "menu",
  "postage",
  "project",
@@ -7818,6 +7825,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
 name = "vim"
 version = "0.1.0"
 dependencies = [
+ "anyhow",
  "assets",
  "async-compat",
  "async-trait",

crates/auto_update/src/auto_update.rs šŸ”—

@@ -65,12 +65,14 @@ impl Setting for AutoUpdateSetting {
 
     type FileContent = Option<bool>;
 
-    fn load(default_value: &Option<bool>, user_values: &[&Option<bool>], _: &AppContext) -> Self {
-        Self(
-            Self::json_merge(default_value, user_values)
-                .unwrap()
-                .unwrap(),
-        )
+    fn load(
+        default_value: &Option<bool>,
+        user_values: &[&Option<bool>],
+        _: &AppContext,
+    ) -> Result<Self> {
+        Ok(Self(
+            Self::json_merge(default_value, user_values)?.ok_or_else(Self::missing_default)?,
+        ))
     }
 }
 

crates/client/src/client.rs šŸ”—

@@ -350,17 +350,18 @@ impl settings::Setting for TelemetrySettings {
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
         _: &AppContext,
-    ) -> Self {
-        Self {
-            diagnostics: user_values
-                .first()
-                .and_then(|v| v.diagnostics)
-                .unwrap_or(default_value.diagnostics.unwrap()),
+    ) -> Result<Self> {
+        Ok(Self {
+            diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or(
+                default_value
+                    .diagnostics
+                    .ok_or_else(Self::missing_default)?,
+            ),
             metrics: user_values
                 .first()
                 .and_then(|v| v.metrics)
-                .unwrap_or(default_value.metrics.unwrap()),
-        }
+                .unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?),
+        })
     }
 }
 

crates/collab/src/tests.rs šŸ”—

@@ -186,7 +186,10 @@ impl TestServer {
                 })
             });
 
-        cx.update(|cx| client::init(&client, cx));
+        cx.update(|cx| {
+            client::init(&client, cx);
+            language::init(cx);
+        });
 
         let fs = FakeFs::new(cx.background());
         let user_store = cx.add_model(|cx| UserStore::new(client.clone(), http, cx));

crates/collab/src/tests/integration_tests.rs šŸ”—

@@ -18,6 +18,7 @@ use gpui::{
 };
 use indoc::indoc;
 use language::{
+    language_settings::{AllLanguageSettings, Formatter},
     tree_sitter_rust, Anchor, Diagnostic, DiagnosticEntry, FakeLspAdapter, Language,
     LanguageConfig, OffsetRangeExt, Point, Rope,
 };
@@ -26,7 +27,7 @@ use lsp::LanguageServerId;
 use project::{search::SearchQuery, DiagnosticSummary, HoverBlockKind, Project, ProjectPath};
 use rand::prelude::*;
 use serde_json::json;
-use settings::{Formatter, Settings};
+use settings::{SettingsStore};
 use std::{
     cell::{Cell, RefCell},
     env, future, mem,
@@ -4219,10 +4220,12 @@ async fn test_formatting_buffer(
     // Ensure buffer can be formatted using an external command. Notice how the
     // host's configuration is honored as opposed to using the guest's settings.
     cx_a.update(|cx| {
-        cx.update_global(|settings: &mut Settings, _| {
-            settings.editor_defaults.formatter = Some(Formatter::External {
-                command: "awk".to_string(),
-                arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()],
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |file| {
+                file.defaults.formatter = Some(Formatter::External {
+                    command: "awk".into(),
+                    arguments: vec!["{sub(/two/,\"{buffer_path}\")}1".to_string()].into(),
+                });
             });
         });
     });

crates/command_palette/Cargo.toml šŸ”—

@@ -23,6 +23,7 @@ workspace = { path = "../workspace" }
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
+language = { path = "../language", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
 serde_json.workspace = true
 workspace = { path = "../workspace", features = ["test-support"] }

crates/command_palette/src/command_palette.rs šŸ”—

@@ -294,14 +294,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_command_palette(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
-        deterministic.forbid_parking();
-        let app_state = cx.update(AppState::test);
-
-        cx.update(|cx| {
-            editor::init(cx);
-            workspace::init(app_state.clone(), cx);
-            init(cx);
-        });
+        let app_state = init_test(cx);
 
         let project = Project::test(app_state.fs.clone(), [], cx).await;
         let (window_id, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
@@ -369,4 +362,15 @@ mod tests {
             assert!(palette.delegate().matches.is_empty())
         });
     }
+
+    fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
+        cx.update(|cx| {
+            let app_state = AppState::test(cx);
+            language::init(cx);
+            editor::init(cx);
+            workspace::init(app_state.clone(), cx);
+            init(cx);
+            app_state
+        })
+    }
 }

crates/copilot/src/copilot.rs šŸ”—

@@ -10,6 +10,7 @@ use gpui::{
     actions, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, Task, WeakModelHandle,
 };
 use language::{
+    language_settings::{all_language_settings, language_settings},
     point_from_lsp, point_to_lsp, Anchor, Bias, Buffer, BufferSnapshot, Language, PointUtf16,
     ToPointUtf16,
 };
@@ -17,7 +18,7 @@ use log::{debug, error};
 use lsp::{LanguageServer, LanguageServerId};
 use node_runtime::NodeRuntime;
 use request::{LogMessage, StatusNotification};
-use settings::Settings;
+use settings::SettingsStore;
 use smol::{fs, io::BufReader, stream::StreamExt};
 use std::{
     ffi::OsString,
@@ -302,56 +303,34 @@ impl Copilot {
         node_runtime: Arc<NodeRuntime>,
         cx: &mut ModelContext<Self>,
     ) -> Self {
-        cx.observe_global::<Settings, _>({
-            let http = http.clone();
-            let node_runtime = node_runtime.clone();
-            move |this, cx| {
-                if cx.global::<Settings>().features.copilot {
-                    if matches!(this.server, CopilotServer::Disabled) {
-                        let start_task = cx
-                            .spawn({
-                                let http = http.clone();
-                                let node_runtime = node_runtime.clone();
-                                move |this, cx| {
-                                    Self::start_language_server(http, node_runtime, this, cx)
-                                }
-                            })
-                            .shared();
-                        this.server = CopilotServer::Starting { task: start_task };
-                        cx.notify();
-                    }
-                } else {
-                    this.server = CopilotServer::Disabled;
-                    cx.notify();
-                }
-            }
-        })
-        .detach();
-
-        if cx.global::<Settings>().features.copilot {
-            let start_task = cx
-                .spawn({
-                    let http = http.clone();
-                    let node_runtime = node_runtime.clone();
-                    move |this, cx| async {
-                        Self::start_language_server(http, node_runtime, this, cx).await
-                    }
-                })
-                .shared();
+        let mut this = Self {
+            http,
+            node_runtime,
+            server: CopilotServer::Disabled,
+            buffers: Default::default(),
+        };
+        this.enable_or_disable_copilot(cx);
+        cx.observe_global::<SettingsStore, _>(move |this, cx| this.enable_or_disable_copilot(cx))
+            .detach();
+        this
+    }
 
-            Self {
-                http,
-                node_runtime,
-                server: CopilotServer::Starting { task: start_task },
-                buffers: Default::default(),
+    fn enable_or_disable_copilot(&mut self, cx: &mut ModelContext<Copilot>) {
+        let http = self.http.clone();
+        let node_runtime = self.node_runtime.clone();
+        if all_language_settings(None, cx).copilot_enabled(None, None) {
+            if matches!(self.server, CopilotServer::Disabled) {
+                let start_task = cx
+                    .spawn({
+                        move |this, cx| Self::start_language_server(http, node_runtime, this, cx)
+                    })
+                    .shared();
+                self.server = CopilotServer::Starting { task: start_task };
+                cx.notify();
             }
         } else {
-            Self {
-                http,
-                node_runtime,
-                server: CopilotServer::Disabled,
-                buffers: Default::default(),
-            }
+            self.server = CopilotServer::Disabled;
+            cx.notify();
         }
     }
 
@@ -805,13 +784,14 @@ impl Copilot {
         let snapshot = registered_buffer.report_changes(buffer, cx);
         let buffer = buffer.read(cx);
         let uri = registered_buffer.uri.clone();
-        let settings = cx.global::<Settings>();
         let position = position.to_point_utf16(buffer);
-        let language = buffer.language_at(position);
-        let language_name = language.map(|language| language.name());
-        let language_name = language_name.as_deref();
-        let tab_size = settings.tab_size(language_name);
-        let hard_tabs = settings.hard_tabs(language_name);
+        let settings = language_settings(
+            None,
+            buffer.language_at(position).map(|l| l.name()).as_deref(),
+            cx,
+        );
+        let tab_size = settings.tab_size;
+        let hard_tabs = settings.hard_tabs;
         let relative_path = buffer
             .file()
             .map(|file| file.path().to_path_buf())

crates/copilot_button/Cargo.toml šŸ”—

@@ -15,6 +15,7 @@ editor = { path = "../editor" }
 fs = { path = "../fs" }
 context_menu = { path = "../context_menu" }
 gpui = { path = "../gpui" }
+language = { path = "../language" }
 settings = { path = "../settings" }
 theme = { path = "../theme" }
 util = { path = "../util" }

crates/copilot_button/src/copilot_button.rs šŸ”—

@@ -9,6 +9,7 @@ use gpui::{
     AnyElement, AppContext, AsyncAppContext, Element, Entity, MouseState, Subscription, View,
     ViewContext, ViewHandle, WeakViewHandle, WindowContext,
 };
+use language::language_settings::{self, all_language_settings, AllLanguageSettings};
 use settings::{update_settings_file, Settings, SettingsStore};
 use std::{path::Path, sync::Arc};
 use util::{paths, ResultExt};
@@ -40,12 +41,12 @@ impl View for CopilotButton {
     }
 
     fn render(&mut self, cx: &mut ViewContext<Self>) -> AnyElement<Self> {
-        let settings = cx.global::<Settings>();
-
-        if !settings.features.copilot {
+        let all_language_settings = &all_language_settings(None, cx);
+        if !all_language_settings.copilot.feature_enabled {
             return Empty::new().into_any();
         }
 
+        let settings = cx.global::<Settings>();
         let theme = settings.theme.clone();
         let active = self.popup_menu.read(cx).visible();
         let Some(copilot) = Copilot::global(cx) else {
@@ -55,7 +56,7 @@ impl View for CopilotButton {
 
         let enabled = self
             .editor_enabled
-            .unwrap_or(settings.show_copilot_suggestions(None, None));
+            .unwrap_or_else(|| all_language_settings.copilot_enabled(None, None));
 
         Stack::new()
             .with_child(
@@ -192,14 +193,14 @@ impl CopilotButton {
     }
 
     pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext<Self>) {
-        let settings = cx.global::<Settings>();
         let fs = self.fs.clone();
-
         let mut menu_options = Vec::with_capacity(8);
 
         if let Some(language) = self.language.clone() {
             let fs = fs.clone();
-            let language_enabled = settings.copilot_enabled_for_language(Some(language.as_ref()));
+            let language_enabled =
+                language_settings::language_settings(None, Some(language.as_ref()), cx)
+                    .show_copilot_suggestions;
             menu_options.push(ContextMenuItem::handler(
                 format!(
                     "{} Suggestions for {}",
@@ -210,6 +211,8 @@ impl CopilotButton {
             ));
         }
 
+        let settings = settings::get_setting::<AllLanguageSettings>(None, cx);
+
         if let Some(path) = self.path.as_ref() {
             let path_enabled = settings.copilot_enabled_for_path(path);
             let path = path.clone();
@@ -234,7 +237,7 @@ impl CopilotButton {
             ));
         }
 
-        let globally_enabled = cx.global::<Settings>().features.copilot;
+        let globally_enabled = settings.copilot_enabled(None, None);
         menu_options.push(ContextMenuItem::handler(
             if globally_enabled {
                 "Hide Suggestions for All Files"
@@ -246,7 +249,7 @@ impl CopilotButton {
 
         menu_options.push(ContextMenuItem::Separator);
 
-        let icon_style = settings.theme.copilot.out_link_icon.clone();
+        let icon_style = cx.global::<Settings>().theme.copilot.out_link_icon.clone();
         menu_options.push(ContextMenuItem::action(
             move |state: &mut MouseState, style: &theme::ContextMenuItem| {
                 Flex::row()
@@ -272,22 +275,19 @@ impl CopilotButton {
 
     pub fn update_enabled(&mut self, editor: ViewHandle<Editor>, cx: &mut ViewContext<Self>) {
         let editor = editor.read(cx);
-
         let snapshot = editor.buffer().read(cx).snapshot(cx);
-        let settings = cx.global::<Settings>();
         let suggestion_anchor = editor.selections.newest_anchor().start;
-
         let language_name = snapshot
             .language_at(suggestion_anchor)
             .map(|language| language.name());
-        let path = snapshot
-            .file_at(suggestion_anchor)
-            .map(|file| file.path().clone());
+        let path = snapshot.file_at(suggestion_anchor).map(|file| file.path());
 
-        self.editor_enabled =
-            Some(settings.show_copilot_suggestions(language_name.as_deref(), path.as_deref()));
+        self.editor_enabled = Some(
+            all_language_settings(None, cx)
+                .copilot_enabled(language_name.as_deref(), path.map(|p| p.as_ref())),
+        );
         self.language = language_name;
-        self.path = path;
+        self.path = path.cloned();
 
         cx.notify()
     }
@@ -328,27 +328,27 @@ async fn configure_disabled_globs(
     settings_editor.downgrade().update(&mut cx, |item, cx| {
         let text = item.buffer().read(cx).snapshot(cx).text();
 
-        let edits = cx
-            .global::<SettingsStore>()
-            .update::<Settings>(&text, |file| {
-                let copilot = file.copilot.get_or_insert_with(Default::default);
-                let globs = copilot.disabled_globs.get_or_insert_with(|| {
-                    cx.global::<Settings>()
-                        .copilot
-                        .disabled_globs
-                        .clone()
-                        .iter()
-                        .map(|glob| glob.as_str().to_string())
-                        .collect::<Vec<_>>()
-                });
-
-                if let Some(path_to_disable) = &path_to_disable {
-                    globs.push(path_to_disable.to_string_lossy().into_owned());
-                } else {
-                    globs.clear();
-                }
+        let settings = cx.global::<SettingsStore>();
+        let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
+            let copilot = file.copilot.get_or_insert_with(Default::default);
+            let globs = copilot.disabled_globs.get_or_insert_with(|| {
+                settings
+                    .get::<AllLanguageSettings>(None)
+                    .copilot
+                    .disabled_globs
+                    .clone()
+                    .iter()
+                    .map(|glob| glob.as_str().to_string())
+                    .collect::<Vec<_>>()
             });
 
+            if let Some(path_to_disable) = &path_to_disable {
+                globs.push(path_to_disable.to_string_lossy().into_owned());
+            } else {
+                globs.clear();
+            }
+        });
+
         if !edits.is_empty() {
             item.change_selections(Some(Autoscroll::newest()), cx, |selections| {
                 selections.select_ranges(edits.iter().map(|e| e.0.clone()));
@@ -365,31 +365,26 @@ async fn configure_disabled_globs(
 }
 
 fn toggle_copilot_globally(fs: Arc<dyn Fs>, cx: &mut AppContext) {
-    let show_copilot_suggestions = cx.global::<Settings>().show_copilot_suggestions(None, None);
-    update_settings_file::<Settings>(fs, cx, move |file_contents| {
-        file_contents.editor.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
+    let show_copilot_suggestions = all_language_settings(None, cx).copilot_enabled(None, None);
+    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
+        file.defaults.show_copilot_suggestions = Some((!show_copilot_suggestions).into())
     });
 }
 
 fn toggle_copilot_for_language(language: Arc<str>, fs: Arc<dyn Fs>, cx: &mut AppContext) {
-    let show_copilot_suggestions = cx
-        .global::<Settings>()
-        .show_copilot_suggestions(Some(&language), None);
-
-    update_settings_file::<Settings>(fs, cx, move |file_contents| {
-        file_contents.languages.insert(
-            language,
-            settings::EditorSettings {
-                show_copilot_suggestions: Some((!show_copilot_suggestions).into()),
-                ..Default::default()
-            },
-        );
+    let show_copilot_suggestions =
+        all_language_settings(None, cx).copilot_enabled(Some(&language), None);
+    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
+        file.languages
+            .entry(language)
+            .or_default()
+            .show_copilot_suggestions = Some(!show_copilot_suggestions);
     });
 }
 
 fn hide_copilot(fs: Arc<dyn Fs>, cx: &mut AppContext) {
-    update_settings_file::<Settings>(fs, cx, move |file_contents| {
-        file_contents.features.copilot = Some(false)
+    update_settings_file::<AllLanguageSettings>(fs, cx, move |file| {
+        file.features.get_or_insert(Default::default()).copilot = Some(false);
     });
 }
 

crates/diagnostics/src/diagnostics.rs šŸ”—

@@ -820,11 +820,13 @@ mod tests {
     use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, PointUtf16, Unclipped};
     use project::FakeFs;
     use serde_json::json;
+    use settings::SettingsStore;
     use unindent::Unindent as _;
 
     #[gpui::test]
     async fn test_diagnostics(cx: &mut TestAppContext) {
-        Settings::test_async(cx);
+        init_test(cx);
+
         let fs = FakeFs::new(cx.background());
         fs.insert_tree(
             "/test",
@@ -1227,7 +1229,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_diagnostics_multiple_servers(cx: &mut TestAppContext) {
-        Settings::test_async(cx);
+        init_test(cx);
+
         let fs = FakeFs::new(cx.background());
         fs.insert_tree(
             "/test",
@@ -1491,6 +1494,14 @@ mod tests {
         });
     }
 
+    fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            cx.set_global(Settings::test(cx));
+            cx.set_global(SettingsStore::test(cx));
+            language::init(cx);
+        });
+    }
+
     fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut WindowContext) -> Vec<(u32, String)> {
         editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);

crates/editor/Cargo.toml šŸ”—

@@ -49,6 +49,7 @@ workspace = { path = "../workspace" }
 aho-corasick = "0.7"
 anyhow.workspace = true
 futures.workspace = true
+glob.workspace = true
 indoc = "1.0.4"
 itertools = "0.10"
 lazy_static.workspace = true
@@ -58,6 +59,7 @@ parking_lot.workspace = true
 postage.workspace = true
 pulldown-cmark = { version = "0.9.2", default-features = false }
 rand = { workspace = true, optional = true }
+schemars.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 smallvec.workspace = true

crates/editor/src/display_map.rs šŸ”—

@@ -13,8 +13,9 @@ use gpui::{
     fonts::{FontId, HighlightStyle},
     Entity, ModelContext, ModelHandle,
 };
-use language::{OffsetUtf16, Point, Subscription as BufferSubscription};
-use settings::Settings;
+use language::{
+    language_settings::language_settings, OffsetUtf16, Point, Subscription as BufferSubscription,
+};
 use std::{any::TypeId, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc};
 pub use suggestion_map::Suggestion;
 use suggestion_map::SuggestionMap;
@@ -276,8 +277,7 @@ impl DisplayMap {
             .as_singleton()
             .and_then(|buffer| buffer.read(cx).language())
             .map(|language| language.name());
-
-        cx.global::<Settings>().tab_size(language_name.as_deref())
+        language_settings(None, language_name.as_deref(), cx).tab_size
     }
 
     #[cfg(test)]
@@ -844,8 +844,12 @@ pub mod tests {
     use super::*;
     use crate::{movement, test::marked_display_snapshot};
     use gpui::{color::Color, elements::*, test::observe, AppContext};
-    use language::{Buffer, Language, LanguageConfig, SelectionGoal};
+    use language::{
+        language_settings::{AllLanguageSettings, AllLanguageSettingsContent},
+        Buffer, Language, LanguageConfig, SelectionGoal,
+    };
     use rand::{prelude::*, Rng};
+    use settings::SettingsStore;
     use smol::stream::StreamExt;
     use std::{env, sync::Arc};
     use theme::SyntaxTheme;
@@ -882,9 +886,7 @@ pub mod tests {
         log::info!("wrap width: {:?}", wrap_width);
 
         cx.update(|cx| {
-            let mut settings = Settings::test(cx);
-            settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
-            cx.set_global(settings)
+            init_test(cx, |s| s.defaults.tab_size = NonZeroU32::new(tab_size));
         });
 
         let buffer = cx.update(|cx| {
@@ -939,9 +941,11 @@ pub mod tests {
                     tab_size = *tab_sizes.choose(&mut rng).unwrap();
                     log::info!("setting tab size to {:?}", tab_size);
                     cx.update(|cx| {
-                        let mut settings = Settings::test(cx);
-                        settings.editor_overrides.tab_size = NonZeroU32::new(tab_size);
-                        cx.set_global(settings)
+                        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+                            store.update_user_settings::<AllLanguageSettings>(cx, |s| {
+                                s.defaults.tab_size = NonZeroU32::new(tab_size);
+                            });
+                        });
                     });
                 }
                 30..=44 => {
@@ -1119,7 +1123,7 @@ pub mod tests {
     #[gpui::test(retries = 5)]
     fn test_soft_wraps(cx: &mut AppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
-        cx.foreground().forbid_parking();
+        init_test(cx, |_| {});
 
         let font_cache = cx.font_cache();
 
@@ -1131,7 +1135,6 @@ pub mod tests {
             .unwrap();
         let font_size = 12.0;
         let wrap_width = Some(64.);
-        cx.set_global(Settings::test(cx));
 
         let text = "one two three four five\nsix seven eight";
         let buffer = MultiBuffer::build_simple(text, cx);
@@ -1211,7 +1214,8 @@ pub mod tests {
 
     #[gpui::test]
     fn test_text_chunks(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx, |_| {});
+
         let text = sample_text(6, 6, 'a');
         let buffer = MultiBuffer::build_simple(&text, cx);
         let family_id = cx
@@ -1225,6 +1229,7 @@ pub mod tests {
         let font_size = 14.0;
         let map =
             cx.add_model(|cx| DisplayMap::new(buffer.clone(), font_id, font_size, None, 1, 1, cx));
+
         buffer.update(cx, |buffer, cx| {
             buffer.edit(
                 vec![
@@ -1289,11 +1294,8 @@ pub mod tests {
             .unwrap(),
         );
         language.set_theme(&theme);
-        cx.update(|cx| {
-            let mut settings = Settings::test(cx);
-            settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
-            cx.set_global(settings);
-        });
+
+        cx.update(|cx| init_test(cx, |s| s.defaults.tab_size = Some(2.try_into().unwrap())));
 
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
         buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
@@ -1382,7 +1384,7 @@ pub mod tests {
         );
         language.set_theme(&theme);
 
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
+        cx.update(|cx| init_test(cx, |_| {}));
 
         let buffer = cx.add_model(|cx| Buffer::new(0, text, cx).with_language(language, cx));
         buffer.condition(cx, |buf, _| !buf.is_parsing()).await;
@@ -1429,9 +1431,8 @@ pub mod tests {
 
     #[gpui::test]
     async fn test_chunks_with_text_highlights(cx: &mut gpui::TestAppContext) {
-        cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
+        cx.update(|cx| init_test(cx, |_| {}));
 
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
         let theme = SyntaxTheme::new(vec![
             ("operator".to_string(), Color::red().into()),
             ("string".to_string(), Color::green().into()),
@@ -1510,7 +1511,8 @@ pub mod tests {
 
     #[gpui::test]
     fn test_clip_point(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx, |_| {});
+
         fn assert(text: &str, shift_right: bool, bias: Bias, cx: &mut gpui::AppContext) {
             let (unmarked_snapshot, mut markers) = marked_display_snapshot(text, cx);
 
@@ -1559,7 +1561,7 @@ pub mod tests {
 
     #[gpui::test]
     fn test_clip_at_line_ends(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx, |_| {});
 
         fn assert(text: &str, cx: &mut gpui::AppContext) {
             let (mut unmarked_snapshot, markers) = marked_display_snapshot(text, cx);
@@ -1578,7 +1580,8 @@ pub mod tests {
 
     #[gpui::test]
     fn test_tabs_with_multibyte_chars(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx, |_| {});
+
         let text = "āœ…\t\tα\nβ\t\nšŸ€Ī²\t\tγ";
         let buffer = MultiBuffer::build_simple(text, cx);
         let font_cache = cx.font_cache();
@@ -1639,7 +1642,8 @@ pub mod tests {
 
     #[gpui::test]
     fn test_max_point(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx, |_| {});
+
         let buffer = MultiBuffer::build_simple("aaa\n\t\tbbb", cx);
         let font_cache = cx.font_cache();
         let family_id = font_cache
@@ -1718,4 +1722,13 @@ pub mod tests {
         }
         chunks
     }
+
+    fn init_test(cx: &mut AppContext, f: impl Fn(&mut AllLanguageSettingsContent)) {
+        cx.foreground().forbid_parking();
+        cx.set_global(SettingsStore::test(cx));
+        language::init(cx);
+        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, f);
+        });
+    }
 }

crates/editor/src/editor.rs šŸ”—

@@ -51,6 +51,7 @@ pub use items::MAX_TAB_TITLE_LEN;
 use itertools::Itertools;
 pub use language::{char_kind, CharKind};
 use language::{
+    language_settings::{self, all_language_settings},
     AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
     Diagnostic, DiagnosticSeverity, File, IndentKind, IndentSize, Language, OffsetRangeExt,
     OffsetUtf16, Point, Selection, SelectionGoal, TransactionId,
@@ -436,7 +437,7 @@ pub enum EditorMode {
     Full,
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub enum SoftWrap {
     None,
     EditorWidth,
@@ -471,7 +472,7 @@ pub struct Editor {
     select_larger_syntax_node_stack: Vec<Box<[Selection<usize>]>>,
     ime_transaction: Option<TransactionId>,
     active_diagnostics: Option<ActiveDiagnosticGroup>,
-    soft_wrap_mode_override: Option<settings::SoftWrap>,
+    soft_wrap_mode_override: Option<language_settings::SoftWrap>,
     get_field_editor_theme: Option<Arc<GetFieldEditorTheme>>,
     override_text_style: Option<Box<OverrideTextStyle>>,
     project: Option<ModelHandle<Project>>,
@@ -1247,7 +1248,7 @@ impl Editor {
         let blink_manager = cx.add_model(|cx| BlinkManager::new(CURSOR_BLINK_INTERVAL, cx));
 
         let soft_wrap_mode_override =
-            (mode == EditorMode::SingleLine).then(|| settings::SoftWrap::None);
+            (mode == EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
         let mut this = Self {
             handle: cx.weak_handle(),
             buffer: buffer.clone(),
@@ -3116,17 +3117,12 @@ impl Editor {
         snapshot: &MultiBufferSnapshot,
         cx: &mut ViewContext<Self>,
     ) -> bool {
-        let settings = cx.global::<Settings>();
-
-        let path = snapshot.file_at(location).map(|file| file.path());
+        let path = snapshot.file_at(location).map(|file| file.path().as_ref());
         let language_name = snapshot
             .language_at(location)
             .map(|language| language.name());
-        if !settings.show_copilot_suggestions(language_name.as_deref(), path.map(|p| p.as_ref())) {
-            return false;
-        }
-
-        true
+        let settings = all_language_settings(None, cx);
+        settings.copilot_enabled(language_name.as_deref(), path)
     }
 
     fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
@@ -3427,12 +3423,9 @@ impl Editor {
                         {
                             let indent_size =
                                 buffer.indent_size_for_line(line_buffer_range.start.row);
-                            let language_name = buffer
-                                .language_at(line_buffer_range.start)
-                                .map(|language| language.name());
                             let indent_len = match indent_size.kind {
                                 IndentKind::Space => {
-                                    cx.global::<Settings>().tab_size(language_name.as_deref())
+                                    buffer.settings_at(line_buffer_range.start, cx).tab_size
                                 }
                                 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
                             };
@@ -3544,12 +3537,11 @@ impl Editor {
             }
 
             // Otherwise, insert a hard or soft tab.
-            let settings = cx.global::<Settings>();
-            let language_name = buffer.language_at(cursor, cx).map(|l| l.name());
-            let tab_size = if settings.hard_tabs(language_name.as_deref()) {
+            let settings = buffer.settings_at(cursor, cx);
+            let tab_size = if settings.hard_tabs {
                 IndentSize::tab()
             } else {
-                let tab_size = settings.tab_size(language_name.as_deref()).get();
+                let tab_size = settings.tab_size.get();
                 let char_column = snapshot
                     .text_for_range(Point::new(cursor.row, 0)..cursor)
                     .flat_map(str::chars)
@@ -3602,10 +3594,9 @@ impl Editor {
         delta_for_start_row: u32,
         cx: &AppContext,
     ) -> u32 {
-        let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
-        let settings = cx.global::<Settings>();
-        let tab_size = settings.tab_size(language_name.as_deref()).get();
-        let indent_kind = if settings.hard_tabs(language_name.as_deref()) {
+        let settings = buffer.settings_at(selection.start, cx);
+        let tab_size = settings.tab_size.get();
+        let indent_kind = if settings.hard_tabs {
             IndentKind::Tab
         } else {
             IndentKind::Space
@@ -3674,11 +3665,8 @@ impl Editor {
             let buffer = self.buffer.read(cx);
             let snapshot = buffer.snapshot(cx);
             for selection in &selections {
-                let language_name = buffer.language_at(selection.start, cx).map(|l| l.name());
-                let tab_size = cx
-                    .global::<Settings>()
-                    .tab_size(language_name.as_deref())
-                    .get();
+                let settings = buffer.settings_at(selection.start, cx);
+                let tab_size = settings.tab_size.get();
                 let mut rows = selection.spanned_rows(false, &display_map);
 
                 // Avoid re-outdenting a row that has already been outdented by a
@@ -6439,27 +6427,24 @@ impl Editor {
     }
 
     pub fn soft_wrap_mode(&self, cx: &AppContext) -> SoftWrap {
-        let language_name = self
-            .buffer
-            .read(cx)
-            .as_singleton()
-            .and_then(|singleton_buffer| singleton_buffer.read(cx).language())
-            .map(|l| l.name());
-
-        let settings = cx.global::<Settings>();
+        let settings = self.buffer.read(cx).settings_at(0, cx);
         let mode = self
             .soft_wrap_mode_override
-            .unwrap_or_else(|| settings.soft_wrap(language_name.as_deref()));
+            .unwrap_or_else(|| settings.soft_wrap);
         match mode {
-            settings::SoftWrap::None => SoftWrap::None,
-            settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
-            settings::SoftWrap::PreferredLineLength => {
-                SoftWrap::Column(settings.preferred_line_length(language_name.as_deref()))
+            language_settings::SoftWrap::None => SoftWrap::None,
+            language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
+            language_settings::SoftWrap::PreferredLineLength => {
+                SoftWrap::Column(settings.preferred_line_length)
             }
         }
     }
 
-    pub fn set_soft_wrap_mode(&mut self, mode: settings::SoftWrap, cx: &mut ViewContext<Self>) {
+    pub fn set_soft_wrap_mode(
+        &mut self,
+        mode: language_settings::SoftWrap,
+        cx: &mut ViewContext<Self>,
+    ) {
         self.soft_wrap_mode_override = Some(mode);
         cx.notify();
     }
@@ -6474,8 +6459,8 @@ impl Editor {
             self.soft_wrap_mode_override.take();
         } else {
             let soft_wrap = match self.soft_wrap_mode(cx) {
-                SoftWrap::None => settings::SoftWrap::EditorWidth,
-                SoftWrap::EditorWidth | SoftWrap::Column(_) => settings::SoftWrap::None,
+                SoftWrap::None => language_settings::SoftWrap::EditorWidth,
+                SoftWrap::EditorWidth | SoftWrap::Column(_) => language_settings::SoftWrap::None,
             };
             self.soft_wrap_mode_override = Some(soft_wrap);
         }
@@ -6874,7 +6859,12 @@ impl Editor {
                 .get("vim_mode")
                 == Some(&serde_json::Value::Bool(true));
             let telemetry_settings = *settings::get_setting::<TelemetrySettings>(None, cx);
-            let settings = cx.global::<Settings>();
+            let copilot_enabled = all_language_settings(None, cx).copilot_enabled(None, None);
+            let copilot_enabled_for_language = self
+                .buffer
+                .read(cx)
+                .settings_at(0, cx)
+                .show_copilot_suggestions;
 
             let extension = Path::new(file.file_name(cx))
                 .extension()
@@ -6893,15 +6883,8 @@ impl Editor {
                 file_extension: extension.map(ToString::to_string),
                 vim_mode,
                 operation: name,
-                copilot_enabled: settings.features.copilot,
-                copilot_enabled_for_language: settings.show_copilot_suggestions(
-                    self.language_at(0, cx)
-                        .map(|language| language.name())
-                        .as_deref(),
-                    self.file_at(0, cx)
-                        .map(|file| file.path().clone())
-                        .as_deref(),
-                ),
+                copilot_enabled,
+                copilot_enabled_for_language,
             };
             telemetry.report_clickhouse_event(event, telemetry_settings)
         }

crates/editor/src/editor_tests.rs šŸ”—

@@ -12,10 +12,12 @@ use gpui::{
     serde_json, TestAppContext,
 };
 use indoc::indoc;
-use language::{BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point};
+use language::{
+    language_settings::{AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent},
+    BracketPairConfig, FakeLspAdapter, LanguageConfig, LanguageRegistry, Point,
+};
 use parking_lot::Mutex;
 use project::FakeFs;
-use settings::EditorSettings;
 use std::{cell::RefCell, future::Future, rc::Rc, time::Instant};
 use unindent::Unindent;
 use util::{
@@ -29,7 +31,8 @@ use workspace::{
 
 #[gpui::test]
 fn test_edit_events(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let buffer = cx.add_model(|cx| {
         let mut buffer = language::Buffer::new(0, "123456", cx);
         buffer.set_group_interval(Duration::from_secs(1));
@@ -156,7 +159,8 @@ fn test_edit_events(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let mut now = Instant::now();
     let buffer = cx.add_model(|cx| language::Buffer::new(0, "123456", cx));
     let group_interval = buffer.read_with(cx, |buffer, _| buffer.transaction_group_interval());
@@ -226,7 +230,8 @@ fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_ime_composition(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let buffer = cx.add_model(|cx| {
         let mut buffer = language::Buffer::new(0, "abcde", cx);
         // Ensure automatic grouping doesn't occur.
@@ -328,7 +333,7 @@ fn test_ime_composition(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_selection_with_mouse(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
 
     let (_, editor) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
@@ -395,7 +400,8 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_canceling_pending_selection(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
         build_editor(buffer, cx)
@@ -429,6 +435,8 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_clone(cx: &mut TestAppContext) {
+    init_test(cx, |_| {});
+
     let (text, selection_ranges) = marked_text_ranges(
         indoc! {"
             one
@@ -439,7 +447,6 @@ fn test_clone(cx: &mut TestAppContext) {
         "},
         true,
     );
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
 
     let (_, editor) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(&text, cx);
@@ -487,7 +494,8 @@ fn test_clone(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_navigation_history(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     cx.set_global(DragAndDrop::<Workspace>::default());
     use workspace::item::Item;
 
@@ -600,7 +608,8 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_cancel(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
         build_editor(buffer, cx)
@@ -642,7 +651,8 @@ fn test_cancel(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_fold_action(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(
             &"
@@ -731,7 +741,8 @@ fn test_fold_action(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_move_cursor(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
     let (_, view) = cx.add_window(|cx| build_editor(buffer.clone(), cx));
 
@@ -806,7 +817,8 @@ fn test_move_cursor(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcde\nαβγΓε\n", cx);
         build_editor(buffer.clone(), cx)
@@ -910,7 +922,8 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
         build_editor(buffer.clone(), cx)
@@ -959,7 +972,8 @@ fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_beginning_end_of_line(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("abc\n  def", cx);
         build_editor(buffer, cx)
@@ -1121,7 +1135,8 @@ fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
         build_editor(buffer, cx)
@@ -1172,7 +1187,8 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
         build_editor(buffer, cx)
@@ -1229,6 +1245,7 @@ fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
     let mut cx = EditorTestContext::new(cx);
 
     let line_height = cx.editor(|editor, cx| editor.style(cx).text.line_height(cx.font_cache()));
@@ -1343,6 +1360,7 @@ async fn test_move_page_up_page_down(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
     let mut cx = EditorTestContext::new(cx);
     cx.set_state("one Ā«two threeˇ» four");
     cx.update_editor(|editor, cx| {
@@ -1353,7 +1371,8 @@ async fn test_delete_to_beginning_of_line(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("one two three four", cx);
         build_editor(buffer.clone(), cx)
@@ -1388,7 +1407,8 @@ fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_newline(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
         build_editor(buffer.clone(), cx)
@@ -1410,7 +1430,8 @@ fn test_newline(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_newline_with_old_selections(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, editor) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(
             "
@@ -1491,11 +1512,8 @@ fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_newline_above(cx: &mut gpui::TestAppContext) {
-    let mut cx = EditorTestContext::new(cx);
-    cx.update(|cx| {
-        cx.update_global::<Settings, _, _>(|settings, _| {
-            settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap());
-        });
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4)
     });
 
     let language = Arc::new(
@@ -1506,8 +1524,9 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) {
         .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
         .unwrap(),
     );
-    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 
+    let mut cx = EditorTestContext::new(cx);
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
     cx.set_state(indoc! {"
         const a: ˇA = (
             (ˇ
@@ -1516,6 +1535,7 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) {
             )ˇ
         ˇ);ˇ
     "});
+
     cx.update_editor(|e, cx| e.newline_above(&NewlineAbove, cx));
     cx.assert_editor_state(indoc! {"
         ˇ
@@ -1540,11 +1560,8 @@ async fn test_newline_above(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_newline_below(cx: &mut gpui::TestAppContext) {
-    let mut cx = EditorTestContext::new(cx);
-    cx.update(|cx| {
-        cx.update_global::<Settings, _, _>(|settings, _| {
-            settings.editor_overrides.tab_size = Some(NonZeroU32::new(4).unwrap());
-        });
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4)
     });
 
     let language = Arc::new(
@@ -1555,8 +1572,9 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
         .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
         .unwrap(),
     );
-    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 
+    let mut cx = EditorTestContext::new(cx);
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
     cx.set_state(indoc! {"
         const a: ˇA = (
             (ˇ
@@ -1565,6 +1583,7 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
             )ˇ
         ˇ);ˇ
     "});
+
     cx.update_editor(|e, cx| e.newline_below(&NewlineBelow, cx));
     cx.assert_editor_state(indoc! {"
         const a: A = (
@@ -1589,7 +1608,8 @@ async fn test_newline_below(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 fn test_insert_with_old_selections(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, editor) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
         let mut editor = build_editor(buffer.clone(), cx);
@@ -1615,12 +1635,11 @@ fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_tab(cx: &mut gpui::TestAppContext) {
-    let mut cx = EditorTestContext::new(cx);
-    cx.update(|cx| {
-        cx.update_global::<Settings, _, _>(|settings, _| {
-            settings.editor_overrides.tab_size = Some(NonZeroU32::new(3).unwrap());
-        });
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(3)
     });
+
+    let mut cx = EditorTestContext::new(cx);
     cx.set_state(indoc! {"
         ˇabˇc
         Ė‡šŸ€Ė‡šŸ€Ė‡efg
@@ -1646,6 +1665,8 @@ async fn test_tab(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
     let language = Arc::new(
         Language::new(
@@ -1704,7 +1725,10 @@ async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut gpui::TestAp
 
 #[gpui::test]
 async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
-    let mut cx = EditorTestContext::new(cx);
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4)
+    });
+
     let language = Arc::new(
         Language::new(
             LanguageConfig::default(),
@@ -1713,14 +1737,9 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
         .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
         .unwrap(),
     );
-    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
-
-    cx.update(|cx| {
-        cx.update_global::<Settings, _, _>(|settings, _| {
-            settings.editor_overrides.tab_size = Some(4.try_into().unwrap());
-        });
-    });
 
+    let mut cx = EditorTestContext::new(cx);
+    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
     cx.set_state(indoc! {"
         fn a() {
             if b {
@@ -1741,6 +1760,10 @@ async fn test_tab_with_mixed_whitespace(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |settings| {
+        settings.defaults.tab_size = NonZeroU32::new(4);
+    });
+
     let mut cx = EditorTestContext::new(cx);
 
     cx.set_state(indoc! {"
@@ -1810,13 +1833,12 @@ async fn test_indent_outdent(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
-    let mut cx = EditorTestContext::new(cx);
-    cx.update(|cx| {
-        cx.update_global::<Settings, _, _>(|settings, _| {
-            settings.editor_overrides.hard_tabs = Some(true);
-        });
+    init_test(cx, |settings| {
+        settings.defaults.hard_tabs = Some(true);
     });
 
+    let mut cx = EditorTestContext::new(cx);
+
     // select two ranges on one line
     cx.set_state(indoc! {"
         Ā«oneˇ» Ā«twoˇ»
@@ -1907,25 +1929,25 @@ async fn test_indent_outdent_with_hard_tabs(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
-    cx.update(|cx| {
-        cx.set_global(
-            Settings::test(cx)
-                .with_language_defaults(
-                    "TOML",
-                    EditorSettings {
-                        tab_size: Some(2.try_into().unwrap()),
-                        ..Default::default()
-                    },
-                )
-                .with_language_defaults(
-                    "Rust",
-                    EditorSettings {
-                        tab_size: Some(4.try_into().unwrap()),
-                        ..Default::default()
-                    },
-                ),
-        );
+    init_test(cx, |settings| {
+        settings.languages.extend([
+            (
+                "TOML".into(),
+                LanguageSettingsContent {
+                    tab_size: NonZeroU32::new(2),
+                    ..Default::default()
+                },
+            ),
+            (
+                "Rust".into(),
+                LanguageSettingsContent {
+                    tab_size: NonZeroU32::new(4),
+                    ..Default::default()
+                },
+            ),
+        ]);
     });
+
     let toml_language = Arc::new(Language::new(
         LanguageConfig {
             name: "TOML".into(),
@@ -2020,6 +2042,8 @@ fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_backspace(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
 
     // Basic backspace
@@ -2067,8 +2091,9 @@ async fn test_backspace(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_delete(cx: &mut gpui::TestAppContext) {
-    let mut cx = EditorTestContext::new(cx);
+    init_test(cx, |_| {});
 
+    let mut cx = EditorTestContext::new(cx);
     cx.set_state(indoc! {"
         onˇe two three
         fouĀ«rˇ» five six
@@ -2095,7 +2120,8 @@ async fn test_delete(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 fn test_delete_line(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
         build_editor(buffer, cx)
@@ -2119,7 +2145,6 @@ fn test_delete_line(cx: &mut TestAppContext) {
         );
     });
 
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
         build_editor(buffer, cx)
@@ -2139,7 +2164,8 @@ fn test_delete_line(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_duplicate_line(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
         build_editor(buffer, cx)
@@ -2191,7 +2217,8 @@ fn test_duplicate_line(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_move_line_up_down(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
         build_editor(buffer, cx)
@@ -2289,7 +2316,8 @@ fn test_move_line_up_down(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, editor) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
         build_editor(buffer, cx)
@@ -2315,7 +2343,7 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_transpose(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
 
     _ = cx
         .add_window(|cx| {
@@ -2417,6 +2445,8 @@ fn test_transpose(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_clipboard(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
 
     cx.set_state("Ā«oneāœ… ˇ»two Ā«three ˇ»four Ā«five ˇ»six ");
@@ -2497,6 +2527,8 @@ async fn test_clipboard(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
     let language = Arc::new(Language::new(
         LanguageConfig::default(),
@@ -2609,7 +2641,8 @@ async fn test_paste_multiline(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 fn test_select_all(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
         build_editor(buffer, cx)
@@ -2625,7 +2658,8 @@ fn test_select_all(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_select_line(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
         build_editor(buffer, cx)
@@ -2671,7 +2705,8 @@ fn test_select_line(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_split_selection_into_lines(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
         build_editor(buffer, cx)
@@ -2741,7 +2776,8 @@ fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_add_selection_above_below(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, view) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple("abc\ndefghi\n\njk\nlmno\n", cx);
         build_editor(buffer, cx)
@@ -2935,6 +2971,8 @@ fn test_add_selection_above_below(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_select_next(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
     cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 
@@ -2959,7 +2997,8 @@ async fn test_select_next(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let language = Arc::new(Language::new(
         LanguageConfig::default(),
         Some(tree_sitter_rust::language()),
@@ -3100,7 +3139,8 @@ async fn test_select_larger_smaller_syntax_node(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let language = Arc::new(
         Language::new(
             LanguageConfig {
@@ -3160,6 +3200,8 @@ async fn test_autoindent_selections(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
 
     let language = Arc::new(Language::new(
@@ -3329,6 +3371,8 @@ async fn test_autoclose_pairs(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
 
     let html_language = Arc::new(
@@ -3563,6 +3607,8 @@ async fn test_autoclose_with_embedded_language(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
 
     let rust_language = Arc::new(
@@ -3660,7 +3706,8 @@ async fn test_autoclose_with_overrides(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let language = Arc::new(Language::new(
         LanguageConfig {
             brackets: BracketPairConfig {
@@ -3814,7 +3861,8 @@ async fn test_surround_with_pair(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let language = Arc::new(Language::new(
         LanguageConfig {
             brackets: BracketPairConfig {
@@ -3919,7 +3967,7 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_snippets(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
 
     let (text, insertion_ranges) = marked_text_ranges(
         indoc! {"
@@ -4027,7 +4075,7 @@ async fn test_snippets(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx, |_| {});
 
     let mut language = Language::new(
         LanguageConfig {
@@ -4111,16 +4159,14 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
     assert!(!cx.read(|cx| editor.is_dirty(cx)));
 
     // Set rust language override and assert overriden tabsize is sent to language server
-    cx.update(|cx| {
-        cx.update_global::<Settings, _, _>(|settings, _| {
-            settings.language_overrides.insert(
-                "Rust".into(),
-                EditorSettings {
-                    tab_size: Some(8.try_into().unwrap()),
-                    ..Default::default()
-                },
-            );
-        })
+    update_test_settings(cx, |settings| {
+        settings.languages.insert(
+            "Rust".into(),
+            LanguageSettingsContent {
+                tab_size: NonZeroU32::new(8),
+                ..Default::default()
+            },
+        );
     });
 
     let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
@@ -4141,7 +4187,7 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx, |_| {});
 
     let mut language = Language::new(
         LanguageConfig {
@@ -4227,16 +4273,14 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
     assert!(!cx.read(|cx| editor.is_dirty(cx)));
 
     // Set rust language override and assert overriden tabsize is sent to language server
-    cx.update(|cx| {
-        cx.update_global::<Settings, _, _>(|settings, _| {
-            settings.language_overrides.insert(
-                "Rust".into(),
-                EditorSettings {
-                    tab_size: Some(8.try_into().unwrap()),
-                    ..Default::default()
-                },
-            );
-        })
+    update_test_settings(cx, |settings| {
+        settings.languages.insert(
+            "Rust".into(),
+            LanguageSettingsContent {
+                tab_size: NonZeroU32::new(8),
+                ..Default::default()
+            },
+        );
     });
 
     let save = editor.update(cx, |editor, cx| editor.save(project.clone(), cx));
@@ -4257,7 +4301,7 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx, |_| {});
 
     let mut language = Language::new(
         LanguageConfig {
@@ -4342,7 +4386,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx, |_| {});
 
     let mut cx = EditorLspTestContext::new_rust(
         lsp::ServerCapabilities {
@@ -4399,7 +4443,7 @@ async fn test_concurrent_format_requests(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx, |_| {});
 
     let mut cx = EditorLspTestContext::new_rust(
         lsp::ServerCapabilities {
@@ -4514,6 +4558,8 @@ async fn test_strip_whitespace_and_format_via_lsp(cx: &mut gpui::TestAppContext)
 
 #[gpui::test]
 async fn test_completion(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorLspTestContext::new_rust(
         lsp::ServerCapabilities {
             completion_provider: Some(lsp::CompletionOptions {
@@ -4681,7 +4727,8 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let language = Arc::new(Language::new(
         LanguageConfig {
             line_comment: Some("// ".into()),
@@ -4764,8 +4811,7 @@ async fn test_toggle_comment(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext) {
-    let mut cx = EditorTestContext::new(cx);
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
 
     let language = Arc::new(Language::new(
         LanguageConfig {
@@ -4778,6 +4824,7 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
     let registry = Arc::new(LanguageRegistry::test());
     registry.add(language.clone());
 
+    let mut cx = EditorTestContext::new(cx);
     cx.update_buffer(|buffer, cx| {
         buffer.set_language_registry(registry);
         buffer.set_language(Some(language), cx);
@@ -4897,6 +4944,8 @@ async fn test_advance_downward_on_toggle_comment(cx: &mut gpui::TestAppContext)
 
 #[gpui::test]
 async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
 
     let html_language = Arc::new(
@@ -5021,7 +5070,8 @@ async fn test_toggle_block_comment(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
     let multibuffer = cx.add_model(|cx| {
         let mut multibuffer = MultiBuffer::new(0);
@@ -5067,7 +5117,8 @@ fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let markers = vec![('[', ']').into(), ('(', ')').into()];
     let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
         indoc! {"
@@ -5140,7 +5191,8 @@ fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_refresh_selections(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
     let mut excerpt1_id = None;
     let multibuffer = cx.add_model(|cx| {
@@ -5224,7 +5276,8 @@ fn test_refresh_selections(cx: &mut TestAppContext) {
 
 #[gpui::test]
 fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let buffer = cx.add_model(|cx| Buffer::new(0, sample_text(3, 4, 'a'), cx));
     let mut excerpt1_id = None;
     let multibuffer = cx.add_model(|cx| {
@@ -5282,7 +5335,8 @@ fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let language = Arc::new(
         Language::new(
             LanguageConfig {
@@ -5355,7 +5409,8 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 fn test_highlighted_ranges(cx: &mut TestAppContext) {
-    cx.update(|cx| cx.set_global(Settings::test(cx)));
+    init_test(cx, |_| {});
+
     let (_, editor) = cx.add_window(|cx| {
         let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
         build_editor(buffer.clone(), cx)
@@ -5437,7 +5492,8 @@ fn test_highlighted_ranges(cx: &mut TestAppContext) {
 
 #[gpui::test]
 async fn test_following(cx: &mut gpui::TestAppContext) {
-    Settings::test_async(cx);
+    init_test(cx, |_| {});
+
     let fs = FakeFs::new(cx.background());
     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
 
@@ -5576,7 +5632,8 @@ async fn test_following(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) {
-    Settings::test_async(cx);
+    init_test(cx, |_| {});
+
     let fs = FakeFs::new(cx.background());
     let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
     let (_, workspace) = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
@@ -5805,6 +5862,8 @@ fn test_combine_syntax_and_fuzzy_match_highlights() {
 
 #[gpui::test]
 async fn go_to_hunk(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorTestContext::new(cx);
 
     let diff_base = r#"
@@ -5924,6 +5983,8 @@ fn test_split_words() {
 
 #[gpui::test]
 async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
     let mut assert = |before, after| {
         let _state_context = cx.set_state(before);
@@ -5972,6 +6033,8 @@ async fn test_move_to_enclosing_bracket(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test(iterations = 10)]
 async fn test_copilot(deterministic: Arc<Deterministic>, cx: &mut gpui::TestAppContext) {
+    init_test(cx, |_| {});
+
     let (copilot, copilot_lsp) = Copilot::fake(cx);
     cx.update(|cx| cx.set_global(copilot));
     let mut cx = EditorLspTestContext::new_rust(
@@ -6223,6 +6286,8 @@ async fn test_copilot_completion_invalidation(
     deterministic: Arc<Deterministic>,
     cx: &mut gpui::TestAppContext,
 ) {
+    init_test(cx, |_| {});
+
     let (copilot, copilot_lsp) = Copilot::fake(cx);
     cx.update(|cx| cx.set_global(copilot));
     let mut cx = EditorLspTestContext::new_rust(
@@ -6288,11 +6353,10 @@ async fn test_copilot_multibuffer(
     deterministic: Arc<Deterministic>,
     cx: &mut gpui::TestAppContext,
 ) {
+    init_test(cx, |_| {});
+
     let (copilot, copilot_lsp) = Copilot::fake(cx);
-    cx.update(|cx| {
-        cx.set_global(Settings::test(cx));
-        cx.set_global(copilot)
-    });
+    cx.update(|cx| cx.set_global(copilot));
 
     let buffer_1 = cx.add_model(|cx| Buffer::new(0, "a = 1\nb = 2\n", cx));
     let buffer_2 = cx.add_model(|cx| Buffer::new(0, "c = 3\nd = 4\n", cx));
@@ -6392,14 +6456,16 @@ async fn test_copilot_disabled_globs(
     deterministic: Arc<Deterministic>,
     cx: &mut gpui::TestAppContext,
 ) {
-    let (copilot, copilot_lsp) = Copilot::fake(cx);
-    cx.update(|cx| {
-        let mut settings = Settings::test(cx);
-        settings.copilot.disabled_globs = vec![glob::Pattern::new(".env*").unwrap()];
-        cx.set_global(settings);
-        cx.set_global(copilot)
+    init_test(cx, |settings| {
+        settings
+            .copilot
+            .get_or_insert(Default::default())
+            .disabled_globs = Some(vec![".env*".to_string()]);
     });
 
+    let (copilot, copilot_lsp) = Copilot::fake(cx);
+    cx.update(|cx| cx.set_global(copilot));
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/test",
@@ -6596,3 +6662,27 @@ fn handle_copilot_completion_request(
         }
     });
 }
+
+pub(crate) fn update_test_settings(
+    cx: &mut TestAppContext,
+    f: impl Fn(&mut AllLanguageSettingsContent),
+) {
+    cx.update(|cx| {
+        cx.update_global::<SettingsStore, _, _>(|store, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, f);
+        });
+    });
+}
+
+pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
+    cx.foreground().forbid_parking();
+
+    cx.update(|cx| {
+        cx.set_global(SettingsStore::test(cx));
+        cx.set_global(Settings::test(cx));
+        language::init(cx);
+        crate::init(cx);
+    });
+
+    update_test_settings(cx, f);
+}

crates/editor/src/element.rs šŸ”—

@@ -35,9 +35,12 @@ use gpui::{
 };
 use itertools::Itertools;
 use json::json;
-use language::{Bias, CursorShape, DiagnosticSeverity, OffsetUtf16, Selection};
+use language::{
+    language_settings::ShowWhitespaceSetting, Bias, CursorShape, DiagnosticSeverity, OffsetUtf16,
+    Selection,
+};
 use project::ProjectPath;
-use settings::{GitGutter, Settings, ShowWhitespaces};
+use settings::{GitGutter, Settings};
 use smallvec::SmallVec;
 use std::{
     borrow::Cow,
@@ -708,6 +711,7 @@ impl EditorElement {
         let scroll_left = scroll_position.x() * max_glyph_width;
         let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.);
         let line_end_overshoot = 0.15 * layout.position_map.line_height;
+        let whitespace_setting = editor.buffer.read(cx).settings_at(0, cx).show_whitespaces;
 
         scene.push_layer(Some(bounds));
 
@@ -882,9 +886,10 @@ impl EditorElement {
                     content_origin,
                     scroll_left,
                     visible_text_bounds,
-                    cx,
+                    whitespace_setting,
                     &invisible_display_ranges,
                     visible_bounds,
+                    cx,
                 )
             }
         }
@@ -1738,9 +1743,10 @@ impl LineWithInvisibles {
         content_origin: Vector2F,
         scroll_left: f32,
         visible_text_bounds: RectF,
-        cx: &mut ViewContext<Editor>,
+        whitespace_setting: ShowWhitespaceSetting,
         selection_ranges: &[Range<DisplayPoint>],
         visible_bounds: RectF,
+        cx: &mut ViewContext<Editor>,
     ) {
         let line_height = layout.position_map.line_height;
         let line_y = row as f32 * line_height - scroll_top;
@@ -1754,7 +1760,6 @@ impl LineWithInvisibles {
         );
 
         self.draw_invisibles(
-            cx,
             &selection_ranges,
             layout,
             content_origin,
@@ -1764,12 +1769,13 @@ impl LineWithInvisibles {
             scene,
             visible_bounds,
             line_height,
+            whitespace_setting,
+            cx,
         );
     }
 
     fn draw_invisibles(
         &self,
-        cx: &mut ViewContext<Editor>,
         selection_ranges: &[Range<DisplayPoint>],
         layout: &LayoutState,
         content_origin: Vector2F,
@@ -1779,17 +1785,13 @@ impl LineWithInvisibles {
         scene: &mut SceneBuilder,
         visible_bounds: RectF,
         line_height: f32,
+        whitespace_setting: ShowWhitespaceSetting,
+        cx: &mut ViewContext<Editor>,
     ) {
-        let settings = cx.global::<Settings>();
-        let allowed_invisibles_regions = match settings
-            .editor_overrides
-            .show_whitespaces
-            .or(settings.editor_defaults.show_whitespaces)
-            .unwrap_or_default()
-        {
-            ShowWhitespaces::None => return,
-            ShowWhitespaces::Selection => Some(selection_ranges),
-            ShowWhitespaces::All => None,
+        let allowed_invisibles_regions = match whitespace_setting {
+            ShowWhitespaceSetting::None => return,
+            ShowWhitespaceSetting::Selection => Some(selection_ranges),
+            ShowWhitespaceSetting::All => None,
         };
 
         for invisible in &self.invisibles {
@@ -2773,17 +2775,19 @@ mod tests {
     use super::*;
     use crate::{
         display_map::{BlockDisposition, BlockProperties},
+        editor_tests::{init_test, update_test_settings},
         Editor, MultiBuffer,
     };
     use gpui::TestAppContext;
+    use language::language_settings;
     use log::info;
-    use settings::Settings;
     use std::{num::NonZeroU32, sync::Arc};
     use util::test::sample_text;
 
     #[gpui::test]
     fn test_layout_line_numbers(cx: &mut TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
+        init_test(cx, |_| {});
+
         let (_, editor) = cx.add_window(|cx| {
             let buffer = MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx);
             Editor::new(EditorMode::Full, buffer, None, None, cx)
@@ -2801,7 +2805,8 @@ mod tests {
 
     #[gpui::test]
     fn test_layout_with_placeholder_text_and_blocks(cx: &mut TestAppContext) {
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
+        init_test(cx, |_| {});
+
         let (_, editor) = cx.add_window(|cx| {
             let buffer = MultiBuffer::build_simple("", cx);
             Editor::new(EditorMode::Full, buffer, None, None, cx)
@@ -2861,26 +2866,27 @@ mod tests {
 
     #[gpui::test]
     fn test_all_invisibles_drawing(cx: &mut TestAppContext) {
-        let tab_size = 4;
+        const TAB_SIZE: u32 = 4;
+
         let input_text = "\t \t|\t| a b";
         let expected_invisibles = vec![
             Invisible::Tab {
                 line_start_offset: 0,
             },
             Invisible::Whitespace {
-                line_offset: tab_size as usize,
+                line_offset: TAB_SIZE as usize,
             },
             Invisible::Tab {
-                line_start_offset: tab_size as usize + 1,
+                line_start_offset: TAB_SIZE as usize + 1,
             },
             Invisible::Tab {
-                line_start_offset: tab_size as usize * 2 + 1,
+                line_start_offset: TAB_SIZE as usize * 2 + 1,
             },
             Invisible::Whitespace {
-                line_offset: tab_size as usize * 3 + 1,
+                line_offset: TAB_SIZE as usize * 3 + 1,
             },
             Invisible::Whitespace {
-                line_offset: tab_size as usize * 3 + 3,
+                line_offset: TAB_SIZE as usize * 3 + 3,
             },
         ];
         assert_eq!(
@@ -2892,12 +2898,11 @@ mod tests {
             "Hardcoded expected invisibles differ from the actual ones in '{input_text}'"
         );
 
-        cx.update(|cx| {
-            let mut test_settings = Settings::test(cx);
-            test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
-            test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap());
-            cx.set_global(test_settings);
+        init_test(cx, |s| {
+            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
+            s.defaults.tab_size = NonZeroU32::new(TAB_SIZE);
         });
+
         let actual_invisibles =
             collect_invisibles_from_new_editor(cx, EditorMode::Full, &input_text, 500.0);
 
@@ -2906,11 +2911,9 @@ mod tests {
 
     #[gpui::test]
     fn test_invisibles_dont_appear_in_certain_editors(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let mut test_settings = Settings::test(cx);
-            test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
-            test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(4).unwrap());
-            cx.set_global(test_settings);
+        init_test(cx, |s| {
+            s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
+            s.defaults.tab_size = NonZeroU32::new(4);
         });
 
         for editor_mode_without_invisibles in [
@@ -2961,19 +2964,18 @@ mod tests {
         );
         info!("Expected invisibles: {expected_invisibles:?}");
 
+        init_test(cx, |_| {});
+
         // Put the same string with repeating whitespace pattern into editors of various size,
         // take deliberately small steps during resizing, to put all whitespace kinds near the wrap point.
         let resize_step = 10.0;
         let mut editor_width = 200.0;
         while editor_width <= 1000.0 {
-            cx.update(|cx| {
-                let mut test_settings = Settings::test(cx);
-                test_settings.editor_defaults.tab_size = Some(NonZeroU32::new(tab_size).unwrap());
-                test_settings.editor_defaults.show_whitespaces = Some(ShowWhitespaces::All);
-                test_settings.editor_defaults.preferred_line_length = Some(editor_width as u32);
-                test_settings.editor_defaults.soft_wrap =
-                    Some(settings::SoftWrap::PreferredLineLength);
-                cx.set_global(test_settings);
+            update_test_settings(cx, |s| {
+                s.defaults.tab_size = NonZeroU32::new(tab_size);
+                s.defaults.show_whitespaces = Some(ShowWhitespaceSetting::All);
+                s.defaults.preferred_line_length = Some(editor_width as u32);
+                s.defaults.soft_wrap = Some(language_settings::SoftWrap::PreferredLineLength);
             });
 
             let actual_invisibles =
@@ -3021,7 +3023,7 @@ mod tests {
 
         let mut element = EditorElement::new(editor.read_with(cx, |editor, cx| editor.style(cx)));
         let (_, layout_state) = editor.update(cx, |editor, cx| {
-            editor.set_soft_wrap_mode(settings::SoftWrap::EditorWidth, cx);
+            editor.set_soft_wrap_mode(language_settings::SoftWrap::EditorWidth, cx);
             editor.set_wrap_width(Some(editor_width), cx);
 
             let mut new_parents = Default::default();

crates/editor/src/highlight_matching_bracket.rs šŸ”—

@@ -33,12 +33,14 @@ pub fn refresh_matching_bracket_highlights(editor: &mut Editor, cx: &mut ViewCon
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::test::editor_lsp_test_context::EditorLspTestContext;
+    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
     use indoc::indoc;
     use language::{BracketPair, BracketPairConfig, Language, LanguageConfig};
 
     #[gpui::test]
     async fn test_matching_bracket_highlights(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+
         let mut cx = EditorLspTestContext::new(
             Language::new(
                 LanguageConfig {

crates/editor/src/hover_popover.rs šŸ”—

@@ -694,7 +694,7 @@ impl DiagnosticPopover {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::test::editor_lsp_test_context::EditorLspTestContext;
+    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
     use gpui::fonts::Weight;
     use indoc::indoc;
     use language::{Diagnostic, DiagnosticSet};
@@ -706,6 +706,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_mouse_hover_info_popover(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+
         let mut cx = EditorLspTestContext::new_rust(
             lsp::ServerCapabilities {
                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -773,6 +775,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_keyboard_hover_info_popover(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+
         let mut cx = EditorLspTestContext::new_rust(
             lsp::ServerCapabilities {
                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -816,6 +820,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+
         let mut cx = EditorLspTestContext::new_rust(
             lsp::ServerCapabilities {
                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -882,7 +888,8 @@ mod tests {
 
     #[gpui::test]
     fn test_render_blocks(cx: &mut gpui::TestAppContext) {
-        Settings::test_async(cx);
+        init_test(cx, |_| {});
+
         cx.add_window(|cx| {
             let editor = Editor::single_line(None, cx);
             let style = editor.style(cx);
@@ -1,10 +1,9 @@
-use std::ops::Range;
-
 use crate::{Anchor, DisplayPoint, Editor, EditorSnapshot, SelectPhase};
 use gpui::{Task, ViewContext};
 use language::{Bias, ToOffset};
 use project::LocationLink;
 use settings::Settings;
+use std::ops::Range;
 use util::TryFutureExt;
 
 #[derive(Debug, Default)]
@@ -297,6 +296,8 @@ fn go_to_fetched_definition_of_kind(
 
 #[cfg(test)]
 mod tests {
+    use super::*;
+    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
     use futures::StreamExt;
     use gpui::{
         platform::{self, Modifiers, ModifiersChangedEvent},
@@ -305,12 +306,10 @@ mod tests {
     use indoc::indoc;
     use lsp::request::{GotoDefinition, GotoTypeDefinition};
 
-    use crate::test::editor_lsp_test_context::EditorLspTestContext;
-
-    use super::*;
-
     #[gpui::test]
     async fn test_link_go_to_type_definition(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+
         let mut cx = EditorLspTestContext::new_rust(
             lsp::ServerCapabilities {
                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
@@ -417,6 +416,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_link_go_to_definition(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+
         let mut cx = EditorLspTestContext::new_rust(
             lsp::ServerCapabilities {
                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),

crates/editor/src/mouse_context_menu.rs šŸ”—

@@ -57,13 +57,14 @@ pub fn deploy_context_menu(
 
 #[cfg(test)]
 mod tests {
-    use crate::test::editor_lsp_test_context::EditorLspTestContext;
-
     use super::*;
+    use crate::{editor_tests::init_test, test::editor_lsp_test_context::EditorLspTestContext};
     use indoc::indoc;
 
     #[gpui::test]
     async fn test_mouse_context_menu(cx: &mut gpui::TestAppContext) {
+        init_test(cx, |_| {});
+
         let mut cx = EditorLspTestContext::new_rust(
             lsp::ServerCapabilities {
                 hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),

crates/editor/src/movement.rs šŸ”—

@@ -367,13 +367,15 @@ pub fn split_display_range_by_lines(
 
 #[cfg(test)]
 mod tests {
+    use settings::{Settings, SettingsStore};
+
     use super::*;
     use crate::{test::marked_display_snapshot, Buffer, DisplayMap, ExcerptRange, MultiBuffer};
-    use settings::Settings;
 
     #[gpui::test]
     fn test_previous_word_start(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -400,7 +402,8 @@ mod tests {
 
     #[gpui::test]
     fn test_previous_subword_start(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -434,7 +437,8 @@ mod tests {
 
     #[gpui::test]
     fn test_find_preceding_boundary(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         fn assert(
             marked_text: &str,
             cx: &mut gpui::AppContext,
@@ -466,7 +470,8 @@ mod tests {
 
     #[gpui::test]
     fn test_next_word_end(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -490,7 +495,8 @@ mod tests {
 
     #[gpui::test]
     fn test_next_subword_end(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -523,7 +529,8 @@ mod tests {
 
     #[gpui::test]
     fn test_find_boundary(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         fn assert(
             marked_text: &str,
             cx: &mut gpui::AppContext,
@@ -555,7 +562,8 @@ mod tests {
 
     #[gpui::test]
     fn test_surrounding_word(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         fn assert(marked_text: &str, cx: &mut gpui::AppContext) {
             let (snapshot, display_points) = marked_display_snapshot(marked_text, cx);
             assert_eq!(
@@ -576,7 +584,8 @@ mod tests {
 
     #[gpui::test]
     fn test_move_up_and_down_with_excerpts(cx: &mut gpui::AppContext) {
-        cx.set_global(Settings::test(cx));
+        init_test(cx);
+
         let family_id = cx
             .font_cache()
             .load_family(&["Helvetica"], &Default::default())
@@ -691,4 +700,11 @@ mod tests {
             (DisplayPoint::new(7, 2), SelectionGoal::Column(2)),
         );
     }
+
+    fn init_test(cx: &mut gpui::AppContext) {
+        cx.set_global(SettingsStore::test(cx));
+        cx.set_global(Settings::test(cx));
+        language::init(cx);
+        crate::init(cx);
+    }
 }

crates/editor/src/multi_buffer.rs šŸ”—

@@ -9,7 +9,9 @@ use git::diff::DiffHunk;
 use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
 pub use language::Completion;
 use language::{
-    char_kind, AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
+    char_kind,
+    language_settings::{language_settings, LanguageSettings},
+    AutoindentMode, Buffer, BufferChunks, BufferSnapshot, CharKind, Chunk, CursorShape,
     DiagnosticEntry, File, IndentSize, Language, LanguageScope, OffsetRangeExt, OffsetUtf16,
     Outline, OutlineItem, Point, PointUtf16, Selection, TextDimension, ToOffset as _,
     ToOffsetUtf16 as _, ToPoint as _, ToPointUtf16 as _, TransactionId, Unclipped,
@@ -1372,6 +1374,15 @@ impl MultiBuffer {
             .and_then(|(buffer, offset)| buffer.read(cx).language_at(offset))
     }
 
+    pub fn settings_at<'a, T: ToOffset>(
+        &self,
+        point: T,
+        cx: &'a AppContext,
+    ) -> &'a LanguageSettings {
+        let language = self.language_at(point, cx);
+        language_settings(None, language.map(|l| l.name()).as_deref(), cx)
+    }
+
     pub fn for_each_buffer(&self, mut f: impl FnMut(&ModelHandle<Buffer>)) {
         self.buffers
             .borrow()
@@ -2764,6 +2775,16 @@ impl MultiBufferSnapshot {
             .and_then(|(buffer, offset)| buffer.language_at(offset))
     }
 
+    pub fn settings_at<'a, T: ToOffset>(
+        &'a self,
+        point: T,
+        cx: &'a AppContext,
+    ) -> &'a LanguageSettings {
+        self.point_to_buffer_offset(point)
+            .map(|(buffer, offset)| buffer.settings_at(offset, cx))
+            .unwrap_or_else(|| language_settings(None, None, cx))
+    }
+
     pub fn language_scope_at<'a, T: ToOffset>(&'a self, point: T) -> Option<LanguageScope> {
         self.point_to_buffer_offset(point)
             .and_then(|(buffer, offset)| buffer.language_scope_at(offset))

crates/editor/src/test/editor_test_context.rs šŸ”—

@@ -1,19 +1,16 @@
-use std::{
-    any::TypeId,
-    ops::{Deref, DerefMut, Range},
-};
-
-use futures::Future;
-use indoc::indoc;
-
 use crate::{
     display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
 };
+use futures::Future;
 use gpui::{
     keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
 };
+use indoc::indoc;
 use language::{Buffer, BufferSnapshot};
-use settings::Settings;
+use std::{
+    any::TypeId,
+    ops::{Deref, DerefMut, Range},
+};
 use util::{
     assert_set_eq,
     test::{generate_marked_text, marked_text_ranges},
@@ -30,15 +27,10 @@ pub struct EditorTestContext<'a> {
 impl<'a> EditorTestContext<'a> {
     pub fn new(cx: &'a mut gpui::TestAppContext) -> EditorTestContext<'a> {
         let (window_id, editor) = cx.update(|cx| {
-            cx.set_global(Settings::test(cx));
-            crate::init(cx);
-
-            let (window_id, editor) = cx.add_window(Default::default(), |cx| {
+            cx.add_window(Default::default(), |cx| {
                 cx.focus_self();
                 build_editor(MultiBuffer::build_simple("", cx), cx)
-            });
-
-            (window_id, editor)
+            })
         });
 
         Self {

crates/file_finder/Cargo.toml šŸ”—

@@ -23,7 +23,9 @@ postage.workspace = true
 
 [dev-dependencies]
 gpui = { path = "../gpui", features = ["test-support"] }
-serde_json.workspace = true
+language = { path = "../language", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }
+
+serde_json.workspace = true
 ctor.workspace = true
 env_logger.workspace = true

crates/file_finder/src/file_finder.rs šŸ”—

@@ -270,6 +270,7 @@ impl PickerDelegate for FileFinderDelegate {
 mod tests {
     use super::*;
     use editor::Editor;
+    use gpui::TestAppContext;
     use menu::{Confirm, SelectNext};
     use serde_json::json;
     use workspace::{AppState, Workspace};
@@ -283,12 +284,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_matching_paths(cx: &mut gpui::TestAppContext) {
-        let app_state = cx.update(|cx| {
-            super::init(cx);
-            editor::init(cx);
-            AppState::test(cx)
-        });
-
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -339,7 +335,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_matching_cancellation(cx: &mut gpui::TestAppContext) {
-        let app_state = cx.update(AppState::test);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -408,7 +404,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_ignored_files(cx: &mut gpui::TestAppContext) {
-        let app_state = cx.update(AppState::test);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -462,7 +458,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_single_file_worktrees(cx: &mut gpui::TestAppContext) {
-        let app_state = cx.update(AppState::test);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -516,9 +512,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_multiple_matches_with_same_relative_path(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-
-        let app_state = cx.update(AppState::test);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -570,9 +564,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_path_distance_ordering(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-
-        let app_state = cx.update(AppState::test);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -622,7 +614,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_search_worktree_without_files(cx: &mut gpui::TestAppContext) {
-        let app_state = cx.update(AppState::test);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -658,4 +650,15 @@ mod tests {
             assert_eq!(finder.delegate().matches.len(), 0);
         });
     }
+
+    fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
+        cx.foreground().forbid_parking();
+        cx.update(|cx| {
+            let state = AppState::test(cx);
+            language::init(cx);
+            super::init(cx);
+            editor::init(cx);
+            state
+        })
+    }
 }

crates/gpui/src/executor.rs šŸ”—

@@ -477,6 +477,14 @@ impl Deterministic {
         state.rng = StdRng::seed_from_u64(state.seed);
     }
 
+    pub fn allow_parking(&self) {
+        use rand::prelude::*;
+
+        let mut state = self.state.lock();
+        state.forbid_parking = false;
+        state.rng = StdRng::seed_from_u64(state.seed);
+    }
+
     pub async fn simulate_random_delay(&self) {
         use rand::prelude::*;
         use smol::future::yield_now;
@@ -698,6 +706,14 @@ impl Foreground {
         }
     }
 
+    #[cfg(any(test, feature = "test-support"))]
+    pub fn allow_parking(&self) {
+        match self {
+            Self::Deterministic { executor, .. } => executor.allow_parking(),
+            _ => panic!("this method can only be called on a deterministic executor"),
+        }
+    }
+
     #[cfg(any(test, feature = "test-support"))]
     pub fn advance_clock(&self, duration: Duration) {
         match self {

crates/journal/src/journal.rs šŸ”—

@@ -1,3 +1,4 @@
+use anyhow::Result;
 use chrono::{Datelike, Local, NaiveTime, Timelike};
 use editor::{scroll::autoscroll::Autoscroll, Editor};
 use gpui::{actions, AppContext};
@@ -40,21 +41,8 @@ impl settings::Setting for JournalSettings {
 
     type FileContent = Self;
 
-    fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Self {
-        Self {
-            path: Some(
-                user_values
-                    .first()
-                    .and_then(|s| s.path.clone())
-                    .unwrap_or(default_value.path.clone().unwrap()),
-            ),
-            hour_format: Some(
-                user_values
-                    .first()
-                    .and_then(|s| s.hour_format.clone())
-                    .unwrap_or(default_value.hour_format.clone().unwrap()),
-            ),
-        }
+    fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
+        Self::load_via_json_merge(default_value, user_values)
     }
 }
 

crates/language/Cargo.toml šŸ”—

@@ -36,16 +36,19 @@ sum_tree = { path = "../sum_tree" }
 text = { path = "../text" }
 theme = { path = "../theme" }
 util = { path = "../util" }
+
 anyhow.workspace = true
 async-broadcast = "0.4"
 async-trait.workspace = true
 futures.workspace = true
+glob.workspace = true
 lazy_static.workspace = true
 log.workspace = true
 parking_lot.workspace = true
 postage.workspace = true
 rand = { workspace = true, optional = true }
 regex.workspace = true
+schemars.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 serde_json.workspace = true

crates/language/src/buffer.rs šŸ”—

@@ -5,6 +5,7 @@ pub use crate::{
 };
 use crate::{
     diagnostic_set::{DiagnosticEntry, DiagnosticGroup},
+    language_settings::{language_settings, LanguageSettings},
     outline::OutlineItem,
     syntax_map::{
         SyntaxMap, SyntaxMapCapture, SyntaxMapCaptures, SyntaxSnapshot, ToTreeSitterPoint,
@@ -18,7 +19,6 @@ use futures::FutureExt as _;
 use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, Task};
 use lsp::LanguageServerId;
 use parking_lot::Mutex;
-use settings::Settings;
 use similar::{ChangeTag, TextDiff};
 use smallvec::SmallVec;
 use smol::future::yield_now;
@@ -1827,11 +1827,11 @@ impl BufferSnapshot {
 
     pub fn language_indent_size_at<T: ToOffset>(&self, position: T, cx: &AppContext) -> IndentSize {
         let language_name = self.language_at(position).map(|language| language.name());
-        let settings = cx.global::<Settings>();
-        if settings.hard_tabs(language_name.as_deref()) {
+        let settings = language_settings(None, language_name.as_deref(), cx);
+        if settings.hard_tabs {
             IndentSize::tab()
         } else {
-            IndentSize::spaces(settings.tab_size(language_name.as_deref()).get())
+            IndentSize::spaces(settings.tab_size.get())
         }
     }
 
@@ -2146,6 +2146,15 @@ impl BufferSnapshot {
             .or(self.language.as_ref())
     }
 
+    pub fn settings_at<'a, D: ToOffset>(
+        &self,
+        position: D,
+        cx: &'a AppContext,
+    ) -> &'a LanguageSettings {
+        let language = self.language_at(position);
+        language_settings(None, language.map(|l| l.name()).as_deref(), cx)
+    }
+
     pub fn language_scope_at<D: ToOffset>(&self, position: D) -> Option<LanguageScope> {
         let offset = position.to_offset(self);
 

crates/language/src/buffer_tests.rs šŸ”—

@@ -1,3 +1,7 @@
+use crate::language_settings::{
+    AllLanguageSettings, AllLanguageSettingsContent, LanguageSettingsContent,
+};
+
 use super::*;
 use clock::ReplicaId;
 use collections::BTreeMap;
@@ -7,7 +11,7 @@ use indoc::indoc;
 use proto::deserialize_operation;
 use rand::prelude::*;
 use regex::RegexBuilder;
-use settings::Settings;
+use settings::SettingsStore;
 use std::{
     cell::RefCell,
     env,
@@ -36,7 +40,8 @@ fn init_logger() {
 
 #[gpui::test]
 fn test_line_endings(cx: &mut gpui::AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let mut buffer =
             Buffer::new(0, "one\r\ntwo\rthree", cx).with_language(Arc::new(rust_lang()), cx);
@@ -862,8 +867,7 @@ fn test_range_for_syntax_ancestor(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
-    let settings = Settings::test(cx);
-    cx.set_global(settings);
+    init_settings(cx, |_| {});
 
     cx.add_model(|cx| {
         let text = "fn a() {}";
@@ -903,9 +907,9 @@ fn test_autoindent_with_soft_tabs(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
-    let mut settings = Settings::test(cx);
-    settings.editor_overrides.hard_tabs = Some(true);
-    cx.set_global(settings);
+    init_settings(cx, |settings| {
+        settings.defaults.hard_tabs = Some(true);
+    });
 
     cx.add_model(|cx| {
         let text = "fn a() {}";
@@ -945,8 +949,7 @@ fn test_autoindent_with_hard_tabs(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppContext) {
-    let settings = Settings::test(cx);
-    cx.set_global(settings);
+    init_settings(cx, |_| {});
 
     cx.add_model(|cx| {
         let mut buffer = Buffer::new(
@@ -1082,8 +1085,7 @@ fn test_autoindent_does_not_adjust_lines_with_unchanged_suggestion(cx: &mut AppC
 
 #[gpui::test]
 fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut AppContext) {
-    let settings = Settings::test(cx);
-    cx.set_global(settings);
+    init_settings(cx, |_| {});
 
     cx.add_model(|cx| {
         let mut buffer = Buffer::new(
@@ -1145,7 +1147,8 @@ fn test_autoindent_does_not_adjust_lines_within_newly_created_errors(cx: &mut Ap
 
 #[gpui::test]
 fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let mut buffer = Buffer::new(
             0,
@@ -1201,7 +1204,8 @@ fn test_autoindent_adjusts_lines_when_only_text_changes(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let text = "a\nb";
         let mut buffer = Buffer::new(0, text, cx).with_language(Arc::new(rust_lang()), cx);
@@ -1217,7 +1221,8 @@ fn test_autoindent_with_edit_at_end_of_buffer(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let text = "
             const a: usize = 1;
@@ -1257,7 +1262,8 @@ fn test_autoindent_multi_line_insertion(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_block_mode(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let text = r#"
             fn a() {
@@ -1339,7 +1345,8 @@ fn test_autoindent_block_mode(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let text = r#"
             fn a() {
@@ -1417,7 +1424,8 @@ fn test_autoindent_block_mode_without_original_indent_columns(cx: &mut AppContex
 
 #[gpui::test]
 fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let text = "
             * one
@@ -1460,25 +1468,23 @@ fn test_autoindent_language_without_indents_query(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
-    cx.set_global({
-        let mut settings = Settings::test(cx);
-        settings.language_overrides.extend([
+    init_settings(cx, |settings| {
+        settings.languages.extend([
             (
                 "HTML".into(),
-                settings::EditorSettings {
+                LanguageSettingsContent {
                     tab_size: Some(2.try_into().unwrap()),
                     ..Default::default()
                 },
             ),
             (
                 "JavaScript".into(),
-                settings::EditorSettings {
+                LanguageSettingsContent {
                     tab_size: Some(8.try_into().unwrap()),
                     ..Default::default()
                 },
             ),
-        ]);
-        settings
+        ])
     });
 
     let html_language = Arc::new(
@@ -1574,9 +1580,10 @@ fn test_autoindent_with_injected_languages(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
-    let mut settings = Settings::test(cx);
-    settings.editor_defaults.tab_size = Some(2.try_into().unwrap());
-    cx.set_global(settings);
+    init_settings(cx, |settings| {
+        settings.defaults.tab_size = Some(2.try_into().unwrap());
+    });
+
     cx.add_model(|cx| {
         let mut buffer = Buffer::new(0, "", cx).with_language(Arc::new(ruby_lang()), cx);
 
@@ -1617,7 +1624,8 @@ fn test_autoindent_query_with_outdent_captures(cx: &mut AppContext) {
 
 #[gpui::test]
 fn test_language_config_at(cx: &mut AppContext) {
-    cx.set_global(Settings::test(cx));
+    init_settings(cx, |_| {});
+
     cx.add_model(|cx| {
         let language = Language::new(
             LanguageConfig {
@@ -2199,7 +2207,6 @@ fn assert_bracket_pairs(
     language: Language,
     cx: &mut AppContext,
 ) {
-    cx.set_global(Settings::test(cx));
     let (expected_text, selection_ranges) = marked_text_ranges(selection_text, false);
     let buffer = cx.add_model(|cx| {
         Buffer::new(0, expected_text.clone(), cx).with_language(Arc::new(language), cx)
@@ -2222,3 +2229,11 @@ fn assert_bracket_pairs(
         bracket_pairs
     );
 }
+
+fn init_settings(cx: &mut AppContext, f: fn(&mut AllLanguageSettingsContent)) {
+    cx.set_global(SettingsStore::test(cx));
+    crate::init(cx);
+    cx.update_global::<SettingsStore, _, _>(|settings, cx| {
+        settings.update_user_settings::<AllLanguageSettings>(cx, f);
+    });
+}

crates/language/src/language.rs šŸ”—

@@ -1,6 +1,7 @@
 mod buffer;
 mod diagnostic_set;
 mod highlight_map;
+pub mod language_settings;
 mod outline;
 pub mod proto;
 mod syntax_map;
@@ -58,6 +59,10 @@ pub use lsp::LanguageServerId;
 pub use outline::{Outline, OutlineItem};
 pub use tree_sitter::{Parser, Tree};
 
+pub fn init(cx: &mut AppContext) {
+    language_settings::init(cx);
+}
+
 thread_local! {
     static PARSER: RefCell<Parser> = RefCell::new(Parser::new());
 }

crates/language/src/language_settings.rs šŸ”—

@@ -0,0 +1,285 @@
+use anyhow::Result;
+use collections::HashMap;
+use gpui::AppContext;
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use std::{num::NonZeroU32, path::Path, sync::Arc};
+
+pub fn init(cx: &mut AppContext) {
+    settings::register_setting::<AllLanguageSettings>(cx);
+}
+
+pub fn language_settings<'a>(
+    path: Option<&Path>,
+    language: Option<&str>,
+    cx: &'a AppContext,
+) -> &'a LanguageSettings {
+    settings::get_setting::<AllLanguageSettings>(path, cx).language(language)
+}
+
+pub fn all_language_settings<'a>(
+    path: Option<&Path>,
+    cx: &'a AppContext,
+) -> &'a AllLanguageSettings {
+    settings::get_setting::<AllLanguageSettings>(path, cx)
+}
+
+#[derive(Debug, Clone)]
+pub struct AllLanguageSettings {
+    pub copilot: CopilotSettings,
+    defaults: LanguageSettings,
+    languages: HashMap<Arc<str>, LanguageSettings>,
+}
+
+#[derive(Debug, Clone, Deserialize)]
+pub struct LanguageSettings {
+    pub tab_size: NonZeroU32,
+    pub hard_tabs: bool,
+    pub soft_wrap: SoftWrap,
+    pub preferred_line_length: u32,
+    pub format_on_save: FormatOnSave,
+    pub remove_trailing_whitespace_on_save: bool,
+    pub ensure_final_newline_on_save: bool,
+    pub formatter: Formatter,
+    pub enable_language_server: bool,
+    pub show_copilot_suggestions: bool,
+    pub show_whitespaces: ShowWhitespaceSetting,
+}
+
+#[derive(Clone, Debug, Default)]
+pub struct CopilotSettings {
+    pub feature_enabled: bool,
+    pub disabled_globs: Vec<glob::Pattern>,
+}
+
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+pub struct AllLanguageSettingsContent {
+    #[serde(default)]
+    pub features: Option<FeaturesContent>,
+    #[serde(default)]
+    pub copilot: Option<CopilotSettingsContent>,
+    #[serde(flatten)]
+    pub defaults: LanguageSettingsContent,
+    #[serde(default, alias = "language_overrides")]
+    pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+pub struct LanguageSettingsContent {
+    #[serde(default)]
+    pub tab_size: Option<NonZeroU32>,
+    #[serde(default)]
+    pub hard_tabs: Option<bool>,
+    #[serde(default)]
+    pub soft_wrap: Option<SoftWrap>,
+    #[serde(default)]
+    pub preferred_line_length: Option<u32>,
+    #[serde(default)]
+    pub format_on_save: Option<FormatOnSave>,
+    #[serde(default)]
+    pub remove_trailing_whitespace_on_save: Option<bool>,
+    #[serde(default)]
+    pub ensure_final_newline_on_save: Option<bool>,
+    #[serde(default)]
+    pub formatter: Option<Formatter>,
+    #[serde(default)]
+    pub enable_language_server: Option<bool>,
+    #[serde(default)]
+    pub show_copilot_suggestions: Option<bool>,
+    #[serde(default)]
+    pub show_whitespaces: Option<ShowWhitespaceSetting>,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+pub struct CopilotSettingsContent {
+    #[serde(default)]
+    pub disabled_globs: Option<Vec<String>>,
+}
+
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub struct FeaturesContent {
+    pub copilot: Option<bool>,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum SoftWrap {
+    None,
+    EditorWidth,
+    PreferredLineLength,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum FormatOnSave {
+    On,
+    Off,
+    LanguageServer,
+    External {
+        command: Arc<str>,
+        arguments: Arc<[String]>,
+    },
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ShowWhitespaceSetting {
+    Selection,
+    None,
+    All,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum Formatter {
+    LanguageServer,
+    External {
+        command: Arc<str>,
+        arguments: Arc<[String]>,
+    },
+}
+
+impl AllLanguageSettings {
+    pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
+        if let Some(name) = language_name {
+            if let Some(overrides) = self.languages.get(name) {
+                return overrides;
+            }
+        }
+        &self.defaults
+    }
+
+    pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
+        !self
+            .copilot
+            .disabled_globs
+            .iter()
+            .any(|glob| glob.matches_path(path))
+    }
+
+    pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool {
+        if !self.copilot.feature_enabled {
+            return false;
+        }
+
+        if let Some(path) = path {
+            if !self.copilot_enabled_for_path(path) {
+                return false;
+            }
+        }
+
+        self.language(language_name).show_copilot_suggestions
+    }
+}
+
+impl settings::Setting for AllLanguageSettings {
+    const KEY: Option<&'static str> = None;
+
+    type FileContent = AllLanguageSettingsContent;
+
+    fn load(
+        default_value: &Self::FileContent,
+        user_settings: &[&Self::FileContent],
+        _: &AppContext,
+    ) -> Result<Self> {
+        // A default is provided for all settings.
+        let mut defaults: LanguageSettings =
+            serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
+
+        let mut languages = HashMap::default();
+        for (language_name, settings) in &default_value.languages {
+            let mut language_settings = defaults.clone();
+            merge_settings(&mut language_settings, &settings);
+            languages.insert(language_name.clone(), language_settings);
+        }
+
+        let mut copilot_enabled = default_value
+            .features
+            .as_ref()
+            .and_then(|f| f.copilot)
+            .ok_or_else(Self::missing_default)?;
+        let mut copilot_globs = default_value
+            .copilot
+            .as_ref()
+            .and_then(|c| c.disabled_globs.as_ref())
+            .ok_or_else(Self::missing_default)?;
+
+        for user_settings in user_settings {
+            if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
+                copilot_enabled = copilot;
+            }
+            if let Some(globs) = user_settings
+                .copilot
+                .as_ref()
+                .and_then(|f| f.disabled_globs.as_ref())
+            {
+                copilot_globs = globs;
+            }
+
+            // A user's global settings override the default global settings and
+            // all default language-specific settings.
+            merge_settings(&mut defaults, &user_settings.defaults);
+            for language_settings in languages.values_mut() {
+                merge_settings(language_settings, &user_settings.defaults);
+            }
+
+            // A user's language-specific settings override default language-specific settings.
+            for (language_name, user_language_settings) in &user_settings.languages {
+                merge_settings(
+                    languages
+                        .entry(language_name.clone())
+                        .or_insert_with(|| defaults.clone()),
+                    &user_language_settings,
+                );
+            }
+        }
+
+        Ok(Self {
+            copilot: CopilotSettings {
+                feature_enabled: copilot_enabled,
+                disabled_globs: copilot_globs
+                    .iter()
+                    .filter_map(|pattern| glob::Pattern::new(pattern).ok())
+                    .collect(),
+            },
+            defaults,
+            languages,
+        })
+    }
+}
+
+fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
+    merge(&mut settings.tab_size, src.tab_size);
+    merge(&mut settings.hard_tabs, src.hard_tabs);
+    merge(&mut settings.soft_wrap, src.soft_wrap);
+    merge(
+        &mut settings.preferred_line_length,
+        src.preferred_line_length,
+    );
+    merge(&mut settings.formatter, src.formatter.clone());
+    merge(&mut settings.format_on_save, src.format_on_save.clone());
+    merge(
+        &mut settings.remove_trailing_whitespace_on_save,
+        src.remove_trailing_whitespace_on_save,
+    );
+    merge(
+        &mut settings.ensure_final_newline_on_save,
+        src.ensure_final_newline_on_save,
+    );
+    merge(
+        &mut settings.enable_language_server,
+        src.enable_language_server,
+    );
+    merge(
+        &mut settings.show_copilot_suggestions,
+        src.show_copilot_suggestions,
+    );
+    merge(&mut settings.show_whitespaces, src.show_whitespaces);
+
+    fn merge<T>(target: &mut T, value: Option<T>) {
+        if let Some(value) = value {
+            *target = value;
+        }
+    }
+}

crates/project/src/project.rs šŸ”—

@@ -23,6 +23,7 @@ use gpui::{
     ModelHandle, Task, WeakModelHandle,
 };
 use language::{
+    language_settings::{all_language_settings, language_settings, FormatOnSave, Formatter},
     point_to_lsp,
     proto::{
         deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version,
@@ -44,7 +45,7 @@ use postage::watch;
 use rand::prelude::*;
 use search::SearchQuery;
 use serde::Serialize;
-use settings::{FormatOnSave, Formatter, Settings};
+use settings::{Settings, SettingsStore};
 use sha2::{Digest, Sha256};
 use similar::{ChangeTag, TextDiff};
 use std::{
@@ -64,9 +65,7 @@ use std::{
     },
     time::{Duration, Instant, SystemTime},
 };
-
 use terminals::Terminals;
-
 use util::{debug_panic, defer, merge_json_value_into, post_inc, ResultExt, TryFutureExt as _};
 
 pub use fs::*;
@@ -454,7 +453,9 @@ impl Project {
                 client_state: None,
                 opened_buffer: watch::channel(),
                 client_subscriptions: Vec::new(),
-                _subscriptions: vec![cx.observe_global::<Settings, _>(Self::on_settings_changed)],
+                _subscriptions: vec![
+                    cx.observe_global::<SettingsStore, _>(Self::on_settings_changed)
+                ],
                 _maintain_buffer_languages: Self::maintain_buffer_languages(&languages, cx),
                 _maintain_workspace_config: Self::maintain_workspace_config(languages.clone(), cx),
                 active_entry: None,
@@ -622,7 +623,7 @@ impl Project {
     }
 
     fn on_settings_changed(&mut self, cx: &mut ModelContext<Self>) {
-        let settings = cx.global::<Settings>();
+        let settings = all_language_settings(None, cx);
 
         let mut language_servers_to_start = Vec::new();
         for buffer in self.opened_buffers.values() {
@@ -630,7 +631,10 @@ impl Project {
                 let buffer = buffer.read(cx);
                 if let Some((file, language)) = File::from_dyn(buffer.file()).zip(buffer.language())
                 {
-                    if settings.enable_language_server(Some(&language.name())) {
+                    if settings
+                        .language(Some(&language.name()))
+                        .enable_language_server
+                    {
                         let worktree = file.worktree.read(cx);
                         language_servers_to_start.push((
                             worktree.id(),
@@ -645,7 +649,10 @@ impl Project {
         let mut language_servers_to_stop = Vec::new();
         for language in self.languages.to_vec() {
             for lsp_adapter in language.lsp_adapters() {
-                if !settings.enable_language_server(Some(&language.name())) {
+                if !settings
+                    .language(Some(&language.name()))
+                    .enable_language_server
+                {
                     let lsp_name = &lsp_adapter.name;
                     for (worktree_id, started_lsp_name) in self.language_server_ids.keys() {
                         if lsp_name == started_lsp_name {
@@ -2178,10 +2185,7 @@ impl Project {
         language: Arc<Language>,
         cx: &mut ModelContext<Self>,
     ) {
-        if !cx
-            .global::<Settings>()
-            .enable_language_server(Some(&language.name()))
-        {
+        if !language_settings(None, Some(&language.name()), cx).enable_language_server {
             return;
         }
 
@@ -3228,24 +3232,18 @@ impl Project {
 
                 let mut project_transaction = ProjectTransaction::default();
                 for (buffer, buffer_abs_path, language_server) in &buffers_with_paths_and_servers {
-                    let (
-                        format_on_save,
-                        remove_trailing_whitespace,
-                        ensure_final_newline,
-                        formatter,
-                        tab_size,
-                    ) = buffer.read_with(&cx, |buffer, cx| {
-                        let settings = cx.global::<Settings>();
+                    let settings = buffer.read_with(&cx, |buffer, cx| {
                         let language_name = buffer.language().map(|language| language.name());
-                        (
-                            settings.format_on_save(language_name.as_deref()),
-                            settings.remove_trailing_whitespace_on_save(language_name.as_deref()),
-                            settings.ensure_final_newline_on_save(language_name.as_deref()),
-                            settings.formatter(language_name.as_deref()),
-                            settings.tab_size(language_name.as_deref()),
-                        )
+                        language_settings(buffer_abs_path.as_deref(), language_name.as_deref(), cx)
+                            .clone()
                     });
 
+                    let remove_trailing_whitespace = settings.remove_trailing_whitespace_on_save;
+                    let ensure_final_newline = settings.ensure_final_newline_on_save;
+                    let format_on_save = settings.format_on_save.clone();
+                    let formatter = settings.formatter.clone();
+                    let tab_size = settings.tab_size;
+
                     // First, format buffer's whitespace according to the settings.
                     let trailing_whitespace_diff = if remove_trailing_whitespace {
                         Some(

crates/project/src/project_tests.rs šŸ”—

@@ -1,10 +1,9 @@
 use crate::{worktree::WorktreeHandle, Event, *};
-use fs::LineEnding;
-use fs::{FakeFs, RealFs};
+use fs::{FakeFs, LineEnding, RealFs};
 use futures::{future, StreamExt};
-use gpui::AppContext;
-use gpui::{executor::Deterministic, test::subscribe};
+use gpui::{executor::Deterministic, test::subscribe, AppContext};
 use language::{
+    language_settings::{AllLanguageSettings, LanguageSettingsContent},
     tree_sitter_rust, tree_sitter_typescript, Diagnostic, FakeLspAdapter, LanguageConfig,
     OffsetRangeExt, Point, ToPoint,
 };
@@ -26,6 +25,9 @@ fn init_logger() {
 
 #[gpui::test]
 async fn test_symlinks(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+    cx.foreground().allow_parking();
+
     let dir = temp_tree(json!({
         "root": {
             "apple": "",
@@ -65,7 +67,7 @@ async fn test_managing_language_servers(
     deterministic: Arc<Deterministic>,
     cx: &mut gpui::TestAppContext,
 ) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let mut rust_language = Language::new(
         LanguageConfig {
@@ -451,7 +453,7 @@ async fn test_managing_language_servers(
 
 #[gpui::test]
 async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let mut language = Language::new(
         LanguageConfig {
@@ -556,7 +558,7 @@ async fn test_reporting_fs_changes_to_language_servers(cx: &mut gpui::TestAppCon
 
 #[gpui::test]
 async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
@@ -648,7 +650,7 @@ async fn test_single_file_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
@@ -719,7 +721,7 @@ async fn test_hidden_worktrees_diagnostics(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let progress_token = "the-progress-token";
     let mut language = Language::new(
@@ -847,7 +849,7 @@ async fn test_disk_based_diagnostics_progress(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let progress_token = "the-progress-token";
     let mut language = Language::new(
@@ -925,7 +927,7 @@ async fn test_restarting_server_with_diagnostics_running(cx: &mut gpui::TestAppC
 
 #[gpui::test]
 async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let mut language = Language::new(
         LanguageConfig {
@@ -973,11 +975,8 @@ async fn test_restarted_server_reporting_invalid_buffer_version(cx: &mut gpui::T
 }
 
 #[gpui::test]
-async fn test_toggling_enable_language_server(
-    deterministic: Arc<Deterministic>,
-    cx: &mut gpui::TestAppContext,
-) {
-    deterministic.forbid_parking();
+async fn test_toggling_enable_language_server(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
 
     let mut rust = Language::new(
         LanguageConfig {
@@ -1051,14 +1050,16 @@ async fn test_toggling_enable_language_server(
 
     // Disable Rust language server, ensuring only that server gets stopped.
     cx.update(|cx| {
-        cx.update_global(|settings: &mut Settings, _| {
-            settings.language_overrides.insert(
-                Arc::from("Rust"),
-                settings::EditorSettings {
-                    enable_language_server: Some(false),
-                    ..Default::default()
-                },
-            );
+        cx.update_global(|settings: &mut SettingsStore, cx| {
+            settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.languages.insert(
+                    Arc::from("Rust"),
+                    LanguageSettingsContent {
+                        enable_language_server: Some(false),
+                        ..Default::default()
+                    },
+                );
+            });
         })
     });
     fake_rust_server_1
@@ -1068,21 +1069,23 @@ async fn test_toggling_enable_language_server(
     // Enable Rust and disable JavaScript language servers, ensuring that the
     // former gets started again and that the latter stops.
     cx.update(|cx| {
-        cx.update_global(|settings: &mut Settings, _| {
-            settings.language_overrides.insert(
-                Arc::from("Rust"),
-                settings::EditorSettings {
-                    enable_language_server: Some(true),
-                    ..Default::default()
-                },
-            );
-            settings.language_overrides.insert(
-                Arc::from("JavaScript"),
-                settings::EditorSettings {
-                    enable_language_server: Some(false),
-                    ..Default::default()
-                },
-            );
+        cx.update_global(|settings: &mut SettingsStore, cx| {
+            settings.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.languages.insert(
+                    Arc::from("Rust"),
+                    LanguageSettingsContent {
+                        enable_language_server: Some(true),
+                        ..Default::default()
+                    },
+                );
+                settings.languages.insert(
+                    Arc::from("JavaScript"),
+                    LanguageSettingsContent {
+                        enable_language_server: Some(false),
+                        ..Default::default()
+                    },
+                );
+            });
         })
     });
     let mut fake_rust_server_2 = fake_rust_servers.next().await.unwrap();
@@ -1102,7 +1105,7 @@ async fn test_toggling_enable_language_server(
 
 #[gpui::test]
 async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let mut language = Language::new(
         LanguageConfig {
@@ -1388,7 +1391,7 @@ async fn test_transforming_diagnostics(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let text = concat!(
         "let one = ;\n", //
@@ -1457,9 +1460,7 @@ async fn test_empty_diagnostic_ranges(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppContext) {
-    println!("hello from stdout");
-    eprintln!("hello from stderr");
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let fs = FakeFs::new(cx.background());
     fs.insert_tree("/dir", json!({ "a.rs": "one two three" }))
@@ -1515,7 +1516,7 @@ async fn test_diagnostics_from_multiple_language_servers(cx: &mut gpui::TestAppC
 
 #[gpui::test]
 async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let mut language = Language::new(
         LanguageConfig {
@@ -1673,7 +1674,7 @@ async fn test_edits_from_lsp_with_past_version(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let text = "
         use a::b;
@@ -1781,7 +1782,7 @@ async fn test_edits_from_lsp_with_edits_on_adjacent_lines(cx: &mut gpui::TestApp
 
 #[gpui::test]
 async fn test_invalid_edits_from_lsp(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let text = "
         use a::b;
@@ -1902,6 +1903,8 @@ fn chunks_with_diagnostics<T: ToOffset + ToPoint>(
 
 #[gpui::test(iterations = 10)]
 async fn test_definition(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let mut language = Language::new(
         LanguageConfig {
             name: "Rust".into(),
@@ -2001,6 +2004,8 @@ async fn test_definition(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let mut language = Language::new(
         LanguageConfig {
             name: "TypeScript".into(),
@@ -2085,6 +2090,8 @@ async fn test_completions_without_edit_ranges(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let mut language = Language::new(
         LanguageConfig {
             name: "TypeScript".into(),
@@ -2138,6 +2145,8 @@ async fn test_completions_with_carriage_returns(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test(iterations = 10)]
 async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let mut language = Language::new(
         LanguageConfig {
             name: "TypeScript".into(),
@@ -2254,6 +2263,8 @@ async fn test_apply_code_actions_with_commands(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test(iterations = 10)]
 async fn test_save_file(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/dir",
@@ -2284,6 +2295,8 @@ async fn test_save_file(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/dir",
@@ -2313,6 +2326,8 @@ async fn test_save_in_single_file_worktree(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_save_as(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree("/dir", json!({})).await;
 
@@ -2373,6 +2388,9 @@ async fn test_rescan_and_remote_updates(
     deterministic: Arc<Deterministic>,
     cx: &mut gpui::TestAppContext,
 ) {
+    init_test(cx);
+    cx.foreground().allow_parking();
+
     let dir = temp_tree(json!({
         "a": {
             "file1": "",
@@ -2529,6 +2547,8 @@ async fn test_buffer_identity_across_renames(
     deterministic: Arc<Deterministic>,
     cx: &mut gpui::TestAppContext,
 ) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/dir",
@@ -2577,6 +2597,8 @@ async fn test_buffer_identity_across_renames(
 
 #[gpui::test]
 async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/dir",
@@ -2621,6 +2643,8 @@ async fn test_buffer_deduping(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/dir",
@@ -2765,6 +2789,8 @@ async fn test_buffer_is_dirty(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let initial_contents = "aaa\nbbbbb\nc\n";
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
@@ -2844,6 +2870,8 @@ async fn test_buffer_file_changes_on_disk(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/dir",
@@ -2904,7 +2932,7 @@ async fn test_buffer_line_endings(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
@@ -3146,7 +3174,7 @@ async fn test_grouped_diagnostics(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_rename(cx: &mut gpui::TestAppContext) {
-    cx.foreground().forbid_parking();
+    init_test(cx);
 
     let mut language = Language::new(
         LanguageConfig {
@@ -3284,6 +3312,8 @@ async fn test_rename(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_search(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let fs = FakeFs::new(cx.background());
     fs.insert_tree(
         "/dir",
@@ -3339,6 +3369,8 @@ async fn test_search(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let search_query = "file";
 
     let fs = FakeFs::new(cx.background());
@@ -3447,6 +3479,8 @@ async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let search_query = "file";
 
     let fs = FakeFs::new(cx.background());
@@ -3554,6 +3588,8 @@ async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) {
 
 #[gpui::test]
 async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) {
+    init_test(cx);
+
     let search_query = "file";
 
     let fs = FakeFs::new(cx.background());
@@ -3680,3 +3716,12 @@ async fn search(
         })
         .collect())
 }
+
+fn init_test(cx: &mut gpui::TestAppContext) {
+    cx.foreground().forbid_parking();
+
+    cx.update(|cx| {
+        cx.set_global(SettingsStore::test(cx));
+        language::init(cx);
+    });
+}

crates/project_panel/Cargo.toml šŸ”—

@@ -24,6 +24,7 @@ futures.workspace = true
 unicase = "2.6"
 
 [dev-dependencies]
+language = { path = "../language", features = ["test-support"] }
 editor = { path = "../editor", features = ["test-support"] }
 gpui = { path = "../gpui", features = ["test-support"] }
 workspace = { path = "../workspace", features = ["test-support"] }

crates/project_panel/src/project_panel.rs šŸ”—

@@ -1360,15 +1360,12 @@ mod tests {
     use gpui::{TestAppContext, ViewHandle};
     use project::FakeFs;
     use serde_json::json;
+    use settings::SettingsStore;
     use std::{collections::HashSet, path::Path};
 
     #[gpui::test]
     async fn test_visible_list(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-        cx.update(|cx| {
-            let settings = Settings::test(cx);
-            cx.set_global(settings);
-        });
+        init_test(cx);
 
         let fs = FakeFs::new(cx.background());
         fs.insert_tree(
@@ -1456,11 +1453,7 @@ mod tests {
 
     #[gpui::test(iterations = 30)]
     async fn test_editing_files(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-        cx.update(|cx| {
-            let settings = Settings::test(cx);
-            cx.set_global(settings);
-        });
+        init_test(cx);
 
         let fs = FakeFs::new(cx.background());
         fs.insert_tree(
@@ -1776,11 +1769,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_copy_paste(cx: &mut gpui::TestAppContext) {
-        cx.foreground().forbid_parking();
-        cx.update(|cx| {
-            let settings = Settings::test(cx);
-            cx.set_global(settings);
-        });
+        init_test(cx);
 
         let fs = FakeFs::new(cx.background());
         fs.insert_tree(
@@ -1940,4 +1929,12 @@ mod tests {
 
         result
     }
+
+    fn init_test(cx: &mut TestAppContext) {
+        cx.foreground().forbid_parking();
+        cx.update(|cx| {
+            cx.set_global(SettingsStore::test(cx));
+            language::init(cx);
+        });
+    }
 }

crates/project_symbols/Cargo.toml šŸ”—

@@ -30,3 +30,4 @@ gpui = { path = "../gpui", features = ["test-support"] }
 language = { path = "../language", features = ["test-support"] }
 lsp = { path = "../lsp", features = ["test-support"] }
 project = { path = "../project", features = ["test-support"] }
+workspace = { path = "../workspace", features = ["test-support"] }

crates/project_symbols/src/project_symbols.rs šŸ”—

@@ -244,12 +244,12 @@ mod tests {
     use gpui::{serde_json::json, TestAppContext};
     use language::{FakeLspAdapter, Language, LanguageConfig};
     use project::FakeFs;
+    use settings::SettingsStore;
     use std::{path::Path, sync::Arc};
 
     #[gpui::test]
     async fn test_project_symbols(cx: &mut TestAppContext) {
-        cx.foreground().forbid_parking();
-        cx.update(|cx| cx.set_global(Settings::test(cx)));
+        init_test(cx);
 
         let mut language = Language::new(
             LanguageConfig {
@@ -368,6 +368,15 @@ mod tests {
         });
     }
 
+    fn init_test(cx: &mut TestAppContext) {
+        cx.foreground().forbid_parking();
+        cx.update(|cx| {
+            cx.set_global(Settings::test(cx));
+            cx.set_global(SettingsStore::test(cx));
+            language::init(cx);
+        });
+    }
+
     fn symbol(name: &str, path: impl AsRef<Path>) -> lsp::SymbolInformation {
         #[allow(deprecated)]
         lsp::SymbolInformation {

crates/search/src/buffer_search.rs šŸ”—

@@ -655,19 +655,11 @@ mod tests {
     use editor::{DisplayPoint, Editor};
     use gpui::{color::Color, test::EmptyView, TestAppContext};
     use language::Buffer;
-    use std::sync::Arc;
     use unindent::Unindent as _;
 
     #[gpui::test]
     async fn test_search_simple(cx: &mut TestAppContext) {
-        let fonts = cx.font_cache();
-        let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default);
-        theme.search.match_background = Color::red();
-        cx.update(|cx| {
-            let mut settings = Settings::test(cx);
-            settings.theme = Arc::new(theme);
-            cx.set_global(settings)
-        });
+        crate::project_search::tests::init_test(cx);
 
         let buffer = cx.add_model(|cx| {
             Buffer::new(

crates/search/src/project_search.rs šŸ”—

@@ -1146,25 +1146,18 @@ impl ToolbarItemView for ProjectSearchBar {
 }
 
 #[cfg(test)]
-mod tests {
+pub mod tests {
     use super::*;
     use editor::DisplayPoint;
     use gpui::{color::Color, executor::Deterministic, TestAppContext};
     use project::FakeFs;
     use serde_json::json;
+    use settings::SettingsStore;
     use std::sync::Arc;
 
     #[gpui::test]
     async fn test_project_search(deterministic: Arc<Deterministic>, cx: &mut TestAppContext) {
-        let fonts = cx.font_cache();
-        let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default);
-        theme.search.match_background = Color::red();
-        cx.update(|cx| {
-            let mut settings = Settings::test(cx);
-            settings.theme = Arc::new(theme);
-            cx.set_global(settings);
-            cx.set_global(ActiveSearches::default());
-        });
+        init_test(cx);
 
         let fs = FakeFs::new(cx.background());
         fs.insert_tree(
@@ -1279,4 +1272,20 @@ mod tests {
             );
         });
     }
+
+    pub fn init_test(cx: &mut TestAppContext) {
+        let fonts = cx.font_cache();
+        let mut theme = gpui::fonts::with_font_cache(fonts.clone(), theme::Theme::default);
+        theme.search.match_background = Color::red();
+
+        cx.update(|cx| {
+            cx.set_global(SettingsStore::test(cx));
+            cx.set_global(ActiveSearches::default());
+            let mut settings = Settings::test(cx);
+            settings.theme = Arc::new(theme);
+            cx.set_global(settings);
+
+            language::init(cx);
+        });
+    }
 }

crates/settings/src/settings.rs šŸ”—

@@ -3,7 +3,7 @@ mod keymap_file;
 mod settings_file;
 mod settings_store;
 
-use anyhow::bail;
+use anyhow::{bail, Result};
 use gpui::{
     font_cache::{FamilyId, FontCache},
     fonts, AppContext, AssetSource,
@@ -19,7 +19,7 @@ use sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
-use std::{borrow::Cow, collections::HashMap, num::NonZeroU32, path::Path, str, sync::Arc};
+use std::{borrow::Cow, collections::HashMap, str, sync::Arc};
 use theme::{Theme, ThemeRegistry};
 use util::ResultExt as _;
 
@@ -33,7 +33,6 @@ pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settin
 
 #[derive(Clone)]
 pub struct Settings {
-    pub features: Features,
     pub buffer_font_family_name: String,
     pub buffer_font_features: fonts::Features,
     pub buffer_font_family: FamilyId,
@@ -46,13 +45,8 @@ pub struct Settings {
     pub show_call_status_icon: bool,
     pub autosave: Autosave,
     pub default_dock_anchor: DockAnchor,
-    pub editor_defaults: EditorSettings,
-    pub editor_overrides: EditorSettings,
     pub git: GitSettings,
     pub git_overrides: GitSettings,
-    pub copilot: CopilotSettings,
-    pub language_defaults: HashMap<Arc<str>, EditorSettings>,
-    pub language_overrides: HashMap<Arc<str>, EditorSettings>,
     pub lsp: HashMap<Arc<str>, LspSettings>,
     pub theme: Arc<Theme>,
     pub base_keymap: BaseKeymap,
@@ -67,7 +61,7 @@ impl Setting for Settings {
         defaults: &Self::FileContent,
         user_values: &[&Self::FileContent],
         cx: &AppContext,
-    ) -> Self {
+    ) -> Result<Self> {
         let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
         let themes = cx.global::<Arc<ThemeRegistry>>();
 
@@ -90,50 +84,18 @@ impl Setting for Settings {
             show_call_status_icon: defaults.show_call_status_icon.unwrap(),
             autosave: defaults.autosave.unwrap(),
             default_dock_anchor: defaults.default_dock_anchor.unwrap(),
-            editor_defaults: EditorSettings {
-                tab_size: defaults.editor.tab_size,
-                hard_tabs: defaults.editor.hard_tabs,
-                soft_wrap: defaults.editor.soft_wrap,
-                preferred_line_length: defaults.editor.preferred_line_length,
-                remove_trailing_whitespace_on_save: defaults
-                    .editor
-                    .remove_trailing_whitespace_on_save,
-                ensure_final_newline_on_save: defaults.editor.ensure_final_newline_on_save,
-                format_on_save: defaults.editor.format_on_save.clone(),
-                formatter: defaults.editor.formatter.clone(),
-                enable_language_server: defaults.editor.enable_language_server,
-                show_copilot_suggestions: defaults.editor.show_copilot_suggestions,
-                show_whitespaces: defaults.editor.show_whitespaces,
-            },
-            editor_overrides: Default::default(),
-            copilot: CopilotSettings {
-                disabled_globs: defaults
-                    .copilot
-                    .clone()
-                    .unwrap()
-                    .disabled_globs
-                    .unwrap()
-                    .into_iter()
-                    .map(|s| glob::Pattern::new(&s).unwrap())
-                    .collect(),
-            },
             git: defaults.git.unwrap(),
             git_overrides: Default::default(),
-            language_defaults: defaults.languages.clone(),
-            language_overrides: Default::default(),
             lsp: defaults.lsp.clone(),
             theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
             base_keymap: Default::default(),
-            features: Features {
-                copilot: defaults.features.copilot.unwrap(),
-            },
         };
 
         for value in user_values.into_iter().copied().cloned() {
             this.set_user_settings(value, themes.as_ref(), cx.font_cache());
         }
 
-        this
+        Ok(this)
     }
 
     fn json_schema(
@@ -247,18 +209,6 @@ impl BaseKeymap {
             .unwrap_or_default()
     }
 }
-
-#[derive(Clone, Debug, Default)]
-pub struct CopilotSettings {
-    pub disabled_globs: Vec<glob::Pattern>,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct CopilotSettingsContent {
-    #[serde(default)]
-    pub disabled_globs: Option<Vec<String>>,
-}
-
 #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 pub struct GitSettings {
     pub git_gutter: Option<GitGutter>,
@@ -273,52 +223,6 @@ pub enum GitGutter {
     Hide,
 }
 
-pub struct GitGutterConfig {}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-pub struct EditorSettings {
-    pub tab_size: Option<NonZeroU32>,
-    pub hard_tabs: Option<bool>,
-    pub soft_wrap: Option<SoftWrap>,
-    pub preferred_line_length: Option<u32>,
-    pub format_on_save: Option<FormatOnSave>,
-    pub remove_trailing_whitespace_on_save: Option<bool>,
-    pub ensure_final_newline_on_save: Option<bool>,
-    pub formatter: Option<Formatter>,
-    pub enable_language_server: Option<bool>,
-    pub show_copilot_suggestions: Option<bool>,
-    pub show_whitespaces: Option<ShowWhitespaces>,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum SoftWrap {
-    None,
-    EditorWidth,
-    PreferredLineLength,
-}
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum FormatOnSave {
-    On,
-    Off,
-    LanguageServer,
-    External {
-        command: String,
-        arguments: Vec<String>,
-    },
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum Formatter {
-    LanguageServer,
-    External {
-        command: String,
-        arguments: Vec<String>,
-    },
-}
-
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum Autosave {
@@ -374,8 +278,6 @@ pub struct SettingsFileContent {
     #[serde(default)]
     pub buffer_font_features: Option<fonts::Features>,
     #[serde(default)]
-    pub copilot: Option<CopilotSettingsContent>,
-    #[serde(default)]
     pub active_pane_magnification: Option<f32>,
     #[serde(default)]
     pub cursor_blink: Option<bool>,
@@ -391,21 +293,14 @@ pub struct SettingsFileContent {
     pub autosave: Option<Autosave>,
     #[serde(default)]
     pub default_dock_anchor: Option<DockAnchor>,
-    #[serde(flatten)]
-    pub editor: EditorSettings,
     #[serde(default)]
     pub git: Option<GitSettings>,
     #[serde(default)]
-    #[serde(alias = "language_overrides")]
-    pub languages: HashMap<Arc<str>, EditorSettings>,
-    #[serde(default)]
     pub lsp: HashMap<Arc<str>, LspSettings>,
     #[serde(default)]
     pub theme: Option<String>,
     #[serde(default)]
     pub base_keymap: Option<BaseKeymap>,
-    #[serde(default)]
-    pub features: FeaturesContent,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -414,26 +309,6 @@ pub struct LspSettings {
     pub initialization_options: Option<Value>,
 }
 
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct Features {
-    pub copilot: bool,
-}
-
-#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct FeaturesContent {
-    pub copilot: Option<bool>,
-}
-
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub enum ShowWhitespaces {
-    #[default]
-    Selection,
-    None,
-    All,
-}
-
 impl Settings {
     pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
         match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
@@ -448,12 +323,6 @@ impl Settings {
         font_cache: &FontCache,
         themes: &ThemeRegistry,
     ) -> Self {
-        #[track_caller]
-        fn required<T>(value: Option<T>) -> Option<T> {
-            assert!(value.is_some(), "missing default setting value");
-            value
-        }
-
         let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
             str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
         )
@@ -478,44 +347,11 @@ impl Settings {
             show_call_status_icon: defaults.show_call_status_icon.unwrap(),
             autosave: defaults.autosave.unwrap(),
             default_dock_anchor: defaults.default_dock_anchor.unwrap(),
-            editor_defaults: EditorSettings {
-                tab_size: required(defaults.editor.tab_size),
-                hard_tabs: required(defaults.editor.hard_tabs),
-                soft_wrap: required(defaults.editor.soft_wrap),
-                preferred_line_length: required(defaults.editor.preferred_line_length),
-                remove_trailing_whitespace_on_save: required(
-                    defaults.editor.remove_trailing_whitespace_on_save,
-                ),
-                ensure_final_newline_on_save: required(
-                    defaults.editor.ensure_final_newline_on_save,
-                ),
-                format_on_save: required(defaults.editor.format_on_save),
-                formatter: required(defaults.editor.formatter),
-                enable_language_server: required(defaults.editor.enable_language_server),
-                show_copilot_suggestions: required(defaults.editor.show_copilot_suggestions),
-                show_whitespaces: required(defaults.editor.show_whitespaces),
-            },
-            editor_overrides: Default::default(),
-            copilot: CopilotSettings {
-                disabled_globs: defaults
-                    .copilot
-                    .unwrap()
-                    .disabled_globs
-                    .unwrap()
-                    .into_iter()
-                    .map(|s| glob::Pattern::new(&s).unwrap())
-                    .collect(),
-            },
             git: defaults.git.unwrap(),
             git_overrides: Default::default(),
-            language_defaults: defaults.languages,
-            language_overrides: Default::default(),
             lsp: defaults.lsp.clone(),
             theme: themes.get(&defaults.theme.unwrap()).unwrap(),
             base_keymap: Default::default(),
-            features: Features {
-                copilot: defaults.features.copilot.unwrap(),
-            },
         }
     }
 
@@ -565,121 +401,11 @@ impl Settings {
         merge(&mut self.autosave, data.autosave);
         merge(&mut self.default_dock_anchor, data.default_dock_anchor);
         merge(&mut self.base_keymap, data.base_keymap);
-        merge(&mut self.features.copilot, data.features.copilot);
-
-        if let Some(copilot) = data.copilot {
-            if let Some(disabled_globs) = copilot.disabled_globs {
-                self.copilot.disabled_globs = disabled_globs
-                    .into_iter()
-                    .filter_map(|s| glob::Pattern::new(&s).ok())
-                    .collect()
-            }
-        }
-        self.editor_overrides = data.editor;
+
         self.git_overrides = data.git.unwrap_or_default();
-        self.language_overrides = data.languages;
         self.lsp = data.lsp;
     }
 
-    pub fn with_language_defaults(
-        mut self,
-        language_name: impl Into<Arc<str>>,
-        overrides: EditorSettings,
-    ) -> Self {
-        self.language_defaults
-            .insert(language_name.into(), overrides);
-        self
-    }
-
-    pub fn features(&self) -> &Features {
-        &self.features
-    }
-
-    pub fn show_copilot_suggestions(&self, language: Option<&str>, path: Option<&Path>) -> bool {
-        if !self.features.copilot {
-            return false;
-        }
-
-        if !self.copilot_enabled_for_language(language) {
-            return false;
-        }
-
-        if let Some(path) = path {
-            if !self.copilot_enabled_for_path(path) {
-                return false;
-            }
-        }
-
-        true
-    }
-
-    pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
-        !self
-            .copilot
-            .disabled_globs
-            .iter()
-            .any(|glob| glob.matches_path(path))
-    }
-
-    pub fn copilot_enabled_for_language(&self, language: Option<&str>) -> bool {
-        self.language_setting(language, |settings| settings.show_copilot_suggestions)
-    }
-
-    pub fn tab_size(&self, language: Option<&str>) -> NonZeroU32 {
-        self.language_setting(language, |settings| settings.tab_size)
-    }
-
-    pub fn show_whitespaces(&self, language: Option<&str>) -> ShowWhitespaces {
-        self.language_setting(language, |settings| settings.show_whitespaces)
-    }
-
-    pub fn hard_tabs(&self, language: Option<&str>) -> bool {
-        self.language_setting(language, |settings| settings.hard_tabs)
-    }
-
-    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
-        self.language_setting(language, |settings| settings.soft_wrap)
-    }
-
-    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
-        self.language_setting(language, |settings| settings.preferred_line_length)
-    }
-
-    pub fn remove_trailing_whitespace_on_save(&self, language: Option<&str>) -> bool {
-        self.language_setting(language, |settings| {
-            settings.remove_trailing_whitespace_on_save.clone()
-        })
-    }
-
-    pub fn ensure_final_newline_on_save(&self, language: Option<&str>) -> bool {
-        self.language_setting(language, |settings| {
-            settings.ensure_final_newline_on_save.clone()
-        })
-    }
-
-    pub fn format_on_save(&self, language: Option<&str>) -> FormatOnSave {
-        self.language_setting(language, |settings| settings.format_on_save.clone())
-    }
-
-    pub fn formatter(&self, language: Option<&str>) -> Formatter {
-        self.language_setting(language, |settings| settings.formatter.clone())
-    }
-
-    pub fn enable_language_server(&self, language: Option<&str>) -> bool {
-        self.language_setting(language, |settings| settings.enable_language_server)
-    }
-
-    fn language_setting<F, R>(&self, language: Option<&str>, f: F) -> R
-    where
-        F: Fn(&EditorSettings) -> Option<R>,
-    {
-        None.or_else(|| language.and_then(|l| self.language_overrides.get(l).and_then(&f)))
-            .or_else(|| f(&self.editor_overrides))
-            .or_else(|| language.and_then(|l| self.language_defaults.get(l).and_then(&f)))
-            .or_else(|| f(&self.editor_defaults))
-            .expect("missing default")
-    }
-
     pub fn git_gutter(&self) -> GitGutter {
         self.git_overrides.git_gutter.unwrap_or_else(|| {
             self.git
@@ -706,29 +432,11 @@ impl Settings {
             show_call_status_icon: true,
             autosave: Autosave::Off,
             default_dock_anchor: DockAnchor::Bottom,
-            editor_defaults: EditorSettings {
-                tab_size: Some(4.try_into().unwrap()),
-                hard_tabs: Some(false),
-                soft_wrap: Some(SoftWrap::None),
-                preferred_line_length: Some(80),
-                remove_trailing_whitespace_on_save: Some(true),
-                ensure_final_newline_on_save: Some(true),
-                format_on_save: Some(FormatOnSave::On),
-                formatter: Some(Formatter::LanguageServer),
-                enable_language_server: Some(true),
-                show_copilot_suggestions: Some(true),
-                show_whitespaces: Some(ShowWhitespaces::None),
-            },
-            editor_overrides: Default::default(),
-            copilot: Default::default(),
             git: Default::default(),
             git_overrides: Default::default(),
-            language_defaults: Default::default(),
-            language_overrides: Default::default(),
             lsp: Default::default(),
             theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
             base_keymap: Default::default(),
-            features: Features { copilot: true },
         }
     }
 

crates/settings/src/settings_file.rs šŸ”—

@@ -42,12 +42,12 @@ pub fn test_settings() -> String {
         serde_json::json!({
             "buffer_font_family": "Courier",
             "buffer_font_features": {},
-            "default_buffer_font_size": 14,
-            "preferred_line_length": 80,
+            "buffer_font_size": 14,
             "theme": theme::EMPTY_THEME_NAME,
         }),
         &mut value,
     );
+    value.as_object_mut().unwrap().remove("languages");
     serde_json::to_string(&value).unwrap()
 }
 

crates/settings/src/settings_store.rs šŸ”—

@@ -1,5 +1,5 @@
-use anyhow::{anyhow, Result};
-use collections::{btree_map, hash_map, BTreeMap, HashMap, HashSet};
+use anyhow::Result;
+use collections::{btree_map, hash_map, BTreeMap, HashMap};
 use gpui::AppContext;
 use lazy_static::lazy_static;
 use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
@@ -8,7 +8,6 @@ use smallvec::SmallVec;
 use std::{
     any::{type_name, Any, TypeId},
     fmt::Debug,
-    mem,
     ops::Range,
     path::Path,
     str,
@@ -37,7 +36,9 @@ pub trait Setting: 'static {
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
         cx: &AppContext,
-    ) -> Self;
+    ) -> Result<Self>
+    where
+        Self: Sized;
 
     fn json_schema(generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams) -> RootSchema {
         generator.root_schema_for::<Self::FileContent>()
@@ -57,7 +58,7 @@ pub trait Setting: 'static {
     fn load_via_json_merge(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-    ) -> Self
+    ) -> Result<Self>
     where
         Self: DeserializeOwned,
     {
@@ -65,7 +66,11 @@ pub trait Setting: 'static {
         for value in [default_value].iter().chain(user_values) {
             merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
         }
-        serde_json::from_value(merged).unwrap()
+        Ok(serde_json::from_value(merged)?)
+    }
+
+    fn missing_default() -> anyhow::Error {
+        anyhow::anyhow!("missing default")
     }
 }
 
@@ -78,10 +83,9 @@ pub struct SettingsJsonSchemaParams<'a> {
 #[derive(Default)]
 pub struct SettingsStore {
     setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
-    default_deserialized_settings: Option<DeserializedSettingMap>,
-    user_deserialized_settings: Option<DeserializedSettingMap>,
-    local_deserialized_settings: BTreeMap<Arc<Path>, DeserializedSettingMap>,
-    changed_setting_types: HashSet<TypeId>,
+    default_deserialized_settings: Option<serde_json::Value>,
+    user_deserialized_settings: Option<serde_json::Value>,
+    local_deserialized_settings: BTreeMap<Arc<Path>, serde_json::Value>,
     tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
 }
 
@@ -98,9 +102,9 @@ trait AnySettingValue {
     fn load_setting(
         &self,
         default_value: &DeserializedSetting,
-        custom: &[&DeserializedSetting],
+        custom: &[DeserializedSetting],
         cx: &AppContext,
-    ) -> Box<dyn Any>;
+    ) -> Result<Box<dyn Any>>;
     fn value_for_path(&self, path: Option<&Path>) -> &dyn Any;
     fn set_global_value(&mut self, value: Box<dyn Any>);
     fn set_local_value(&mut self, path: Arc<Path>, value: Box<dyn Any>);
@@ -113,11 +117,6 @@ trait AnySettingValue {
 
 struct DeserializedSetting(Box<dyn Any>);
 
-struct DeserializedSettingMap {
-    untyped: serde_json::Value,
-    typed: HashMap<TypeId, DeserializedSetting>,
-}
-
 impl SettingsStore {
     /// Add a new type of setting to the store.
     pub fn register_setting<T: Setting>(&mut self, cx: &AppContext) {
@@ -132,23 +131,27 @@ impl SettingsStore {
             local_values: Vec::new(),
         }));
 
-        if let Some(default_settings) = self.default_deserialized_settings.as_mut() {
-            Self::load_setting_in_map(setting_type_id, setting_value, default_settings);
+        if let Some(default_settings) = &self.default_deserialized_settings {
+            if let Some(default_settings) = setting_value
+                .deserialize_setting(default_settings)
+                .log_err()
+            {
+                let mut user_values_stack = Vec::new();
 
-            let mut user_values_stack = Vec::new();
-            if let Some(user_settings) = self.user_deserialized_settings.as_mut() {
-                Self::load_setting_in_map(setting_type_id, setting_value, user_settings);
-                if let Some(user_value) = user_settings.typed.get(&setting_type_id) {
-                    user_values_stack = vec![user_value];
+                if let Some(user_settings) = &self.user_deserialized_settings {
+                    if let Some(user_settings) =
+                        setting_value.deserialize_setting(user_settings).log_err()
+                    {
+                        user_values_stack = vec![user_settings];
+                    }
                 }
-            }
 
-            if let Some(default_deserialized_value) = default_settings.typed.get(&setting_type_id) {
-                setting_value.set_global_value(setting_value.load_setting(
-                    default_deserialized_value,
-                    &user_values_stack,
-                    cx,
-                ));
+                if let Some(setting) = setting_value
+                    .load_setting(&default_settings, &user_values_stack, cx)
+                    .log_err()
+                {
+                    setting_value.set_global_value(setting);
+                }
             }
         }
     }
@@ -173,7 +176,7 @@ impl SettingsStore {
     pub fn untyped_user_settings(&self) -> &serde_json::Value {
         self.user_deserialized_settings
             .as_ref()
-            .map_or(&serde_json::Value::Null, |s| &s.untyped)
+            .unwrap_or(&serde_json::Value::Null)
     }
 
     #[cfg(any(test, feature = "test-support"))]
@@ -181,6 +184,7 @@ impl SettingsStore {
         let mut this = Self::default();
         this.set_default_settings(&crate::test_settings(), cx)
             .unwrap();
+        this.set_user_settings("{}", cx).unwrap();
         this
     }
 
@@ -194,11 +198,11 @@ impl SettingsStore {
         cx: &AppContext,
         update: impl FnOnce(&mut T::FileContent),
     ) {
-        let old_text = if let Some(user_settings) = &self.user_deserialized_settings {
-            serde_json::to_string(&user_settings.untyped).unwrap()
-        } else {
-            String::new()
-        };
+        if self.user_deserialized_settings.is_none() {
+            self.set_user_settings("{}", cx).unwrap();
+        }
+        let old_text =
+            serde_json::to_string(self.user_deserialized_settings.as_ref().unwrap()).unwrap();
         let new_text = self.new_text_for_update::<T>(old_text, update);
         self.set_user_settings(&new_text, cx).unwrap();
     }
@@ -212,7 +216,7 @@ impl SettingsStore {
     ) -> String {
         let edits = self.edits_for_update::<T>(&old_text, update);
         let mut new_text = old_text;
-        for (range, replacement) in edits.into_iter().rev() {
+        for (range, replacement) in edits.into_iter() {
             new_text.replace_range(range, &replacement);
         }
         new_text
@@ -226,26 +230,31 @@ impl SettingsStore {
         update: impl FnOnce(&mut T::FileContent),
     ) -> Vec<(Range<usize>, String)> {
         let setting_type_id = TypeId::of::<T>();
+
         let old_content = self
-            .user_deserialized_settings
-            .as_ref()
-            .unwrap()
-            .typed
+            .setting_values
             .get(&setting_type_id)
-            .unwrap()
+            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
+            .deserialize_setting(
+                self.user_deserialized_settings
+                    .as_ref()
+                    .expect("no user settings loaded"),
+            )
+            .unwrap_or_else(|e| {
+                panic!(
+                    "could not deserialize setting type {} from user settings: {}",
+                    type_name::<T>(),
+                    e
+                )
+            })
             .0
-            .downcast_ref::<T::FileContent>()
-            .unwrap()
-            .clone();
+            .downcast::<T::FileContent>()
+            .unwrap();
         let mut new_content = old_content.clone();
         update(&mut new_content);
 
-        let mut parser = tree_sitter::Parser::new();
-        parser.set_language(tree_sitter_json::language()).unwrap();
-        let tree = parser.parse(text, None).unwrap();
-
-        let old_value = &serde_json::to_value(old_content).unwrap();
-        let new_value = &serde_json::to_value(new_content).unwrap();
+        let old_value = &serde_json::to_value(&old_content).unwrap();
+        let new_value = serde_json::to_value(new_content).unwrap();
 
         let mut key_path = Vec::new();
         if let Some(key) = T::KEY {
@@ -254,16 +263,15 @@ impl SettingsStore {
 
         let mut edits = Vec::new();
         let tab_size = self.json_tab_size();
+        let mut text = text.to_string();
         update_value_in_json_text(
-            &text,
-            &tree,
+            &mut text,
             &mut key_path,
             tab_size,
             &old_value,
             &new_value,
             &mut edits,
         );
-        edits.sort_unstable_by_key(|e| e.0.start);
         return edits;
     }
 
@@ -300,19 +308,8 @@ impl SettingsStore {
         default_settings_content: &str,
         cx: &AppContext,
     ) -> Result<()> {
-        let deserialized_setting_map = self.load_setting_map(default_settings_content)?;
-        if deserialized_setting_map.typed.len() != self.setting_values.len() {
-            return Err(anyhow!(
-                "default settings file is missing fields: {:?}",
-                self.setting_values
-                    .iter()
-                    .filter(|(type_id, _)| !deserialized_setting_map.typed.contains_key(type_id))
-                    .map(|(name, _)| *name)
-                    .collect::<Vec<_>>()
-            ));
-        }
-        self.default_deserialized_settings = Some(deserialized_setting_map);
-        self.recompute_values(false, None, None, cx);
+        self.default_deserialized_settings = Some(serde_json::from_str(default_settings_content)?);
+        self.recompute_values(None, cx)?;
         Ok(())
     }
 
@@ -322,10 +319,8 @@ impl SettingsStore {
         user_settings_content: &str,
         cx: &AppContext,
     ) -> Result<()> {
-        let user_settings = self.load_setting_map(user_settings_content)?;
-        let old_user_settings =
-            mem::replace(&mut self.user_deserialized_settings, Some(user_settings));
-        self.recompute_values(true, None, old_user_settings, cx);
+        self.user_deserialized_settings = Some(serde_json::from_str(user_settings_content)?);
+        self.recompute_values(None, cx)?;
         Ok(())
     }
 
@@ -336,14 +331,13 @@ impl SettingsStore {
         settings_content: Option<&str>,
         cx: &AppContext,
     ) -> Result<()> {
-        let removed_map = if let Some(settings_content) = settings_content {
+        if let Some(content) = settings_content {
             self.local_deserialized_settings
-                .insert(path.clone(), self.load_setting_map(settings_content)?);
-            None
+                .insert(path.clone(), serde_json::from_str(content)?);
         } else {
-            self.local_deserialized_settings.remove(&path)
-        };
-        self.recompute_values(true, Some(&path), removed_map, cx);
+            self.local_deserialized_settings.remove(&path);
+        }
+        self.recompute_values(Some(&path), cx)?;
         Ok(())
     }
 
@@ -422,136 +416,78 @@ impl SettingsStore {
 
     fn recompute_values(
         &mut self,
-        user_settings_changed: bool,
         changed_local_path: Option<&Path>,
-        old_settings_map: Option<DeserializedSettingMap>,
         cx: &AppContext,
-    ) {
-        // Identify all of the setting types that have changed.
-        let new_settings_map = if let Some(changed_path) = changed_local_path {
-            self.local_deserialized_settings.get(changed_path)
-        } else if user_settings_changed {
-            self.user_deserialized_settings.as_ref()
-        } else {
-            self.default_deserialized_settings.as_ref()
-        };
-        self.changed_setting_types.clear();
-        for map in [old_settings_map.as_ref(), new_settings_map] {
-            if let Some(map) = map {
-                self.changed_setting_types.extend(map.typed.keys());
-            }
-        }
-
-        // Reload the global and local values for every changed setting.
-        let mut user_values_stack = Vec::<&DeserializedSetting>::new();
-        for setting_type_id in self.changed_setting_types.iter() {
-            let setting_value = self.setting_values.get_mut(setting_type_id).unwrap();
-
-            // Build the prioritized list of deserialized values to pass to the setting's
-            // load function.
-            user_values_stack.clear();
-            if let Some(user_settings) = &self.user_deserialized_settings {
-                if let Some(user_value) = user_settings.typed.get(setting_type_id) {
-                    user_values_stack.push(&user_value);
+    ) -> Result<()> {
+        // Reload the global and local values for every setting.
+        let mut user_settings_stack = Vec::<DeserializedSetting>::new();
+        let mut paths_stack = Vec::<Option<&Path>>::new();
+        for setting_value in self.setting_values.values_mut() {
+            if let Some(default_settings) = &self.default_deserialized_settings {
+                let default_settings = setting_value.deserialize_setting(default_settings)?;
+
+                user_settings_stack.clear();
+                paths_stack.clear();
+
+                if let Some(user_settings) = &self.user_deserialized_settings {
+                    if let Some(user_settings) =
+                        setting_value.deserialize_setting(user_settings).log_err()
+                    {
+                        user_settings_stack.push(user_settings);
+                        paths_stack.push(None);
+                    }
                 }
-            }
 
-            let default_deserialized_value = if let Some(value) = self
-                .default_deserialized_settings
-                .as_ref()
-                .and_then(|map| map.typed.get(setting_type_id))
-            {
-                value
-            } else {
-                continue;
-            };
-
-            // If the global settings file changed, reload the global value for the field.
-            if changed_local_path.is_none() {
-                setting_value.set_global_value(setting_value.load_setting(
-                    default_deserialized_value,
-                    &user_values_stack,
-                    cx,
-                ));
-            }
-
-            // Reload the local values for the setting.
-            let user_value_stack_len = user_values_stack.len();
-            for (path, deserialized_values) in &self.local_deserialized_settings {
-                // If a local settings file changed, then avoid recomputing local
-                // settings for any path outside of that directory.
-                if changed_local_path.map_or(false, |changed_local_path| {
-                    !path.starts_with(changed_local_path)
-                }) {
-                    continue;
+                // If the global settings file changed, reload the global value for the field.
+                if changed_local_path.is_none() {
+                    setting_value.set_global_value(setting_value.load_setting(
+                        &default_settings,
+                        &user_settings_stack,
+                        cx,
+                    )?);
                 }
 
-                // Ignore recomputing settings for any path that hasn't customized that setting.
-                let Some(deserialized_value) = deserialized_values.typed.get(setting_type_id) else {
-                    continue;
-                };
-
-                // Build a stack of all of the local values for that setting.
-                user_values_stack.truncate(user_value_stack_len);
-                for (preceding_path, preceding_deserialized_values) in
-                    &self.local_deserialized_settings
-                {
-                    if preceding_path >= path {
+                // Reload the local values for the setting.
+                for (path, local_settings) in &self.local_deserialized_settings {
+                    // Build a stack of all of the local values for that setting.
+                    while let Some(prev_path) = paths_stack.last() {
+                        if let Some(prev_path) = prev_path {
+                            if !path.starts_with(prev_path) {
+                                paths_stack.pop();
+                                user_settings_stack.pop();
+                                continue;
+                            }
+                        }
                         break;
                     }
-                    if !path.starts_with(preceding_path) {
-                        continue;
-                    }
 
-                    if let Some(preceding_deserialized_value) =
-                        preceding_deserialized_values.typed.get(setting_type_id)
+                    if let Some(local_settings) =
+                        setting_value.deserialize_setting(&local_settings).log_err()
                     {
-                        user_values_stack.push(&*preceding_deserialized_value);
+                        paths_stack.push(Some(path.as_ref()));
+                        user_settings_stack.push(local_settings);
+
+                        // If a local settings file changed, then avoid recomputing local
+                        // settings for any path outside of that directory.
+                        if changed_local_path.map_or(false, |changed_local_path| {
+                            !path.starts_with(changed_local_path)
+                        }) {
+                            continue;
+                        }
+
+                        setting_value.set_local_value(
+                            path.clone(),
+                            setting_value.load_setting(
+                                &default_settings,
+                                &user_settings_stack,
+                                cx,
+                            )?,
+                        );
                     }
                 }
-                user_values_stack.push(&*deserialized_value);
-
-                // Load the local value for the field.
-                setting_value.set_local_value(
-                    path.clone(),
-                    setting_value.load_setting(default_deserialized_value, &user_values_stack, cx),
-                );
             }
         }
-    }
-
-    /// Deserialize the given JSON string into a map keyed by setting type.
-    ///
-    /// Returns an error if the string doesn't contain a valid JSON object.
-    fn load_setting_map(&self, json: &str) -> Result<DeserializedSettingMap> {
-        let mut map = DeserializedSettingMap {
-            untyped: parse_json_with_comments(json)?,
-            typed: HashMap::default(),
-        };
-        for (setting_type_id, setting_value) in self.setting_values.iter() {
-            Self::load_setting_in_map(*setting_type_id, setting_value, &mut map);
-        }
-        Ok(map)
-    }
-
-    fn load_setting_in_map(
-        setting_type_id: TypeId,
-        setting_value: &Box<dyn AnySettingValue>,
-        map: &mut DeserializedSettingMap,
-    ) {
-        let value = if let Some(setting_key) = setting_value.key() {
-            if let Some(value) = map.untyped.get(setting_key) {
-                value
-            } else {
-                return;
-            }
-        } else {
-            &map.untyped
-        };
-
-        if let Some(deserialized_value) = setting_value.deserialize_setting(&value).log_err() {
-            map.typed.insert(setting_type_id, deserialized_value);
-        }
+        Ok(())
     }
 }
 
@@ -567,18 +503,21 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
     fn load_setting(
         &self,
         default_value: &DeserializedSetting,
-        user_values: &[&DeserializedSetting],
+        user_values: &[DeserializedSetting],
         cx: &AppContext,
-    ) -> Box<dyn Any> {
+    ) -> Result<Box<dyn Any>> {
         let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap();
         let values: SmallVec<[&T::FileContent; 6]> = user_values
             .iter()
             .map(|value| value.0.downcast_ref().unwrap())
             .collect();
-        Box::new(T::load(default_value, &values, cx))
+        Ok(Box::new(T::load(default_value, &values, cx)?))
     }
 
-    fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting> {
+    fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> {
+        if let Some(key) = T::KEY {
+            json = json.get(key).unwrap_or(&serde_json::Value::Null);
+        }
         let value = T::FileContent::deserialize(json)?;
         Ok(DeserializedSetting(Box::new(value)))
     }
@@ -593,7 +532,7 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
         }
         self.global_value
             .as_ref()
-            .expect("no default value for setting")
+            .unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
     }
 
     fn set_global_value(&mut self, value: Box<dyn Any>) {
@@ -634,8 +573,7 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
 // }
 
 fn update_value_in_json_text<'a>(
-    text: &str,
-    syntax_tree: &tree_sitter::Tree,
+    text: &mut String,
     key_path: &mut Vec<&'a str>,
     tab_size: usize,
     old_value: &'a serde_json::Value,
@@ -653,7 +591,6 @@ fn update_value_in_json_text<'a>(
             let new_sub_value = new_object.get(key).unwrap_or(&serde_json::Value::Null);
             update_value_in_json_text(
                 text,
-                syntax_tree,
                 key_path,
                 tab_size,
                 old_sub_value,
@@ -667,7 +604,6 @@ fn update_value_in_json_text<'a>(
             if !old_object.contains_key(key) {
                 update_value_in_json_text(
                     text,
-                    syntax_tree,
                     key_path,
                     tab_size,
                     &serde_json::Value::Null,
@@ -679,14 +615,14 @@ fn update_value_in_json_text<'a>(
         }
     } else if old_value != new_value {
         let (range, replacement) =
-            replace_value_in_json_text(text, syntax_tree, &key_path, tab_size, &new_value);
+            replace_value_in_json_text(text, &key_path, tab_size, &new_value);
+        text.replace_range(range.clone(), &replacement);
         edits.push((range, replacement));
     }
 }
 
 fn replace_value_in_json_text(
     text: &str,
-    syntax_tree: &tree_sitter::Tree,
     key_path: &[&str],
     tab_size: usize,
     new_value: impl Serialize,
@@ -702,6 +638,10 @@ fn replace_value_in_json_text(
         .unwrap();
     }
 
+    let mut parser = tree_sitter::Parser::new();
+    parser.set_language(tree_sitter_json::language()).unwrap();
+    let syntax_tree = parser.parse(text, None).unwrap();
+
     let mut cursor = tree_sitter::QueryCursor::new();
 
     let has_language_overrides = text.contains(LANGUAGE_OVERRIDES);
@@ -1152,7 +1092,7 @@ mod tests {
         store.set_user_settings(&old_json, cx).ok();
         let edits = store.edits_for_update::<T>(&old_json, update);
         let mut new_json = old_json;
-        for (range, replacement) in edits.into_iter().rev() {
+        for (range, replacement) in edits.into_iter() {
             new_json.replace_range(range, &replacement);
         }
         pretty_assertions::assert_eq!(new_json, expected_new_json);
@@ -1180,7 +1120,7 @@ mod tests {
             default_value: &UserSettingsJson,
             user_values: &[&UserSettingsJson],
             _: &AppContext,
-        ) -> Self {
+        ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
     }
@@ -1196,7 +1136,7 @@ mod tests {
             default_value: &Option<bool>,
             user_values: &[&Option<bool>],
             _: &AppContext,
-        ) -> Self {
+        ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
     }
@@ -1224,7 +1164,7 @@ mod tests {
             default_value: &MultiKeySettingsJson,
             user_values: &[&MultiKeySettingsJson],
             _: &AppContext,
-        ) -> Self {
+        ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
     }
@@ -1257,7 +1197,7 @@ mod tests {
             default_value: &JournalSettingsJson,
             user_values: &[&JournalSettingsJson],
             _: &AppContext,
-        ) -> Self {
+        ) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
     }
@@ -1278,7 +1218,7 @@ mod tests {
 
         type FileContent = Self;
 
-        fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Self {
+        fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
             Self::load_via_json_merge(default_value, user_values)
         }
     }

crates/terminal/src/terminal.rs šŸ”—

@@ -159,7 +159,7 @@ impl settings::Setting for TerminalSettings {
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
         _: &AppContext,
-    ) -> Self {
+    ) -> Result<Self> {
         Self::load_via_json_merge(default_value, user_values)
     }
 }

crates/vim/Cargo.toml šŸ”—

@@ -12,6 +12,7 @@ doctest = false
 neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"]
 
 [dependencies]
+anyhow.workspace = true
 serde.workspace = true
 serde_derive.workspace = true
 itertools = "0.10"

crates/vim/src/test/vim_test_context.rs šŸ”—

@@ -17,14 +17,16 @@ pub struct VimTestContext<'a> {
 impl<'a> VimTestContext<'a> {
     pub async fn new(cx: &'a mut gpui::TestAppContext, enabled: bool) -> VimTestContext<'a> {
         let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
+
         cx.update(|cx| {
             search::init(cx);
             crate::init(cx);
+        });
 
+        cx.update(|cx| {
             cx.update_global(|store: &mut SettingsStore, cx| {
                 store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
             });
-
             settings::KeymapFileContent::load("keymaps/vim.json", cx).unwrap();
         });
 

crates/vim/src/vim.rs šŸ”—

@@ -10,8 +10,7 @@ mod state;
 mod utils;
 mod visual;
 
-use std::sync::Arc;
-
+use anyhow::Result;
 use collections::CommandPaletteFilter;
 use editor::{Bias, Cancel, Editor, EditorMode, Event};
 use gpui::{
@@ -24,6 +23,7 @@ use normal::normal_replace;
 use serde::Deserialize;
 use settings::{Setting, SettingsStore};
 use state::{Mode, Operator, VimState};
+use std::sync::Arc;
 use visual::visual_replace;
 use workspace::{self, Workspace};
 
@@ -343,14 +343,10 @@ impl Setting for VimModeSetting {
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
         _: &AppContext,
-    ) -> Self {
-        Self(
-            user_values
-                .first()
-                .map(|e| **e)
-                .flatten()
-                .unwrap_or(default_value.unwrap()),
-        )
+    ) -> Result<Self> {
+        Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
+            default_value.ok_or_else(Self::missing_default)?,
+        )))
     }
 }
 

crates/workspace/src/workspace.rs šŸ”—

@@ -369,8 +369,12 @@ pub struct AppState {
 impl AppState {
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut AppContext) -> Arc<Self> {
-        cx.set_global(settings::SettingsStore::test(cx));
-        cx.set_global(Settings::test(cx));
+        use settings::SettingsStore;
+
+        if !cx.has_global::<SettingsStore>() {
+            cx.set_global(SettingsStore::test(cx));
+            cx.set_global(Settings::test(cx));
+        }
 
         let fs = fs::FakeFs::new(cx.background().clone());
         let languages = Arc::new(LanguageRegistry::test());

crates/zed/src/languages/c.rs šŸ”—

@@ -249,16 +249,21 @@ impl super::LspAdapter for CLspAdapter {
 #[cfg(test)]
 mod tests {
     use gpui::TestAppContext;
-    use language::{AutoindentMode, Buffer};
-    use settings::Settings;
+    use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
+    use settings::SettingsStore;
+    use std::num::NonZeroU32;
 
     #[gpui::test]
     async fn test_c_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
         cx.update(|cx| {
-            let mut settings = Settings::test(cx);
-            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-            cx.set_global(settings);
+            cx.set_global(SettingsStore::test(cx));
+            language::init(cx);
+            cx.update_global::<SettingsStore, _, _>(|store, cx| {
+                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
+                    s.defaults.tab_size = NonZeroU32::new(2);
+                });
+            });
         });
         let language = crate::languages::language("c", tree_sitter_c::language(), None).await;
 

crates/zed/src/languages/python.rs šŸ”—

@@ -170,8 +170,9 @@ impl LspAdapter for PythonLspAdapter {
 #[cfg(test)]
 mod tests {
     use gpui::{ModelContext, TestAppContext};
-    use language::{AutoindentMode, Buffer};
-    use settings::Settings;
+    use language::{language_settings::AllLanguageSettings, AutoindentMode, Buffer};
+    use settings::SettingsStore;
+    use std::num::NonZeroU32;
 
     #[gpui::test]
     async fn test_python_autoindent(cx: &mut TestAppContext) {
@@ -179,9 +180,13 @@ mod tests {
         let language =
             crate::languages::language("python", tree_sitter_python::language(), None).await;
         cx.update(|cx| {
-            let mut settings = Settings::test(cx);
-            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-            cx.set_global(settings);
+            cx.set_global(SettingsStore::test(cx));
+            language::init(cx);
+            cx.update_global::<SettingsStore, _, _>(|store, cx| {
+                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
+                    s.defaults.tab_size = NonZeroU32::new(2);
+                });
+            });
         });
 
         cx.add_model(|cx| {

crates/zed/src/languages/rust.rs šŸ”—

@@ -253,10 +253,13 @@ impl LspAdapter for RustLspAdapter {
 
 #[cfg(test)]
 mod tests {
+    use std::num::NonZeroU32;
+
     use super::*;
     use crate::languages::language;
     use gpui::{color::Color, TestAppContext};
-    use settings::Settings;
+    use language::language_settings::AllLanguageSettings;
+    use settings::SettingsStore;
     use theme::SyntaxTheme;
 
     #[gpui::test]
@@ -435,9 +438,13 @@ mod tests {
     async fn test_rust_autoindent(cx: &mut TestAppContext) {
         cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX);
         cx.update(|cx| {
-            let mut settings = Settings::test(cx);
-            settings.editor_overrides.tab_size = Some(2.try_into().unwrap());
-            cx.set_global(settings);
+            cx.set_global(SettingsStore::test(cx));
+            language::init(cx);
+            cx.update_global::<SettingsStore, _, _>(|store, cx| {
+                store.update_user_settings::<AllLanguageSettings>(cx, |s| {
+                    s.defaults.tab_size = NonZeroU32::new(2);
+                });
+            });
         });
 
         let language = crate::languages::language("rust", tree_sitter_rust::language(), None).await;

crates/zed/src/languages/yaml.rs šŸ”—

@@ -2,10 +2,11 @@ use anyhow::{anyhow, Result};
 use async_trait::async_trait;
 use futures::{future::BoxFuture, FutureExt, StreamExt};
 use gpui::AppContext;
-use language::{LanguageServerBinary, LanguageServerName, LspAdapter};
+use language::{
+    language_settings::language_settings, LanguageServerBinary, LanguageServerName, LspAdapter,
+};
 use node_runtime::NodeRuntime;
 use serde_json::Value;
-use settings::Settings;
 use smol::fs;
 use std::{
     any::Any,
@@ -100,14 +101,13 @@ impl LspAdapter for YamlLspAdapter {
     }
 
     fn workspace_configuration(&self, cx: &mut AppContext) -> Option<BoxFuture<'static, Value>> {
-        let settings = cx.global::<Settings>();
         Some(
             future::ready(serde_json::json!({
                 "yaml": {
                     "keyOrdering": false
                 },
                 "[yaml]": {
-                    "editor.tabSize": settings.tab_size(Some("YAML"))
+                    "editor.tabSize": language_settings(None, Some("YAML"), cx).tab_size,
                 }
             }))
             .boxed(),

crates/zed/src/zed.rs šŸ”—

@@ -597,7 +597,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_open_paths_action(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -697,7 +697,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_window_edit_state(executor: Arc<Deterministic>, cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -777,7 +777,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_new_empty_workspace(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         cx.update(|cx| {
             open_new(&app_state, cx, |workspace, cx| {
                 Editor::new_file(workspace, &Default::default(), cx)
@@ -816,7 +816,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_open_entry(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -929,7 +929,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_open_paths(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
 
         app_state
             .fs
@@ -1099,7 +1099,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_save_conflicting_item(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -1143,7 +1143,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_open_and_save_new_file(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
 
         let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
@@ -1232,7 +1232,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_setting_language_when_saving_as_single_file_worktree(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state.fs.create_dir(Path::new("/root")).await.unwrap();
 
         let project = Project::test(app_state.fs.clone(), [], cx).await;
@@ -1271,7 +1271,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_pane_actions(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -1345,7 +1345,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_navigation(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -1622,7 +1622,7 @@ mod tests {
 
     #[gpui::test]
     async fn test_reopening_closed_items(cx: &mut TestAppContext) {
-        let app_state = init(cx);
+        let app_state = init_test(cx);
         app_state
             .fs
             .as_fake()
@@ -1843,7 +1843,7 @@ mod tests {
         cx.foreground().run_until_parked();
     }
 
-    fn init(cx: &mut TestAppContext) -> Arc<AppState> {
+    fn init_test(cx: &mut TestAppContext) -> Arc<AppState> {
         cx.foreground().forbid_parking();
         cx.update(|cx| {
             let mut app_state = AppState::test(cx);
@@ -1852,6 +1852,7 @@ mod tests {
             state.build_window_options = build_window_options;
             call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
             workspace::init(app_state.clone(), cx);
+            language::init(cx);
             editor::init(cx);
             pane::init(cx);
             app_state