Detailed changes
@@ -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",
@@ -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)?,
+ ))
}
}
@@ -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)?),
+ })
}
}
@@ -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));
@@ -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(),
+ });
});
});
});
@@ -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"] }
@@ -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
+ })
+ }
}
@@ -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())
@@ -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" }
@@ -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);
});
}
@@ -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);
@@ -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
@@ -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);
+ });
+ }
}
@@ -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)
}
@@ -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);
+}
@@ -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();
@@ -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 {
@@ -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)),
@@ -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)),
@@ -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);
+ }
}
@@ -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))
@@ -37,6 +37,7 @@ impl<'a> EditorLspTestContext<'a> {
let app_state = cx.update(AppState::test);
cx.update(|cx| {
+ language::init(cx);
crate::init(cx);
pane::init(cx);
});
@@ -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 {
@@ -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
@@ -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
+ })
+ }
}
@@ -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 {
@@ -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)
}
}
@@ -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
@@ -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);
@@ -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);
+ });
+}
@@ -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());
}
@@ -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;
+ }
+ }
+}
@@ -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(
@@ -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);
+ });
+}
@@ -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"] }
@@ -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);
+ });
+ }
}
@@ -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"] }
@@ -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 {
@@ -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(
@@ -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);
+ });
+ }
}
@@ -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 },
}
}
@@ -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()
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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"
@@ -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();
});
@@ -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)?,
+ )))
}
}
@@ -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());
@@ -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;
@@ -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| {
@@ -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;
@@ -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(),
@@ -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