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

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