mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 19:10:24 +00:00
Replicate project-specific settings when collaborating
This commit is contained in:
parent
ed0fa2404c
commit
8f95435548
10 changed files with 432 additions and 18 deletions
|
@ -112,6 +112,16 @@ CREATE INDEX "index_worktree_repository_statuses_on_project_id" ON "worktree_rep
|
||||||
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
|
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id" ON "worktree_repository_statuses" ("project_id", "worktree_id");
|
||||||
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id_and_work_directory_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
|
CREATE INDEX "index_worktree_repository_statuses_on_project_id_and_worktree_id_and_work_directory_id" ON "worktree_repository_statuses" ("project_id", "worktree_id", "work_directory_id");
|
||||||
|
|
||||||
|
CREATE TABLE "worktree_settings_files" (
|
||||||
|
"project_id" INTEGER NOT NULL,
|
||||||
|
"worktree_id" INTEGER NOT NULL,
|
||||||
|
"path" VARCHAR NOT NULL,
|
||||||
|
"content" TEXT,
|
||||||
|
PRIMARY KEY(project_id, worktree_id, path),
|
||||||
|
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_worktree_settings_files_on_project_id" ON "worktree_settings_files" ("project_id");
|
||||||
|
CREATE INDEX "index_worktree_settings_files_on_project_id_and_worktree_id" ON "worktree_settings_files" ("project_id", "worktree_id");
|
||||||
|
|
||||||
CREATE TABLE "worktree_diagnostic_summaries" (
|
CREATE TABLE "worktree_diagnostic_summaries" (
|
||||||
"project_id" INTEGER NOT NULL,
|
"project_id" INTEGER NOT NULL,
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
CREATE TABLE "worktree_settings_files" (
|
||||||
|
"project_id" INTEGER NOT NULL,
|
||||||
|
"worktree_id" INT8 NOT NULL,
|
||||||
|
"path" VARCHAR NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
PRIMARY KEY(project_id, worktree_id, path),
|
||||||
|
FOREIGN KEY(project_id, worktree_id) REFERENCES worktrees (project_id, id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
CREATE INDEX "index_settings_files_on_project_id" ON "worktree_settings_files" ("project_id");
|
||||||
|
CREATE INDEX "index_settings_files_on_project_id_and_wt_id" ON "worktree_settings_files" ("project_id", "worktree_id");
|
|
@ -16,6 +16,7 @@ mod worktree_diagnostic_summary;
|
||||||
mod worktree_entry;
|
mod worktree_entry;
|
||||||
mod worktree_repository;
|
mod worktree_repository;
|
||||||
mod worktree_repository_statuses;
|
mod worktree_repository_statuses;
|
||||||
|
mod worktree_settings_file;
|
||||||
|
|
||||||
use crate::executor::Executor;
|
use crate::executor::Executor;
|
||||||
use crate::{Error, Result};
|
use crate::{Error, Result};
|
||||||
|
@ -1494,6 +1495,7 @@ impl Database {
|
||||||
updated_repositories: Default::default(),
|
updated_repositories: Default::default(),
|
||||||
removed_repositories: Default::default(),
|
removed_repositories: Default::default(),
|
||||||
diagnostic_summaries: Default::default(),
|
diagnostic_summaries: Default::default(),
|
||||||
|
settings_files: Default::default(),
|
||||||
scan_id: db_worktree.scan_id as u64,
|
scan_id: db_worktree.scan_id as u64,
|
||||||
completed_scan_id: db_worktree.completed_scan_id as u64,
|
completed_scan_id: db_worktree.completed_scan_id as u64,
|
||||||
};
|
};
|
||||||
|
@ -1638,6 +1640,25 @@ impl Database {
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut db_settings_files = worktree_settings_file::Entity::find()
|
||||||
|
.filter(worktree_settings_file::Column::ProjectId.eq(project_id))
|
||||||
|
.stream(&*tx)
|
||||||
|
.await?;
|
||||||
|
while let Some(db_settings_file) = db_settings_files.next().await {
|
||||||
|
let db_settings_file = db_settings_file?;
|
||||||
|
if let Some(worktree) = worktrees
|
||||||
|
.iter_mut()
|
||||||
|
.find(|w| w.id == db_settings_file.worktree_id as u64)
|
||||||
|
{
|
||||||
|
worktree.settings_files.push(WorktreeSettingsFile {
|
||||||
|
path: db_settings_file.path,
|
||||||
|
content: db_settings_file.content,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut collaborators = project
|
let mut collaborators = project
|
||||||
.find_related(project_collaborator::Entity)
|
.find_related(project_collaborator::Entity)
|
||||||
.all(&*tx)
|
.all(&*tx)
|
||||||
|
@ -2637,6 +2658,58 @@ impl Database {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn update_worktree_settings(
|
||||||
|
&self,
|
||||||
|
update: &proto::UpdateWorktreeSettings,
|
||||||
|
connection: ConnectionId,
|
||||||
|
) -> Result<RoomGuard<Vec<ConnectionId>>> {
|
||||||
|
let project_id = ProjectId::from_proto(update.project_id);
|
||||||
|
let room_id = self.room_id_for_project(project_id).await?;
|
||||||
|
self.room_transaction(room_id, |tx| async move {
|
||||||
|
// Ensure the update comes from the host.
|
||||||
|
let project = project::Entity::find_by_id(project_id)
|
||||||
|
.one(&*tx)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| anyhow!("no such project"))?;
|
||||||
|
if project.host_connection()? != connection {
|
||||||
|
return Err(anyhow!("can't update a project hosted by someone else"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(content) = &update.content {
|
||||||
|
worktree_settings_file::Entity::insert(worktree_settings_file::ActiveModel {
|
||||||
|
project_id: ActiveValue::Set(project_id),
|
||||||
|
worktree_id: ActiveValue::Set(update.worktree_id as i64),
|
||||||
|
path: ActiveValue::Set(update.path.clone()),
|
||||||
|
content: ActiveValue::Set(content.clone()),
|
||||||
|
})
|
||||||
|
.on_conflict(
|
||||||
|
OnConflict::columns([
|
||||||
|
worktree_settings_file::Column::ProjectId,
|
||||||
|
worktree_settings_file::Column::WorktreeId,
|
||||||
|
worktree_settings_file::Column::Path,
|
||||||
|
])
|
||||||
|
.update_column(worktree_settings_file::Column::Content)
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.exec(&*tx)
|
||||||
|
.await?;
|
||||||
|
} else {
|
||||||
|
worktree_settings_file::Entity::delete(worktree_settings_file::ActiveModel {
|
||||||
|
project_id: ActiveValue::Set(project_id),
|
||||||
|
worktree_id: ActiveValue::Set(update.worktree_id as i64),
|
||||||
|
path: ActiveValue::Set(update.path.clone()),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.exec(&*tx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
|
||||||
|
Ok(connection_ids)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn join_project(
|
pub async fn join_project(
|
||||||
&self,
|
&self,
|
||||||
project_id: ProjectId,
|
project_id: ProjectId,
|
||||||
|
@ -2707,6 +2780,7 @@ impl Database {
|
||||||
entries: Default::default(),
|
entries: Default::default(),
|
||||||
repository_entries: Default::default(),
|
repository_entries: Default::default(),
|
||||||
diagnostic_summaries: Default::default(),
|
diagnostic_summaries: Default::default(),
|
||||||
|
settings_files: Default::default(),
|
||||||
scan_id: db_worktree.scan_id as u64,
|
scan_id: db_worktree.scan_id as u64,
|
||||||
completed_scan_id: db_worktree.completed_scan_id as u64,
|
completed_scan_id: db_worktree.completed_scan_id as u64,
|
||||||
},
|
},
|
||||||
|
@ -2819,6 +2893,25 @@ impl Database {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Populate worktree settings files
|
||||||
|
{
|
||||||
|
let mut db_settings_files = worktree_settings_file::Entity::find()
|
||||||
|
.filter(worktree_settings_file::Column::ProjectId.eq(project_id))
|
||||||
|
.stream(&*tx)
|
||||||
|
.await?;
|
||||||
|
while let Some(db_settings_file) = db_settings_files.next().await {
|
||||||
|
let db_settings_file = db_settings_file?;
|
||||||
|
if let Some(worktree) =
|
||||||
|
worktrees.get_mut(&(db_settings_file.worktree_id as u64))
|
||||||
|
{
|
||||||
|
worktree.settings_files.push(WorktreeSettingsFile {
|
||||||
|
path: db_settings_file.path,
|
||||||
|
content: db_settings_file.content,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Populate language servers.
|
// Populate language servers.
|
||||||
let language_servers = project
|
let language_servers = project
|
||||||
.find_related(language_server::Entity)
|
.find_related(language_server::Entity)
|
||||||
|
@ -3482,6 +3575,7 @@ pub struct RejoinedWorktree {
|
||||||
pub updated_repositories: Vec<proto::RepositoryEntry>,
|
pub updated_repositories: Vec<proto::RepositoryEntry>,
|
||||||
pub removed_repositories: Vec<u64>,
|
pub removed_repositories: Vec<u64>,
|
||||||
pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
|
pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
|
||||||
|
pub settings_files: Vec<WorktreeSettingsFile>,
|
||||||
pub scan_id: u64,
|
pub scan_id: u64,
|
||||||
pub completed_scan_id: u64,
|
pub completed_scan_id: u64,
|
||||||
}
|
}
|
||||||
|
@ -3537,10 +3631,17 @@ pub struct Worktree {
|
||||||
pub entries: Vec<proto::Entry>,
|
pub entries: Vec<proto::Entry>,
|
||||||
pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
|
pub repository_entries: BTreeMap<u64, proto::RepositoryEntry>,
|
||||||
pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
|
pub diagnostic_summaries: Vec<proto::DiagnosticSummary>,
|
||||||
|
pub settings_files: Vec<WorktreeSettingsFile>,
|
||||||
pub scan_id: u64,
|
pub scan_id: u64,
|
||||||
pub completed_scan_id: u64,
|
pub completed_scan_id: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct WorktreeSettingsFile {
|
||||||
|
pub path: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub use test::*;
|
pub use test::*;
|
||||||
|
|
||||||
|
|
19
crates/collab/src/db/worktree_settings_file.rs
Normal file
19
crates/collab/src/db/worktree_settings_file.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use super::ProjectId;
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||||
|
#[sea_orm(table_name = "worktree_settings_files")]
|
||||||
|
pub struct Model {
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub project_id: ProjectId,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub worktree_id: i64,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub path: String,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -200,6 +200,7 @@ impl Server {
|
||||||
.add_message_handler(start_language_server)
|
.add_message_handler(start_language_server)
|
||||||
.add_message_handler(update_language_server)
|
.add_message_handler(update_language_server)
|
||||||
.add_message_handler(update_diagnostic_summary)
|
.add_message_handler(update_diagnostic_summary)
|
||||||
|
.add_message_handler(update_worktree_settings)
|
||||||
.add_request_handler(forward_project_request::<proto::GetHover>)
|
.add_request_handler(forward_project_request::<proto::GetHover>)
|
||||||
.add_request_handler(forward_project_request::<proto::GetDefinition>)
|
.add_request_handler(forward_project_request::<proto::GetDefinition>)
|
||||||
.add_request_handler(forward_project_request::<proto::GetTypeDefinition>)
|
.add_request_handler(forward_project_request::<proto::GetTypeDefinition>)
|
||||||
|
@ -1088,6 +1089,18 @@ async fn rejoin_room(
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for settings_file in worktree.settings_files {
|
||||||
|
session.peer.send(
|
||||||
|
session.connection_id,
|
||||||
|
proto::UpdateWorktreeSettings {
|
||||||
|
project_id: project.id.to_proto(),
|
||||||
|
worktree_id: worktree.id,
|
||||||
|
path: settings_file.path,
|
||||||
|
content: Some(settings_file.content),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for language_server in &project.language_servers {
|
for language_server in &project.language_servers {
|
||||||
|
@ -1410,6 +1423,18 @@ async fn join_project(
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for settings_file in dbg!(worktree.settings_files) {
|
||||||
|
session.peer.send(
|
||||||
|
session.connection_id,
|
||||||
|
proto::UpdateWorktreeSettings {
|
||||||
|
project_id: project_id.to_proto(),
|
||||||
|
worktree_id: worktree.id,
|
||||||
|
path: settings_file.path,
|
||||||
|
content: Some(settings_file.content),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for language_server in &project.language_servers {
|
for language_server in &project.language_servers {
|
||||||
|
@ -1525,6 +1550,31 @@ async fn update_diagnostic_summary(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_worktree_settings(
|
||||||
|
message: proto::UpdateWorktreeSettings,
|
||||||
|
session: Session,
|
||||||
|
) -> Result<()> {
|
||||||
|
dbg!(&message);
|
||||||
|
|
||||||
|
let guest_connection_ids = session
|
||||||
|
.db()
|
||||||
|
.await
|
||||||
|
.update_worktree_settings(&message, session.connection_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
broadcast(
|
||||||
|
Some(session.connection_id),
|
||||||
|
guest_connection_ids.iter().copied(),
|
||||||
|
|connection_id| {
|
||||||
|
session
|
||||||
|
.peer
|
||||||
|
.forward_send(session.connection_id, connection_id, message.clone())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn start_language_server(
|
async fn start_language_server(
|
||||||
request: proto::StartLanguageServer,
|
request: proto::StartLanguageServer,
|
||||||
session: Session,
|
session: Session,
|
||||||
|
|
|
@ -3114,6 +3114,135 @@ async fn test_fs_operations(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gpui::test(iterations = 10)]
|
||||||
|
async fn test_local_settings(
|
||||||
|
deterministic: Arc<Deterministic>,
|
||||||
|
cx_a: &mut TestAppContext,
|
||||||
|
cx_b: &mut TestAppContext,
|
||||||
|
) {
|
||||||
|
deterministic.forbid_parking();
|
||||||
|
let mut server = TestServer::start(&deterministic).await;
|
||||||
|
let client_a = server.create_client(cx_a, "user_a").await;
|
||||||
|
let client_b = server.create_client(cx_b, "user_b").await;
|
||||||
|
server
|
||||||
|
.create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
|
||||||
|
.await;
|
||||||
|
let active_call_a = cx_a.read(ActiveCall::global);
|
||||||
|
|
||||||
|
// As client A, open a project that contains some local settings files
|
||||||
|
client_a
|
||||||
|
.fs
|
||||||
|
.insert_tree(
|
||||||
|
"/dir",
|
||||||
|
json!({
|
||||||
|
".zed": {
|
||||||
|
"settings.json": r#"{ "tab_size": 2 }"#
|
||||||
|
},
|
||||||
|
"a": {
|
||||||
|
".zed": {
|
||||||
|
"settings.json": r#"{ "tab_size": 8 }"#
|
||||||
|
},
|
||||||
|
"a.txt": "a-contents",
|
||||||
|
},
|
||||||
|
"b": {
|
||||||
|
"b.txt": "b-contents",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let (project_a, _) = client_a.build_local_project("/dir", cx_a).await;
|
||||||
|
let project_id = active_call_a
|
||||||
|
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// As client B, join that project and observe the local settings.
|
||||||
|
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||||
|
let worktree_b = project_b.read_with(cx_b, |project, cx| project.worktrees(cx).next().unwrap());
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx_b.read(|cx| {
|
||||||
|
let store = cx.global::<SettingsStore>();
|
||||||
|
assert_eq!(
|
||||||
|
store.local_settings(worktree_b.id()).collect::<Vec<_>>(),
|
||||||
|
&[
|
||||||
|
(Path::new("").into(), r#"{"tab_size":2}"#.to_string()),
|
||||||
|
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// As client A, update a settings file. As Client B, see the changed settings.
|
||||||
|
client_a
|
||||||
|
.fs
|
||||||
|
.insert_file("/dir/.zed/settings.json", r#"{}"#.into())
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx_b.read(|cx| {
|
||||||
|
let store = cx.global::<SettingsStore>();
|
||||||
|
assert_eq!(
|
||||||
|
store.local_settings(worktree_b.id()).collect::<Vec<_>>(),
|
||||||
|
&[
|
||||||
|
(Path::new("").into(), r#"{}"#.to_string()),
|
||||||
|
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// As client A, create and remove some settings files. As client B, see the changed settings.
|
||||||
|
client_a
|
||||||
|
.fs
|
||||||
|
.remove_file("/dir/.zed/settings.json".as_ref(), Default::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
client_a
|
||||||
|
.fs
|
||||||
|
.create_dir("/dir/b/.zed".as_ref())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
client_a
|
||||||
|
.fs
|
||||||
|
.insert_file("/dir/b/.zed/settings.json", r#"{"tab_size": 4}"#.into())
|
||||||
|
.await;
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
cx_b.read(|cx| {
|
||||||
|
let store = cx.global::<SettingsStore>();
|
||||||
|
assert_eq!(
|
||||||
|
store.local_settings(worktree_b.id()).collect::<Vec<_>>(),
|
||||||
|
&[
|
||||||
|
(Path::new("a").into(), r#"{"tab_size":8}"#.to_string()),
|
||||||
|
(Path::new("b").into(), r#"{"tab_size":4}"#.to_string()),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
// As client B, disconnect.
|
||||||
|
server.forbid_connections();
|
||||||
|
server.disconnect_client(client_b.peer_id().unwrap());
|
||||||
|
|
||||||
|
// As client A, change and remove settings files while client B is disconnected.
|
||||||
|
client_a
|
||||||
|
.fs
|
||||||
|
.insert_file("/dir/a/.zed/settings.json", r#"{"hard_tabs":true}"#.into())
|
||||||
|
.await;
|
||||||
|
client_a
|
||||||
|
.fs
|
||||||
|
.remove_file("/dir/b/.zed/settings.json".as_ref(), Default::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
deterministic.run_until_parked();
|
||||||
|
|
||||||
|
// As client B, reconnect and see the changed settings.
|
||||||
|
server.allow_connections();
|
||||||
|
deterministic.advance_clock(RECEIVE_TIMEOUT);
|
||||||
|
cx_b.read(|cx| {
|
||||||
|
let store = cx.global::<SettingsStore>();
|
||||||
|
assert_eq!(
|
||||||
|
store.local_settings(worktree_b.id()).collect::<Vec<_>>(),
|
||||||
|
&[(Path::new("a").into(), r#"{"hard_tabs":true}"#.to_string()),]
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test(iterations = 10)]
|
#[gpui::test(iterations = 10)]
|
||||||
async fn test_buffer_conflict_after_save(
|
async fn test_buffer_conflict_after_save(
|
||||||
deterministic: Arc<Deterministic>,
|
deterministic: Arc<Deterministic>,
|
||||||
|
|
|
@ -462,6 +462,7 @@ impl Project {
|
||||||
client.add_model_request_handler(Self::handle_update_buffer);
|
client.add_model_request_handler(Self::handle_update_buffer);
|
||||||
client.add_model_message_handler(Self::handle_update_diagnostic_summary);
|
client.add_model_message_handler(Self::handle_update_diagnostic_summary);
|
||||||
client.add_model_message_handler(Self::handle_update_worktree);
|
client.add_model_message_handler(Self::handle_update_worktree);
|
||||||
|
client.add_model_message_handler(Self::handle_update_worktree_settings);
|
||||||
client.add_model_request_handler(Self::handle_create_project_entry);
|
client.add_model_request_handler(Self::handle_create_project_entry);
|
||||||
client.add_model_request_handler(Self::handle_rename_project_entry);
|
client.add_model_request_handler(Self::handle_rename_project_entry);
|
||||||
client.add_model_request_handler(Self::handle_copy_project_entry);
|
client.add_model_request_handler(Self::handle_copy_project_entry);
|
||||||
|
@ -1105,6 +1106,21 @@ impl Project {
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let store = cx.global::<SettingsStore>();
|
||||||
|
for worktree in self.worktrees(cx) {
|
||||||
|
let worktree_id = worktree.read(cx).id().to_proto();
|
||||||
|
for (path, content) in store.local_settings(worktree.id()) {
|
||||||
|
self.client
|
||||||
|
.send(proto::UpdateWorktreeSettings {
|
||||||
|
project_id,
|
||||||
|
worktree_id,
|
||||||
|
path: path.to_string_lossy().into(),
|
||||||
|
content: Some(content),
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let (updates_tx, mut updates_rx) = mpsc::unbounded();
|
let (updates_tx, mut updates_rx) = mpsc::unbounded();
|
||||||
let client = self.client.clone();
|
let client = self.client.clone();
|
||||||
self.client_state = Some(ProjectClientState::Local {
|
self.client_state = Some(ProjectClientState::Local {
|
||||||
|
@ -1217,6 +1233,14 @@ impl Project {
|
||||||
message_id: u32,
|
message_id: u32,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
|
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||||
|
for worktree in &self.worktrees {
|
||||||
|
store
|
||||||
|
.clear_local_settings(worktree.handle_id(), cx)
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
self.join_project_response_message_id = message_id;
|
self.join_project_response_message_id = message_id;
|
||||||
self.set_worktrees_from_proto(message.worktrees, cx)?;
|
self.set_worktrees_from_proto(message.worktrees, cx)?;
|
||||||
self.set_collaborators_from_proto(message.collaborators, cx)?;
|
self.set_collaborators_from_proto(message.collaborators, cx)?;
|
||||||
|
@ -4888,8 +4912,12 @@ impl Project {
|
||||||
.push(WorktreeHandle::Weak(worktree.downgrade()));
|
.push(WorktreeHandle::Weak(worktree.downgrade()));
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.observe_release(worktree, |this, worktree, cx| {
|
let handle_id = worktree.id();
|
||||||
|
cx.observe_release(worktree, move |this, worktree, cx| {
|
||||||
let _ = this.remove_worktree(worktree.id(), cx);
|
let _ = this.remove_worktree(worktree.id(), cx);
|
||||||
|
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||||
|
store.clear_local_settings(handle_id, cx).log_err()
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
|
@ -5174,14 +5202,16 @@ impl Project {
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_local_worktree_settings(
|
fn update_local_worktree_settings(
|
||||||
&mut self,
|
&mut self,
|
||||||
worktree: &ModelHandle<Worktree>,
|
worktree: &ModelHandle<Worktree>,
|
||||||
changes: &UpdatedEntriesSet,
|
changes: &UpdatedEntriesSet,
|
||||||
cx: &mut ModelContext<Self>,
|
cx: &mut ModelContext<Self>,
|
||||||
) {
|
) {
|
||||||
|
let project_id = self.remote_id();
|
||||||
let worktree_id = worktree.id();
|
let worktree_id = worktree.id();
|
||||||
let worktree = worktree.read(cx).as_local().unwrap();
|
let worktree = worktree.read(cx).as_local().unwrap();
|
||||||
|
let remote_worktree_id = worktree.id();
|
||||||
|
|
||||||
let mut settings_contents = Vec::new();
|
let mut settings_contents = Vec::new();
|
||||||
for (path, _, change) in changes.iter() {
|
for (path, _, change) in changes.iter() {
|
||||||
|
@ -5195,10 +5225,7 @@ impl Project {
|
||||||
let removed = *change == PathChange::Removed;
|
let removed = *change == PathChange::Removed;
|
||||||
let abs_path = worktree.absolutize(path);
|
let abs_path = worktree.absolutize(path);
|
||||||
settings_contents.push(async move {
|
settings_contents.push(async move {
|
||||||
anyhow::Ok((
|
(settings_dir, (!removed).then_some(fs.load(&abs_path).await))
|
||||||
settings_dir,
|
|
||||||
(!removed).then_some(fs.load(&abs_path).await?),
|
|
||||||
))
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5207,19 +5234,30 @@ impl Project {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let client = self.client.clone();
|
||||||
cx.spawn_weak(move |_, mut cx| async move {
|
cx.spawn_weak(move |_, mut cx| async move {
|
||||||
let settings_contents = futures::future::join_all(settings_contents).await;
|
let settings_contents: Vec<(Arc<Path>, _)> =
|
||||||
|
futures::future::join_all(settings_contents).await;
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||||
for entry in settings_contents {
|
for (directory, file_content) in settings_contents {
|
||||||
if let Some((directory, file_content)) = entry.log_err() {
|
let file_content = file_content.and_then(|content| content.log_err());
|
||||||
store
|
store
|
||||||
.set_local_settings(
|
.set_local_settings(
|
||||||
worktree_id,
|
worktree_id,
|
||||||
directory,
|
directory.clone(),
|
||||||
file_content.as_ref().map(String::as_str),
|
file_content.as_ref().map(String::as_str),
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
.log_err();
|
||||||
|
if let Some(remote_id) = project_id {
|
||||||
|
client
|
||||||
|
.send(proto::UpdateWorktreeSettings {
|
||||||
|
project_id: remote_id,
|
||||||
|
worktree_id: remote_worktree_id.to_proto(),
|
||||||
|
path: directory.to_string_lossy().into_owned(),
|
||||||
|
content: file_content,
|
||||||
|
})
|
||||||
.log_err();
|
.log_err();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5467,6 +5505,30 @@ impl Project {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_update_worktree_settings(
|
||||||
|
this: ModelHandle<Self>,
|
||||||
|
envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
|
||||||
|
_: Arc<Client>,
|
||||||
|
mut cx: AsyncAppContext,
|
||||||
|
) -> Result<()> {
|
||||||
|
this.update(&mut cx, |this, cx| {
|
||||||
|
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||||
|
if let Some(worktree) = this.worktree_for_id(worktree_id, cx) {
|
||||||
|
cx.update_global::<SettingsStore, _, _>(|store, cx| {
|
||||||
|
store
|
||||||
|
.set_local_settings(
|
||||||
|
worktree.id(),
|
||||||
|
PathBuf::from(&envelope.payload.path).into(),
|
||||||
|
envelope.payload.content.as_ref().map(String::as_str),
|
||||||
|
cx,
|
||||||
|
)
|
||||||
|
.log_err();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn handle_create_project_entry(
|
async fn handle_create_project_entry(
|
||||||
this: ModelHandle<Self>,
|
this: ModelHandle<Self>,
|
||||||
envelope: TypedEnvelope<proto::CreateProjectEntry>,
|
envelope: TypedEnvelope<proto::CreateProjectEntry>,
|
||||||
|
@ -6557,8 +6619,8 @@ impl Project {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.metadata_changed(cx);
|
self.metadata_changed(cx);
|
||||||
for (id, _) in old_worktrees_by_id {
|
for id in old_worktrees_by_id.keys() {
|
||||||
cx.emit(Event::WorktreeRemoved(id));
|
cx.emit(Event::WorktreeRemoved(*id));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -6928,6 +6990,13 @@ impl WorktreeHandle {
|
||||||
WorktreeHandle::Weak(handle) => handle.upgrade(cx),
|
WorktreeHandle::Weak(handle) => handle.upgrade(cx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_id(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
WorktreeHandle::Strong(handle) => handle.id(),
|
||||||
|
WorktreeHandle::Weak(handle) => handle.id(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OpenBuffer {
|
impl OpenBuffer {
|
||||||
|
|
|
@ -132,6 +132,8 @@ message Envelope {
|
||||||
|
|
||||||
OnTypeFormatting on_type_formatting = 111;
|
OnTypeFormatting on_type_formatting = 111;
|
||||||
OnTypeFormattingResponse on_type_formatting_response = 112;
|
OnTypeFormattingResponse on_type_formatting_response = 112;
|
||||||
|
|
||||||
|
UpdateWorktreeSettings update_worktree_settings = 113;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,6 +341,13 @@ message UpdateWorktree {
|
||||||
string abs_path = 10;
|
string abs_path = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message UpdateWorktreeSettings {
|
||||||
|
uint64 project_id = 1;
|
||||||
|
uint64 worktree_id = 2;
|
||||||
|
string path = 3;
|
||||||
|
optional string content = 4;
|
||||||
|
}
|
||||||
|
|
||||||
message CreateProjectEntry {
|
message CreateProjectEntry {
|
||||||
uint64 project_id = 1;
|
uint64 project_id = 1;
|
||||||
uint64 worktree_id = 2;
|
uint64 worktree_id = 2;
|
||||||
|
|
|
@ -236,6 +236,7 @@ messages!(
|
||||||
(UpdateProject, Foreground),
|
(UpdateProject, Foreground),
|
||||||
(UpdateProjectCollaborator, Foreground),
|
(UpdateProjectCollaborator, Foreground),
|
||||||
(UpdateWorktree, Foreground),
|
(UpdateWorktree, Foreground),
|
||||||
|
(UpdateWorktreeSettings, Foreground),
|
||||||
(UpdateDiffBase, Foreground),
|
(UpdateDiffBase, Foreground),
|
||||||
(GetPrivateUserInfo, Foreground),
|
(GetPrivateUserInfo, Foreground),
|
||||||
(GetPrivateUserInfoResponse, Foreground),
|
(GetPrivateUserInfoResponse, Foreground),
|
||||||
|
@ -345,6 +346,7 @@ entity_messages!(
|
||||||
UpdateProject,
|
UpdateProject,
|
||||||
UpdateProjectCollaborator,
|
UpdateProjectCollaborator,
|
||||||
UpdateWorktree,
|
UpdateWorktree,
|
||||||
|
UpdateWorktreeSettings,
|
||||||
UpdateDiffBase
|
UpdateDiffBase
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -359,6 +359,21 @@ impl SettingsStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Add or remove a set of local settings via a JSON string.
|
||||||
|
pub fn clear_local_settings(&mut self, root_id: usize, cx: &AppContext) -> Result<()> {
|
||||||
|
eprintln!("clearing local settings {root_id}");
|
||||||
|
self.local_deserialized_settings
|
||||||
|
.retain(|k, _| k.0 != root_id);
|
||||||
|
self.recompute_values(Some((root_id, "".as_ref())), cx)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn local_settings(&self, root_id: usize) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
|
||||||
|
self.local_deserialized_settings
|
||||||
|
.range((root_id, Path::new("").into())..(root_id + 1, Path::new("").into()))
|
||||||
|
.map(|((_, path), content)| (path.clone(), serde_json::to_string(content).unwrap()))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn json_schema(
|
pub fn json_schema(
|
||||||
&self,
|
&self,
|
||||||
schema_params: &SettingsJsonSchemaParams,
|
schema_params: &SettingsJsonSchemaParams,
|
||||||
|
|
Loading…
Reference in a new issue