Prepare to sync other kinds of settings (#18616)

This PR does not change how things work for settings, but lays the
ground work for the future functionality.
After this change, Zed is prepared to sync more than just
`settings.json` files from local worktree and user config.

* ssh tasks

Part of this work is to streamline the task sync mechanism.
Instead of having an extra set of requests to fetch the task contents
from the server (as remote-via-collab does now and does not cover all
sync cases), we want to reuse the existing mechanism for synchronizing
user and local settings.

* editorconfig

Part of the task is to sync .editorconfig file changes to everyone which
involves sending and storing those configs.


Both ssh (and remove-over-collab) .zed/tasks.json and .editorconfig
files behave similar to .zed/settings.json local files: they belong to a
certain path in a certain worktree; may update over time, changing Zed's
functionality; can be merged hierarchically.
Settings sync follows the same "config file changed -> send to watchers
-> parse and merge locally and on watchers" path that's needed for both
new kinds of files, ergo the messaging layer is extended to send more
types of settings for future watch & parse and merge impls to follow.

Release Notes:

- N/A
This commit is contained in:
Kirill Bulatov 2024-10-02 22:00:40 +03:00 committed by GitHub
parent 7c4615519b
commit 778dedec6c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 221 additions and 73 deletions

View file

@ -112,6 +112,7 @@ CREATE TABLE "worktree_settings_files" (
"worktree_id" INTEGER NOT NULL, "worktree_id" INTEGER NOT NULL,
"path" VARCHAR NOT NULL, "path" VARCHAR NOT NULL,
"content" TEXT, "content" TEXT,
"kind" VARCHAR,
PRIMARY KEY(project_id, worktree_id, path), PRIMARY KEY(project_id, worktree_id, path),
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
); );

View file

@ -0,0 +1 @@
ALTER TABLE "worktree_settings_files" ADD COLUMN "kind" VARCHAR;

View file

