Start work on respecting project-specific settings

This commit is contained in:
Max Brunsfeld 2023-05-29 13:48:27 -07:00
parent e4530471de
commit 89446c7fd4
16 changed files with 326 additions and 83 deletions

View file

@ -4,7 +4,14 @@ use assets::Assets;
use fs::Fs;
use futures::{channel::mpsc, StreamExt};
use gpui::{executor::Background, AppContext, AssetSource};
use std::{borrow::Cow, io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};
use std::{
borrow::Cow,
io::ErrorKind,
path::{Path, PathBuf},
str,
sync::Arc,
time::Duration,
};
use util::{paths, ResultExt};
pub fn register<T: Setting>(cx: &mut AppContext) {
@ -17,6 +24,10 @@ pub fn get<'a, T: Setting>(cx: &'a AppContext) -> &'a T {
cx.global::<SettingsStore>().get(None)
}
pub fn get_local<'a, T: Setting>(location: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a T {
cx.global::<SettingsStore>().get(location)
}
pub fn default_settings() -> Cow<'static, str> {
match Assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap() {
Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),

View file

@ -89,14 +89,14 @@ pub struct SettingsStore {
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
default_deserialized_settings: Option<serde_json::Value>,
user_deserialized_settings: Option<serde_json::Value>,
local_deserialized_settings: BTreeMap<Arc<Path>, serde_json::Value>,
local_deserialized_settings: BTreeMap<(usize, Arc<Path>), serde_json::Value>,
tab_size_callback: Option<(TypeId, Box<dyn Fn(&dyn Any) -> Option<usize>>)>,
}
#[derive(Debug)]
struct SettingValue<T> {
global_value: Option<T>,
local_values: Vec<(Arc<Path>, T)>,
local_values: Vec<(usize, Arc<Path>, T)>,
}
trait AnySettingValue {
@ -109,9 +109,9 @@ trait AnySettingValue {
custom: &[DeserializedSetting],
cx: &AppContext,
) -> Result<Box<dyn Any>>;
fn value_for_path(&self, path: Option<&Path>) -> &dyn Any;
fn value_for_path(&self, path: Option<(usize, &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>);
fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>);
fn json_schema(
&self,
generator: &mut SchemaGenerator,
@ -165,7 +165,7 @@ impl SettingsStore {
///
/// Panics if the given setting type has not been registered, or if there is no
/// value for this setting.
pub fn get<T: Setting>(&self, path: Option<&Path>) -> &T {
pub fn get<T: Setting>(&self, path: Option<(usize, &Path)>) -> &T {
self.setting_values
.get(&TypeId::of::<T>())
.unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))
@ -343,17 +343,19 @@ impl SettingsStore {
/// Add or remove a set of local settings via a JSON string.
pub fn set_local_settings(
&mut self,
root_id: usize,
path: Arc<Path>,
settings_content: Option<&str>,
cx: &AppContext,
) -> Result<()> {
if let Some(content) = settings_content {
self.local_deserialized_settings
.insert(path.clone(), parse_json_with_comments(content)?);
.insert((root_id, path.clone()), parse_json_with_comments(content)?);
} else {
self.local_deserialized_settings.remove(&path);
self.local_deserialized_settings
.remove(&(root_id, path.clone()));
}
self.recompute_values(Some(&path), cx)?;
self.recompute_values(Some((root_id, &path)), cx)?;
Ok(())
}
@ -436,12 +438,12 @@ impl SettingsStore {
fn recompute_values(
&mut self,
changed_local_path: Option<&Path>,
changed_local_path: Option<(usize, &Path)>,
cx: &AppContext,
) -> 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();
let mut paths_stack = Vec::<Option<(usize, &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)?;
@ -469,11 +471,11 @@ impl SettingsStore {
}
// Reload the local values for the setting.
for (path, local_settings) in &self.local_deserialized_settings {
for ((root_id, 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) {
while let Some(prev_entry) = paths_stack.last() {
if let Some((prev_root_id, prev_path)) = prev_entry {
if root_id != prev_root_id || !path.starts_with(prev_path) {
paths_stack.pop();
user_settings_stack.pop();
continue;
@ -485,14 +487,17 @@ impl SettingsStore {
if let Some(local_settings) =
setting_value.deserialize_setting(&local_settings).log_err()
{
paths_stack.push(Some(path.as_ref()));
paths_stack.push(Some((*root_id, 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)
}) {
if changed_local_path.map_or(
false,
|(changed_root_id, changed_local_path)| {
*root_id != changed_root_id || !path.starts_with(changed_local_path)
},
) {
continue;
}
@ -500,7 +505,7 @@ impl SettingsStore {
.load_setting(&default_settings, &user_settings_stack, cx)
.log_err()
{
setting_value.set_local_value(path.clone(), value);
setting_value.set_local_value(*root_id, path.clone(), value);
}
}
}
@ -510,6 +515,24 @@ impl SettingsStore {
}
}
impl Debug for SettingsStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SettingsStore")
.field(
"types",
&self
.setting_values
.values()
.map(|value| value.setting_type_name())
.collect::<Vec<_>>(),
)
.field("default_settings", &self.default_deserialized_settings)
.field("user_settings", &self.user_deserialized_settings)
.field("local_settings", &self.local_deserialized_settings)
.finish_non_exhaustive()
}
}
impl<T: Setting> AnySettingValue for SettingValue<T> {
fn key(&self) -> Option<&'static str> {
T::KEY
@ -546,10 +569,10 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
Ok(DeserializedSetting(Box::new(value)))
}
fn value_for_path(&self, path: Option<&Path>) -> &dyn Any {
if let Some(path) = path {
for (settings_path, value) in self.local_values.iter().rev() {
if path.starts_with(&settings_path) {
fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any {
if let Some((root_id, path)) = path {
for (settings_root_id, settings_path, value) in self.local_values.iter().rev() {
if root_id == *settings_root_id && path.starts_with(&settings_path) {
return value;
}
}
@ -563,11 +586,14 @@ impl<T: Setting> AnySettingValue for SettingValue<T> {
self.global_value = Some(*value.downcast().unwrap());
}
fn set_local_value(&mut self, path: Arc<Path>, value: Box<dyn Any>) {
fn set_local_value(&mut self, root_id: usize, path: Arc<Path>, value: Box<dyn Any>) {
let value = *value.downcast().unwrap();
match self.local_values.binary_search_by_key(&&path, |e| &e.0) {
Ok(ix) => self.local_values[ix].1 = value,
Err(ix) => self.local_values.insert(ix, (path, value)),
match self
.local_values
.binary_search_by_key(&(root_id, &path), |e| (e.0, &e.1))
{
Ok(ix) => self.local_values[ix].2 = value,
Err(ix) => self.local_values.insert(ix, (root_id, path, value)),
}
}
@ -884,6 +910,7 @@ mod tests {
store
.set_local_settings(
1,
Path::new("/root1").into(),
Some(r#"{ "user": { "staff": true } }"#),
cx,
@ -891,6 +918,7 @@ mod tests {
.unwrap();
store
.set_local_settings(
1,
Path::new("/root1/subdir").into(),
Some(r#"{ "user": { "name": "Jane Doe" } }"#),
cx,
@ -899,6 +927,7 @@ mod tests {
store
.set_local_settings(
1,
Path::new("/root2").into(),
Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
cx,
@ -906,7 +935,7 @@ mod tests {
.unwrap();
assert_eq!(
store.get::<UserSettings>(Some(Path::new("/root1/something"))),
store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
&UserSettings {
name: "John Doe".to_string(),
age: 31,
@ -914,7 +943,7 @@ mod tests {
}
);
assert_eq!(
store.get::<UserSettings>(Some(Path::new("/root1/subdir/something"))),
store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
&UserSettings {
name: "Jane Doe".to_string(),
age: 31,
@ -922,7 +951,7 @@ mod tests {
}
);
assert_eq!(
store.get::<UserSettings>(Some(Path::new("/root2/something"))),
store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
&UserSettings {
name: "John Doe".to_string(),
age: 42,
@ -930,7 +959,7 @@ mod tests {
}
);
assert_eq!(
store.get::<MultiKeySettings>(Some(Path::new("/root2/something"))),
store.get::<MultiKeySettings>(Some((1, Path::new("/root2/something")))),
&MultiKeySettings {
key1: "a".to_string(),
key2: "b".to_string(),