Define language settings in the language crate

This commit is contained in:
Max Brunsfeld 2023-05-11 14:40:35 -07:00
parent 9ae10a5dd9
commit 39618ae32d
54 changed files with 1348 additions and 1161 deletions

8
Cargo.lock generated
View file

@ -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",

View file

@ -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)?,
))
}
}

View file

@ -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)?),
})
}
}

View file

@ -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));

View file

@ -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(),
});
});
});
});

View file

@ -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"] }

View file

@ -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
})
}
}

View file

@ -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();
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
}
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();
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())

View file

@ -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" }

View file

@ -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);
});
}

View file

@ -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);

View file

@ -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

View file

@ -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);
});
}
}

View file

@ -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)
}

View file

@ -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(),
);
let mut cx = EditorTestContext::new(cx);
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());
});
});
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«» 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);
}

View file

@ -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();

View file

@ -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 {

View file

@ -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);

View file

@ -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)),

View file

@ -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)),

View file

@ -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);
}
}

View file

@ -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))

View file

@ -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);
});

View file

@ -1,19 +1,16 @@
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 std::{
any::TypeId,
ops::{Deref, DerefMut, Range},
};
use futures::Future;
use indoc::indoc;
use crate::{
display_map::ToDisplayPoint, AnchorRangeExt, Autoscroll, DisplayPoint, Editor, MultiBuffer,
};
use gpui::{
keymap_matcher::Keystroke, AppContext, ContextHandle, ModelContext, ViewContext, ViewHandle,
};
use language::{Buffer, BufferSnapshot};
use settings::Settings;
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 {

View file

@ -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

View file

@ -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
})
}
}

View file

@ -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 {

View file

@ -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)
}
}

View file

@ -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

View file

@ -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);

View file

@ -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);
});
}

View file

@ -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());
}

View file

@ -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;
}
}
}

View file

@ -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(

View file

@ -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);
});
}

View file

@ -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"] }

View file

@ -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);
});
}
}

View file

@ -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"] }

View file

@ -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 {

View file

@ -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(

View file

@ -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);
});
}
}

View file

@ -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 },
}
}

View file

@ -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()
}

View file

@ -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());
}
}
) -> 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)?;
// 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();
user_settings_stack.clear();
paths_stack.clear();
// 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);
}
}
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 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);
}
}
// 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;
};
// 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,
)?);
}
// 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)
}
}

View file

@ -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)
}
}

View file

@ -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"

View file

@ -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();
});

View file

@ -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)?,
)))
}
}

View file

@ -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());

View file

@ -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;

View file

@ -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| {

View file

@ -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;

View file

@ -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(),

View file

@ -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