@ -35,6 +35,7 @@ use std::{
}; };
use time::PrimitiveDateTime; use time::PrimitiveDateTime;
use tokio::sync::{Mutex, OwnedMutexGuard}; use tokio::sync::{Mutex, OwnedMutexGuard};
use worktree_settings_file::LocalSettingsKind;
#[cfg(test)] #[cfg(test)]
pub use tests::TestDb; pub use tests::TestDb;
@ -766,6 +767,7 @@ pub struct Worktree {
pub struct WorktreeSettingsFile { pub struct WorktreeSettingsFile {
pub path: String, pub path: String,
pub content: String, pub content: String,
pub kind: LocalSettingsKind,
} }
pub struct NewExtensionVersion { pub struct NewExtensionVersion {
@ -783,3 +785,21 @@ pub struct ExtensionVersionConstraints {
pub schema_versions: RangeInclusive<i32>, pub schema_versions: RangeInclusive<i32>,
pub wasm_api_versions: RangeInclusive<SemanticVersion>, pub wasm_api_versions: RangeInclusive<SemanticVersion>,
} }
impl LocalSettingsKind {
pub fn from_proto(proto_kind: proto::LocalSettingsKind) -> Self {
match proto_kind {
proto::LocalSettingsKind::Settings => Self::Settings,
proto::LocalSettingsKind::Tasks => Self::Tasks,
proto::LocalSettingsKind::Editorconfig => Self::Editorconfig,
}
}
pub fn to_proto(&self) -> proto::LocalSettingsKind {
match self {
Self::Settings => proto::LocalSettingsKind::Settings,
Self::Tasks => proto::LocalSettingsKind::Tasks,
Self::Editorconfig => proto::LocalSettingsKind::Editorconfig,
}
}
}

View file

@ -1,3 +1,4 @@
use anyhow::Context as _;
use util::ResultExt; use util::ResultExt;
use super::*; use super::*;
@ -527,6 +528,12 @@ impl Database {
connection: ConnectionId, connection: ConnectionId,
) -> Result<TransactionGuard<Vec<ConnectionId>>> { ) -> Result<TransactionGuard<Vec<ConnectionId>>> {
let project_id = ProjectId::from_proto(update.project_id); let project_id = ProjectId::from_proto(update.project_id);
let kind = match update.kind {
Some(kind) => proto::LocalSettingsKind::from_i32(kind)
.with_context(|| format!("unknown worktree settings kind: {kind}"))?,
None => proto::LocalSettingsKind::Settings,
};
let kind = LocalSettingsKind::from_proto(kind);
self.project_transaction(project_id, |tx| async move { self.project_transaction(project_id, |tx| async move {
// Ensure the update comes from the host. // Ensure the update comes from the host.
let project = project::Entity::find_by_id(project_id) let project = project::Entity::find_by_id(project_id)
@ -543,6 +550,7 @@ impl Database {
worktree_id: ActiveValue::Set(update.worktree_id as i64), worktree_id: ActiveValue::Set(update.worktree_id as i64),
path: ActiveValue::Set(update.path.clone()), path: ActiveValue::Set(update.path.clone()),
content: ActiveValue::Set(content.clone()), content: ActiveValue::Set(content.clone()),
kind: ActiveValue::Set(kind),
}) })
.on_conflict( .on_conflict(
OnConflict::columns([ OnConflict::columns([
@ -800,6 +808,7 @@ impl Database {
worktree.settings_files.push(WorktreeSettingsFile { worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path, path: db_settings_file.path,
content: db_settings_file.content, content: db_settings_file.content,
kind: db_settings_file.kind,
}); });
} }
} }

View file

@ -735,6 +735,7 @@ impl Database {
worktree.settings_files.push(WorktreeSettingsFile { worktree.settings_files.push(WorktreeSettingsFile {
path: db_settings_file.path, path: db_settings_file.path,
content: db_settings_file.content, content: db_settings_file.content,
kind: db_settings_file.kind,
}); });
} }
} }

View file

@ -11,9 +11,25 @@ pub struct Model {
#[sea_orm(primary_key)] #[sea_orm(primary_key)]
pub path: String, pub path: String,
pub content: String, pub content: String,
pub kind: LocalSettingsKind,
} }
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {} pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}
#[derive(
Copy, Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Default, Hash, serde::Serialize,
)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
#[serde(rename_all = "snake_case")]
pub enum LocalSettingsKind {
#[default]
#[sea_orm(string_value = "settings")]
Settings,
#[sea_orm(string_value = "tasks")]
Tasks,
#[sea_orm(string_value = "editorconfig")]
Editorconfig,
}

View file

@ -1739,6 +1739,7 @@ fn notify_rejoined_projects(
worktree_id: worktree.id, worktree_id: worktree.id,
path: settings_file.path, path: settings_file.path,
content: Some(settings_file.content), content: Some(settings_file.content),
kind: Some(settings_file.kind.to_proto().into()),
}, },
)?; )?;
} }
@ -2220,6 +2221,7 @@ fn join_project_internal(
worktree_id: worktree.id, worktree_id: worktree.id,
path: settings_file.path, path: settings_file.path,
content: Some(settings_file.content), content: Some(settings_file.content),
kind: Some(proto::update_user_settings::Kind::Settings.into()),
}, },
)?; )?;
} }

View file

@ -33,7 +33,7 @@ use project::{
}; };
use rand::prelude::*; use rand::prelude::*;
use serde_json::json; use serde_json::json;
use settings::SettingsStore; use settings::{LocalSettingsKind, SettingsStore};
use std::{ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
env, future, mem, env, future, mem,
@ -3327,8 +3327,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[ &[
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()), (
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), Path::new("").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":2}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
] ]
) )
}); });
@ -3346,8 +3354,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[ &[
(Path::new("").into(), r#"{}"#.to_string()), (
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), Path::new("").into(),
LocalSettingsKind::Settings,
r#"{}"#.to_string()
),
(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
] ]
) )
}); });
@ -3375,8 +3391,16 @@ async fn test_local_settings(
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[ &[
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()), (
(Path::new("b").into(), r#"{"tab_size":4}"#.to_string()), Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":8}"#.to_string()
),
(
Path::new("b").into(),
LocalSettingsKind::Settings,
r#"{"tab_size":4}"#.to_string()
),
] ]
) )
}); });
@ -3406,7 +3430,11 @@ async fn test_local_settings(
store store
.local_settings(worktree_b.read(cx).id()) .local_settings(worktree_b.read(cx).id())
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),] &[(
Path::new("a").into(),
LocalSettingsKind::Settings,
r#"{"hard_tabs":true}"#.to_string()
),]
) )
}); });
} }

