mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-08 07:35:01 +03:00
hosted projects (#8627)
- **Allow joining a hosted project** You can't yet do anything in a hosted project, but you can join it and look how empty it is. Release Notes: - N/A
This commit is contained in:
parent
4167c66b86
commit
27c5343707
@ -1182,19 +1182,10 @@ impl Room {
|
||||
) -> Task<Result<Model<Project>>> {
|
||||
let client = self.client.clone();
|
||||
let user_store = self.user_store.clone();
|
||||
let role = self.local_participant.role;
|
||||
cx.emit(Event::RemoteProjectJoined { project_id: id });
|
||||
cx.spawn(move |this, mut cx| async move {
|
||||
let project = Project::remote(
|
||||
id,
|
||||
client,
|
||||
user_store,
|
||||
language_registry,
|
||||
fs,
|
||||
role,
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
let project =
|
||||
Project::remote(id, client, user_store, language_registry, fs, cx.clone()).await?;
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.joined_projects.retain(|project| {
|
||||
|
@ -11,7 +11,7 @@ pub use channel_chat::{
|
||||
mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId,
|
||||
MessageParams,
|
||||
};
|
||||
pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore, HostedProjectId};
|
||||
pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore};
|
||||
|
||||
#[cfg(test)]
|
||||
mod channel_store_tests;
|
||||
|
@ -3,7 +3,9 @@ mod channel_index;
|
||||
use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage};
|
||||
use anyhow::{anyhow, Result};
|
||||
use channel_index::ChannelIndex;
|
||||
use client::{ChannelId, Client, ClientSettings, Subscription, User, UserId, UserStore};
|
||||
use client::{
|
||||
ChannelId, Client, ClientSettings, HostedProjectId, Subscription, User, UserId, UserStore,
|
||||
};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt};
|
||||
use gpui::{
|
||||
@ -27,9 +29,6 @@ pub fn init(client: &Arc<Client>, user_store: Model<UserStore>, cx: &mut AppCont
|
||||
cx.set_global(GlobalChannelStore(channel_store));
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub struct HostedProjectId(pub u64);
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
struct NotesVersion {
|
||||
epoch: u64,
|
||||
|
@ -24,6 +24,9 @@ impl std::fmt::Display for ChannelId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub struct HostedProjectId(pub u64);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct ParticipantIndex(pub u32);
|
||||
|
||||
|
@ -46,10 +46,11 @@ CREATE UNIQUE INDEX "index_rooms_on_channel_id" ON "rooms" ("channel_id");
|
||||
CREATE TABLE "projects" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"room_id" INTEGER REFERENCES rooms (id) ON DELETE CASCADE NOT NULL,
|
||||
"host_user_id" INTEGER REFERENCES users (id) NOT NULL,
|
||||
"host_user_id" INTEGER REFERENCES users (id),
|
||||
"host_connection_id" INTEGER,
|
||||
"host_connection_server_id" INTEGER REFERENCES servers (id) ON DELETE CASCADE,
|
||||
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE
|
||||
"unregistered" BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
"hosted_project_id" INTEGER REFERENCES hosted_projects (id)
|
||||
);
|
||||
CREATE INDEX "index_projects_on_host_connection_server_id" ON "projects" ("host_connection_server_id");
|
||||
CREATE INDEX "index_projects_on_host_connection_id_and_host_connection_server_id" ON "projects" ("host_connection_id", "host_connection_server_id");
|
||||
|
@ -0,0 +1,3 @@
|
||||
-- Add migration script here
|
||||
ALTER TABLE projects ALTER COLUMN host_user_id DROP NOT NULL;
|
||||
ALTER TABLE projects ADD COLUMN hosted_project_id INTEGER REFERENCES hosted_projects(id) UNIQUE NULL;
|
@ -670,6 +670,8 @@ pub struct RefreshedChannelBuffer {
|
||||
}
|
||||
|
||||
pub struct Project {
|
||||
pub id: ProjectId,
|
||||
pub role: ChannelRole,
|
||||
pub collaborators: Vec<ProjectCollaborator>,
|
||||
pub worktrees: BTreeMap<u64, Worktree>,
|
||||
pub language_servers: Vec<proto::LanguageServer>,
|
||||
@ -695,7 +697,7 @@ impl ProjectCollaborator {
|
||||
#[derive(Debug)]
|
||||
pub struct LeftProject {
|
||||
pub id: ProjectId,
|
||||
pub host_user_id: UserId,
|
||||
pub host_user_id: Option<UserId>,
|
||||
pub host_connection_id: Option<ConnectionId>,
|
||||
pub connection_ids: Vec<ConnectionId>,
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use rpc::proto;
|
||||
use rpc::{proto, ErrorCode};
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -39,4 +39,44 @@ impl Database {
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn get_hosted_project(
|
||||
&self,
|
||||
hosted_project_id: HostedProjectId,
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<(hosted_project::Model, ChannelRole)> {
|
||||
let project = hosted_project::Entity::find_by_id(hosted_project_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?;
|
||||
let channel = channel::Entity::find_by_id(project.channel_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!(ErrorCode::NoSuchChannel))?;
|
||||
|
||||
let role = match project.visibility {
|
||||
ChannelVisibility::Public => {
|
||||
self.check_user_is_channel_participant(&channel, user_id, tx)
|
||||
.await?
|
||||
}
|
||||
ChannelVisibility::Members => {
|
||||
self.check_user_is_channel_member(&channel, user_id, tx)
|
||||
.await?
|
||||
}
|
||||
};
|
||||
|
||||
Ok((project, role))
|
||||
}
|
||||
|
||||
pub async fn is_hosted_project(&self, project_id: ProjectId) -> Result<bool> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(project::Entity::find_by_id(project_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.map(|project| project.hosted_project_id.is_some())
|
||||
.ok_or_else(|| anyhow!(ErrorCode::NoSuchProject))?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
@ -57,13 +57,14 @@ impl Database {
|
||||
}
|
||||
|
||||
let project = project::ActiveModel {
|
||||
room_id: ActiveValue::set(participant.room_id),
|
||||
host_user_id: ActiveValue::set(participant.user_id),
|
||||
room_id: ActiveValue::set(Some(participant.room_id)),
|
||||
host_user_id: ActiveValue::set(Some(participant.user_id)),
|
||||
host_connection_id: ActiveValue::set(Some(connection.id as i32)),
|
||||
host_connection_server_id: ActiveValue::set(Some(ServerId(
|
||||
connection.owner_id as i32,
|
||||
))),
|
||||
..Default::default()
|
||||
id: ActiveValue::NotSet,
|
||||
hosted_project_id: ActiveValue::Set(None),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
@ -153,8 +154,12 @@ impl Database {
|
||||
self.update_project_worktrees(project.id, worktrees, &tx)
|
||||
.await?;
|
||||
|
||||
let room_id = project
|
||||
.room_id
|
||||
.ok_or_else(|| anyhow!("project not in a room"))?;
|
||||
|
||||
let guest_connection_ids = self.project_guest_connection_ids(project.id, &tx).await?;
|
||||
let room = self.get_room(project.room_id, &tx).await?;
|
||||
let room = self.get_room(room_id, &tx).await?;
|
||||
Ok((room, guest_connection_ids))
|
||||
})
|
||||
.await
|
||||
@ -504,8 +509,30 @@ impl Database {
|
||||
.await
|
||||
}
|
||||
|
||||
/// Adds the given connection to the specified project.
|
||||
pub async fn join_project(
|
||||
/// Adds the given connection to the specified hosted project
|
||||
pub async fn join_hosted_project(
|
||||
&self,
|
||||
id: HostedProjectId,
|
||||
user_id: UserId,
|
||||
connection: ConnectionId,
|
||||
) -> Result<(Project, ReplicaId)> {
|
||||
self.transaction(|tx| async move {
|
||||
let (hosted_project, role) = self.get_hosted_project(id, user_id, &tx).await?;
|
||||
let project = project::Entity::find()
|
||||
.filter(project::Column::HostedProjectId.eq(hosted_project.id))
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("hosted project is no longer shared"))?;
|
||||
|
||||
self.join_project_internal(project, user_id, connection, role, &tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Adds the given connection to the specified project
|
||||
/// in the current room.
|
||||
pub async fn join_project_in_room(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
connection: ConnectionId,
|
||||
@ -532,180 +559,240 @@ impl Database {
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
if project.room_id != participant.room_id {
|
||||
if project.room_id != Some(participant.room_id) {
|
||||
return Err(anyhow!("no such project"))?;
|
||||
}
|
||||
self.join_project_internal(
|
||||
project,
|
||||
participant.user_id,
|
||||
connection,
|
||||
participant.role.unwrap_or(ChannelRole::Member),
|
||||
&tx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
let mut collaborators = project
|
||||
async fn join_project_internal(
|
||||
&self,
|
||||
project: project::Model,
|
||||
user_id: UserId,
|
||||
connection: ConnectionId,
|
||||
role: ChannelRole,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<(Project, ReplicaId)> {
|
||||
let mut collaborators = project
|
||||
.find_related(project_collaborator::Entity)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
let replica_ids = collaborators
|
||||
.iter()
|
||||
.map(|c| c.replica_id)
|
||||
.collect::<HashSet<_>>();
|
||||
let mut replica_id = ReplicaId(1);
|
||||
while replica_ids.contains(&replica_id) {
|
||||
replica_id.0 += 1;
|
||||
}
|
||||
let new_collaborator = project_collaborator::ActiveModel {
|
||||
project_id: ActiveValue::set(project.id),
|
||||
connection_id: ActiveValue::set(connection.id as i32),
|
||||
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
|
||||
user_id: ActiveValue::set(user_id),
|
||||
replica_id: ActiveValue::set(replica_id),
|
||||
is_host: ActiveValue::set(false),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
collaborators.push(new_collaborator);
|
||||
|
||||
let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?;
|
||||
let mut worktrees = db_worktrees
|
||||
.into_iter()
|
||||
.map(|db_worktree| {
|
||||
(
|
||||
db_worktree.id as u64,
|
||||
Worktree {
|
||||
id: db_worktree.id as u64,
|
||||
abs_path: db_worktree.abs_path,
|
||||
root_name: db_worktree.root_name,
|
||||
visible: db_worktree.visible,
|
||||
entries: Default::default(),
|
||||
repository_entries: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
settings_files: Default::default(),
|
||||
scan_id: db_worktree.scan_id as u64,
|
||||
completed_scan_id: db_worktree.completed_scan_id as u64,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
// Populate worktree entries.
|
||||
{
|
||||
let mut db_entries = worktree_entry::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_entry::Column::ProjectId.eq(project.id))
|
||||
.add(worktree_entry::Column::IsDeleted.eq(false)),
|
||||
)
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(db_entry) = db_entries.next().await {
|
||||
let db_entry = db_entry?;
|
||||
if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) {
|
||||
worktree.entries.push(proto::Entry {
|
||||
id: db_entry.id as u64,
|
||||
is_dir: db_entry.is_dir,
|
||||
path: db_entry.path,
|
||||
inode: db_entry.inode as u64,
|
||||
mtime: Some(proto::Timestamp {
|
||||
seconds: db_entry.mtime_seconds as u64,
|
||||
nanos: db_entry.mtime_nanos as u32,
|
||||
}),
|
||||
is_symlink: db_entry.is_symlink,
|
||||
is_ignored: db_entry.is_ignored,
|
||||
is_external: db_entry.is_external,
|
||||
git_status: db_entry.git_status.map(|status| status as i32),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate repository entries.
|
||||
{
|
||||
let mut db_repository_entries = worktree_repository::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_repository::Column::ProjectId.eq(project.id))
|
||||
.add(worktree_repository::Column::IsDeleted.eq(false)),
|
||||
)
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(db_repository_entry) = db_repository_entries.next().await {
|
||||
let db_repository_entry = db_repository_entry?;
|
||||
if let Some(worktree) = worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
|
||||
{
|
||||
worktree.repository_entries.insert(
|
||||
db_repository_entry.work_directory_id as u64,
|
||||
proto::RepositoryEntry {
|
||||
work_directory_id: db_repository_entry.work_directory_id as u64,
|
||||
branch: db_repository_entry.branch,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate worktree diagnostic summaries.
|
||||
{
|
||||
let mut db_summaries = worktree_diagnostic_summary::Entity::find()
|
||||
.filter(worktree_diagnostic_summary::Column::ProjectId.eq(project.id))
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(db_summary) = db_summaries.next().await {
|
||||
let db_summary = db_summary?;
|
||||
if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) {
|
||||
worktree
|
||||
.diagnostic_summaries
|
||||
.push(proto::DiagnosticSummary {
|
||||
path: db_summary.path,
|
||||
language_server_id: db_summary.language_server_id as u64,
|
||||
error_count: db_summary.error_count as u32,
|
||||
warning_count: db_summary.warning_count as u32,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
let language_servers = project
|
||||
.find_related(language_server::Entity)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let project = Project {
|
||||
id: project.id,
|
||||
role,
|
||||
collaborators: collaborators
|
||||
.into_iter()
|
||||
.map(|collaborator| ProjectCollaborator {
|
||||
connection_id: collaborator.connection(),
|
||||
user_id: collaborator.user_id,
|
||||
replica_id: collaborator.replica_id,
|
||||
is_host: collaborator.is_host,
|
||||
})
|
||||
.collect(),
|
||||
worktrees,
|
||||
language_servers: language_servers
|
||||
.into_iter()
|
||||
.map(|language_server| proto::LanguageServer {
|
||||
id: language_server.id as u64,
|
||||
name: language_server.name,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
Ok((project, replica_id as ReplicaId))
|
||||
}
|
||||
|
||||
pub async fn leave_hosted_project(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
connection: ConnectionId,
|
||||
) -> Result<LeftProject> {
|
||||
self.transaction(|tx| async move {
|
||||
let result = project_collaborator::Entity::delete_many()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(project_collaborator::Column::ProjectId.eq(project_id))
|
||||
.add(project_collaborator::Column::ConnectionId.eq(connection.id as i32))
|
||||
.add(
|
||||
project_collaborator::Column::ConnectionServerId
|
||||
.eq(connection.owner_id as i32),
|
||||
),
|
||||
)
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
if result.rows_affected == 0 {
|
||||
return Err(anyhow!("not in the project"))?;
|
||||
}
|
||||
|
||||
let project = project::Entity::find_by_id(project_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
let collaborators = project
|
||||
.find_related(project_collaborator::Entity)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
let replica_ids = collaborators
|
||||
.iter()
|
||||
.map(|c| c.replica_id)
|
||||
.collect::<HashSet<_>>();
|
||||
let mut replica_id = ReplicaId(1);
|
||||
while replica_ids.contains(&replica_id) {
|
||||
replica_id.0 += 1;
|
||||
}
|
||||
let new_collaborator = project_collaborator::ActiveModel {
|
||||
project_id: ActiveValue::set(project_id),
|
||||
connection_id: ActiveValue::set(connection.id as i32),
|
||||
connection_server_id: ActiveValue::set(ServerId(connection.owner_id as i32)),
|
||||
user_id: ActiveValue::set(participant.user_id),
|
||||
replica_id: ActiveValue::set(replica_id),
|
||||
is_host: ActiveValue::set(false),
|
||||
..Default::default()
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
collaborators.push(new_collaborator);
|
||||
|
||||
let db_worktrees = project.find_related(worktree::Entity).all(&*tx).await?;
|
||||
let mut worktrees = db_worktrees
|
||||
let connection_ids = collaborators
|
||||
.into_iter()
|
||||
.map(|db_worktree| {
|
||||
(
|
||||
db_worktree.id as u64,
|
||||
Worktree {
|
||||
id: db_worktree.id as u64,
|
||||
abs_path: db_worktree.abs_path,
|
||||
root_name: db_worktree.root_name,
|
||||
visible: db_worktree.visible,
|
||||
entries: Default::default(),
|
||||
repository_entries: Default::default(),
|
||||
diagnostic_summaries: Default::default(),
|
||||
settings_files: Default::default(),
|
||||
scan_id: db_worktree.scan_id as u64,
|
||||
completed_scan_id: db_worktree.completed_scan_id as u64,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
// Populate worktree entries.
|
||||
{
|
||||
let mut db_entries = worktree_entry::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_entry::Column::ProjectId.eq(project_id))
|
||||
.add(worktree_entry::Column::IsDeleted.eq(false)),
|
||||
)
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(db_entry) = db_entries.next().await {
|
||||
let db_entry = db_entry?;
|
||||
if let Some(worktree) = worktrees.get_mut(&(db_entry.worktree_id as u64)) {
|
||||
worktree.entries.push(proto::Entry {
|
||||
id: db_entry.id as u64,
|
||||
is_dir: db_entry.is_dir,
|
||||
path: db_entry.path,
|
||||
inode: db_entry.inode as u64,
|
||||
mtime: Some(proto::Timestamp {
|
||||
seconds: db_entry.mtime_seconds as u64,
|
||||
nanos: db_entry.mtime_nanos as u32,
|
||||
}),
|
||||
is_symlink: db_entry.is_symlink,
|
||||
is_ignored: db_entry.is_ignored,
|
||||
is_external: db_entry.is_external,
|
||||
git_status: db_entry.git_status.map(|status| status as i32),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate repository entries.
|
||||
{
|
||||
let mut db_repository_entries = worktree_repository::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(worktree_repository::Column::ProjectId.eq(project_id))
|
||||
.add(worktree_repository::Column::IsDeleted.eq(false)),
|
||||
)
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(db_repository_entry) = db_repository_entries.next().await {
|
||||
let db_repository_entry = db_repository_entry?;
|
||||
if let Some(worktree) =
|
||||
worktrees.get_mut(&(db_repository_entry.worktree_id as u64))
|
||||
{
|
||||
worktree.repository_entries.insert(
|
||||
db_repository_entry.work_directory_id as u64,
|
||||
proto::RepositoryEntry {
|
||||
work_directory_id: db_repository_entry.work_directory_id as u64,
|
||||
branch: db_repository_entry.branch,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Populate worktree diagnostic summaries.
|
||||
{
|
||||
let mut db_summaries = worktree_diagnostic_summary::Entity::find()
|
||||
.filter(worktree_diagnostic_summary::Column::ProjectId.eq(project_id))
|
||||
.stream(&*tx)
|
||||
.await?;
|
||||
while let Some(db_summary) = db_summaries.next().await {
|
||||
let db_summary = db_summary?;
|
||||
if let Some(worktree) = worktrees.get_mut(&(db_summary.worktree_id as u64)) {
|
||||
worktree
|
||||
.diagnostic_summaries
|
||||
.push(proto::DiagnosticSummary {
|
||||
path: db_summary.path,
|
||||
language_server_id: db_summary.language_server_id as u64,
|
||||
error_count: db_summary.error_count as u32,
|
||||
warning_count: db_summary.warning_count as u32,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
let language_servers = project
|
||||
.find_related(language_server::Entity)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let project = Project {
|
||||
collaborators: collaborators
|
||||
.into_iter()
|
||||
.map(|collaborator| ProjectCollaborator {
|
||||
connection_id: collaborator.connection(),
|
||||
user_id: collaborator.user_id,
|
||||
replica_id: collaborator.replica_id,
|
||||
is_host: collaborator.is_host,
|
||||
})
|
||||
.collect(),
|
||||
worktrees,
|
||||
language_servers: language_servers
|
||||
.into_iter()
|
||||
.map(|language_server| proto::LanguageServer {
|
||||
id: language_server.id as u64,
|
||||
name: language_server.name,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
Ok((project, replica_id as ReplicaId))
|
||||
.map(|collaborator| collaborator.connection())
|
||||
.collect();
|
||||
Ok(LeftProject {
|
||||
id: project.id,
|
||||
connection_ids,
|
||||
host_user_id: None,
|
||||
host_connection_id: None,
|
||||
})
|
||||
})
|
||||
.await
|
||||
}
|
||||
@ -772,7 +859,7 @@ impl Database {
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
let room = self.get_room(project.room_id, &tx).await?;
|
||||
let room = self.get_room(room_id, &tx).await?;
|
||||
let left_project = LeftProject {
|
||||
id: project_id,
|
||||
host_user_id: project.host_user_id,
|
||||
@ -996,7 +1083,9 @@ impl Database {
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("project {} not found", project_id))?;
|
||||
Ok(project.room_id)
|
||||
Ok(project
|
||||
.room_id
|
||||
.ok_or_else(|| anyhow!("project not in room"))?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -491,7 +491,7 @@ impl Database {
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("project does not exist"))?;
|
||||
if project.host_user_id != user_id {
|
||||
if project.host_user_id != Some(user_id) {
|
||||
return Err(anyhow!("no such project"))?;
|
||||
}
|
||||
|
||||
@ -851,7 +851,7 @@ impl Database {
|
||||
}
|
||||
|
||||
if collaborator.is_host {
|
||||
left_project.host_user_id = collaborator.user_id;
|
||||
left_project.host_user_id = Some(collaborator.user_id);
|
||||
left_project.host_connection_id = Some(collaborator_connection_id);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use crate::db::{ProjectId, Result, RoomId, ServerId, UserId};
|
||||
use crate::db::{HostedProjectId, ProjectId, Result, RoomId, ServerId, UserId};
|
||||
use anyhow::anyhow;
|
||||
use rpc::ConnectionId;
|
||||
use sea_orm::entity::prelude::*;
|
||||
@ -8,10 +8,11 @@ use sea_orm::entity::prelude::*;
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: ProjectId,
|
||||
pub room_id: RoomId,
|
||||
pub host_user_id: UserId,
|
||||
pub room_id: Option<RoomId>,
|
||||
pub host_user_id: Option<UserId>,
|
||||
pub host_connection_id: Option<i32>,
|
||||
pub host_connection_server_id: Option<ServerId>,
|
||||
pub hosted_project_id: Option<HostedProjectId>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
@ -4,8 +4,9 @@ use crate::{
|
||||
auth::{self, Impersonator},
|
||||
db::{
|
||||
self, BufferId, ChannelId, ChannelRole, ChannelsForUser, CreatedChannelMessage, Database,
|
||||
InviteMemberResult, MembershipUpdated, MessageId, NotificationId, ProjectId,
|
||||
RemoveChannelMemberResult, RespondToChannelInvite, RoomId, ServerId, User, UserId,
|
||||
HostedProjectId, InviteMemberResult, MembershipUpdated, MessageId, NotificationId, Project,
|
||||
ProjectId, RemoveChannelMemberResult, ReplicaId, RespondToChannelInvite, RoomId, ServerId,
|
||||
User, UserId,
|
||||
},
|
||||
executor::Executor,
|
||||
AppState, Error, Result,
|
||||
@ -197,6 +198,7 @@ impl Server {
|
||||
.add_request_handler(share_project)
|
||||
.add_message_handler(unshare_project)
|
||||
.add_request_handler(join_project)
|
||||
.add_request_handler(join_hosted_project)
|
||||
.add_message_handler(leave_project)
|
||||
.add_request_handler(update_project)
|
||||
.add_request_handler(update_worktree)
|
||||
@ -1584,22 +1586,46 @@ async fn join_project(
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let project_id = ProjectId::from_proto(request.project_id);
|
||||
let guest_user_id = session.user_id;
|
||||
|
||||
tracing::info!(%project_id, "join project");
|
||||
|
||||
let (project, replica_id) = &mut *session
|
||||
.db()
|
||||
.await
|
||||
.join_project(project_id, session.connection_id)
|
||||
.join_project_in_room(project_id, session.connection_id)
|
||||
.await?;
|
||||
|
||||
join_project_internal(response, session, project, replica_id)
|
||||
}
|
||||
|
||||
trait JoinProjectInternalResponse {
|
||||
fn send(self, result: proto::JoinProjectResponse) -> Result<()>;
|
||||
}
|
||||
impl JoinProjectInternalResponse for Response<proto::JoinProject> {
|
||||
fn send(self, result: proto::JoinProjectResponse) -> Result<()> {
|
||||
Response::<proto::JoinProject>::send(self, result)
|
||||
}
|
||||
}
|
||||
impl JoinProjectInternalResponse for Response<proto::JoinHostedProject> {
|
||||
fn send(self, result: proto::JoinProjectResponse) -> Result<()> {
|
||||
Response::<proto::JoinHostedProject>::send(self, result)
|
||||
}
|
||||
}
|
||||
|
||||
fn join_project_internal(
|
||||
response: impl JoinProjectInternalResponse,
|
||||
session: Session,
|
||||
project: &mut Project,
|
||||
replica_id: &ReplicaId,
|
||||
) -> Result<()> {
|
||||
let collaborators = project
|
||||
.collaborators
|
||||
.iter()
|
||||
.filter(|collaborator| collaborator.connection_id != session.connection_id)
|
||||
.map(|collaborator| collaborator.to_proto())
|
||||
.collect::<Vec<_>>();
|
||||
let project_id = project.id;
|
||||
let guest_user_id = session.user_id;
|
||||
|
||||
let worktrees = project
|
||||
.worktrees
|
||||
@ -1631,10 +1657,12 @@ async fn join_project(
|
||||
|
||||
// First, we send the metadata associated with each worktree.
|
||||
response.send(proto::JoinProjectResponse {
|
||||
project_id: project.id.0 as u64,
|
||||
worktrees: worktrees.clone(),
|
||||
replica_id: replica_id.0 as u32,
|
||||
collaborators: collaborators.clone(),
|
||||
language_servers: project.language_servers.clone(),
|
||||
role: project.role.into(), // todo
|
||||
})?;
|
||||
|
||||
for (worktree_id, worktree) in mem::take(&mut project.worktrees) {
|
||||
@ -1707,15 +1735,17 @@ async fn join_project(
|
||||
async fn leave_project(request: proto::LeaveProject, session: Session) -> Result<()> {
|
||||
let sender_id = session.connection_id;
|
||||
let project_id = ProjectId::from_proto(request.project_id);
|
||||
let db = session.db().await;
|
||||
if db.is_hosted_project(project_id).await? {
|
||||
let project = db.leave_hosted_project(project_id, sender_id).await?;
|
||||
project_left(&project, &session);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (room, project) = &*session
|
||||
.db()
|
||||
.await
|
||||
.leave_project(project_id, sender_id)
|
||||
.await?;
|
||||
let (room, project) = &*db.leave_project(project_id, sender_id).await?;
|
||||
tracing::info!(
|
||||
%project_id,
|
||||
host_user_id = %project.host_user_id,
|
||||
host_user_id = ?project.host_user_id,
|
||||
host_connection_id = ?project.host_connection_id,
|
||||
"leave project"
|
||||
);
|
||||
@ -1726,6 +1756,24 @@ async fn leave_project(request: proto::LeaveProject, session: Session) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn join_hosted_project(
|
||||
request: proto::JoinHostedProject,
|
||||
response: Response<proto::JoinHostedProject>,
|
||||
session: Session,
|
||||
) -> Result<()> {
|
||||
let (mut project, replica_id) = session
|
||||
.db()
|
||||
.await
|
||||
.join_hosted_project(
|
||||
HostedProjectId(request.id as i32),
|
||||
session.user_id,
|
||||
session.connection_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
join_project_internal(response, session, &mut project, &replica_id)
|
||||
}
|
||||
|
||||
/// Updates other participants with changes to the project
|
||||
async fn update_project(
|
||||
request: proto::UpdateProject,
|
||||
@ -3624,7 +3672,7 @@ async fn leave_channel_buffers_for_session(session: &Session) -> Result<()> {
|
||||
|
||||
fn project_left(project: &db::LeftProject, session: &Session) {
|
||||
for connection_id in &project.connection_ids {
|
||||
if project.host_user_id == session.user_id {
|
||||
if project.host_user_id == Some(session.user_id) {
|
||||
session
|
||||
.peer
|
||||
.send(
|
||||
|
@ -1534,7 +1534,7 @@ async fn test_following_across_workspaces(cx_a: &mut TestAppContext, cx_b: &mut
|
||||
executor.run_until_parked();
|
||||
assert_eq!(visible_push_notifications(cx_a).len(), 1);
|
||||
cx_a.update(|cx| {
|
||||
workspace::join_remote_project(
|
||||
workspace::join_in_room_project(
|
||||
project_b_id,
|
||||
client_b.user_id().unwrap(),
|
||||
client_a.app_state.clone(),
|
||||
|
@ -22,7 +22,6 @@ use project::{
|
||||
search::SearchQuery, DiagnosticSummary, FormatTrigger, HoverBlockKind, Project, ProjectPath,
|
||||
};
|
||||
use rand::prelude::*;
|
||||
use rpc::proto::ChannelRole;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::{
|
||||
@ -3742,7 +3741,6 @@ async fn test_leaving_project(
|
||||
client_b.user_store().clone(),
|
||||
client_b.language_registry().clone(),
|
||||
FakeFs::new(cx.background_executor().clone()),
|
||||
ChannelRole::Member,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
|
@ -7,8 +7,8 @@ use crate::{
|
||||
CollaborationPanelSettings,
|
||||
};
|
||||
use call::ActiveCall;
|
||||
use channel::{Channel, ChannelEvent, ChannelStore, HostedProjectId};
|
||||
use client::{ChannelId, Client, Contact, User, UserStore};
|
||||
use channel::{Channel, ChannelEvent, ChannelStore};
|
||||
use client::{ChannelId, Client, Contact, HostedProjectId, User, UserStore};
|
||||
use contact_finder::ContactFinder;
|
||||
use db::kvp::KEY_VALUE_STORE;
|
||||
use editor::{Editor, EditorElement, EditorStyle};
|
||||
@ -911,7 +911,7 @@ impl CollabPanel {
|
||||
this.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
let app_state = workspace.app_state().clone();
|
||||
workspace::join_remote_project(project_id, host_user_id, app_state, cx)
|
||||
workspace::join_in_room_project(project_id, host_user_id, app_state, cx)
|
||||
.detach_and_prompt_err("Failed to join project", cx, |_, _| None);
|
||||
})
|
||||
.ok();
|
||||
@ -1047,8 +1047,15 @@ impl CollabPanel {
|
||||
.indent_level(2)
|
||||
.indent_step_size(px(20.))
|
||||
.selected(is_selected)
|
||||
.on_click(cx.listener(move |_this, _, _cx| {
|
||||
// todo()
|
||||
.on_click(cx.listener(move |this, _, cx| {
|
||||
if let Some(workspace) = this.workspace.upgrade() {
|
||||
let app_state = workspace.read(cx).app_state().clone();
|
||||
workspace::join_hosted_project(id, app_state, cx).detach_and_prompt_err(
|
||||
"Failed to open project",
|
||||
cx,
|
||||
|_, _| None,
|
||||
)
|
||||
}
|
||||
}))
|
||||
.start_slot(
|
||||
h_flex()
|
||||
@ -1461,7 +1468,7 @@ impl CollabPanel {
|
||||
} => {
|
||||
if let Some(workspace) = self.workspace.upgrade() {
|
||||
let app_state = workspace.read(cx).app_state().clone();
|
||||
workspace::join_remote_project(
|
||||
workspace::join_in_room_project(
|
||||
*project_id,
|
||||
*host_user_id,
|
||||
app_state,
|
||||
|
@ -82,7 +82,7 @@ impl IncomingCallNotificationState {
|
||||
if let Some(project_id) = initial_project_id {
|
||||
cx.update(|cx| {
|
||||
if let Some(app_state) = app_state.upgrade() {
|
||||
workspace::join_remote_project(
|
||||
workspace::join_in_room_project(
|
||||
project_id,
|
||||
caller_user_id,
|
||||
app_state,
|
||||
|
@ -98,7 +98,7 @@ impl ProjectSharedNotification {
|
||||
|
||||
fn join(&mut self, cx: &mut ViewContext<Self>) {
|
||||
if let Some(app_state) = self.app_state.upgrade() {
|
||||
workspace::join_remote_project(self.project_id, self.owner.id, app_state, cx)
|
||||
workspace::join_in_room_project(self.project_id, self.owner.id, app_state, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ mod project_tests;
|
||||
|
||||
use anyhow::{anyhow, bail, Context as _, Result};
|
||||
use async_trait::async_trait;
|
||||
use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
|
||||
use client::{proto, Client, Collaborator, HostedProjectId, TypedEnvelope, UserStore};
|
||||
use clock::ReplicaId;
|
||||
use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
|
||||
use copilot::Copilot;
|
||||
@ -167,6 +167,7 @@ pub struct Project {
|
||||
prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
|
||||
prettier_instances: HashMap<PathBuf, PrettierInstance>,
|
||||
tasks: Model<Inventory>,
|
||||
hosted_project_id: Option<HostedProjectId>,
|
||||
}
|
||||
|
||||
pub enum LanguageServerToQuery {
|
||||
@ -605,6 +606,7 @@ impl Project {
|
||||
prettiers_per_worktree: HashMap::default(),
|
||||
prettier_instances: HashMap::default(),
|
||||
tasks,
|
||||
hosted_project_id: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -615,17 +617,30 @@ impl Project {
|
||||
user_store: Model<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
role: proto::ChannelRole,
|
||||
mut cx: AsyncAppContext,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
client.authenticate_and_connect(true, &cx).await?;
|
||||
|
||||
let subscription = client.subscribe_to_entity(remote_id)?;
|
||||
let response = client
|
||||
.request_envelope(proto::JoinProject {
|
||||
project_id: remote_id,
|
||||
})
|
||||
.await?;
|
||||
Self::from_join_project_response(response, None, client, user_store, languages, fs, cx)
|
||||
.await
|
||||
}
|
||||
async fn from_join_project_response(
|
||||
response: TypedEnvelope<proto::JoinProjectResponse>,
|
||||
hosted_project_id: Option<HostedProjectId>,
|
||||
client: Arc<Client>,
|
||||
user_store: Model<UserStore>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
let remote_id = response.payload.project_id;
|
||||
let role = response.payload.role();
|
||||
let subscription = client.subscribe_to_entity(remote_id)?;
|
||||
let this = cx.new_model(|cx| {
|
||||
let replica_id = response.payload.replica_id as ReplicaId;
|
||||
let tasks = Inventory::new(cx);
|
||||
@ -714,6 +729,7 @@ impl Project {
|
||||
prettiers_per_worktree: HashMap::default(),
|
||||
prettier_instances: HashMap::default(),
|
||||
tasks,
|
||||
hosted_project_id,
|
||||
};
|
||||
this.set_role(role, cx);
|
||||
for worktree in worktrees {
|
||||
@ -742,6 +758,31 @@ impl Project {
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub async fn hosted(
|
||||
hosted_project_id: HostedProjectId,
|
||||
user_store: Model<UserStore>,
|
||||
client: Arc<Client>,
|
||||
languages: Arc<LanguageRegistry>,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<Model<Self>> {
|
||||
let response = client
|
||||
.request_envelope(proto::JoinHostedProject {
|
||||
id: hosted_project_id.0,
|
||||
})
|
||||
.await?;
|
||||
Self::from_join_project_response(
|
||||
response,
|
||||
Some(hosted_project_id),
|
||||
client,
|
||||
user_store,
|
||||
languages,
|
||||
fs,
|
||||
cx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
fn release(&mut self, cx: &mut AppContext) {
|
||||
match &self.client_state {
|
||||
ProjectClientState::Local => {}
|
||||
@ -987,6 +1028,10 @@ impl Project {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn hosted_project_id(&self) -> Option<HostedProjectId> {
|
||||
self.hosted_project_id
|
||||
}
|
||||
|
||||
pub fn replica_id(&self) -> ReplicaId {
|
||||
match self.client_state {
|
||||
ProjectClientState::Remote { replica_id, .. } => replica_id,
|
||||
|
@ -196,6 +196,8 @@ message Envelope {
|
||||
|
||||
GetImplementation get_implementation = 162;
|
||||
GetImplementationResponse get_implementation_response = 163;
|
||||
|
||||
JoinHostedProject join_hosted_project = 164;
|
||||
}
|
||||
|
||||
reserved 158 to 161;
|
||||
@ -230,6 +232,7 @@ enum ErrorCode {
|
||||
CircularNesting = 10;
|
||||
WrongMoveTarget = 11;
|
||||
UnsharedItem = 12;
|
||||
NoSuchProject = 13;
|
||||
reserved 6;
|
||||
}
|
||||
|
||||
@ -404,11 +407,17 @@ message JoinProject {
|
||||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
message JoinHostedProject {
|
||||
uint64 id = 1;
|
||||
}
|
||||
|
||||
message JoinProjectResponse {
|
||||
uint64 project_id = 5;
|
||||
uint32 replica_id = 1;
|
||||
repeated WorktreeMetadata worktrees = 2;
|
||||
repeated Collaborator collaborators = 3;
|
||||
repeated LanguageServer language_servers = 4;
|
||||
ChannelRole role = 6;
|
||||
}
|
||||
|
||||
message LeaveProject {
|
||||
|
@ -206,6 +206,7 @@ messages!(
|
||||
(JoinChannelChat, Foreground),
|
||||
(JoinChannelChatResponse, Foreground),
|
||||
(JoinProject, Foreground),
|
||||
(JoinHostedProject, Foreground),
|
||||
(JoinProjectResponse, Foreground),
|
||||
(JoinRoom, Foreground),
|
||||
(JoinRoomResponse, Foreground),
|
||||
@ -329,6 +330,7 @@ request_messages!(
|
||||
(JoinChannel, JoinRoomResponse),
|
||||
(JoinChannelBuffer, JoinChannelBufferResponse),
|
||||
(JoinChannelChat, JoinChannelChatResponse),
|
||||
(JoinHostedProject, JoinProjectResponse),
|
||||
(JoinProject, JoinProjectResponse),
|
||||
(JoinRoom, JoinRoomResponse),
|
||||
(LeaveChannelBuffer, Ack),
|
||||
|
@ -268,7 +268,7 @@ impl Member {
|
||||
this.cursor_pointer().on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _, cx| {
|
||||
crate::join_remote_project(
|
||||
crate::join_in_room_project(
|
||||
leader_project_id,
|
||||
leader_user_id,
|
||||
this.app_state().clone(),
|
||||
|
@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result};
|
||||
use call::{call_settings::CallSettings, ActiveCall};
|
||||
use client::{
|
||||
proto::{self, ErrorCode, PeerId},
|
||||
ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore,
|
||||
ChannelId, Client, ErrorExt, HostedProjectId, Status, TypedEnvelope, UserStore,
|
||||
};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
@ -2635,7 +2635,7 @@ impl Workspace {
|
||||
// if they are active in another project, follow there.
|
||||
if let Some(project_id) = other_project_id {
|
||||
let app_state = self.app_state.clone();
|
||||
crate::join_remote_project(project_id, remote_participant.user.id, app_state, cx)
|
||||
crate::join_in_room_project(project_id, remote_participant.user.id, app_state, cx)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
|
||||
@ -4158,7 +4158,7 @@ async fn join_channel_internal(
|
||||
if let Some(room) = open_room {
|
||||
let task = room.update(cx, |room, cx| {
|
||||
if let Some((project, host)) = room.most_active_project(cx) {
|
||||
return Some(join_remote_project(project, host, app_state.clone(), cx));
|
||||
return Some(join_in_room_project(project, host, app_state.clone(), cx));
|
||||
}
|
||||
|
||||
None
|
||||
@ -4229,7 +4229,7 @@ async fn join_channel_internal(
|
||||
|
||||
let task = room.update(cx, |room, cx| {
|
||||
if let Some((project, host)) = room.most_active_project(cx) {
|
||||
return Some(join_remote_project(project, host, app_state.clone(), cx));
|
||||
return Some(join_in_room_project(project, host, app_state.clone(), cx));
|
||||
}
|
||||
|
||||
// if you are the first to join a channel, share your project
|
||||
@ -4464,7 +4464,56 @@ pub fn create_and_open_local_file(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn join_remote_project(
|
||||
pub fn join_hosted_project(
|
||||
hosted_project_id: HostedProjectId,
|
||||
app_state: Arc<AppState>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
cx.spawn(|mut cx| async move {
|
||||
let existing_window = cx.update(|cx| {
|
||||
cx.windows().into_iter().find_map(|window| {
|
||||
let workspace = window.downcast::<Workspace>()?;
|
||||
workspace
|
||||
.read(cx)
|
||||
.is_ok_and(|workspace| {
|
||||
workspace.project().read(cx).hosted_project_id() == Some(hosted_project_id)
|
||||
})
|
||||
.then(|| workspace)
|
||||
})
|
||||
})?;
|
||||
|
||||
let workspace = if let Some(existing_window) = existing_window {
|
||||
existing_window
|
||||
} else {
|
||||
let project = Project::hosted(
|
||||
hosted_project_id,
|
||||
app_state.user_store.clone(),
|
||||
app_state.client.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let window_bounds_override = window_bounds_env_override(&cx);
|
||||
cx.update(|cx| {
|
||||
let options = (app_state.build_window_options)(window_bounds_override, None, cx);
|
||||
cx.open_window(options, |cx| {
|
||||
cx.new_view(|cx| Workspace::new(0, project, app_state.clone(), cx))
|
||||
})
|
||||
})?
|
||||
};
|
||||
|
||||
workspace.update(&mut cx, |_, cx| {
|
||||
cx.activate(true);
|
||||
cx.activate_window();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn join_in_room_project(
|
||||
project_id: u64,
|
||||
follow_user_id: u64,
|
||||
app_state: Arc<AppState>,
|
||||
|
Loading…
Reference in New Issue
Block a user