View file

@ -1,3 +1,4 @@
use anyhow::Context;
use collections::HashMap; use collections::HashMap;
use fs::Fs; use fs::Fs;
use gpui::{AppContext, AsyncAppContext, BorrowAppContext, EventEmitter, Model, ModelContext}; use gpui::{AppContext, AsyncAppContext, BorrowAppContext, EventEmitter, Model, ModelContext};
@ -6,7 +7,7 @@ use paths::local_settings_file_relative_path;
use rpc::{proto, AnyProtoClient, TypedEnvelope}; use rpc::{proto, AnyProtoClient, TypedEnvelope};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{InvalidSettingsError, Settings, SettingsSources, SettingsStore}; use settings::{InvalidSettingsError, LocalSettingsKind, Settings, SettingsSources, SettingsStore};
use std::{ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
sync::Arc, sync::Arc,
@ -266,13 +267,14 @@ impl SettingsObserver {
let store = cx.global::<SettingsStore>(); let store = cx.global::<SettingsStore>();
for worktree in self.worktree_store.read(cx).worktrees() { for worktree in self.worktree_store.read(cx).worktrees() {
let worktree_id = worktree.read(cx).id().to_proto(); let worktree_id = worktree.read(cx).id().to_proto();
for (path, content) in store.local_settings(worktree.read(cx).id()) { for (path, kind, content) in store.local_settings(worktree.read(cx).id()) {
downstream_client downstream_client
.send(proto::UpdateWorktreeSettings { .send(proto::UpdateWorktreeSettings {
project_id, project_id,
worktree_id, worktree_id,
path: path.to_string_lossy().into(), path: path.to_string_lossy().into(),
content: Some(content), content: Some(content),
kind: Some(local_settings_kind_to_proto(kind).into()),
}) })
.log_err(); .log_err();
} }
@ -288,6 +290,11 @@ impl SettingsObserver {
envelope: TypedEnvelope<proto::UpdateWorktreeSettings>, envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
mut cx: AsyncAppContext, mut cx: AsyncAppContext,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let kind = match envelope.payload.kind {
Some(kind) => proto::LocalSettingsKind::from_i32(kind)
.with_context(|| format!("unknown kind {kind}"))?,
None => proto::LocalSettingsKind::Settings,
};
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id); let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
let Some(worktree) = this let Some(worktree) = this
@ -297,10 +304,12 @@ impl SettingsObserver {
else { else {
return; return;
}; };
this.update_settings( this.update_settings(
worktree, worktree,
[( [(
PathBuf::from(&envelope.payload.path).into(), PathBuf::from(&envelope.payload.path).into(),
local_settings_kind_from_proto(kind),
envelope.payload.content, envelope.payload.content,
)], )],
cx, cx,
@ -327,6 +336,7 @@ impl SettingsObserver {
ssh.send(proto::UpdateUserSettings { ssh.send(proto::UpdateUserSettings {
project_id: 0, project_id: 0,
content, content,
kind: Some(proto::LocalSettingsKind::Settings.into()),
}) })
.log_err(); .log_err();
} }
@ -342,6 +352,7 @@ impl SettingsObserver {
ssh.send(proto::UpdateUserSettings { ssh.send(proto::UpdateUserSettings {
project_id: 0, project_id: 0,
content, content,
kind: Some(proto::LocalSettingsKind::Settings.into()),
}) })
.log_err(); .log_err();
} }
@ -397,6 +408,7 @@ impl SettingsObserver {
settings_contents.push(async move { settings_contents.push(async move {
( (
settings_dir, settings_dir,
LocalSettingsKind::Settings,
if removed { if removed {
None None
} else { } else {
@ -413,15 +425,15 @@ impl SettingsObserver {
let worktree = worktree.clone(); let worktree = worktree.clone();
cx.spawn(move |this, cx| async move { cx.spawn(move |this, cx| async move {
let settings_contents: Vec<(Arc<Path>, _)> = let settings_contents: Vec<(Arc<Path>, _, _)> =
futures::future::join_all(settings_contents).await; futures::future::join_all(settings_contents).await;
cx.update(|cx| { cx.update(|cx| {
this.update(cx, |this, cx| { this.update(cx, |this, cx| {
this.update_settings( this.update_settings(
worktree, worktree,
settings_contents settings_contents.into_iter().map(|(path, kind, content)| {
.into_iter() (path, kind, content.and_then(|c| c.log_err()))
.map(|(path, content)| (path, content.and_then(|c| c.log_err()))), }),
cx, cx,
) )
}) })
@ -433,17 +445,18 @@ impl SettingsObserver {
fn update_settings( fn update_settings(
&mut self, &mut self,
worktree: Model<Worktree>, worktree: Model<Worktree>,
settings_contents: impl IntoIterator<Item = (Arc<Path>, Option<String>)>, settings_contents: impl IntoIterator<Item = (Arc<Path>, LocalSettingsKind, Option<String>)>,
cx: &mut ModelContext<Self>, cx: &mut ModelContext<Self>,
) { ) {
let worktree_id = worktree.read(cx).id(); let worktree_id = worktree.read(cx).id();
let remote_worktree_id = worktree.read(cx).id(); let remote_worktree_id = worktree.read(cx).id();
let result = cx.update_global::<SettingsStore, anyhow::Result<()>>(|store, cx| { let result = cx.update_global::<SettingsStore, anyhow::Result<()>>(|store, cx| {
for (directory, file_content) in settings_contents { for (directory, kind, file_content) in settings_contents {
store.set_local_settings( store.set_local_settings(
worktree_id, worktree_id,
directory.clone(), directory.clone(),
kind,
file_content.as_deref(), file_content.as_deref(),
cx, cx,
)?; )?;
@ -455,6 +468,7 @@ impl SettingsObserver {
worktree_id: remote_worktree_id.to_proto(), worktree_id: remote_worktree_id.to_proto(),
path: directory.to_string_lossy().into_owned(), path: directory.to_string_lossy().into_owned(),
content: file_content, content: file_content,
kind: Some(local_settings_kind_to_proto(kind).into()),
}) })
.log_err(); .log_err();
} }
@ -481,3 +495,19 @@ impl SettingsObserver {
} }
} }
} }
pub fn local_settings_kind_from_proto(kind: proto::LocalSettingsKind) -> LocalSettingsKind {
match kind {
proto::LocalSettingsKind::Settings => LocalSettingsKind::Settings,
proto::LocalSettingsKind::Tasks => LocalSettingsKind::Tasks,
proto::LocalSettingsKind::Editorconfig => LocalSettingsKind::Editorconfig,
}
}
pub fn local_settings_kind_to_proto(kind: LocalSettingsKind) -> proto::LocalSettingsKind {
match kind {
LocalSettingsKind::Settings => proto::LocalSettingsKind::Settings,
LocalSettingsKind::Tasks => proto::LocalSettingsKind::Tasks,
LocalSettingsKind::Editorconfig => proto::LocalSettingsKind::Editorconfig,
}
}

View file

@ -642,6 +642,13 @@ message UpdateWorktreeSettings {
uint64 worktree_id = 2; uint64 worktree_id = 2;
string path = 3; string path = 3;
optional string content = 4; optional string content = 4;
optional LocalSettingsKind kind = 5;
}
enum LocalSettingsKind {
Settings = 0;
Tasks = 1;
Editorconfig = 2;
} }
message CreateProjectEntry { message CreateProjectEntry {
@ -2487,6 +2494,12 @@ message AddWorktreeResponse {
message UpdateUserSettings { message UpdateUserSettings {
uint64 project_id = 1; uint64 project_id = 1;
string content = 2; string content = 2;
optional Kind kind = 3;
enum Kind {
Settings = 0;
Tasks = 1;
}
} }
message CheckFileExists { message CheckFileExists {

View file

@ -14,7 +14,8 @@ pub use json_schema::*;
pub use keymap_file::KeymapFile; pub use keymap_file::KeymapFile;
pub use settings_file::*; pub use settings_file::*;
pub use settings_store::{ pub use settings_store::{
InvalidSettingsError, Settings, SettingsLocation, SettingsSources, SettingsStore, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
SettingsStore,
}; };
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)] #[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, PartialOrd, Ord)]

View file

@ -157,13 +157,14 @@ pub struct SettingsLocation<'a> {
pub path: &'a Path, pub path: &'a Path,
} }
/// A set of strongly-typed setting values defined via multiple JSON files. /// A set of strongly-typed setting values defined via multiple config files.
pub struct SettingsStore { pub struct SettingsStore {
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>, setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
raw_default_settings: serde_json::Value, raw_default_settings: serde_json::Value,
raw_user_settings: serde_json::Value, raw_user_settings: serde_json::Value,
raw_extension_settings: serde_json::Value, raw_extension_settings: serde_json::Value,
raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), serde_json::Value>, raw_local_settings:
BTreeMap<(WorktreeId, Arc<Path>), HashMap<LocalSettingsKind, serde_json::Value>>,
tab_size_callback: Option<( tab_size_callback: Option<(
TypeId, TypeId,
Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>, Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
@ -174,6 +175,13 @@ pub struct SettingsStore {
>, >,
} }
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum LocalSettingsKind {
Settings,
Tasks,
Editorconfig,
}
impl Global for SettingsStore {} impl Global for SettingsStore {}
#[derive(Debug)] #[derive(Debug)]
@ -520,19 +528,21 @@ impl SettingsStore {
pub fn set_local_settings( pub fn set_local_settings(
&mut self, &mut self,
root_id: WorktreeId, root_id: WorktreeId,
path: Arc<Path>, directory_path: Arc<Path>,
kind: LocalSettingsKind,
settings_content: Option<&str>, settings_content: Option<&str>,
cx: &mut AppContext, cx: &mut AppContext,
) -> Result<()> { ) -> Result<()> {
let raw_local_settings = self
.raw_local_settings
.entry((root_id, directory_path.clone()))
.or_default();
if settings_content.is_some_and(|content| !content.is_empty()) { if settings_content.is_some_and(|content| !content.is_empty()) {
self.raw_local_settings.insert( raw_local_settings.insert(kind, parse_json_with_comments(settings_content.unwrap())?);
(root_id, path.clone()),
parse_json_with_comments(settings_content.unwrap())?,
);
} else { } else {
self.raw_local_settings.remove(&(root_id, path.clone())); raw_local_settings.remove(&kind);
} }
self.recompute_values(Some((root_id, &path)), cx)?; self.recompute_values(Some((root_id, &directory_path)), cx)?;
Ok(()) Ok(())
} }
@ -553,7 +563,8 @@ impl SettingsStore {
/// Add or remove a set of local settings via a JSON string. /// Add or remove a set of local settings via a JSON string.
pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut AppContext) -> Result<()> { pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut AppContext) -> Result<()> {
self.raw_local_settings.retain(|k, _| k.0 != root_id); self.raw_local_settings
.retain(|(worktree_id, _), _| worktree_id != &root_id);
self.recompute_values(Some((root_id, "".as_ref())), cx)?; self.recompute_values(Some((root_id, "".as_ref())), cx)?;
Ok(()) Ok(())
} }
@ -561,7 +572,7 @@ impl SettingsStore {
pub fn local_settings( pub fn local_settings(
&self, &self,
root_id: WorktreeId, root_id: WorktreeId,
) -> impl '_ + Iterator<Item = (Arc<Path>, String)> { ) -> impl '_ + Iterator<Item = (Arc<Path>, LocalSettingsKind, String)> {
self.raw_local_settings self.raw_local_settings
.range( .range(
(root_id, Path::new("").into()) (root_id, Path::new("").into())
@ -570,7 +581,12 @@ impl SettingsStore {
Path::new("").into(), Path::new("").into(),
), ),
) )
.map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap())) .flat_map(|((_, path), content)| {
content.iter().filter_map(|(&kind, raw_content)| {
let parsed_content = serde_json::to_string(raw_content).log_err()?;
Some((path.clone(), kind, parsed_content))
})
})
} }
pub fn json_schema( pub fn json_schema(
@ -739,56 +755,63 @@ impl SettingsStore {
// Reload the local values for the setting. // Reload the local values for the setting.
paths_stack.clear(); paths_stack.clear();
project_settings_stack.clear(); project_settings_stack.clear();
for ((root_id, path), local_settings) in &self.raw_local_settings { for ((root_id, directory_path), local_settings) in &self.raw_local_settings {
// Build a stack of all of the local values for that setting. if let Some(local_settings) = local_settings.get(&LocalSettingsKind::Settings) {
while let Some(prev_entry) = paths_stack.last() { // Build a stack of all of the local values for that setting.
if let Some((prev_root_id, prev_path)) = prev_entry { while let Some(prev_entry) = paths_stack.last() {
if root_id != prev_root_id || !path.starts_with(prev_path) { if let Some((prev_root_id, prev_path)) = prev_entry {
paths_stack.pop(); if root_id != prev_root_id || !directory_path.starts_with(prev_path) {
project_settings_stack.pop(); paths_stack.pop();
continue; project_settings_stack.pop();
continue;
}
} }
break;
} }
break;
}
match setting_value.deserialize_setting(local_settings) { match setting_value.deserialize_setting(local_settings) {
Ok(local_settings) => { Ok(local_settings) => {
paths_stack.push(Some((*root_id, path.as_ref()))); paths_stack.push(Some((*root_id, directory_path.as_ref())));
project_settings_stack.push(local_settings); project_settings_stack.push(local_settings);
// If a local settings file changed, then avoid recomputing local // If a local settings file changed, then avoid recomputing local
// settings for any path outside of that directory. // settings for any path outside of that directory.
if changed_local_path.map_or( if changed_local_path.map_or(
false, false,
|(changed_root_id, changed_local_path)| { |(changed_root_id, changed_local_path)| {
*root_id != changed_root_id || !path.starts_with(changed_local_path) *root_id != changed_root_id
}, || !directory_path.starts_with(changed_local_path)
) {
continue;
}
if let Some(value) = setting_value
.load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
project: &project_settings_stack.iter().collect::<Vec<_>>(),
}, },
cx, ) {
) continue;
.log_err() }
{
setting_value.set_local_value(*root_id, path.clone(), value); if let Some(value) = setting_value
.load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
project: &project_settings_stack.iter().collect::<Vec<_>>(),
},
cx,
)
.log_err()
{
setting_value.set_local_value(
*root_id,
directory_path.clone(),
value,
);
}
}
Err(error) => {
return Err(anyhow!(InvalidSettingsError::LocalSettings {
path: directory_path.join(local_settings_file_relative_path()),
message: error.to_string()
}));
} }
}
Err(error) => {
return Err(anyhow!(InvalidSettingsError::LocalSettings {
path: path.join(local_settings_file_relative_path()),
message: error.to_string()
}));
} }
} }
} }
@ -1201,6 +1224,7 @@ mod tests {
.set_local_settings( .set_local_settings(
WorktreeId::from_usize(1), WorktreeId::from_usize(1),
Path::new("/root1").into(), Path::new("/root1").into(),
LocalSettingsKind::Settings,
Some(r#"{ "user": { "staff": true } }"#), Some(r#"{ "user": { "staff": true } }"#),
cx, cx,
) )
@ -1209,6 +1233,7 @@ mod tests {
.set_local_settings( .set_local_settings(
WorktreeId::from_usize(1), WorktreeId::from_usize(1),
Path::new("/root1/subdir").into(), Path::new("/root1/subdir").into(),
LocalSettingsKind::Settings,
Some(r#"{ "user": { "name": "Jane Doe" } }"#), Some(r#"{ "user": { "name": "Jane Doe" } }"#),
cx, cx,
) )
@ -1218,6 +1243,7 @@ mod tests {
.set_local_settings( .set_local_settings(
WorktreeId::from_usize(1), WorktreeId::from_usize(1),
Path::new("/root2").into(), Path::new("/root2").into(),
LocalSettingsKind::Settings,
Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#), Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
cx, cx,
) )