mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-01 04:28:57 +00:00
Remove dev servers (#19638)
TODO: - [ ] Check that workspace migration worked - [ ] Add server migrations and make sure SeaORM files are in sync (maybe?) Release Notes: - N/A --------- Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
parent
b5f816dde5
commit
02718284ef
55 changed files with 391 additions and 5024 deletions
45
Cargo.lock
generated
45
Cargo.lock
generated
|
@ -2550,7 +2550,6 @@ dependencies = [
|
|||
"ctor",
|
||||
"dashmap 6.0.1",
|
||||
"derive_more",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"env_logger",
|
||||
"envy",
|
||||
|
@ -2561,7 +2560,6 @@ dependencies = [
|
|||
"git_hosting_providers",
|
||||
"google_ai",
|
||||
"gpui",
|
||||
"headless",
|
||||
"hex",
|
||||
"http_client",
|
||||
"hyper 0.14.30",
|
||||
|
@ -3476,18 +3474,6 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dev_server_projects"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"gpui",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diagnostics"
|
||||
version = "0.1.0"
|
||||
|
@ -5274,28 +5260,6 @@ dependencies = [
|
|||
"http 0.2.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headless"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"client",
|
||||
"extension",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
"gpui",
|
||||
"language",
|
||||
"log",
|
||||
"node_runtime",
|
||||
"postage",
|
||||
"project",
|
||||
"proto",
|
||||
"settings",
|
||||
"shellexpand 2.1.2",
|
||||
"signal-hook",
|
||||
"util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
|
@ -8443,7 +8407,6 @@ dependencies = [
|
|||
"client",
|
||||
"clock",
|
||||
"collections",
|
||||
"dev_server_projects",
|
||||
"env_logger",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
|
@ -8981,8 +8944,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"auto_update",
|
||||
"client",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"file_finder",
|
||||
"futures 0.3.30",
|
||||
|
@ -8999,14 +8960,12 @@ dependencies = [
|
|||
"project",
|
||||
"release_channel",
|
||||
"remote",
|
||||
"rpc",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"settings",
|
||||
"smol",
|
||||
"task",
|
||||
"terminal_view",
|
||||
"theme",
|
||||
"ui",
|
||||
"util",
|
||||
|
@ -11912,7 +11871,6 @@ dependencies = [
|
|||
"client",
|
||||
"collections",
|
||||
"command_palette",
|
||||
"dev_server_projects",
|
||||
"editor",
|
||||
"extensions_ui",
|
||||
"feature_flags",
|
||||
|
@ -14309,7 +14267,6 @@ dependencies = [
|
|||
"collections",
|
||||
"db",
|
||||
"derive_more",
|
||||
"dev_server_projects",
|
||||
"env_logger",
|
||||
"fs",
|
||||
"futures 0.3.30",
|
||||
|
@ -14628,7 +14585,6 @@ dependencies = [
|
|||
"command_palette_hooks",
|
||||
"copilot",
|
||||
"db",
|
||||
"dev_server_projects",
|
||||
"diagnostics",
|
||||
"editor",
|
||||
"env_logger",
|
||||
|
@ -14644,7 +14600,6 @@ dependencies = [
|
|||
"git_hosting_providers",
|
||||
"go_to_line",
|
||||
"gpui",
|
||||
"headless",
|
||||
"http_client",
|
||||
"image_viewer",
|
||||
"inline_completion_button",
|
||||
|
|
|
@ -23,7 +23,6 @@ members = [
|
|||
"crates/context_servers",
|
||||
"crates/copilot",
|
||||
"crates/db",
|
||||
"crates/dev_server_projects",
|
||||
"crates/diagnostics",
|
||||
"crates/docs_preprocessor",
|
||||
"crates/editor",
|
||||
|
@ -45,7 +44,6 @@ members = [
|
|||
"crates/google_ai",
|
||||
"crates/gpui",
|
||||
"crates/gpui_macros",
|
||||
"crates/headless",
|
||||
"crates/html_to_markdown",
|
||||
"crates/http_client",
|
||||
"crates/image_viewer",
|
||||
|
@ -201,7 +199,6 @@ command_palette_hooks = { path = "crates/command_palette_hooks" }
|
|||
context_servers = { path = "crates/context_servers" }
|
||||
copilot = { path = "crates/copilot" }
|
||||
db = { path = "crates/db" }
|
||||
dev_server_projects = { path = "crates/dev_server_projects" }
|
||||
diagnostics = { path = "crates/diagnostics" }
|
||||
editor = { path = "crates/editor" }
|
||||
extension = { path = "crates/extension" }
|
||||
|
@ -219,7 +216,6 @@ go_to_line = { path = "crates/go_to_line" }
|
|||
google_ai = { path = "crates/google_ai" }
|
||||
gpui = { path = "crates/gpui", default-features = false, features = ["http_client"]}
|
||||
gpui_macros = { path = "crates/gpui_macros" }
|
||||
headless = { path = "crates/headless" }
|
||||
html_to_markdown = { path = "crates/html_to_markdown" }
|
||||
http_client = { path = "crates/http_client" }
|
||||
image_viewer = { path = "crates/image_viewer" }
|
||||
|
|
|
@ -963,7 +963,7 @@ impl AssistantPanel {
|
|||
|
||||
fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
|
||||
let project = self.project.read(cx);
|
||||
if project.is_via_collab() && project.dev_server_project_id().is_none() {
|
||||
if project.is_via_collab() {
|
||||
let task = self
|
||||
.context_store
|
||||
.update(cx, |store, cx| store.create_remote_context(cx));
|
||||
|
|
|
@ -1194,26 +1194,15 @@ impl Room {
|
|||
project: Model<Project>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<u64>> {
|
||||
let request = if let Some(dev_server_project_id) = project.read(cx).dev_server_project_id()
|
||||
{
|
||||
self.client.request(proto::ShareProject {
|
||||
room_id: self.id(),
|
||||
worktrees: vec![],
|
||||
dev_server_project_id: Some(dev_server_project_id.0),
|
||||
is_ssh_project: false,
|
||||
})
|
||||
} else {
|
||||
if let Some(project_id) = project.read(cx).remote_id() {
|
||||
return Task::ready(Ok(project_id));
|
||||
}
|
||||
if let Some(project_id) = project.read(cx).remote_id() {
|
||||
return Task::ready(Ok(project_id));
|
||||
}
|
||||
|
||||
self.client.request(proto::ShareProject {
|
||||
room_id: self.id(),
|
||||
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
||||
dev_server_project_id: None,
|
||||
is_ssh_project: project.read(cx).is_via_ssh(),
|
||||
})
|
||||
};
|
||||
let request = self.client.request(proto::ShareProject {
|
||||
room_id: self.id(),
|
||||
worktrees: project.read(cx).worktree_metadata_protos(cx),
|
||||
is_ssh_project: project.read(cx).is_via_ssh(),
|
||||
});
|
||||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let response = request.await?;
|
||||
|
|
|
@ -15,7 +15,6 @@ pub enum CliRequest {
|
|||
urls: Vec<String>,
|
||||
wait: bool,
|
||||
open_new_workspace: Option<bool>,
|
||||
dev_server_token: Option<String>,
|
||||
env: Option<HashMap<String, String>>,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -151,6 +151,12 @@ fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(_) = args.dev_server_token {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Dev servers were removed in v0.157.x please upgrade to SSH remoting: https://zed.dev/docs/remote-development"
|
||||
))?;
|
||||
}
|
||||
|
||||
let sender: JoinHandle<anyhow::Result<()>> = thread::spawn({
|
||||
let exit_status = exit_status.clone();
|
||||
move || {
|
||||
|
@ -162,7 +168,6 @@ fn main() -> Result<()> {
|
|||
urls,
|
||||
wait: args.wait,
|
||||
open_new_workspace,
|
||||
dev_server_token: args.dev_server_token,
|
||||
env,
|
||||
})?;
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsSources};
|
||||
use socks::connect_socks_proxy_stream;
|
||||
use std::fmt;
|
||||
use std::pin::Pin;
|
||||
use std::{
|
||||
any::TypeId,
|
||||
|
@ -54,15 +53,6 @@ pub use rpc::*;
|
|||
pub use telemetry_events::Event;
|
||||
pub use user::*;
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct DevServerToken(pub String);
|
||||
|
||||
impl fmt::Display for DevServerToken {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
static ZED_SERVER_URL: LazyLock<Option<String>> =
|
||||
LazyLock::new(|| std::env::var("ZED_SERVER_URL").ok());
|
||||
static ZED_RPC_URL: LazyLock<Option<String>> = LazyLock::new(|| std::env::var("ZED_RPC_URL").ok());
|
||||
|
@ -304,20 +294,14 @@ struct ClientState {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Credentials {
|
||||
DevServer { token: DevServerToken },
|
||||
User { user_id: u64, access_token: String },
|
||||
pub struct Credentials {
|
||||
pub user_id: u64,
|
||||
pub access_token: String,
|
||||
}
|
||||
|
||||
impl Credentials {
|
||||
pub fn authorization_header(&self) -> String {
|
||||
match self {
|
||||
Credentials::DevServer { token } => format!("dev-server-token {}", token),
|
||||
Credentials::User {
|
||||
user_id,
|
||||
access_token,
|
||||
} => format!("{} {}", user_id, access_token),
|
||||
}
|
||||
format!("{} {}", self.user_id, self.access_token)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,11 +584,11 @@ impl Client {
|
|||
}
|
||||
|
||||
pub fn user_id(&self) -> Option<u64> {
|
||||
if let Some(Credentials::User { user_id, .. }) = self.state.read().credentials.as_ref() {
|
||||
Some(*user_id)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.state
|
||||
.read()
|
||||
.credentials
|
||||
.as_ref()
|
||||
.map(|credentials| credentials.user_id)
|
||||
}
|
||||
|
||||
pub fn peer_id(&self) -> Option<PeerId> {
|
||||
|
@ -793,11 +777,6 @@ impl Client {
|
|||
.is_some()
|
||||
}
|
||||
|
||||
pub fn set_dev_server_token(&self, token: DevServerToken) -> &Self {
|
||||
self.state.write().credentials = Some(Credentials::DevServer { token });
|
||||
self
|
||||
}
|
||||
|
||||
#[async_recursion(?Send)]
|
||||
pub async fn authenticate_and_connect(
|
||||
self: &Arc<Self>,
|
||||
|
@ -848,9 +827,7 @@ impl Client {
|
|||
}
|
||||
}
|
||||
let credentials = credentials.unwrap();
|
||||
if let Credentials::User { user_id, .. } = &credentials {
|
||||
self.set_id(*user_id);
|
||||
}
|
||||
self.set_id(credentials.user_id);
|
||||
|
||||
if was_disconnected {
|
||||
self.set_status(Status::Connecting, cx);
|
||||
|
@ -866,9 +843,8 @@ impl Client {
|
|||
Ok(conn) => {
|
||||
self.state.write().credentials = Some(credentials.clone());
|
||||
if !read_from_provider && IMPERSONATE_LOGIN.is_none() {
|
||||
if let Credentials::User{user_id, access_token} = credentials {
|
||||
self.credentials_provider.write_credentials(user_id, access_token, cx).await.log_err();
|
||||
}
|
||||
self.credentials_provider.write_credentials(credentials.user_id, credentials.access_token, cx).await.log_err();
|
||||
|
||||
}
|
||||
|
||||
futures::select_biased! {
|
||||
|
@ -1301,7 +1277,7 @@ impl Client {
|
|||
.decrypt_string(&access_token)
|
||||
.context("failed to decrypt access token")?;
|
||||
|
||||
Ok(Credentials::User {
|
||||
Ok(Credentials {
|
||||
user_id: user_id.parse()?,
|
||||
access_token,
|
||||
})
|
||||
|
@ -1422,7 +1398,7 @@ impl Client {
|
|||
|
||||
// Use the admin API token to authenticate as the impersonated user.
|
||||
api_token.insert_str(0, "ADMIN_TOKEN:");
|
||||
Ok(Credentials::User {
|
||||
Ok(Credentials {
|
||||
user_id: response.user.id,
|
||||
access_token: api_token,
|
||||
})
|
||||
|
@ -1667,7 +1643,7 @@ impl CredentialsProvider for DevelopmentCredentialsProvider {
|
|||
|
||||
let credentials: DevelopmentCredentials = serde_json::from_slice(&json).log_err()?;
|
||||
|
||||
Some(Credentials::User {
|
||||
Some(Credentials {
|
||||
user_id: credentials.user_id,
|
||||
access_token: credentials.access_token,
|
||||
})
|
||||
|
@ -1721,7 +1697,7 @@ impl CredentialsProvider for KeychainCredentialsProvider {
|
|||
.await
|
||||
.log_err()??;
|
||||
|
||||
Some(Credentials::User {
|
||||
Some(Credentials {
|
||||
user_id: user_id.parse().ok()?,
|
||||
access_token: String::from_utf8(access_token).ok()?,
|
||||
})
|
||||
|
@ -1855,7 +1831,7 @@ mod tests {
|
|||
// Time out when client tries to connect.
|
||||
client.override_authenticate(move |cx| {
|
||||
cx.background_executor().spawn(async move {
|
||||
Ok(Credentials::User {
|
||||
Ok(Credentials {
|
||||
user_id,
|
||||
access_token: "token".into(),
|
||||
})
|
||||
|
|
|
@ -49,7 +49,7 @@ impl FakeServer {
|
|||
let mut state = state.lock();
|
||||
state.auth_count += 1;
|
||||
let access_token = state.access_token.to_string();
|
||||
Ok(Credentials::User {
|
||||
Ok(Credentials {
|
||||
user_id: client_user_id,
|
||||
access_token,
|
||||
})
|
||||
|
@ -73,7 +73,7 @@ impl FakeServer {
|
|||
}
|
||||
|
||||
if credentials
|
||||
!= (Credentials::User {
|
||||
!= (Credentials {
|
||||
user_id: client_user_id,
|
||||
access_token: state.lock().access_token.to_string(),
|
||||
})
|
||||
|
|
|
@ -28,9 +28,6 @@ impl std::fmt::Display for ChannelId {
|
|||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub struct ProjectId(pub u64);
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
pub struct DevServerId(pub u64);
|
||||
|
||||
#[derive(
|
||||
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Serialize, serde::Deserialize,
|
||||
)]
|
||||
|
|
|
@ -86,7 +86,6 @@ client = { workspace = true, features = ["test-support"] }
|
|||
collab_ui = { workspace = true, features = ["test-support"] }
|
||||
collections = { workspace = true, features = ["test-support"] }
|
||||
ctor.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
editor = { workspace = true, features = ["test-support"] }
|
||||
env_logger.workspace = true
|
||||
file_finder.workspace = true
|
||||
|
@ -94,7 +93,6 @@ fs = { workspace = true, features = ["test-support"] }
|
|||
git = { workspace = true, features = ["test-support"] }
|
||||
git_hosting_providers.workspace = true
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
headless.workspace = true
|
||||
hyper.workspace = true
|
||||
indoc.workspace = true
|
||||
language = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
db::{self, dev_server, AccessTokenId, Database, DevServerId, UserId},
|
||||
db::{self, AccessTokenId, Database, UserId},
|
||||
rpc::Principal,
|
||||
AppState, Error, Result,
|
||||
};
|
||||
|
@ -44,19 +44,10 @@ pub async fn validate_header<B>(mut req: Request<B>, next: Next<B>) -> impl Into
|
|||
|
||||
let first = auth_header.next().unwrap_or("");
|
||||
if first == "dev-server-token" {
|
||||
let dev_server_token = auth_header.next().ok_or_else(|| {
|
||||
Error::http(
|
||||
StatusCode::BAD_REQUEST,
|
||||
"missing dev-server-token token in authorization header".to_string(),
|
||||
)
|
||||
})?;
|
||||
let dev_server = verify_dev_server_token(dev_server_token, &state.db)
|
||||
.await
|
||||
.map_err(|e| Error::http(StatusCode::UNAUTHORIZED, format!("{}", e)))?;
|
||||
|
||||
req.extensions_mut()
|
||||
.insert(Principal::DevServer(dev_server));
|
||||
return Ok::<_, Error>(next.run(req).await);
|
||||
Err(Error::http(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Dev servers were removed in Zed 0.157 please upgrade to SSH remoting".to_string(),
|
||||
))?;
|
||||
}
|
||||
|
||||
let user_id = UserId(first.parse().map_err(|_| {
|
||||
|
@ -240,41 +231,6 @@ pub async fn verify_access_token(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn generate_dev_server_token(id: usize, access_token: String) -> String {
|
||||
format!("{}.{}", id, access_token)
|
||||
}
|
||||
|
||||
pub async fn verify_dev_server_token(
|
||||
dev_server_token: &str,
|
||||
db: &Arc<Database>,
|
||||
) -> anyhow::Result<dev_server::Model> {
|
||||
let (id, token) = split_dev_server_token(dev_server_token)?;
|
||||
let token_hash = hash_access_token(token);
|
||||
let server = db.get_dev_server(id).await?;
|
||||
|
||||
if server
|
||||
.hashed_token
|
||||
.as_bytes()
|
||||
.ct_eq(token_hash.as_ref())
|
||||
.into()
|
||||
{
|
||||
Ok(server)
|
||||
} else {
|
||||
Err(anyhow!("wrong token for dev server"))
|
||||
}
|
||||
}
|
||||
|
||||
// a dev_server_token has the format <id>.<base64>. This is to make them
|
||||
// relatively easy to copy/paste around.
|
||||
pub fn split_dev_server_token(dev_server_token: &str) -> anyhow::Result<(DevServerId, &str)> {
|
||||
let mut parts = dev_server_token.splitn(2, '.');
|
||||
let id = DevServerId(parts.next().unwrap_or_default().parse()?);
|
||||
let token = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("invalid dev server token format"))?;
|
||||
Ok((id, token))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use rand::thread_rng;
|
||||
|
|
|
@ -726,7 +726,6 @@ pub struct Project {
|
|||
pub collaborators: Vec<ProjectCollaborator>,
|
||||
pub worktrees: BTreeMap<u64, Worktree>,
|
||||
pub language_servers: Vec<proto::LanguageServer>,
|
||||
pub dev_server_project_id: Option<DevServerProjectId>,
|
||||
}
|
||||
|
||||
pub struct ProjectCollaborator {
|
||||
|
|
|
@ -79,7 +79,6 @@ id_type!(ChannelChatParticipantId);
|
|||
id_type!(ChannelId);
|
||||
id_type!(ChannelMemberId);
|
||||
id_type!(ContactId);
|
||||
id_type!(DevServerId);
|
||||
id_type!(ExtensionId);
|
||||
id_type!(FlagId);
|
||||
id_type!(FollowerId);
|
||||
|
@ -89,7 +88,6 @@ id_type!(NotificationId);
|
|||
id_type!(NotificationKindId);
|
||||
id_type!(ProjectCollaboratorId);
|
||||
id_type!(ProjectId);
|
||||
id_type!(DevServerProjectId);
|
||||
id_type!(ReplicaId);
|
||||
id_type!(RoomId);
|
||||
id_type!(RoomParticipantId);
|
||||
|
@ -277,12 +275,6 @@ impl From<ChannelVisibility> for i32 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, PartialEq)]
|
||||
pub enum PrincipalId {
|
||||
UserId(UserId),
|
||||
DevServerId(DevServerId),
|
||||
}
|
||||
|
||||
/// Indicate whether a [Buffer] has permissions to edit.
|
||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||
pub enum Capability {
|
||||
|
|
|
@ -8,8 +8,6 @@ pub mod buffers;
|
|||
pub mod channels;
|
||||
pub mod contacts;
|
||||
pub mod contributors;
|
||||
pub mod dev_server_projects;
|
||||
pub mod dev_servers;
|
||||
pub mod embeddings;
|
||||
pub mod extensions;
|
||||
pub mod hosted_projects;
|
||||
|
|
|
@ -1,365 +1 @@
|
|||
use anyhow::anyhow;
|
||||
use rpc::{
|
||||
proto::{self},
|
||||
ConnectionId,
|
||||
};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue, ColumnTrait, Condition, DatabaseTransaction, EntityTrait,
|
||||
IntoActiveModel, ModelTrait, QueryFilter,
|
||||
};
|
||||
|
||||
use crate::db::ProjectId;
|
||||
|
||||
use super::{
|
||||
dev_server, dev_server_project, project, project_collaborator, worktree, Database, DevServerId,
|
||||
DevServerProjectId, RejoinedProject, ResharedProject, ServerId, UserId,
|
||||
};
|
||||
|
||||
impl Database {
|
||||
pub async fn get_dev_server_project(
|
||||
&self,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
) -> crate::Result<dev_server_project::Model> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(
|
||||
dev_server_project::Entity::find_by_id(dev_server_project_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
anyhow!("no dev server project with id {}", dev_server_project_id)
|
||||
})?,
|
||||
)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_projects_for_dev_server(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
) -> crate::Result<Vec<proto::DevServerProject>> {
|
||||
self.transaction(|tx| async move {
|
||||
self.get_projects_for_dev_server_internal(dev_server_id, &tx)
|
||||
.await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_projects_for_dev_server_internal(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> crate::Result<Vec<proto::DevServerProject>> {
|
||||
let servers = dev_server_project::Entity::find()
|
||||
.filter(dev_server_project::Column::DevServerId.eq(dev_server_id))
|
||||
.find_also_related(project::Entity)
|
||||
.all(tx)
|
||||
.await?;
|
||||
Ok(servers
|
||||
.into_iter()
|
||||
.map(|(dev_server_project, project)| dev_server_project.to_proto(project))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn dev_server_project_ids_for_user(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> crate::Result<Vec<DevServerProjectId>> {
|
||||
let dev_servers = dev_server::Entity::find()
|
||||
.filter(dev_server::Column::UserId.eq(user_id))
|
||||
.find_with_related(dev_server_project::Entity)
|
||||
.all(tx)
|
||||
.await?;
|
||||
|
||||
Ok(dev_servers
|
||||
.into_iter()
|
||||
.flat_map(|(_, projects)| projects.into_iter().map(|p| p.id))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn owner_for_dev_server_project(
|
||||
&self,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> crate::Result<UserId> {
|
||||
let dev_server = dev_server_project::Entity::find_by_id(dev_server_project_id)
|
||||
.find_also_related(dev_server::Entity)
|
||||
.one(tx)
|
||||
.await?
|
||||
.and_then(|(_, dev_server)| dev_server)
|
||||
.ok_or_else(|| anyhow!("no dev server project"))?;
|
||||
|
||||
Ok(dev_server.user_id)
|
||||
}
|
||||
|
||||
pub async fn get_stale_dev_server_projects(
|
||||
&self,
|
||||
connection: ConnectionId,
|
||||
) -> crate::Result<Vec<ProjectId>> {
|
||||
self.transaction(|tx| async move {
|
||||
let projects = project::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(project::Column::HostConnectionId.eq(connection.id))
|
||||
.add(project::Column::HostConnectionServerId.eq(connection.owner_id)),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
Ok(projects.into_iter().map(|p| p.id).collect())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_dev_server_project(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
path: &str,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
|
||||
self.transaction(|tx| async move {
|
||||
let dev_server = dev_server::Entity::find_by_id(dev_server_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?;
|
||||
if dev_server.user_id != user_id {
|
||||
return Err(anyhow!("not your dev server"))?;
|
||||
}
|
||||
|
||||
let project = dev_server_project::Entity::insert(dev_server_project::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
dev_server_id: ActiveValue::Set(dev_server_id),
|
||||
paths: ActiveValue::Set(dev_server_project::JSONPaths(vec![path.to_string()])),
|
||||
})
|
||||
.exec_with_returning(&*tx)
|
||||
.await?;
|
||||
|
||||
let status = self
|
||||
.dev_server_projects_update_internal(user_id, &tx)
|
||||
.await?;
|
||||
|
||||
Ok((project, status))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_dev_server_project(
|
||||
&self,
|
||||
id: DevServerProjectId,
|
||||
paths: &[String],
|
||||
user_id: UserId,
|
||||
) -> crate::Result<(dev_server_project::Model, proto::DevServerProjectsUpdate)> {
|
||||
self.transaction(move |tx| async move {
|
||||
let paths = paths.to_owned();
|
||||
let Some((project, Some(dev_server))) = dev_server_project::Entity::find_by_id(id)
|
||||
.find_also_related(dev_server::Entity)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
else {
|
||||
return Err(anyhow!("no such dev server project"))?;
|
||||
};
|
||||
|
||||
if dev_server.user_id != user_id {
|
||||
return Err(anyhow!("not your dev server"))?;
|
||||
}
|
||||
let mut project = project.into_active_model();
|
||||
project.paths = ActiveValue::Set(dev_server_project::JSONPaths(paths));
|
||||
let project = project.update(&*tx).await?;
|
||||
|
||||
let status = self
|
||||
.dev_server_projects_update_internal(user_id, &tx)
|
||||
.await?;
|
||||
|
||||
Ok((project, status))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_dev_server_project(
|
||||
&self,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
dev_server_id: DevServerId,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<(Vec<proto::DevServerProject>, proto::DevServerProjectsUpdate)> {
|
||||
self.transaction(|tx| async move {
|
||||
project::Entity::delete_many()
|
||||
.filter(project::Column::DevServerProjectId.eq(dev_server_project_id))
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
let result = dev_server_project::Entity::delete_by_id(dev_server_project_id)
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
if result.rows_affected != 1 {
|
||||
return Err(anyhow!(
|
||||
"no dev server project with id {}",
|
||||
dev_server_project_id
|
||||
))?;
|
||||
}
|
||||
|
||||
let status = self
|
||||
.dev_server_projects_update_internal(user_id, &tx)
|
||||
.await?;
|
||||
|
||||
let projects = self
|
||||
.get_projects_for_dev_server_internal(dev_server_id, &tx)
|
||||
.await?;
|
||||
Ok((projects, status))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn share_dev_server_project(
|
||||
&self,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
dev_server_id: DevServerId,
|
||||
connection: ConnectionId,
|
||||
worktrees: &[proto::WorktreeMetadata],
|
||||
) -> crate::Result<(
|
||||
proto::DevServerProject,
|
||||
UserId,
|
||||
proto::DevServerProjectsUpdate,
|
||||
)> {
|
||||
self.transaction(|tx| async move {
|
||||
let dev_server = dev_server::Entity::find_by_id(dev_server_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no dev server with id {}", dev_server_id))?;
|
||||
|
||||
let dev_server_project = dev_server_project::Entity::find_by_id(dev_server_project_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
anyhow!("no dev server project with id {}", dev_server_project_id)
|
||||
})?;
|
||||
|
||||
if dev_server_project.dev_server_id != dev_server_id {
|
||||
return Err(anyhow!("dev server project shared from wrong server"))?;
|
||||
}
|
||||
|
||||
let project = project::ActiveModel {
|
||||
room_id: ActiveValue::Set(None),
|
||||
host_user_id: ActiveValue::Set(None),
|
||||
host_connection_id: ActiveValue::set(Some(connection.id as i32)),
|
||||
host_connection_server_id: ActiveValue::set(Some(ServerId(
|
||||
connection.owner_id as i32,
|
||||
))),
|
||||
id: ActiveValue::NotSet,
|
||||
hosted_project_id: ActiveValue::Set(None),
|
||||
dev_server_project_id: ActiveValue::Set(Some(dev_server_project_id)),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
|
||||
if !worktrees.is_empty() {
|
||||
worktree::Entity::insert_many(worktrees.iter().map(|worktree| {
|
||||
worktree::ActiveModel {
|
||||
id: ActiveValue::set(worktree.id as i64),
|
||||
project_id: ActiveValue::set(project.id),
|
||||
abs_path: ActiveValue::set(worktree.abs_path.clone()),
|
||||
root_name: ActiveValue::set(worktree.root_name.clone()),
|
||||
visible: ActiveValue::set(worktree.visible),
|
||||
scan_id: ActiveValue::set(0),
|
||||
completed_scan_id: ActiveValue::set(0),
|
||||
}
|
||||
}))
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let status = self
|
||||
.dev_server_projects_update_internal(dev_server.user_id, &tx)
|
||||
.await?;
|
||||
|
||||
Ok((
|
||||
dev_server_project.to_proto(Some(project)),
|
||||
dev_server.user_id,
|
||||
status,
|
||||
))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn reshare_dev_server_projects(
|
||||
&self,
|
||||
reshared_projects: &Vec<proto::UpdateProject>,
|
||||
dev_server_id: DevServerId,
|
||||
connection: ConnectionId,
|
||||
) -> crate::Result<Vec<ResharedProject>> {
|
||||
self.transaction(|tx| async move {
|
||||
let mut ret = Vec::new();
|
||||
for reshared_project in reshared_projects {
|
||||
let project_id = ProjectId::from_proto(reshared_project.project_id);
|
||||
let (project, dev_server_project) = project::Entity::find_by_id(project_id)
|
||||
.find_also_related(dev_server_project::Entity)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("project does not exist"))?;
|
||||
|
||||
if dev_server_project.map(|rp| rp.dev_server_id) != Some(dev_server_id) {
|
||||
return Err(anyhow!("dev server project reshared from wrong server"))?;
|
||||
}
|
||||
|
||||
let Ok(old_connection_id) = project.host_connection() else {
|
||||
return Err(anyhow!("dev server project was not shared"))?;
|
||||
};
|
||||
|
||||
project::Entity::update(project::ActiveModel {
|
||||
id: ActiveValue::set(project_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()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
let collaborators = project
|
||||
.find_related(project_collaborator::Entity)
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
self.update_project_worktrees(project_id, &reshared_project.worktrees, &tx)
|
||||
.await?;
|
||||
|
||||
ret.push(super::ResharedProject {
|
||||
id: project_id,
|
||||
old_connection_id,
|
||||
collaborators: collaborators
|
||||
.iter()
|
||||
.map(|collaborator| super::ProjectCollaborator {
|
||||
connection_id: collaborator.connection(),
|
||||
user_id: collaborator.user_id,
|
||||
replica_id: collaborator.replica_id,
|
||||
is_host: collaborator.is_host,
|
||||
})
|
||||
.collect(),
|
||||
worktrees: reshared_project.worktrees.clone(),
|
||||
});
|
||||
}
|
||||
Ok(ret)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn rejoin_dev_server_projects(
|
||||
&self,
|
||||
rejoined_projects: &Vec<proto::RejoinProject>,
|
||||
user_id: UserId,
|
||||
connection_id: ConnectionId,
|
||||
) -> crate::Result<Vec<RejoinedProject>> {
|
||||
self.transaction(|tx| async move {
|
||||
let mut ret = Vec::new();
|
||||
for rejoined_project in rejoined_projects {
|
||||
if let Some(project) = self
|
||||
.rejoin_project_internal(&tx, rejoined_project, user_id, connection_id)
|
||||
.await?
|
||||
{
|
||||
ret.push(project);
|
||||
}
|
||||
}
|
||||
Ok(ret)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,222 +1 @@
|
|||
use rpc::proto;
|
||||
use sea_orm::{
|
||||
ActiveValue, ColumnTrait, DatabaseTransaction, EntityTrait, IntoActiveModel, QueryFilter,
|
||||
};
|
||||
|
||||
use super::{dev_server, dev_server_project, Database, DevServerId, UserId};
|
||||
|
||||
impl Database {
|
||||
pub async fn get_dev_server(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
) -> crate::Result<dev_server::Model> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(dev_server::Entity::find_by_id(dev_server_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("no dev server with id {}", dev_server_id))?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_dev_server_for_user(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<dev_server::Model> {
|
||||
self.transaction(|tx| async move {
|
||||
let server = dev_server::Entity::find_by_id(dev_server_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow::anyhow!("no dev server with id {}", dev_server_id))?;
|
||||
if server.user_id != user_id {
|
||||
return Err(anyhow::anyhow!(
|
||||
"dev server {} is not owned by user {}",
|
||||
dev_server_id,
|
||||
user_id
|
||||
))?;
|
||||
}
|
||||
Ok(server)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_dev_servers(&self, user_id: UserId) -> crate::Result<Vec<dev_server::Model>> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(dev_server::Entity::find()
|
||||
.filter(dev_server::Column::UserId.eq(user_id))
|
||||
.all(&*tx)
|
||||
.await?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn dev_server_projects_update(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<proto::DevServerProjectsUpdate> {
|
||||
self.transaction(|tx| async move {
|
||||
self.dev_server_projects_update_internal(user_id, &tx).await
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn dev_server_projects_update_internal(
|
||||
&self,
|
||||
user_id: UserId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> crate::Result<proto::DevServerProjectsUpdate> {
|
||||
let dev_servers = dev_server::Entity::find()
|
||||
.filter(dev_server::Column::UserId.eq(user_id))
|
||||
.all(tx)
|
||||
.await?;
|
||||
|
||||
let dev_server_projects = dev_server_project::Entity::find()
|
||||
.filter(
|
||||
dev_server_project::Column::DevServerId
|
||||
.is_in(dev_servers.iter().map(|d| d.id).collect::<Vec<_>>()),
|
||||
)
|
||||
.find_also_related(super::project::Entity)
|
||||
.all(tx)
|
||||
.await?;
|
||||
|
||||
Ok(proto::DevServerProjectsUpdate {
|
||||
dev_servers: dev_servers
|
||||
.into_iter()
|
||||
.map(|d| d.to_proto(proto::DevServerStatus::Offline))
|
||||
.collect(),
|
||||
dev_server_projects: dev_server_projects
|
||||
.into_iter()
|
||||
.map(|(dev_server_project, project)| dev_server_project.to_proto(project))
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn create_dev_server(
|
||||
&self,
|
||||
name: &str,
|
||||
ssh_connection_string: Option<&str>,
|
||||
hashed_access_token: &str,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<(dev_server::Model, proto::DevServerProjectsUpdate)> {
|
||||
self.transaction(|tx| async move {
|
||||
if name.trim().is_empty() {
|
||||
return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
|
||||
}
|
||||
|
||||
let dev_server = dev_server::Entity::insert(dev_server::ActiveModel {
|
||||
id: ActiveValue::NotSet,
|
||||
hashed_token: ActiveValue::Set(hashed_access_token.to_string()),
|
||||
name: ActiveValue::Set(name.trim().to_string()),
|
||||
user_id: ActiveValue::Set(user_id),
|
||||
ssh_connection_string: ActiveValue::Set(
|
||||
ssh_connection_string.map(ToOwned::to_owned),
|
||||
),
|
||||
})
|
||||
.exec_with_returning(&*tx)
|
||||
.await?;
|
||||
|
||||
let dev_server_projects = self
|
||||
.dev_server_projects_update_internal(user_id, &tx)
|
||||
.await?;
|
||||
|
||||
Ok((dev_server, dev_server_projects))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_dev_server_token(
|
||||
&self,
|
||||
id: DevServerId,
|
||||
hashed_token: &str,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<proto::DevServerProjectsUpdate> {
|
||||
self.transaction(|tx| async move {
|
||||
let Some(dev_server) = dev_server::Entity::find_by_id(id).one(&*tx).await? else {
|
||||
return Err(anyhow::anyhow!("no dev server with id {}", id))?;
|
||||
};
|
||||
if dev_server.user_id != user_id {
|
||||
return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
|
||||
}
|
||||
|
||||
dev_server::Entity::update(dev_server::ActiveModel {
|
||||
hashed_token: ActiveValue::Set(hashed_token.to_string()),
|
||||
..dev_server.clone().into_active_model()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
let dev_server_projects = self
|
||||
.dev_server_projects_update_internal(user_id, &tx)
|
||||
.await?;
|
||||
|
||||
Ok(dev_server_projects)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn rename_dev_server(
|
||||
&self,
|
||||
id: DevServerId,
|
||||
name: &str,
|
||||
ssh_connection_string: Option<&str>,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<proto::DevServerProjectsUpdate> {
|
||||
self.transaction(|tx| async move {
|
||||
let Some(dev_server) = dev_server::Entity::find_by_id(id).one(&*tx).await? else {
|
||||
return Err(anyhow::anyhow!("no dev server with id {}", id))?;
|
||||
};
|
||||
if dev_server.user_id != user_id || name.trim().is_empty() {
|
||||
return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
|
||||
}
|
||||
|
||||
dev_server::Entity::update(dev_server::ActiveModel {
|
||||
name: ActiveValue::Set(name.trim().to_string()),
|
||||
ssh_connection_string: ActiveValue::Set(
|
||||
ssh_connection_string.map(ToOwned::to_owned),
|
||||
),
|
||||
..dev_server.clone().into_active_model()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
let dev_server_projects = self
|
||||
.dev_server_projects_update_internal(user_id, &tx)
|
||||
.await?;
|
||||
|
||||
Ok(dev_server_projects)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete_dev_server(
|
||||
&self,
|
||||
id: DevServerId,
|
||||
user_id: UserId,
|
||||
) -> crate::Result<proto::DevServerProjectsUpdate> {
|
||||
self.transaction(|tx| async move {
|
||||
let Some(dev_server) = dev_server::Entity::find_by_id(id).one(&*tx).await? else {
|
||||
return Err(anyhow::anyhow!("no dev server with id {}", id))?;
|
||||
};
|
||||
if dev_server.user_id != user_id {
|
||||
return Err(anyhow::anyhow!(proto::ErrorCode::Forbidden))?;
|
||||
}
|
||||
|
||||
dev_server_project::Entity::delete_many()
|
||||
.filter(dev_server_project::Column::DevServerId.eq(id))
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
dev_server::Entity::delete(dev_server.into_active_model())
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
let dev_server_projects = self
|
||||
.dev_server_projects_update_internal(user_id, &tx)
|
||||
.await?;
|
||||
|
||||
Ok(dev_server_projects)
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ impl Database {
|
|||
connection: ConnectionId,
|
||||
worktrees: &[proto::WorktreeMetadata],
|
||||
is_ssh_project: bool,
|
||||
dev_server_project_id: Option<DevServerProjectId>,
|
||||
) -> Result<TransactionGuard<(ProjectId, proto::Room)>> {
|
||||
self.room_transaction(room_id, |tx| async move {
|
||||
let participant = room_participant::Entity::find()
|
||||
|
@ -61,38 +60,6 @@ impl Database {
|
|||
return Err(anyhow!("guests cannot share projects"))?;
|
||||
}
|
||||
|
||||
if let Some(dev_server_project_id) = dev_server_project_id {
|
||||
let project = project::Entity::find()
|
||||
.filter(project::Column::DevServerProjectId.eq(Some(dev_server_project_id)))
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no remote project"))?;
|
||||
|
||||
let (_, dev_server) = dev_server_project::Entity::find_by_id(dev_server_project_id)
|
||||
.find_also_related(dev_server::Entity)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no dev_server_project"))?;
|
||||
|
||||
if !dev_server.is_some_and(|dev_server| dev_server.user_id == participant.user_id) {
|
||||
return Err(anyhow!("not your dev server"))?;
|
||||
}
|
||||
|
||||
if project.room_id.is_some() {
|
||||
return Err(anyhow!("project already shared"))?;
|
||||
};
|
||||
|
||||
let project = project::Entity::update(project::ActiveModel {
|
||||
room_id: ActiveValue::Set(Some(room_id)),
|
||||
..project.into_active_model()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
let room = self.get_room(room_id, &tx).await?;
|
||||
return Ok((project.id, room));
|
||||
}
|
||||
|
||||
let project = project::ActiveModel {
|
||||
room_id: ActiveValue::set(Some(participant.room_id)),
|
||||
host_user_id: ActiveValue::set(Some(participant.user_id)),
|
||||
|
@ -102,7 +69,6 @@ impl Database {
|
|||
))),
|
||||
id: ActiveValue::NotSet,
|
||||
hosted_project_id: ActiveValue::Set(None),
|
||||
dev_server_project_id: ActiveValue::Set(None),
|
||||
}
|
||||
.insert(&*tx)
|
||||
.await?;
|
||||
|
@ -156,7 +122,6 @@ impl Database {
|
|||
&self,
|
||||
project_id: ProjectId,
|
||||
connection: ConnectionId,
|
||||
user_id: Option<UserId>,
|
||||
) -> Result<TransactionGuard<(bool, Option<proto::Room>, Vec<ConnectionId>)>> {
|
||||
self.project_transaction(project_id, |tx| async move {
|
||||
let guest_connection_ids = self.project_guest_connection_ids(project_id, &tx).await?;
|
||||
|
@ -172,25 +137,6 @@ impl Database {
|
|||
if project.host_connection()? == connection {
|
||||
return Ok((true, room, guest_connection_ids));
|
||||
}
|
||||
if let Some(dev_server_project_id) = project.dev_server_project_id {
|
||||
if let Some(user_id) = user_id {
|
||||
if user_id
|
||||
!= self
|
||||
.owner_for_dev_server_project(dev_server_project_id, &tx)
|
||||
.await?
|
||||
{
|
||||
Err(anyhow!("cannot unshare a project hosted by another user"))?
|
||||
}
|
||||
project::Entity::update(project::ActiveModel {
|
||||
room_id: ActiveValue::Set(None),
|
||||
..project.into_active_model()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
return Ok((false, room, guest_connection_ids));
|
||||
}
|
||||
}
|
||||
|
||||
Err(anyhow!("cannot unshare a project hosted by another user"))?
|
||||
})
|
||||
.await
|
||||
|
@ -633,17 +579,6 @@ impl Database {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn find_dev_server_project(&self, id: DevServerProjectId) -> Result<project::Model> {
|
||||
self.transaction(|tx| async move {
|
||||
Ok(project::Entity::find()
|
||||
.filter(project::Column::DevServerProjectId.eq(id))
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such project"))?)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Adds the given connection to the specified project
|
||||
/// in the current room.
|
||||
pub async fn join_project(
|
||||
|
@ -654,13 +589,7 @@ impl Database {
|
|||
) -> Result<TransactionGuard<(Project, ReplicaId)>> {
|
||||
self.project_transaction(project_id, |tx| async move {
|
||||
let (project, role) = self
|
||||
.access_project(
|
||||
project_id,
|
||||
connection,
|
||||
PrincipalId::UserId(user_id),
|
||||
Capability::ReadOnly,
|
||||
&tx,
|
||||
)
|
||||
.access_project(project_id, connection, Capability::ReadOnly, &tx)
|
||||
.await?;
|
||||
self.join_project_internal(project, user_id, connection, role, &tx)
|
||||
.await
|
||||
|
@ -851,7 +780,6 @@ impl Database {
|
|||
worktree_id: None,
|
||||
})
|
||||
.collect(),
|
||||
dev_server_project_id: project.dev_server_project_id,
|
||||
};
|
||||
Ok((project, replica_id as ReplicaId))
|
||||
}
|
||||
|
@ -1007,29 +935,14 @@ impl Database {
|
|||
&self,
|
||||
project_id: ProjectId,
|
||||
connection_id: ConnectionId,
|
||||
principal_id: PrincipalId,
|
||||
capability: Capability,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<(project::Model, ChannelRole)> {
|
||||
let (mut project, dev_server_project) = project::Entity::find_by_id(project_id)
|
||||
.find_also_related(dev_server_project::Entity)
|
||||
let project = project::Entity::find_by_id(project_id)
|
||||
.one(tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
|
||||
let user_id = match principal_id {
|
||||
PrincipalId::DevServerId(_) => {
|
||||
if project
|
||||
.host_connection()
|
||||
.is_ok_and(|connection| connection == connection_id)
|
||||
{
|
||||
return Ok((project, ChannelRole::Admin));
|
||||
}
|
||||
return Err(anyhow!("not the project host"))?;
|
||||
}
|
||||
PrincipalId::UserId(user_id) => user_id,
|
||||
};
|
||||
|
||||
let role_from_room = if let Some(room_id) = project.room_id {
|
||||
room_participant::Entity::find()
|
||||
.filter(room_participant::Column::RoomId.eq(room_id))
|
||||
|
@ -1040,34 +953,8 @@ impl Database {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let role_from_dev_server = if let Some(dev_server_project) = dev_server_project {
|
||||
let dev_server = dev_server::Entity::find_by_id(dev_server_project.dev_server_id)
|
||||
.one(tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such channel"))?;
|
||||
if user_id == dev_server.user_id {
|
||||
// If the user left the room "uncleanly" they may rejoin the
|
||||
// remote project before leave_room runs. IN that case kick
|
||||
// the project out of the room pre-emptively.
|
||||
if role_from_room.is_none() {
|
||||
project = project::Entity::update(project::ActiveModel {
|
||||
room_id: ActiveValue::Set(None),
|
||||
..project.into_active_model()
|
||||
})
|
||||
.exec(tx)
|
||||
.await?;
|
||||
}
|
||||
Some(ChannelRole::Admin)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let role = role_from_dev_server
|
||||
.or(role_from_room)
|
||||
.unwrap_or(ChannelRole::Banned);
|
||||
let role = role_from_room.unwrap_or(ChannelRole::Banned);
|
||||
|
||||
match capability {
|
||||
Capability::ReadWrite => {
|
||||
|
@ -1090,17 +977,10 @@ impl Database {
|
|||
&self,
|
||||
project_id: ProjectId,
|
||||
connection_id: ConnectionId,
|
||||
user_id: UserId,
|
||||
) -> Result<ConnectionId> {
|
||||
self.project_transaction(project_id, |tx| async move {
|
||||
let (project, _) = self
|
||||
.access_project(
|
||||
project_id,
|
||||
connection_id,
|
||||
PrincipalId::UserId(user_id),
|
||||
Capability::ReadOnly,
|
||||
&tx,
|
||||
)
|
||||
.access_project(project_id, connection_id, Capability::ReadOnly, &tx)
|
||||
.await?;
|
||||
project.host_connection()
|
||||
})
|
||||
|
@ -1113,17 +993,10 @@ impl Database {
|
|||
&self,
|
||||
project_id: ProjectId,
|
||||
connection_id: ConnectionId,
|
||||
user_id: UserId,
|
||||
) -> Result<ConnectionId> {
|
||||
self.project_transaction(project_id, |tx| async move {
|
||||
let (project, _) = self
|
||||
.access_project(
|
||||
project_id,
|
||||
connection_id,
|
||||
PrincipalId::UserId(user_id),
|
||||
Capability::ReadWrite,
|
||||
&tx,
|
||||
)
|
||||
.access_project(project_id, connection_id, Capability::ReadWrite, &tx)
|
||||
.await?;
|
||||
project.host_connection()
|
||||
})
|
||||
|
@ -1131,47 +1004,16 @@ impl Database {
|
|||
.map(|guard| guard.into_inner())
|
||||
}
|
||||
|
||||
/// Returns the host connection for a request to join a shared project.
|
||||
pub async fn host_for_owner_project_request(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
_connection_id: ConnectionId,
|
||||
user_id: UserId,
|
||||
) -> Result<ConnectionId> {
|
||||
self.project_transaction(project_id, |tx| async move {
|
||||
let (project, dev_server_project) = project::Entity::find_by_id(project_id)
|
||||
.find_also_related(dev_server_project::Entity)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such project"))?;
|
||||
|
||||
let Some(dev_server_project) = dev_server_project else {
|
||||
return Err(anyhow!("not a dev server project"))?;
|
||||
};
|
||||
let dev_server = dev_server::Entity::find_by_id(dev_server_project.dev_server_id)
|
||||
.one(&*tx)
|
||||
.await?
|
||||
.ok_or_else(|| anyhow!("no such dev server"))?;
|
||||
if dev_server.user_id != user_id {
|
||||
return Err(anyhow!("not your project"))?;
|
||||
}
|
||||
project.host_connection()
|
||||
})
|
||||
.await
|
||||
.map(|guard| guard.into_inner())
|
||||
}
|
||||
|
||||
pub async fn connections_for_buffer_update(
|
||||
&self,
|
||||
project_id: ProjectId,
|
||||
principal_id: PrincipalId,
|
||||
connection_id: ConnectionId,
|
||||
capability: Capability,
|
||||
) -> Result<TransactionGuard<(ConnectionId, Vec<ConnectionId>)>> {
|
||||
self.project_transaction(project_id, |tx| async move {
|
||||
// Authorize
|
||||
let (project, _) = self
|
||||
.access_project(project_id, connection_id, principal_id, capability, &tx)
|
||||
.access_project(project_id, connection_id, capability, &tx)
|
||||
.await?;
|
||||
|
||||
let host_connection_id = project.host_connection()?;
|
||||
|
|
|
@ -858,25 +858,6 @@ impl Database {
|
|||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
// if any project in the room has a remote-project-id that belongs to a dev server that this user owns.
|
||||
let dev_server_projects_for_user = self
|
||||
.dev_server_project_ids_for_user(leaving_participant.user_id, &tx)
|
||||
.await?;
|
||||
|
||||
let dev_server_projects_to_unshare = project::Entity::find()
|
||||
.filter(
|
||||
Condition::all()
|
||||
.add(project::Column::RoomId.eq(room_id))
|
||||
.add(
|
||||
project::Column::DevServerProjectId
|
||||
.is_in(dev_server_projects_for_user.clone()),
|
||||
),
|
||||
)
|
||||
.all(&*tx)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|project| project.id)
|
||||
.collect::<HashSet<_>>();
|
||||
let mut left_projects = HashMap::default();
|
||||
let mut collaborators = project_collaborator::Entity::find()
|
||||
.filter(project_collaborator::Column::ProjectId.is_in(project_ids))
|
||||
|
@ -899,9 +880,7 @@ impl Database {
|
|||
left_project.connection_ids.push(collaborator_connection_id);
|
||||
}
|
||||
|
||||
if (collaborator.is_host && collaborator.connection() == connection)
|
||||
|| dev_server_projects_to_unshare.contains(&collaborator.project_id)
|
||||
{
|
||||
if collaborator.is_host && collaborator.connection() == connection {
|
||||
left_project.should_unshare = true;
|
||||
}
|
||||
}
|
||||
|
@ -944,17 +923,6 @@ impl Database {
|
|||
.exec(&*tx)
|
||||
.await?;
|
||||
|
||||
if !dev_server_projects_to_unshare.is_empty() {
|
||||
project::Entity::update_many()
|
||||
.filter(project::Column::Id.is_in(dev_server_projects_to_unshare))
|
||||
.set(project::ActiveModel {
|
||||
room_id: ActiveValue::Set(None),
|
||||
..Default::default()
|
||||
})
|
||||
.exec(&*tx)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let (channel, room) = self.get_channel_room(room_id, &tx).await?;
|
||||
let deleted = if room.participants.is_empty() {
|
||||
let result = room::Entity::delete_by_id(room_id).exec(&*tx).await?;
|
||||
|
@ -1323,26 +1291,6 @@ impl Database {
|
|||
project.worktree_root_names.push(db_worktree.root_name);
|
||||
}
|
||||
}
|
||||
} else if let Some(dev_server_project_id) = db_project.dev_server_project_id {
|
||||
let host = self
|
||||
.owner_for_dev_server_project(dev_server_project_id, tx)
|
||||
.await?;
|
||||
if let Some((_, participant)) = participants
|
||||
.iter_mut()
|
||||
.find(|(_, v)| v.user_id == host.to_proto())
|
||||
{
|
||||
participant.projects.push(proto::ParticipantProject {
|
||||
id: db_project.id.to_proto(),
|
||||
worktree_root_names: Default::default(),
|
||||
});
|
||||
let project = participant.projects.last_mut().unwrap();
|
||||
|
||||
for db_worktree in db_worktrees {
|
||||
if db_worktree.visible {
|
||||
project.worktree_root_names.push(db_worktree.root_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ pub mod channel_message;
|
|||
pub mod channel_message_mention;
|
||||
pub mod contact;
|
||||
pub mod contributor;
|
||||
pub mod dev_server;
|
||||
pub mod dev_server_project;
|
||||
pub mod embedding;
|
||||
pub mod extension;
|
||||
pub mod extension_version;
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
use crate::db::{DevServerId, UserId};
|
||||
use rpc::proto;
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "dev_servers")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: DevServerId,
|
||||
pub name: String,
|
||||
pub user_id: UserId,
|
||||
pub hashed_token: String,
|
||||
pub ssh_connection_string: Option<String>,
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::dev_server_project::Entity")]
|
||||
RemoteProject,
|
||||
}
|
||||
|
||||
impl Related<super::dev_server_project::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RemoteProject.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn to_proto(&self, status: proto::DevServerStatus) -> proto::DevServer {
|
||||
proto::DevServer {
|
||||
dev_server_id: self.id.to_proto(),
|
||||
name: self.name.clone(),
|
||||
status: status as i32,
|
||||
ssh_connection_string: self.ssh_connection_string.clone(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
use super::project;
|
||||
use crate::db::{DevServerId, DevServerProjectId};
|
||||
use rpc::proto;
|
||||
use sea_orm::{entity::prelude::*, FromJsonQueryResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "dev_server_projects")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: DevServerProjectId,
|
||||
pub dev_server_id: DevServerId,
|
||||
pub paths: JSONPaths,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
|
||||
pub struct JSONPaths(pub Vec<String>);
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_one = "super::project::Entity")]
|
||||
Project,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::dev_server::Entity",
|
||||
from = "Column::DevServerId",
|
||||
to = "super::dev_server::Column::Id"
|
||||
)]
|
||||
DevServer,
|
||||
}
|
||||
|
||||
impl Related<super::project::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Project.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::dev_server::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::DevServer.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Model {
|
||||
pub fn to_proto(&self, project: Option<project::Model>) -> proto::DevServerProject {
|
||||
proto::DevServerProject {
|
||||
id: self.id.to_proto(),
|
||||
project_id: project.map(|p| p.id.to_proto()),
|
||||
dev_server_id: self.dev_server_id.to_proto(),
|
||||
path: self.paths().first().cloned().unwrap_or_default(),
|
||||
paths: self.paths().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn paths(&self) -> &Vec<String> {
|
||||
&self.paths.0
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use crate::db::{DevServerProjectId, HostedProjectId, 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::*;
|
||||
|
@ -13,7 +13,6 @@ pub struct Model {
|
|||
pub host_connection_id: Option<i32>,
|
||||
pub host_connection_server_id: Option<ServerId>,
|
||||
pub hosted_project_id: Option<HostedProjectId>,
|
||||
pub dev_server_project_id: Option<DevServerProjectId>,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
@ -57,12 +56,6 @@ pub enum Relation {
|
|||
to = "super::hosted_project::Column::Id"
|
||||
)]
|
||||
HostedProject,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::dev_server_project::Entity",
|
||||
from = "Column::DevServerProjectId",
|
||||
to = "super::dev_server_project::Column::Id"
|
||||
)]
|
||||
RemoteProject,
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
|
@ -101,10 +94,4 @@ impl Related<super::hosted_project::Entity> for Entity {
|
|||
}
|
||||
}
|
||||
|
||||
impl Related<super::dev_server_project::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RemoteProject.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
|
@ -540,18 +540,18 @@ async fn test_project_count(db: &Arc<Database>) {
|
|||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 0);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 1);
|
||||
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false, None)
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 1 }, &[], false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
||||
// Projects shared by admins aren't counted.
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false, None)
|
||||
db.share_project(room_id, ConnectionId { owner_id, id: 0 }, &[], false)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(db.project_count_excluding_admins().await.unwrap(), 2);
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
use crate::db::{ChannelId, ChannelRole, DevServerId, PrincipalId, UserId};
|
||||
use crate::db::{ChannelId, ChannelRole, UserId};
|
||||
use anyhow::{anyhow, Result};
|
||||
use collections::{BTreeMap, HashMap, HashSet};
|
||||
use rpc::{proto, ConnectionId};
|
||||
use rpc::ConnectionId;
|
||||
use semantic_version::SemanticVersion;
|
||||
use serde::Serialize;
|
||||
use std::fmt;
|
||||
|
@ -11,9 +11,7 @@ use tracing::instrument;
|
|||
pub struct ConnectionPool {
|
||||
connections: BTreeMap<ConnectionId, Connection>,
|
||||
connected_users: BTreeMap<UserId, ConnectedPrincipal>,
|
||||
connected_dev_servers: BTreeMap<DevServerId, ConnectionId>,
|
||||
channels: ChannelPool,
|
||||
offline_dev_servers: HashSet<DevServerId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Serialize)]
|
||||
|
@ -32,13 +30,13 @@ impl fmt::Display for ZedVersion {
|
|||
|
||||
impl ZedVersion {
|
||||
pub fn can_collaborate(&self) -> bool {
|
||||
self.0 >= SemanticVersion::new(0, 151, 0)
|
||||
self.0 >= SemanticVersion::new(0, 157, 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Connection {
|
||||
pub principal_id: PrincipalId,
|
||||
pub user_id: UserId,
|
||||
pub admin: bool,
|
||||
pub zed_version: ZedVersion,
|
||||
}
|
||||
|
@ -47,7 +45,6 @@ impl ConnectionPool {
|
|||
pub fn reset(&mut self) {
|
||||
self.connections.clear();
|
||||
self.connected_users.clear();
|
||||
self.connected_dev_servers.clear();
|
||||
self.channels.clear();
|
||||
}
|
||||
|
||||
|
@ -66,7 +63,7 @@ impl ConnectionPool {
|
|||
self.connections.insert(
|
||||
connection_id,
|
||||
Connection {
|
||||
principal_id: PrincipalId::UserId(user_id),
|
||||
user_id,
|
||||
admin,
|
||||
zed_version,
|
||||
},
|
||||
|
@ -75,25 +72,6 @@ impl ConnectionPool {
|
|||
connected_user.connection_ids.insert(connection_id);
|
||||
}
|
||||
|
||||
pub fn add_dev_server(
|
||||
&mut self,
|
||||
connection_id: ConnectionId,
|
||||
dev_server_id: DevServerId,
|
||||
zed_version: ZedVersion,
|
||||
) {
|
||||
self.connections.insert(
|
||||
connection_id,
|
||||
Connection {
|
||||
principal_id: PrincipalId::DevServerId(dev_server_id),
|
||||
admin: false,
|
||||
zed_version,
|
||||
},
|
||||
);
|
||||
|
||||
self.connected_dev_servers
|
||||
.insert(dev_server_id, connection_id);
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
pub fn remove_connection(&mut self, connection_id: ConnectionId) -> Result<()> {
|
||||
let connection = self
|
||||
|
@ -101,28 +79,18 @@ impl ConnectionPool {
|
|||
.get_mut(&connection_id)
|
||||
.ok_or_else(|| anyhow!("no such connection"))?;
|
||||
|
||||
match connection.principal_id {
|
||||
PrincipalId::UserId(user_id) => {
|
||||
let connected_user = self.connected_users.get_mut(&user_id).unwrap();
|
||||
connected_user.connection_ids.remove(&connection_id);
|
||||
if connected_user.connection_ids.is_empty() {
|
||||
self.connected_users.remove(&user_id);
|
||||
self.channels.remove_user(&user_id);
|
||||
}
|
||||
}
|
||||
PrincipalId::DevServerId(dev_server_id) => {
|
||||
self.connected_dev_servers.remove(&dev_server_id);
|
||||
self.offline_dev_servers.remove(&dev_server_id);
|
||||
}
|
||||
}
|
||||
let user_id = connection.user_id;
|
||||
|
||||
let connected_user = self.connected_users.get_mut(&user_id).unwrap();
|
||||
connected_user.connection_ids.remove(&connection_id);
|
||||
if connected_user.connection_ids.is_empty() {
|
||||
self.connected_users.remove(&user_id);
|
||||
self.channels.remove_user(&user_id);
|
||||
};
|
||||
self.connections.remove(&connection_id).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_dev_server_offline(&mut self, dev_server_id: DevServerId) {
|
||||
self.offline_dev_servers.insert(dev_server_id);
|
||||
}
|
||||
|
||||
pub fn connections(&self) -> impl Iterator<Item = &Connection> {
|
||||
self.connections.values()
|
||||
}
|
||||
|
@ -147,42 +115,6 @@ impl ConnectionPool {
|
|||
.copied()
|
||||
}
|
||||
|
||||
pub fn dev_server_status(&self, dev_server_id: DevServerId) -> proto::DevServerStatus {
|
||||
if self.dev_server_connection_id(dev_server_id).is_some()
|
||||
&& !self.offline_dev_servers.contains(&dev_server_id)
|
||||
{
|
||||
proto::DevServerStatus::Online
|
||||
} else {
|
||||
proto::DevServerStatus::Offline
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dev_server_connection_id(&self, dev_server_id: DevServerId) -> Option<ConnectionId> {
|
||||
self.connected_dev_servers.get(&dev_server_id).copied()
|
||||
}
|
||||
|
||||
pub fn online_dev_server_connection_id(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
) -> Result<ConnectionId> {
|
||||
match self.connected_dev_servers.get(&dev_server_id) {
|
||||
Some(cid) => Ok(*cid),
|
||||
None => Err(anyhow!(proto::ErrorCode::DevServerOffline)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn dev_server_connection_id_supporting(
|
||||
&self,
|
||||
dev_server_id: DevServerId,
|
||||
required: ZedVersion,
|
||||
) -> Result<ConnectionId> {
|
||||
match self.connected_dev_servers.get(&dev_server_id) {
|
||||
Some(cid) if self.connections[cid].zed_version >= required => Ok(*cid),
|
||||
Some(_) => Err(anyhow!(proto::ErrorCode::RemoteUpgradeRequired)),
|
||||
None => Err(anyhow!(proto::ErrorCode::DevServerOffline)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel_user_ids(
|
||||
&self,
|
||||
channel_id: ChannelId,
|
||||
|
@ -227,39 +159,22 @@ impl ConnectionPool {
|
|||
#[cfg(test)]
|
||||
pub fn check_invariants(&self) {
|
||||
for (connection_id, connection) in &self.connections {
|
||||
match &connection.principal_id {
|
||||
PrincipalId::UserId(user_id) => {
|
||||
assert!(self
|
||||
.connected_users
|
||||
.get(user_id)
|
||||
.unwrap()
|
||||
.connection_ids
|
||||
.contains(connection_id));
|
||||
}
|
||||
PrincipalId::DevServerId(dev_server_id) => {
|
||||
assert_eq!(
|
||||
self.connected_dev_servers.get(dev_server_id).unwrap(),
|
||||
connection_id
|
||||
);
|
||||
}
|
||||
}
|
||||
assert!(self
|
||||
.connected_users
|
||||
.get(&connection.user_id)
|
||||
.unwrap()
|
||||
.connection_ids
|
||||
.contains(connection_id));
|
||||
}
|
||||
|
||||
for (user_id, state) in &self.connected_users {
|
||||
for connection_id in &state.connection_ids {
|
||||
assert_eq!(
|
||||
self.connections.get(connection_id).unwrap().principal_id,
|
||||
PrincipalId::UserId(*user_id)
|
||||
self.connections.get(connection_id).unwrap().user_id,
|
||||
*user_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (dev_server_id, connection_id) in &self.connected_dev_servers {
|
||||
assert_eq!(
|
||||
self.connections.get(connection_id).unwrap().principal_id,
|
||||
PrincipalId::DevServerId(*dev_server_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ mod channel_buffer_tests;
|
|||
mod channel_guest_tests;
|
||||
mod channel_message_tests;
|
||||
mod channel_tests;
|
||||
mod dev_server_tests;
|
||||
mod editor_tests;
|
||||
mod following_tests;
|
||||
mod integration_tests;
|
||||
|
|
|
@ -1,643 +0,0 @@
|
|||
use std::{path::Path, sync::Arc};
|
||||
|
||||
use call::ActiveCall;
|
||||
use editor::Editor;
|
||||
use fs::Fs;
|
||||
use gpui::{TestAppContext, VisualTestContext, WindowHandle};
|
||||
use rpc::{proto::DevServerStatus, ErrorCode, ErrorExt};
|
||||
use serde_json::json;
|
||||
use workspace::{AppState, Workspace};
|
||||
|
||||
use crate::tests::{following_tests::join_channel, TestServer};
|
||||
|
||||
use super::TestClient;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server(cx: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
|
||||
let (server, client) = TestServer::start1(cx).await;
|
||||
|
||||
let store = cx.update(|cx| dev_server_projects::Store::global(cx).clone());
|
||||
|
||||
let resp = store
|
||||
.update(cx, |store, cx| {
|
||||
store.create_dev_server("server-1".to_string(), None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
store.update(cx, |store, _| {
|
||||
assert_eq!(store.dev_servers().len(), 1);
|
||||
assert_eq!(store.dev_servers()[0].name, "server-1");
|
||||
assert_eq!(store.dev_servers()[0].status, DevServerStatus::Offline);
|
||||
});
|
||||
|
||||
let dev_server = server.create_dev_server(resp.access_token, cx2).await;
|
||||
cx.executor().run_until_parked();
|
||||
store.update(cx, |store, _| {
|
||||
assert_eq!(store.dev_servers()[0].status, DevServerStatus::Online);
|
||||
});
|
||||
|
||||
dev_server
|
||||
.fs()
|
||||
.insert_tree(
|
||||
"/remote",
|
||||
json!({
|
||||
"1.txt": "remote\nremote\nremote",
|
||||
"2.js": "function two() { return 2; }",
|
||||
"3.rs": "mod test",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
store
|
||||
.update(cx, |store, cx| {
|
||||
store.create_dev_server_project(
|
||||
client::DevServerId(resp.dev_server_id),
|
||||
"/remote".to_string(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let remote_workspace = store
|
||||
.update(cx, |store, cx| {
|
||||
let projects = store.dev_server_projects();
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].paths, vec!["/remote"]);
|
||||
workspace::join_dev_server_project(
|
||||
projects[0].id,
|
||||
projects[0].project_id.unwrap(),
|
||||
client.app_state.clone(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let cx = VisualTestContext::from_window(remote_workspace.into(), cx).as_mut();
|
||||
cx.simulate_keystrokes("cmd-p 1 enter");
|
||||
|
||||
let editor = remote_workspace
|
||||
.update(cx, |ws, cx| {
|
||||
ws.active_item_as::<Editor>(cx).unwrap().clone()
|
||||
})
|
||||
.unwrap();
|
||||
editor.update(cx, |ed, cx| {
|
||||
assert_eq!(ed.text(cx).to_string(), "remote\nremote\nremote");
|
||||
});
|
||||
cx.simulate_input("wow!");
|
||||
cx.simulate_keystrokes("cmd-s");
|
||||
|
||||
let content = dev_server
|
||||
.fs()
|
||||
.load(Path::new("/remote/1.txt"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(content, "wow!remote\nremote\nremote\n");
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server_env_files(
|
||||
cx1: &mut gpui::TestAppContext,
|
||||
cx2: &mut gpui::TestAppContext,
|
||||
cx3: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (server, client1, client2, channel_id) = TestServer::start2(cx1, cx2).await;
|
||||
|
||||
let (_dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx3).await;
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
let cx1 = VisualTestContext::from_window(remote_workspace.into(), cx1).as_mut();
|
||||
cx1.simulate_keystrokes("cmd-p . e enter");
|
||||
|
||||
let editor = remote_workspace
|
||||
.update(cx1, |ws, cx| {
|
||||
ws.active_item_as::<Editor>(cx).unwrap().clone()
|
||||
})
|
||||
.unwrap();
|
||||
editor.update(cx1, |ed, cx| {
|
||||
assert_eq!(ed.text(cx).to_string(), "SECRET");
|
||||
});
|
||||
|
||||
cx1.update(|cx| {
|
||||
workspace::join_channel(
|
||||
channel_id,
|
||||
client1.app_state.clone(),
|
||||
Some(remote_workspace),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
remote_workspace
|
||||
.update(cx1, |ws, cx| {
|
||||
assert!(ws.project().read(cx).is_shared());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
join_channel(channel_id, &client2, cx2).await.unwrap();
|
||||
cx2.executor().run_until_parked();
|
||||
|
||||
let (workspace2, cx2) = client2.active_workspace(cx2);
|
||||
let editor = workspace2.update(cx2, |ws, cx| {
|
||||
ws.active_item_as::<Editor>(cx).unwrap().clone()
|
||||
});
|
||||
// TODO: it'd be nice to hide .env files from other people
|
||||
editor.update(cx2, |ed, cx| {
|
||||
assert_eq!(ed.text(cx).to_string(), "SECRET");
|
||||
});
|
||||
}
|
||||
|
||||
async fn create_dev_server_project(
|
||||
server: &TestServer,
|
||||
client_app_state: Arc<AppState>,
|
||||
cx: &mut TestAppContext,
|
||||
cx_devserver: &mut TestAppContext,
|
||||
) -> (TestClient, WindowHandle<Workspace>) {
|
||||
let store = cx.update(|cx| dev_server_projects::Store::global(cx).clone());
|
||||
|
||||
let resp = store
|
||||
.update(cx, |store, cx| {
|
||||
store.create_dev_server("server-1".to_string(), None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let dev_server = server
|
||||
.create_dev_server(resp.access_token, cx_devserver)
|
||||
.await;
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
dev_server
|
||||
.fs()
|
||||
.insert_tree(
|
||||
"/remote",
|
||||
json!({
|
||||
"1.txt": "remote\nremote\nremote",
|
||||
".env": "SECRET",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
store
|
||||
.update(cx, |store, cx| {
|
||||
store.create_dev_server_project(
|
||||
client::DevServerId(resp.dev_server_id),
|
||||
"/remote".to_string(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let workspace = store
|
||||
.update(cx, |store, cx| {
|
||||
let projects = store.dev_server_projects();
|
||||
assert_eq!(projects.len(), 1);
|
||||
assert_eq!(projects[0].paths, vec!["/remote"]);
|
||||
workspace::join_dev_server_project(
|
||||
projects[0].id,
|
||||
projects[0].project_id.unwrap(),
|
||||
client_app_state,
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
(dev_server, workspace)
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server_leave_room(
|
||||
cx1: &mut gpui::TestAppContext,
|
||||
cx2: &mut gpui::TestAppContext,
|
||||
cx3: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (server, client1, client2, channel_id) = TestServer::start2(cx1, cx2).await;
|
||||
|
||||
let (_dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx3).await;
|
||||
|
||||
cx1.update(|cx| {
|
||||
workspace::join_channel(
|
||||
channel_id,
|
||||
client1.app_state.clone(),
|
||||
Some(remote_workspace),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
remote_workspace
|
||||
.update(cx1, |ws, cx| {
|
||||
assert!(ws.project().read(cx).is_shared());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
join_channel(channel_id, &client2, cx2).await.unwrap();
|
||||
cx2.executor().run_until_parked();
|
||||
|
||||
cx1.update(|cx| ActiveCall::global(cx).update(cx, |active_call, cx| active_call.hang_up(cx)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
let (workspace, cx2) = client2.active_workspace(cx2);
|
||||
cx2.update(|cx| assert!(workspace.read(cx).project().read(cx).is_disconnected(cx)));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server_delete(
|
||||
cx1: &mut gpui::TestAppContext,
|
||||
cx2: &mut gpui::TestAppContext,
|
||||
cx3: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (server, client1, client2, channel_id) = TestServer::start2(cx1, cx2).await;
|
||||
|
||||
let (_dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx3).await;
|
||||
|
||||
cx1.update(|cx| {
|
||||
workspace::join_channel(
|
||||
channel_id,
|
||||
client1.app_state.clone(),
|
||||
Some(remote_workspace),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
remote_workspace
|
||||
.update(cx1, |ws, cx| {
|
||||
assert!(ws.project().read(cx).is_shared());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
join_channel(channel_id, &client2, cx2).await.unwrap();
|
||||
cx2.executor().run_until_parked();
|
||||
|
||||
cx1.update(|cx| {
|
||||
dev_server_projects::Store::global(cx).update(cx, |store, cx| {
|
||||
store.delete_dev_server_project(store.dev_server_projects().first().unwrap().id, cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
let (workspace, cx2) = client2.active_workspace(cx2);
|
||||
cx2.update(|cx| assert!(workspace.read(cx).project().read(cx).is_disconnected(cx)));
|
||||
|
||||
cx1.update(|cx| {
|
||||
dev_server_projects::Store::global(cx).update(cx, |store, _| {
|
||||
assert_eq!(store.dev_server_projects().len(), 0);
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server_rename(
|
||||
cx1: &mut gpui::TestAppContext,
|
||||
cx2: &mut gpui::TestAppContext,
|
||||
cx3: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (server, client1, client2, channel_id) = TestServer::start2(cx1, cx2).await;
|
||||
|
||||
let (_dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx3).await;
|
||||
|
||||
cx1.update(|cx| {
|
||||
workspace::join_channel(
|
||||
channel_id,
|
||||
client1.app_state.clone(),
|
||||
Some(remote_workspace),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
remote_workspace
|
||||
.update(cx1, |ws, cx| {
|
||||
assert!(ws.project().read(cx).is_shared());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
join_channel(channel_id, &client2, cx2).await.unwrap();
|
||||
cx2.executor().run_until_parked();
|
||||
|
||||
cx1.update(|cx| {
|
||||
dev_server_projects::Store::global(cx).update(cx, |store, cx| {
|
||||
store.rename_dev_server(
|
||||
store.dev_servers().first().unwrap().id,
|
||||
"name-edited".to_string(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
cx1.update(|cx| {
|
||||
dev_server_projects::Store::global(cx).update(cx, |store, _| {
|
||||
assert_eq!(store.dev_servers().first().unwrap().name, "name-edited");
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server_refresh_access_token(
|
||||
cx1: &mut gpui::TestAppContext,
|
||||
cx2: &mut gpui::TestAppContext,
|
||||
cx3: &mut gpui::TestAppContext,
|
||||
cx4: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (server, client1, client2, channel_id) = TestServer::start2(cx1, cx2).await;
|
||||
|
||||
let (_dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx3).await;
|
||||
|
||||
cx1.update(|cx| {
|
||||
workspace::join_channel(
|
||||
channel_id,
|
||||
client1.app_state.clone(),
|
||||
Some(remote_workspace),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
remote_workspace
|
||||
.update(cx1, |ws, cx| {
|
||||
assert!(ws.project().read(cx).is_shared());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
join_channel(channel_id, &client2, cx2).await.unwrap();
|
||||
cx2.executor().run_until_parked();
|
||||
|
||||
// Regenerate the access token
|
||||
let new_token_response = cx1
|
||||
.update(|cx| {
|
||||
dev_server_projects::Store::global(cx).update(cx, |store, cx| {
|
||||
store.regenerate_dev_server_token(store.dev_servers().first().unwrap().id, cx)
|
||||
})
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
// Assert that the other client was disconnected
|
||||
let (workspace, cx2) = client2.active_workspace(cx2);
|
||||
cx2.update(|cx| assert!(workspace.read(cx).project().read(cx).is_disconnected(cx)));
|
||||
|
||||
// Assert that the owner of the dev server does not see the dev server as online anymore
|
||||
let (workspace, cx1) = client1.active_workspace(cx1);
|
||||
cx1.update(|cx| {
|
||||
assert!(workspace.read(cx).project().read(cx).is_disconnected(cx));
|
||||
dev_server_projects::Store::global(cx).update(cx, |store, _| {
|
||||
assert_eq!(
|
||||
store.dev_servers().first().unwrap().status,
|
||||
DevServerStatus::Offline
|
||||
);
|
||||
})
|
||||
});
|
||||
|
||||
// Reconnect the dev server with the new token
|
||||
let _dev_server = server
|
||||
.create_dev_server(new_token_response.access_token, cx4)
|
||||
.await;
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
// Assert that the dev server is online again
|
||||
cx1.update(|cx| {
|
||||
dev_server_projects::Store::global(cx).update(cx, |store, _| {
|
||||
assert_eq!(store.dev_servers().len(), 1);
|
||||
assert_eq!(
|
||||
store.dev_servers().first().unwrap().status,
|
||||
DevServerStatus::Online
|
||||
);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server_reconnect(
|
||||
cx1: &mut gpui::TestAppContext,
|
||||
cx2: &mut gpui::TestAppContext,
|
||||
cx3: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (mut server, client1) = TestServer::start1(cx1).await;
|
||||
let channel_id = server
|
||||
.make_channel("test", None, (&client1, cx1), &mut [])
|
||||
.await;
|
||||
|
||||
let (_dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx3).await;
|
||||
|
||||
cx1.update(|cx| {
|
||||
workspace::join_channel(
|
||||
channel_id,
|
||||
client1.app_state.clone(),
|
||||
Some(remote_workspace),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
remote_workspace
|
||||
.update(cx1, |ws, cx| {
|
||||
assert!(ws.project().read(cx).is_shared());
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
drop(client1);
|
||||
|
||||
let client2 = server.create_client(cx2, "user_a").await;
|
||||
|
||||
let store = cx2.update(|cx| dev_server_projects::Store::global(cx).clone());
|
||||
|
||||
store
|
||||
.update(cx2, |store, cx| {
|
||||
let projects = store.dev_server_projects();
|
||||
workspace::join_dev_server_project(
|
||||
projects[0].id,
|
||||
projects[0].project_id.unwrap(),
|
||||
client2.app_state.clone(),
|
||||
None,
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_dev_server_restart(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
|
||||
let (server, client1) = TestServer::start1(cx1).await;
|
||||
|
||||
let (_dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx2).await;
|
||||
let cx = VisualTestContext::from_window(remote_workspace.into(), cx1).as_mut();
|
||||
|
||||
server.reset().await;
|
||||
cx.run_until_parked();
|
||||
|
||||
cx.simulate_keystrokes("cmd-p 1 enter");
|
||||
remote_workspace
|
||||
.update(cx, |ws, cx| {
|
||||
ws.active_item_as::<Editor>(cx)
|
||||
.unwrap()
|
||||
.update(cx, |ed, cx| {
|
||||
assert_eq!(ed.text(cx).to_string(), "remote\nremote\nremote");
|
||||
})
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_create_dev_server_project_path_validation(
|
||||
cx1: &mut gpui::TestAppContext,
|
||||
cx2: &mut gpui::TestAppContext,
|
||||
cx3: &mut gpui::TestAppContext,
|
||||
) {
|
||||
let (server, client1) = TestServer::start1(cx1).await;
|
||||
let _channel_id = server
|
||||
.make_channel("test", None, (&client1, cx1), &mut [])
|
||||
.await;
|
||||
|
||||
// Creating a project with a path that does exist should not fail
|
||||
let (_dev_server, _) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx2).await;
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
let store = cx1.update(|cx| dev_server_projects::Store::global(cx).clone());
|
||||
|
||||
let resp = store
|
||||
.update(cx1, |store, cx| {
|
||||
store.create_dev_server("server-2".to_string(), None, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
let _dev_server = server.create_dev_server(resp.access_token, cx3).await;
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
// Creating a remote project with a path that does not exist should fail
|
||||
let result = store
|
||||
.update(cx1, |store, cx| {
|
||||
store.create_dev_server_project(
|
||||
client::DevServerId(resp.dev_server_id),
|
||||
"/notfound".to_string(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await;
|
||||
|
||||
cx1.executor().run_until_parked();
|
||||
|
||||
let error = result.unwrap_err();
|
||||
assert!(matches!(
|
||||
error.error_code(),
|
||||
ErrorCode::DevServerProjectPathDoesNotExist
|
||||
));
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_save_as_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
|
||||
let (server, client1) = TestServer::start1(cx1).await;
|
||||
|
||||
// Creating a project with a path that does exist should not fail
|
||||
let (dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx2).await;
|
||||
|
||||
let mut cx = VisualTestContext::from_window(remote_workspace.into(), cx1);
|
||||
|
||||
cx.simulate_keystrokes("cmd-p 1 enter");
|
||||
cx.simulate_keystrokes("cmd-shift-s");
|
||||
cx.simulate_input("2.txt");
|
||||
cx.simulate_keystrokes("enter");
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let title = remote_workspace
|
||||
.update(&mut cx, |ws, cx| {
|
||||
let active_item = ws.active_item(cx).unwrap();
|
||||
active_item.tab_description(0, cx).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(title, "2.txt");
|
||||
|
||||
let path = Path::new("/remote/2.txt");
|
||||
assert_eq!(
|
||||
dev_server.fs().load(path).await.unwrap(),
|
||||
"remote\nremote\nremote"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_new_file_remote(cx1: &mut gpui::TestAppContext, cx2: &mut gpui::TestAppContext) {
|
||||
let (server, client1) = TestServer::start1(cx1).await;
|
||||
|
||||
// Creating a project with a path that does exist should not fail
|
||||
let (dev_server, remote_workspace) =
|
||||
create_dev_server_project(&server, client1.app_state.clone(), cx1, cx2).await;
|
||||
|
||||
let mut cx = VisualTestContext::from_window(remote_workspace.into(), cx1);
|
||||
|
||||
cx.simulate_keystrokes("cmd-n");
|
||||
cx.simulate_input("new!");
|
||||
cx.simulate_keystrokes("cmd-shift-s");
|
||||
cx.simulate_input("2.txt");
|
||||
cx.simulate_keystrokes("enter");
|
||||
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
let title = remote_workspace
|
||||
.update(&mut cx, |ws, cx| {
|
||||
ws.active_item(cx).unwrap().tab_description(0, cx).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(title, "2.txt");
|
||||
|
||||
let path = Path::new("/remote/2.txt");
|
||||
assert_eq!(dev_server.fs().load(path).await.unwrap(), "new!");
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use crate::{
|
||||
auth::split_dev_server_token,
|
||||
db::{tests::TestDb, NewUserParams, UserId},
|
||||
executor::Executor,
|
||||
rpc::{Principal, Server, ZedVersion, CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
|
||||
|
@ -204,7 +203,7 @@ impl TestServer {
|
|||
.override_authenticate(move |cx| {
|
||||
cx.spawn(|_| async move {
|
||||
let access_token = "the-token".to_string();
|
||||
Ok(Credentials::User {
|
||||
Ok(Credentials {
|
||||
user_id: user_id.to_proto(),
|
||||
access_token,
|
||||
})
|
||||
|
@ -213,7 +212,7 @@ impl TestServer {
|
|||
.override_establish_connection(move |credentials, cx| {
|
||||
assert_eq!(
|
||||
credentials,
|
||||
&Credentials::User {
|
||||
&Credentials {
|
||||
user_id: user_id.0 as u64,
|
||||
access_token: "the-token".into()
|
||||
}
|
||||
|
@ -297,7 +296,6 @@ impl TestServer {
|
|||
collab_ui::init(&app_state, cx);
|
||||
file_finder::init(cx);
|
||||
menu::init();
|
||||
dev_server_projects::init(client.clone(), cx);
|
||||
settings::KeymapFile::load_asset(os_keymap, cx).unwrap();
|
||||
language_model::LanguageModelRegistry::test(cx);
|
||||
assistant::context_store::init(&client.clone().into());
|
||||
|
@ -319,135 +317,6 @@ impl TestServer {
|
|||
client
|
||||
}
|
||||
|
||||
pub async fn create_dev_server(
|
||||
&self,
|
||||
access_token: String,
|
||||
cx: &mut TestAppContext,
|
||||
) -> TestClient {
|
||||
cx.update(|cx| {
|
||||
if cx.has_global::<SettingsStore>() {
|
||||
panic!("Same cx used to create two test clients")
|
||||
}
|
||||
let settings = SettingsStore::test(cx);
|
||||
cx.set_global(settings);
|
||||
release_channel::init(SemanticVersion::default(), cx);
|
||||
client::init_settings(cx);
|
||||
});
|
||||
let (dev_server_id, _) = split_dev_server_token(&access_token).unwrap();
|
||||
|
||||
let clock = Arc::new(FakeSystemClock::default());
|
||||
let http = FakeHttpClient::with_404_response();
|
||||
let mut client = cx.update(|cx| Client::new(clock, http.clone(), cx));
|
||||
let server = self.server.clone();
|
||||
let db = self.app_state.db.clone();
|
||||
let connection_killers = self.connection_killers.clone();
|
||||
let forbid_connections = self.forbid_connections.clone();
|
||||
Arc::get_mut(&mut client)
|
||||
.unwrap()
|
||||
.set_id(1)
|
||||
.set_dev_server_token(client::DevServerToken(access_token.clone()))
|
||||
.override_establish_connection(move |credentials, cx| {
|
||||
assert_eq!(
|
||||
credentials,
|
||||
&Credentials::DevServer {
|
||||
token: client::DevServerToken(access_token.to_string())
|
||||
}
|
||||
);
|
||||
|
||||
let server = server.clone();
|
||||
let db = db.clone();
|
||||
let connection_killers = connection_killers.clone();
|
||||
let forbid_connections = forbid_connections.clone();
|
||||
cx.spawn(move |cx| async move {
|
||||
if forbid_connections.load(SeqCst) {
|
||||
Err(EstablishConnectionError::other(anyhow!(
|
||||
"server is forbidding connections"
|
||||
)))
|
||||
} else {
|
||||
let (client_conn, server_conn, killed) =
|
||||
Connection::in_memory(cx.background_executor().clone());
|
||||
let (connection_id_tx, connection_id_rx) = oneshot::channel();
|
||||
let dev_server = db
|
||||
.get_dev_server(dev_server_id)
|
||||
.await
|
||||
.expect("retrieving dev_server failed");
|
||||
cx.background_executor()
|
||||
.spawn(server.handle_connection(
|
||||
server_conn,
|
||||
"dev-server".to_string(),
|
||||
Principal::DevServer(dev_server),
|
||||
ZedVersion(SemanticVersion::new(1, 0, 0)),
|
||||
None,
|
||||
Some(connection_id_tx),
|
||||
Executor::Deterministic(cx.background_executor().clone()),
|
||||
))
|
||||
.detach();
|
||||
let connection_id = connection_id_rx.await.map_err(|e| {
|
||||
EstablishConnectionError::Other(anyhow!(
|
||||
"{} (is server shutting down?)",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
connection_killers
|
||||
.lock()
|
||||
.insert(connection_id.into(), killed);
|
||||
Ok(client_conn)
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
let user_store = cx.new_model(|cx| UserStore::new(client.clone(), cx));
|
||||
let workspace_store = cx.new_model(|cx| WorkspaceStore::new(client.clone(), cx));
|
||||
let language_registry = Arc::new(LanguageRegistry::test(cx.executor()));
|
||||
let session = cx.new_model(|cx| AppSession::new(Session::test(), cx));
|
||||
let app_state = Arc::new(workspace::AppState {
|
||||
client: client.clone(),
|
||||
user_store: user_store.clone(),
|
||||
workspace_store,
|
||||
languages: language_registry,
|
||||
fs: fs.clone(),
|
||||
build_window_options: |_, _| Default::default(),
|
||||
node_runtime: NodeRuntime::unavailable(),
|
||||
session,
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
theme::init(theme::LoadThemes::JustBase, cx);
|
||||
Project::init(&client, cx);
|
||||
client::init(&client, cx);
|
||||
language::init(cx);
|
||||
editor::init(cx);
|
||||
workspace::init(app_state.clone(), cx);
|
||||
call::init(client.clone(), user_store.clone(), cx);
|
||||
channel::init(&client, user_store.clone(), cx);
|
||||
notifications::init(client.clone(), user_store, cx);
|
||||
collab_ui::init(&app_state, cx);
|
||||
file_finder::init(cx);
|
||||
menu::init();
|
||||
headless::init(
|
||||
client.clone(),
|
||||
headless::AppState {
|
||||
languages: app_state.languages.clone(),
|
||||
user_store: app_state.user_store.clone(),
|
||||
fs: fs.clone(),
|
||||
node_runtime: app_state.node_runtime.clone(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
TestClient {
|
||||
app_state,
|
||||
username: "dev-server".to_string(),
|
||||
channel_store: cx.read(ChannelStore::global).clone(),
|
||||
notification_store: cx.read(NotificationStore::global).clone(),
|
||||
state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect_client(&self, peer_id: PeerId) {
|
||||
self.connection_killers
|
||||
.lock()
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
[package]
|
||||
name = "dev_server_projects"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/dev_server_projects.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
gpui.workspace = true
|
||||
serde.workspace = true
|
||||
client.workspace = true
|
||||
rpc.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json.workspace = true
|
|
@ -1 +0,0 @@
|
|||
../../LICENSE-GPL
|
|
@ -1,249 +1 @@
|
|||
use anyhow::Result;
|
||||
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ModelContext, SharedString, Task};
|
||||
use rpc::{
|
||||
proto::{self, DevServerStatus},
|
||||
TypedEnvelope,
|
||||
};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use client::{Client, ProjectId};
|
||||
pub use client::{DevServerId, DevServerProjectId};
|
||||
|
||||
pub struct Store {
|
||||
dev_server_projects: HashMap<DevServerProjectId, DevServerProject>,
|
||||
dev_servers: HashMap<DevServerId, DevServer>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
client: Arc<Client>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DevServerProject {
|
||||
pub id: DevServerProjectId,
|
||||
pub project_id: Option<ProjectId>,
|
||||
pub paths: Vec<SharedString>,
|
||||
pub dev_server_id: DevServerId,
|
||||
}
|
||||
|
||||
impl From<proto::DevServerProject> for DevServerProject {
|
||||
fn from(project: proto::DevServerProject) -> Self {
|
||||
Self {
|
||||
id: DevServerProjectId(project.id),
|
||||
project_id: project.project_id.map(ProjectId),
|
||||
paths: project.paths.into_iter().map(|path| path.into()).collect(),
|
||||
dev_server_id: DevServerId(project.dev_server_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DevServer {
|
||||
pub id: DevServerId,
|
||||
pub name: SharedString,
|
||||
pub ssh_connection_string: Option<SharedString>,
|
||||
pub status: DevServerStatus,
|
||||
}
|
||||
|
||||
impl From<proto::DevServer> for DevServer {
|
||||
fn from(dev_server: proto::DevServer) -> Self {
|
||||
Self {
|
||||
id: DevServerId(dev_server.dev_server_id),
|
||||
status: dev_server.status(),
|
||||
name: dev_server.name.into(),
|
||||
ssh_connection_string: dev_server.ssh_connection_string.map(|s| s.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GlobalStore(Model<Store>);
|
||||
|
||||
impl Global for GlobalStore {}
|
||||
|
||||
pub fn init(client: Arc<Client>, cx: &mut AppContext) {
|
||||
let store = cx.new_model(|cx| Store::new(client, cx));
|
||||
cx.set_global(GlobalStore(store));
|
||||
}
|
||||
|
||||
impl Store {
|
||||
pub fn global(cx: &AppContext) -> Model<Store> {
|
||||
cx.global::<GlobalStore>().0.clone()
|
||||
}
|
||||
|
||||
pub fn new(client: Arc<Client>, cx: &ModelContext<Self>) -> Self {
|
||||
Self {
|
||||
dev_server_projects: Default::default(),
|
||||
dev_servers: Default::default(),
|
||||
_subscriptions: vec![client
|
||||
.add_message_handler(cx.weak_model(), Self::handle_dev_server_projects_update)],
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn projects_for_server(&self, id: DevServerId) -> Vec<DevServerProject> {
|
||||
let mut projects: Vec<DevServerProject> = self
|
||||
.dev_server_projects
|
||||
.values()
|
||||
.filter(|project| project.dev_server_id == id)
|
||||
.cloned()
|
||||
.collect();
|
||||
projects.sort_by_key(|p| (p.paths.clone(), p.id));
|
||||
projects
|
||||
}
|
||||
|
||||
pub fn dev_servers(&self) -> Vec<DevServer> {
|
||||
let mut dev_servers: Vec<DevServer> = self.dev_servers.values().cloned().collect();
|
||||
dev_servers.sort_by_key(|d| (d.status == DevServerStatus::Offline, d.name.clone(), d.id));
|
||||
dev_servers
|
||||
}
|
||||
|
||||
pub fn dev_server(&self, id: DevServerId) -> Option<&DevServer> {
|
||||
self.dev_servers.get(&id)
|
||||
}
|
||||
|
||||
pub fn dev_server_status(&self, id: DevServerId) -> DevServerStatus {
|
||||
self.dev_server(id)
|
||||
.map(|server| server.status)
|
||||
.unwrap_or(DevServerStatus::Offline)
|
||||
}
|
||||
|
||||
pub fn dev_server_projects(&self) -> Vec<DevServerProject> {
|
||||
let mut projects: Vec<DevServerProject> =
|
||||
self.dev_server_projects.values().cloned().collect();
|
||||
projects.sort_by_key(|p| (p.paths.clone(), p.id));
|
||||
projects
|
||||
}
|
||||
|
||||
pub fn dev_server_project(&self, id: DevServerProjectId) -> Option<&DevServerProject> {
|
||||
self.dev_server_projects.get(&id)
|
||||
}
|
||||
|
||||
pub fn dev_server_for_project(&self, id: DevServerProjectId) -> Option<&DevServer> {
|
||||
self.dev_server_project(id)
|
||||
.and_then(|project| self.dev_server(project.dev_server_id))
|
||||
}
|
||||
|
||||
async fn handle_dev_server_projects_update(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::DevServerProjectsUpdate>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.dev_servers = envelope
|
||||
.payload
|
||||
.dev_servers
|
||||
.into_iter()
|
||||
.map(|dev_server| (DevServerId(dev_server.dev_server_id), dev_server.into()))
|
||||
.collect();
|
||||
this.dev_server_projects = envelope
|
||||
.payload
|
||||
.dev_server_projects
|
||||
.into_iter()
|
||||
.map(|project| (DevServerProjectId(project.id), project.into()))
|
||||
.collect();
|
||||
|
||||
cx.notify();
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create_dev_server_project(
|
||||
&mut self,
|
||||
dev_server_id: DevServerId,
|
||||
path: String,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<proto::CreateDevServerProjectResponse>> {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::CreateDevServerProject {
|
||||
dev_server_id: dev_server_id.0,
|
||||
path,
|
||||
})
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
pub fn create_dev_server(
|
||||
&mut self,
|
||||
name: String,
|
||||
ssh_connection_string: Option<String>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<proto::CreateDevServerResponse>> {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
let result = client
|
||||
.request(proto::CreateDevServer {
|
||||
name,
|
||||
ssh_connection_string,
|
||||
})
|
||||
.await?;
|
||||
Ok(result)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn rename_dev_server(
|
||||
&mut self,
|
||||
dev_server_id: DevServerId,
|
||||
name: String,
|
||||
ssh_connection_string: Option<String>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::RenameDevServer {
|
||||
dev_server_id: dev_server_id.0,
|
||||
name,
|
||||
ssh_connection_string,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn regenerate_dev_server_token(
|
||||
&mut self,
|
||||
dev_server_id: DevServerId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<proto::RegenerateDevServerTokenResponse>> {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::RegenerateDevServerToken {
|
||||
dev_server_id: dev_server_id.0,
|
||||
})
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_dev_server(
|
||||
&mut self,
|
||||
id: DevServerId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::DeleteDevServer {
|
||||
dev_server_id: id.0,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_dev_server_project(
|
||||
&mut self,
|
||||
id: DevServerProjectId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<()>> {
|
||||
let client = self.client.clone();
|
||||
cx.background_executor().spawn(async move {
|
||||
client
|
||||
.request(proto::DeleteDevServerProject {
|
||||
dev_server_project_id: id.0,
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
[package]
|
||||
name = "headless"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
license = "GPL-3.0-or-later"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[lib]
|
||||
path = "src/headless.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
client.workspace = true
|
||||
extension.workspace = true
|
||||
signal-hook.workspace = true
|
||||
gpui.workspace = true
|
||||
log.workspace = true
|
||||
util.workspace = true
|
||||
node_runtime.workspace = true
|
||||
language.workspace = true
|
||||
project.workspace = true
|
||||
proto.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
settings.workspace = true
|
||||
shellexpand.workspace = true
|
||||
postage.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
client = { workspace = true, features = ["test-support"] }
|
||||
fs = { workspace = true, features = ["test-support"] }
|
||||
gpui = { workspace = true, features = ["test-support"] }
|
||||
util = { workspace = true, features = ["test-support"] }
|
|
@ -1 +0,0 @@
|
|||
../../LICENSE-GPL
|
|
@ -1,397 +0,0 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use client::DevServerProjectId;
|
||||
use client::{user::UserStore, Client, ClientSettings};
|
||||
use extension::ExtensionStore;
|
||||
use fs::Fs;
|
||||
use futures::{Future, StreamExt};
|
||||
use gpui::{AppContext, AsyncAppContext, Context, Global, Model, ModelContext, Task, WeakModel};
|
||||
use language::LanguageRegistry;
|
||||
use node_runtime::NodeRuntime;
|
||||
use postage::stream::Stream;
|
||||
use project::Project;
|
||||
use proto::{self, ErrorCode, TypedEnvelope};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use std::path::Path;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
|
||||
pub struct DevServer {
|
||||
client: Arc<Client>,
|
||||
app_state: AppState,
|
||||
remote_shutdown: bool,
|
||||
projects: HashMap<DevServerProjectId, Model<Project>>,
|
||||
_subscriptions: Vec<client::Subscription>,
|
||||
_maintain_connection: Task<Option<()>>,
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub node_runtime: NodeRuntime,
|
||||
pub user_store: Model<UserStore>,
|
||||
pub languages: Arc<LanguageRegistry>,
|
||||
pub fs: Arc<dyn Fs>,
|
||||
}
|
||||
|
||||
struct GlobalDevServer(Model<DevServer>);
|
||||
|
||||
impl Global for GlobalDevServer {}
|
||||
|
||||
pub fn init(client: Arc<Client>, app_state: AppState, cx: &mut AppContext) -> Task<Result<()>> {
|
||||
let dev_server = cx.new_model(|cx| DevServer::new(client.clone(), app_state, cx));
|
||||
cx.set_global(GlobalDevServer(dev_server.clone()));
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
use signal_hook::consts::{SIGINT, SIGTERM};
|
||||
use signal_hook::iterator::Signals;
|
||||
// Set up a handler when the dev server is shut down
|
||||
// with ctrl-c or kill
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let mut signals = Signals::new([SIGTERM, SIGINT]).unwrap();
|
||||
std::thread::spawn({
|
||||
move || {
|
||||
if let Some(sig) = signals.forever().next() {
|
||||
tx.send(sig).log_err();
|
||||
}
|
||||
}
|
||||
});
|
||||
cx.spawn(|cx| async move {
|
||||
if let Ok(sig) = rx.await {
|
||||
log::info!("received signal {sig:?}");
|
||||
cx.update(|cx| cx.quit()).log_err();
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
||||
let server_url = ClientSettings::get_global(cx).server_url.clone();
|
||||
cx.spawn(|cx| async move {
|
||||
client
|
||||
.authenticate_and_connect(false, &cx)
|
||||
.await
|
||||
.map_err(|e| anyhow!("Error connecting to '{}': {}", server_url, e))
|
||||
})
|
||||
}
|
||||
|
||||
impl DevServer {
|
||||
pub fn global(cx: &AppContext) -> Model<DevServer> {
|
||||
cx.global::<GlobalDevServer>().0.clone()
|
||||
}
|
||||
|
||||
pub fn new(client: Arc<Client>, app_state: AppState, cx: &mut ModelContext<Self>) -> Self {
|
||||
cx.on_app_quit(Self::app_will_quit).detach();
|
||||
|
||||
let maintain_connection = cx.spawn({
|
||||
let client = client.clone();
|
||||
move |this, cx| Self::maintain_connection(this, client.clone(), cx).log_err()
|
||||
});
|
||||
|
||||
cx.observe_global::<SettingsStore>(|_, cx| {
|
||||
ExtensionStore::global(cx).update(cx, |store, cx| store.auto_install_extensions(cx))
|
||||
})
|
||||
.detach();
|
||||
|
||||
DevServer {
|
||||
_subscriptions: vec![
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_dev_server_instructions),
|
||||
client.add_request_handler(
|
||||
cx.weak_model(),
|
||||
Self::handle_validate_dev_server_project_request,
|
||||
),
|
||||
client.add_request_handler(cx.weak_model(), Self::handle_list_remote_directory),
|
||||
client.add_message_handler(cx.weak_model(), Self::handle_shutdown),
|
||||
],
|
||||
_maintain_connection: maintain_connection,
|
||||
projects: Default::default(),
|
||||
remote_shutdown: false,
|
||||
app_state,
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
fn app_will_quit(&mut self, _: &mut ModelContext<Self>) -> impl Future<Output = ()> {
|
||||
let request = if self.remote_shutdown {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
self.client
|
||||
.request(proto::ShutdownDevServer { reason: None }),
|
||||
)
|
||||
};
|
||||
async move {
|
||||
if let Some(request) = request {
|
||||
request.await.log_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_dev_server_instructions(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::DevServerInstructions>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let (added_projects, retained_projects, removed_projects_ids) =
|
||||
this.read_with(&mut cx, |this, _| {
|
||||
let removed_projects = this
|
||||
.projects
|
||||
.keys()
|
||||
.filter(|dev_server_project_id| {
|
||||
!envelope
|
||||
.payload
|
||||
.projects
|
||||
.iter()
|
||||
.any(|p| p.id == dev_server_project_id.0)
|
||||
})
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut added_projects = vec![];
|
||||
let mut retained_projects = vec![];
|
||||
|
||||
for project in envelope.payload.projects.iter() {
|
||||
if this.projects.contains_key(&DevServerProjectId(project.id)) {
|
||||
retained_projects.push(project.clone());
|
||||
} else {
|
||||
added_projects.push(project.clone());
|
||||
}
|
||||
}
|
||||
|
||||
(added_projects, retained_projects, removed_projects)
|
||||
})?;
|
||||
|
||||
for dev_server_project in added_projects {
|
||||
DevServer::share_project(this.clone(), &dev_server_project, &mut cx).await?;
|
||||
}
|
||||
|
||||
for dev_server_project in retained_projects {
|
||||
DevServer::update_project(this.clone(), &dev_server_project, &mut cx).await?;
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
for old_project_id in &removed_projects_ids {
|
||||
this.unshare_project(old_project_id, cx)?;
|
||||
}
|
||||
Ok::<(), anyhow::Error>(())
|
||||
})??;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_validate_dev_server_project_request(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ValidateDevServerProjectRequest>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<proto::Ack> {
|
||||
let expanded = shellexpand::tilde(&envelope.payload.path).to_string();
|
||||
let path = std::path::Path::new(&expanded);
|
||||
let fs = cx.read_model(&this, |this, _| this.app_state.fs.clone())?;
|
||||
|
||||
let path_exists = fs.metadata(path).await.is_ok_and(|result| result.is_some());
|
||||
if !path_exists {
|
||||
return Err(anyhow!(ErrorCode::DevServerProjectPathDoesNotExist))?;
|
||||
}
|
||||
|
||||
Ok(proto::Ack {})
|
||||
}
|
||||
|
||||
async fn handle_list_remote_directory(
|
||||
this: Model<Self>,
|
||||
envelope: TypedEnvelope<proto::ListRemoteDirectory>,
|
||||
cx: AsyncAppContext,
|
||||
) -> Result<proto::ListRemoteDirectoryResponse> {
|
||||
let expanded = shellexpand::tilde(&envelope.payload.path).to_string();
|
||||
let fs = cx.read_model(&this, |this, _| this.app_state.fs.clone())?;
|
||||
|
||||
let mut entries = Vec::new();
|
||||
let mut response = fs.read_dir(Path::new(&expanded)).await?;
|
||||
while let Some(path) = response.next().await {
|
||||
if let Some(file_name) = path?.file_name() {
|
||||
entries.push(file_name.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
Ok(proto::ListRemoteDirectoryResponse { entries })
|
||||
}
|
||||
|
||||
async fn handle_shutdown(
|
||||
this: Model<Self>,
|
||||
_envelope: TypedEnvelope<proto::ShutdownDevServer>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.remote_shutdown = true;
|
||||
cx.quit();
|
||||
})
|
||||
}
|
||||
|
||||
fn unshare_project(
|
||||
&mut self,
|
||||
dev_server_project_id: &DevServerProjectId,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Result<()> {
|
||||
if let Some(project) = self.projects.remove(dev_server_project_id) {
|
||||
project.update(cx, |project, cx| project.unshare(cx))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn share_project(
|
||||
this: Model<Self>,
|
||||
dev_server_project: &proto::DevServerProject,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let (client, project) = this.update(cx, |this, cx| {
|
||||
let project = Project::local(
|
||||
this.client.clone(),
|
||||
this.app_state.node_runtime.clone(),
|
||||
this.app_state.user_store.clone(),
|
||||
this.app_state.languages.clone(),
|
||||
this.app_state.fs.clone(),
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
|
||||
(this.client.clone(), project)
|
||||
})?;
|
||||
|
||||
for path in &dev_server_project.paths {
|
||||
let path = shellexpand::tilde(path).to_string();
|
||||
|
||||
let (worktree, _) = project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(&path, true, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
worktree.update(cx, |worktree, cx| {
|
||||
worktree.as_local_mut().unwrap().share_private_files(cx)
|
||||
})?;
|
||||
}
|
||||
|
||||
let worktrees =
|
||||
project.read_with(cx, |project, cx| project.worktree_metadata_protos(cx))?;
|
||||
|
||||
let response = client
|
||||
.request(proto::ShareDevServerProject {
|
||||
dev_server_project_id: dev_server_project.id,
|
||||
worktrees,
|
||||
})
|
||||
.await?;
|
||||
|
||||
let project_id = response.project_id;
|
||||
project.update(cx, |project, cx| project.shared(project_id, cx))??;
|
||||
this.update(cx, |this, _| {
|
||||
this.projects
|
||||
.insert(DevServerProjectId(dev_server_project.id), project);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_project(
|
||||
this: Model<Self>,
|
||||
dev_server_project: &proto::DevServerProject,
|
||||
cx: &mut AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let tasks = this.update(cx, |this, cx| {
|
||||
let Some(project) = this
|
||||
.projects
|
||||
.get(&DevServerProjectId(dev_server_project.id))
|
||||
else {
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let mut to_delete = vec![];
|
||||
let mut tasks = vec![];
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
for worktree in project.visible_worktrees(cx) {
|
||||
let mut delete = true;
|
||||
for config in dev_server_project.paths.iter() {
|
||||
if worktree.read(cx).abs_path().to_string_lossy()
|
||||
== shellexpand::tilde(config)
|
||||
{
|
||||
delete = false;
|
||||
}
|
||||
}
|
||||
if delete {
|
||||
to_delete.push(worktree.read(cx).id())
|
||||
}
|
||||
}
|
||||
|
||||
for worktree_id in to_delete {
|
||||
project.remove_worktree(worktree_id, cx)
|
||||
}
|
||||
|
||||
for config in dev_server_project.paths.iter() {
|
||||
tasks.push(project.find_or_create_worktree(
|
||||
shellexpand::tilde(config).to_string(),
|
||||
true,
|
||||
cx,
|
||||
));
|
||||
}
|
||||
|
||||
tasks
|
||||
})
|
||||
})?;
|
||||
futures::future::join_all(tasks).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn maintain_connection(
|
||||
this: WeakModel<Self>,
|
||||
client: Arc<Client>,
|
||||
mut cx: AsyncAppContext,
|
||||
) -> Result<()> {
|
||||
let mut client_status = client.status();
|
||||
|
||||
let _ = client_status.try_recv();
|
||||
let current_status = *client_status.borrow();
|
||||
if current_status.is_connected() {
|
||||
// wait for first disconnect
|
||||
client_status.recv().await;
|
||||
}
|
||||
|
||||
loop {
|
||||
let Some(current_status) = client_status.recv().await else {
|
||||
return Ok(());
|
||||
};
|
||||
let Some(this) = this.upgrade() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if !current_status.is_connected() {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.update(&mut cx, |this, cx| this.rejoin(cx))?.await?;
|
||||
}
|
||||
}
|
||||
|
||||
fn rejoin(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
|
||||
let mut projects: HashMap<u64, Model<Project>> = HashMap::default();
|
||||
let request = self.client.request(proto::ReconnectDevServer {
|
||||
reshared_projects: self
|
||||
.projects
|
||||
.iter()
|
||||
.flat_map(|(_, handle)| {
|
||||
let project = handle.read(cx);
|
||||
let project_id = project.remote_id()?;
|
||||
projects.insert(project_id, handle.clone());
|
||||
Some(proto::UpdateProject {
|
||||
project_id,
|
||||
worktrees: project.worktree_metadata_protos(cx),
|
||||
})
|
||||
})
|
||||
.collect(),
|
||||
});
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
let response = request.await?;
|
||||
|
||||
for reshared_project in response.reshared_projects {
|
||||
if let Some(project) = projects.get(&reshared_project.id) {
|
||||
project.update(&mut cx, |project, cx| {
|
||||
project.reshared(reshared_project, cx).log_err();
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ async-trait.workspace = true
|
|||
client.workspace = true
|
||||
clock.workspace = true
|
||||
collections.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
|
|
|
@ -25,8 +25,7 @@ mod yarn;
|
|||
use anyhow::{anyhow, Context as _, Result};
|
||||
use buffer_store::{BufferStore, BufferStoreEvent};
|
||||
use client::{
|
||||
proto, Client, Collaborator, DevServerProjectId, PendingEntitySubscription, ProjectId,
|
||||
TypedEnvelope, UserStore,
|
||||
proto, Client, Collaborator, PendingEntitySubscription, ProjectId, TypedEnvelope, UserStore,
|
||||
};
|
||||
use clock::ReplicaId;
|
||||
use collections::{BTreeSet, HashMap, HashSet};
|
||||
|
@ -156,7 +155,6 @@ pub struct Project {
|
|||
terminals: Terminals,
|
||||
node: Option<NodeRuntime>,
|
||||
hosted_project_id: Option<ProjectId>,
|
||||
dev_server_project_id: Option<client::DevServerProjectId>,
|
||||
search_history: SearchHistory,
|
||||
search_included_history: SearchHistory,
|
||||
search_excluded_history: SearchHistory,
|
||||
|
@ -217,7 +215,6 @@ enum ProjectClientState {
|
|||
capability: Capability,
|
||||
remote_id: u64,
|
||||
replica_id: ReplicaId,
|
||||
in_room: bool,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -675,7 +672,6 @@ impl Project {
|
|||
},
|
||||
node: Some(node),
|
||||
hosted_project_id: None,
|
||||
dev_server_project_id: None,
|
||||
search_history: Self::new_search_history(),
|
||||
environment,
|
||||
remotely_created_models: Default::default(),
|
||||
|
@ -705,7 +701,7 @@ impl Project {
|
|||
|
||||
let ssh_proto = ssh.read(cx).proto_client();
|
||||
let worktree_store =
|
||||
cx.new_model(|_| WorktreeStore::remote(false, ssh_proto.clone(), 0, None));
|
||||
cx.new_model(|_| WorktreeStore::remote(false, ssh_proto.clone(), 0));
|
||||
cx.subscribe(&worktree_store, Self::on_worktree_store_event)
|
||||
.detach();
|
||||
|
||||
|
@ -794,7 +790,6 @@ impl Project {
|
|||
},
|
||||
node: Some(node),
|
||||
hosted_project_id: None,
|
||||
dev_server_project_id: None,
|
||||
search_history: Self::new_search_history(),
|
||||
environment,
|
||||
remotely_created_models: Default::default(),
|
||||
|
@ -898,15 +893,7 @@ impl Project {
|
|||
let role = response.payload.role();
|
||||
|
||||
let worktree_store = cx.new_model(|_| {
|
||||
WorktreeStore::remote(
|
||||
true,
|
||||
client.clone().into(),
|
||||
response.payload.project_id,
|
||||
response
|
||||
.payload
|
||||
.dev_server_project_id
|
||||
.map(DevServerProjectId),
|
||||
)
|
||||
WorktreeStore::remote(true, client.clone().into(), response.payload.project_id)
|
||||
})?;
|
||||
let buffer_store = cx.new_model(|cx| {
|
||||
BufferStore::remote(worktree_store.clone(), client.clone().into(), remote_id, cx)
|
||||
|
@ -992,7 +979,6 @@ impl Project {
|
|||
capability: Capability::ReadWrite,
|
||||
remote_id,
|
||||
replica_id,
|
||||
in_room: response.payload.dev_server_project_id.is_none(),
|
||||
},
|
||||
buffers_needing_diff: Default::default(),
|
||||
git_diff_debouncer: DebouncedDelay::new(),
|
||||
|
@ -1001,10 +987,6 @@ impl Project {
|
|||
},
|
||||
node: None,
|
||||
hosted_project_id: None,
|
||||
dev_server_project_id: response
|
||||
.payload
|
||||
.dev_server_project_id
|
||||
.map(DevServerProjectId),
|
||||
search_history: Self::new_search_history(),
|
||||
search_included_history: Self::new_search_history(),
|
||||
search_excluded_history: Self::new_search_history(),
|
||||
|
@ -1305,39 +1287,23 @@ impl Project {
|
|||
self.hosted_project_id
|
||||
}
|
||||
|
||||
pub fn dev_server_project_id(&self) -> Option<DevServerProjectId> {
|
||||
self.dev_server_project_id
|
||||
}
|
||||
|
||||
pub fn supports_terminal(&self, cx: &AppContext) -> bool {
|
||||
pub fn supports_terminal(&self, _cx: &AppContext) -> bool {
|
||||
if self.is_local() {
|
||||
return true;
|
||||
}
|
||||
if self.is_via_ssh() {
|
||||
return true;
|
||||
}
|
||||
let Some(id) = self.dev_server_project_id else {
|
||||
return false;
|
||||
};
|
||||
let Some(server) = dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_for_project(id)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
server.ssh_connection_string.is_some()
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
pub fn ssh_connection_string(&self, cx: &AppContext) -> Option<SharedString> {
|
||||
if let Some(ssh_state) = &self.ssh_client {
|
||||
return Some(ssh_state.read(cx).connection_string().into());
|
||||
}
|
||||
let dev_server_id = self.dev_server_project_id()?;
|
||||
dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_for_project(dev_server_id)?
|
||||
.ssh_connection_string
|
||||
.clone()
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn ssh_connection_state(&self, cx: &AppContext) -> Option<remote::ConnectionState> {
|
||||
|
@ -1549,17 +1515,9 @@ impl Project {
|
|||
|
||||
pub fn shared(&mut self, project_id: u64, cx: &mut ModelContext<Self>) -> Result<()> {
|
||||
if !matches!(self.client_state, ProjectClientState::Local) {
|
||||
if let ProjectClientState::Remote { in_room, .. } = &mut self.client_state {
|
||||
if *in_room || self.dev_server_project_id.is_none() {
|
||||
return Err(anyhow!("project was already shared"));
|
||||
} else {
|
||||
*in_room = true;
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!("project was already shared"));
|
||||
}
|
||||
return Err(anyhow!("project was already shared"));
|
||||
}
|
||||
|
||||
self.client_subscriptions.extend([
|
||||
self.client
|
||||
.subscribe_to_entity(project_id)?
|
||||
|
@ -1657,14 +1615,7 @@ impl Project {
|
|||
|
||||
fn unshare_internal(&mut self, cx: &mut AppContext) -> Result<()> {
|
||||
if self.is_via_collab() {
|
||||
if self.dev_server_project_id().is_some() {
|
||||
if let ProjectClientState::Remote { in_room, .. } = &mut self.client_state {
|
||||
*in_room = false
|
||||
}
|
||||
return Ok(());
|
||||
} else {
|
||||
return Err(anyhow!("attempted to unshare a remote project"));
|
||||
}
|
||||
return Err(anyhow!("attempted to unshare a remote project"));
|
||||
}
|
||||
|
||||
if let ProjectClientState::Shared { remote_id, .. } = self.client_state {
|
||||
|
@ -2265,29 +2216,6 @@ impl Project {
|
|||
}
|
||||
|
||||
fn on_worktree_released(&mut self, id_to_remove: WorktreeId, cx: &mut ModelContext<Self>) {
|
||||
if let Some(dev_server_project_id) = self.dev_server_project_id {
|
||||
let paths: Vec<String> = self
|
||||
.visible_worktrees(cx)
|
||||
.filter_map(|worktree| {
|
||||
if worktree.read(cx).id() == id_to_remove {
|
||||
None
|
||||
} else {
|
||||
Some(worktree.read(cx).abs_path().to_string_lossy().to_string())
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
if !paths.is_empty() {
|
||||
let request = self.client.request(proto::UpdateDevServerProject {
|
||||
dev_server_project_id: dev_server_project_id.0,
|
||||
paths,
|
||||
});
|
||||
cx.background_executor()
|
||||
.spawn(request)
|
||||
.detach_and_log_err(cx);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(ssh) = &self.ssh_client {
|
||||
ssh.read(cx)
|
||||
.proto_client()
|
||||
|
@ -3152,7 +3080,7 @@ impl Project {
|
|||
match &self.client_state {
|
||||
ProjectClientState::Shared { .. } => true,
|
||||
ProjectClientState::Local => false,
|
||||
ProjectClientState::Remote { in_room, .. } => *in_room,
|
||||
ProjectClientState::Remote { .. } => true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3279,20 +3207,6 @@ impl Project {
|
|||
let response = response.await?;
|
||||
Ok(response.entries.into_iter().map(PathBuf::from).collect())
|
||||
})
|
||||
} else if let Some(dev_server) = self.dev_server_project_id().and_then(|id| {
|
||||
dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_for_project(id)
|
||||
}) {
|
||||
let request = proto::ListRemoteDirectory {
|
||||
dev_server_id: dev_server.id.0,
|
||||
path: query,
|
||||
};
|
||||
let response = self.client.request(request);
|
||||
cx.background_executor().spawn(async move {
|
||||
let response = response.await?;
|
||||
Ok(response.entries.into_iter().map(PathBuf::from).collect())
|
||||
})
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("cannot list directory in remote project")))
|
||||
}
|
||||
|
|
|
@ -37,11 +37,8 @@ pub enum TerminalKind {
|
|||
|
||||
/// SshCommand describes how to connect to a remote server
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SshCommand {
|
||||
/// DevServers give a string from the user
|
||||
DevServer(String),
|
||||
/// Direct ssh has a list of arguments to pass to ssh
|
||||
Direct(Vec<String>),
|
||||
pub struct SshCommand {
|
||||
arguments: Vec<String>,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
|
@ -73,19 +70,12 @@ impl Project {
|
|||
if let Some(args) = ssh_client.ssh_args() {
|
||||
return Some((
|
||||
ssh_client.connection_options().host.clone(),
|
||||
SshCommand::Direct(args),
|
||||
SshCommand { arguments: args },
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let dev_server_project_id = self.dev_server_project_id()?;
|
||||
let projects_store = dev_server_projects::Store::global(cx).read(cx);
|
||||
let ssh_command = projects_store
|
||||
.dev_server_for_project(dev_server_project_id)?
|
||||
.ssh_connection_string
|
||||
.as_ref()?
|
||||
.to_string();
|
||||
Some(("".to_string(), SshCommand::DevServer(ssh_command)))
|
||||
return None;
|
||||
}
|
||||
|
||||
pub fn create_terminal(
|
||||
|
@ -399,14 +389,8 @@ pub fn wrap_for_ssh(
|
|||
};
|
||||
let shell_invocation = format!("sh -c {}", shlex::try_quote(&commands).unwrap());
|
||||
|
||||
let (program, mut args) = match ssh_command {
|
||||
SshCommand::DevServer(ssh_command) => {
|
||||
let mut args = shlex::split(ssh_command).unwrap_or_default();
|
||||
let program = args.drain(0..1).next().unwrap_or("ssh".to_string());
|
||||
(program, args)
|
||||
}
|
||||
SshCommand::Direct(ssh_args) => ("ssh".to_string(), ssh_args.clone()),
|
||||
};
|
||||
let program = "ssh".to_string();
|
||||
let mut args = ssh_command.arguments.clone();
|
||||
|
||||
args.push("-t".to_string());
|
||||
args.push(shell_invocation);
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use std::{
|
||||
cell::RefCell,
|
||||
path::{Path, PathBuf},
|
||||
sync::{atomic::AtomicUsize, Arc},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use client::DevServerProjectId;
|
||||
use collections::{HashMap, HashSet};
|
||||
use fs::Fs;
|
||||
use futures::{
|
||||
|
@ -41,7 +39,6 @@ enum WorktreeStoreState {
|
|||
fs: Arc<dyn Fs>,
|
||||
},
|
||||
Remote {
|
||||
dev_server_project_id: Option<DevServerProjectId>,
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
},
|
||||
|
@ -94,7 +91,6 @@ impl WorktreeStore {
|
|||
retain_worktrees: bool,
|
||||
upstream_client: AnyProtoClient,
|
||||
upstream_project_id: u64,
|
||||
dev_server_project_id: Option<DevServerProjectId>,
|
||||
) -> Self {
|
||||
Self {
|
||||
next_entry_id: Default::default(),
|
||||
|
@ -106,7 +102,6 @@ impl WorktreeStore {
|
|||
state: WorktreeStoreState::Remote {
|
||||
upstream_client,
|
||||
upstream_project_id,
|
||||
dev_server_project_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -196,18 +191,9 @@ impl WorktreeStore {
|
|||
if !self.loading_worktrees.contains_key(&path) {
|
||||
let task = match &self.state {
|
||||
WorktreeStoreState::Remote {
|
||||
upstream_client,
|
||||
dev_server_project_id,
|
||||
..
|
||||
upstream_client, ..
|
||||
} => {
|
||||
if let Some(dev_server_project_id) = dev_server_project_id {
|
||||
self.create_dev_server_worktree(
|
||||
upstream_client.clone(),
|
||||
*dev_server_project_id,
|
||||
abs_path,
|
||||
cx,
|
||||
)
|
||||
} else if upstream_client.is_via_collab() {
|
||||
if upstream_client.is_via_collab() {
|
||||
Task::ready(Err(Arc::new(anyhow!("cannot create worktrees via collab"))))
|
||||
} else {
|
||||
self.create_ssh_worktree(upstream_client.clone(), abs_path, visible, cx)
|
||||
|
@ -322,51 +308,6 @@ impl WorktreeStore {
|
|||
})
|
||||
}
|
||||
|
||||
fn create_dev_server_worktree(
|
||||
&mut self,
|
||||
client: AnyProtoClient,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
abs_path: impl AsRef<Path>,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<Model<Worktree>, Arc<anyhow::Error>>> {
|
||||
let path: Arc<Path> = abs_path.as_ref().into();
|
||||
let mut paths: Vec<String> = self
|
||||
.visible_worktrees(cx)
|
||||
.map(|worktree| worktree.read(cx).abs_path().to_string_lossy().to_string())
|
||||
.collect();
|
||||
paths.push(path.to_string_lossy().to_string());
|
||||
let request = client.request(proto::UpdateDevServerProject {
|
||||
dev_server_project_id: dev_server_project_id.0,
|
||||
paths,
|
||||
});
|
||||
|
||||
let abs_path = abs_path.as_ref().to_path_buf();
|
||||
cx.spawn(move |project, cx| async move {
|
||||
let (tx, rx) = futures::channel::oneshot::channel();
|
||||
let tx = RefCell::new(Some(tx));
|
||||
let Some(project) = project.upgrade() else {
|
||||
return Err(anyhow!("project dropped"))?;
|
||||
};
|
||||
let observer = cx.update(|cx| {
|
||||
cx.observe(&project, move |project, cx| {
|
||||
let abs_path = abs_path.clone();
|
||||
project.update(cx, |project, cx| {
|
||||
if let Some((worktree, _)) = project.find_worktree(&abs_path, cx) {
|
||||
if let Some(tx) = tx.borrow_mut().take() {
|
||||
tx.send(worktree).ok();
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})?;
|
||||
|
||||
request.await?;
|
||||
let worktree = rx.await.map_err(|e| anyhow!(e))?;
|
||||
drop(observer);
|
||||
Ok(worktree)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add(&mut self, worktree: &Model<Worktree>, cx: &mut ModelContext<Self>) {
|
||||
let worktree_id = worktree.read(cx).id();
|
||||
debug_assert!(self.worktrees().all(|w| w.read(cx).id() != worktree_id));
|
||||
|
|
|
@ -503,7 +503,7 @@ impl ProjectPanel {
|
|||
let is_unfoldable = auto_fold_dirs && self.is_unfoldable(entry, worktree);
|
||||
let worktree_id = worktree.id();
|
||||
let is_read_only = project.is_read_only(cx);
|
||||
let is_remote = project.is_via_collab() && project.dev_server_project_id().is_none();
|
||||
let is_remote = project.is_via_collab();
|
||||
let is_local = project.is_local();
|
||||
|
||||
let context_menu = ContextMenu::build(cx, |menu, cx| {
|
||||
|
@ -3334,12 +3334,11 @@ impl Panel for ProjectPanel {
|
|||
|
||||
fn starts_open(&self, cx: &WindowContext) -> bool {
|
||||
let project = &self.project.read(cx);
|
||||
project.dev_server_project_id().is_some()
|
||||
|| project.visible_worktrees(cx).any(|tree| {
|
||||
tree.read(cx)
|
||||
.root_entry()
|
||||
.map_or(false, |entry| entry.is_dir())
|
||||
})
|
||||
project.visible_worktrees(cx).any(|tree| {
|
||||
tree.read(cx)
|
||||
.root_entry()
|
||||
.map_or(false, |entry| entry.is_dir())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -217,33 +217,14 @@ message Envelope {
|
|||
MultiLspQueryResponse multi_lsp_query_response = 176;
|
||||
RestartLanguageServers restart_language_servers = 208;
|
||||
|
||||
CreateDevServerProject create_dev_server_project = 177;
|
||||
CreateDevServerProjectResponse create_dev_server_project_response = 188;
|
||||
CreateDevServer create_dev_server = 178;
|
||||
CreateDevServerResponse create_dev_server_response = 179;
|
||||
ShutdownDevServer shutdown_dev_server = 180;
|
||||
DevServerInstructions dev_server_instructions = 181;
|
||||
ReconnectDevServer reconnect_dev_server = 182;
|
||||
ReconnectDevServerResponse reconnect_dev_server_response = 183;
|
||||
|
||||
ShareDevServerProject share_dev_server_project = 184;
|
||||
JoinDevServerProject join_dev_server_project = 185;
|
||||
RejoinRemoteProjects rejoin_remote_projects = 186;
|
||||
RejoinRemoteProjectsResponse rejoin_remote_projects_response = 187;
|
||||
|
||||
DevServerProjectsUpdate dev_server_projects_update = 193;
|
||||
ValidateDevServerProjectRequest validate_dev_server_project_request = 194;
|
||||
DeleteDevServer delete_dev_server = 195;
|
||||
OpenNewBuffer open_new_buffer = 196;
|
||||
DeleteDevServerProject delete_dev_server_project = 197;
|
||||
|
||||
GetSupermavenApiKey get_supermaven_api_key = 198;
|
||||
GetSupermavenApiKeyResponse get_supermaven_api_key_response = 199;
|
||||
|
||||
RegenerateDevServerToken regenerate_dev_server_token = 200;
|
||||
RegenerateDevServerTokenResponse regenerate_dev_server_token_response = 201;
|
||||
RenameDevServer rename_dev_server = 202;
|
||||
|
||||
TaskContextForLocation task_context_for_location = 203;
|
||||
TaskContext task_context = 204;
|
||||
|
||||
|
@ -264,7 +245,6 @@ message Envelope {
|
|||
|
||||
ListRemoteDirectory list_remote_directory = 219;
|
||||
ListRemoteDirectoryResponse list_remote_directory_response = 220;
|
||||
UpdateDevServerProject update_dev_server_project = 221;
|
||||
AddWorktree add_worktree = 222;
|
||||
AddWorktreeResponse add_worktree_response = 223;
|
||||
|
||||
|
@ -304,10 +284,17 @@ message Envelope {
|
|||
LanguageServerPromptResponse language_server_prompt_response = 269; // current max
|
||||
}
|
||||
|
||||
|
||||
reserved 87 to 88;
|
||||
reserved 158 to 161;
|
||||
reserved 166 to 169;
|
||||
reserved 177 to 185;
|
||||
reserved 188;
|
||||
reserved 193 to 195;
|
||||
reserved 197;
|
||||
reserved 200 to 202;
|
||||
reserved 205 to 206;
|
||||
reserved 221;
|
||||
reserved 224 to 229;
|
||||
reserved 247 to 254;
|
||||
}
|
||||
|
@ -342,12 +329,11 @@ enum ErrorCode {
|
|||
WrongMoveTarget = 11;
|
||||
UnsharedItem = 12;
|
||||
NoSuchProject = 13;
|
||||
DevServerAlreadyOnline = 14;
|
||||
DevServerOffline = 15;
|
||||
DevServerProjectPathDoesNotExist = 16;
|
||||
RemoteUpgradeRequired = 17;
|
||||
RateLimitExceeded = 18;
|
||||
reserved 6;
|
||||
reserved 14 to 15;
|
||||
}
|
||||
|
||||
message EndStream {}
|
||||
|
@ -511,7 +497,7 @@ message LiveKitConnectionInfo {
|
|||
message ShareProject {
|
||||
uint64 room_id = 1;
|
||||
repeated WorktreeMetadata worktrees = 2;
|
||||
optional uint64 dev_server_project_id = 3;
|
||||
reserved 3;
|
||||
bool is_ssh_project = 4;
|
||||
}
|
||||
|
||||
|
@ -536,19 +522,6 @@ message JoinHostedProject {
|
|||
uint64 project_id = 1;
|
||||
}
|
||||
|
||||
message CreateDevServerProject {
|
||||
reserved 1;
|
||||
reserved 2;
|
||||
uint64 dev_server_id = 3;
|
||||
string path = 4;
|
||||
}
|
||||
message CreateDevServerProjectResponse {
|
||||
DevServerProject dev_server_project = 1;
|
||||
}
|
||||
|
||||
message ValidateDevServerProjectRequest {
|
||||
string path = 1;
|
||||
}
|
||||
|
||||
message ListRemoteDirectory {
|
||||
uint64 dev_server_id = 1;
|
||||
|
@ -559,77 +532,6 @@ message ListRemoteDirectoryResponse {
|
|||
repeated string entries = 1;
|
||||
}
|
||||
|
||||
message UpdateDevServerProject {
|
||||
uint64 dev_server_project_id = 1;
|
||||
repeated string paths = 2;
|
||||
}
|
||||
|
||||
message CreateDevServer {
|
||||
reserved 1;
|
||||
string name = 2;
|
||||
optional string ssh_connection_string = 3;
|
||||
}
|
||||
|
||||
message RegenerateDevServerToken {
|
||||
uint64 dev_server_id = 1;
|
||||
}
|
||||
|
||||
message RegenerateDevServerTokenResponse {
|
||||
uint64 dev_server_id = 1;
|
||||
string access_token = 2;
|
||||
}
|
||||
|
||||
message CreateDevServerResponse {
|
||||
uint64 dev_server_id = 1;
|
||||
reserved 2;
|
||||
string access_token = 3;
|
||||
string name = 4;
|
||||
}
|
||||
|
||||
message ShutdownDevServer {
|
||||
optional string reason = 1;
|
||||
}
|
||||
|
||||
message RenameDevServer {
|
||||
uint64 dev_server_id = 1;
|
||||
string name = 2;
|
||||
optional string ssh_connection_string = 3;
|
||||
}
|
||||
|
||||
message DeleteDevServer {
|
||||
uint64 dev_server_id = 1;
|
||||
}
|
||||
|
||||
message DeleteDevServerProject {
|
||||
uint64 dev_server_project_id = 1;
|
||||
}
|
||||
|
||||
message ReconnectDevServer {
|
||||
repeated UpdateProject reshared_projects = 1;
|
||||
}
|
||||
|
||||
message ReconnectDevServerResponse {
|
||||
repeated ResharedProject reshared_projects = 1;
|
||||
}
|
||||
|
||||
message DevServerInstructions {
|
||||
repeated DevServerProject projects = 1;
|
||||
}
|
||||
|
||||
message DevServerProjectsUpdate {
|
||||
repeated DevServer dev_servers = 1;
|
||||
repeated DevServerProject dev_server_projects = 2;
|
||||
}
|
||||
|
||||
message ShareDevServerProject {
|
||||
uint64 dev_server_project_id = 1;
|
||||
repeated WorktreeMetadata worktrees = 2;
|
||||
}
|
||||
|
||||
message JoinDevServerProject {
|
||||
uint64 dev_server_project_id = 1;
|
||||
}
|
||||
|
||||
message JoinProjectResponse {
|
||||
uint64 project_id = 5;
|
||||
uint32 replica_id = 1;
|
||||
|
@ -637,7 +539,7 @@ message JoinProjectResponse {
|
|||
repeated Collaborator collaborators = 3;
|
||||
repeated LanguageServer language_servers = 4;
|
||||
ChannelRole role = 6;
|
||||
optional uint64 dev_server_project_id = 7;
|
||||
reserved 7;
|
||||
}
|
||||
|
||||
message LeaveProject {
|
||||
|
@ -1429,29 +1331,6 @@ message HostedProject {
|
|||
ChannelVisibility visibility = 4;
|
||||
}
|
||||
|
||||
message DevServerProject {
|
||||
uint64 id = 1;
|
||||
optional uint64 project_id = 2;
|
||||
reserved 3;
|
||||
reserved 4;
|
||||
uint64 dev_server_id = 5;
|
||||
string path = 6;
|
||||
repeated string paths = 7;
|
||||
}
|
||||
|
||||
message DevServer {
|
||||
reserved 1;
|
||||
uint64 dev_server_id = 2;
|
||||
string name = 3;
|
||||
DevServerStatus status = 4;
|
||||
optional string ssh_connection_string = 5;
|
||||
}
|
||||
|
||||
enum DevServerStatus {
|
||||
Offline = 0;
|
||||
Online = 1;
|
||||
}
|
||||
|
||||
message JoinChannel {
|
||||
uint64 channel_id = 1;
|
||||
}
|
||||
|
|
|
@ -318,30 +318,12 @@ messages!(
|
|||
(SetRoomParticipantRole, Foreground),
|
||||
(BlameBuffer, Foreground),
|
||||
(BlameBufferResponse, Foreground),
|
||||
(CreateDevServerProject, Background),
|
||||
(CreateDevServerProjectResponse, Foreground),
|
||||
(CreateDevServer, Foreground),
|
||||
(CreateDevServerResponse, Foreground),
|
||||
(DevServerInstructions, Foreground),
|
||||
(ShutdownDevServer, Foreground),
|
||||
(ReconnectDevServer, Foreground),
|
||||
(ReconnectDevServerResponse, Foreground),
|
||||
(ShareDevServerProject, Foreground),
|
||||
(JoinDevServerProject, Foreground),
|
||||
(RejoinRemoteProjects, Foreground),
|
||||
(RejoinRemoteProjectsResponse, Foreground),
|
||||
(MultiLspQuery, Background),
|
||||
(MultiLspQueryResponse, Background),
|
||||
(DevServerProjectsUpdate, Foreground),
|
||||
(ValidateDevServerProjectRequest, Background),
|
||||
(ListRemoteDirectory, Background),
|
||||
(ListRemoteDirectoryResponse, Background),
|
||||
(UpdateDevServerProject, Background),
|
||||
(DeleteDevServer, Foreground),
|
||||
(DeleteDevServerProject, Foreground),
|
||||
(RegenerateDevServerToken, Foreground),
|
||||
(RegenerateDevServerTokenResponse, Foreground),
|
||||
(RenameDevServer, Foreground),
|
||||
(OpenNewBuffer, Foreground),
|
||||
(RestartLanguageServers, Foreground),
|
||||
(LinkedEditingRange, Background),
|
||||
|
@ -419,7 +401,6 @@ request_messages!(
|
|||
(GetTypeDefinition, GetTypeDefinitionResponse),
|
||||
(LinkedEditingRange, LinkedEditingRangeResponse),
|
||||
(ListRemoteDirectory, ListRemoteDirectoryResponse),
|
||||
(UpdateDevServerProject, Ack),
|
||||
(GetUsers, UsersResponse),
|
||||
(IncomingCall, Ack),
|
||||
(InlayHints, InlayHintsResponse),
|
||||
|
@ -477,19 +458,8 @@ request_messages!(
|
|||
(LspExtExpandMacro, LspExtExpandMacroResponse),
|
||||
(SetRoomParticipantRole, Ack),
|
||||
(BlameBuffer, BlameBufferResponse),
|
||||
(CreateDevServerProject, CreateDevServerProjectResponse),
|
||||
(CreateDevServer, CreateDevServerResponse),
|
||||
(ShutdownDevServer, Ack),
|
||||
(ShareDevServerProject, ShareProjectResponse),
|
||||
(JoinDevServerProject, JoinProjectResponse),
|
||||
(RejoinRemoteProjects, RejoinRemoteProjectsResponse),
|
||||
(ReconnectDevServer, ReconnectDevServerResponse),
|
||||
(ValidateDevServerProjectRequest, Ack),
|
||||
(MultiLspQuery, MultiLspQueryResponse),
|
||||
(DeleteDevServer, Ack),
|
||||
(DeleteDevServerProject, Ack),
|
||||
(RegenerateDevServerToken, RegenerateDevServerTokenResponse),
|
||||
(RenameDevServer, Ack),
|
||||
(RestartLanguageServers, Ack),
|
||||
(OpenContext, OpenContextResponse),
|
||||
(CreateContext, CreateContextResponse),
|
||||
|
|
|
@ -16,7 +16,6 @@ doctest = false
|
|||
anyhow.workspace = true
|
||||
auto_update.workspace = true
|
||||
release_channel.workspace = true
|
||||
client.workspace = true
|
||||
editor.workspace = true
|
||||
file_finder.workspace = true
|
||||
futures.workspace = true
|
||||
|
@ -30,15 +29,12 @@ menu.workspace = true
|
|||
ordered-float.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
remote.workspace = true
|
||||
rpc.workspace = true
|
||||
schemars.workspace = true
|
||||
serde.workspace = true
|
||||
settings.workspace = true
|
||||
smol.workspace = true
|
||||
task.workspace = true
|
||||
terminal_view.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use dev_server_projects::DevServer;
|
||||
use gpui::{ClickEvent, DismissEvent, EventEmitter, FocusHandle, FocusableView, Render, WeakView};
|
||||
use project::project_settings::ProjectSettings;
|
||||
use remote::SshConnectionOptions;
|
||||
|
@ -12,14 +11,10 @@ use ui::{
|
|||
};
|
||||
use workspace::{notifications::DetachAndPromptErr, ModalView, OpenOptions, Workspace};
|
||||
|
||||
use crate::{
|
||||
open_dev_server_project, open_ssh_project, remote_servers::reconnect_to_dev_server_project,
|
||||
RemoteServerProjects, SshSettings,
|
||||
};
|
||||
use crate::{open_ssh_project, SshSettings};
|
||||
|
||||
enum Host {
|
||||
RemoteProject,
|
||||
DevServerProject(DevServer),
|
||||
SshRemoteProject(SshConnectionOptions),
|
||||
}
|
||||
|
||||
|
@ -55,20 +50,9 @@ impl DisconnectedOverlay {
|
|||
return;
|
||||
}
|
||||
let handle = cx.view().downgrade();
|
||||
let dev_server = project
|
||||
.read(cx)
|
||||
.dev_server_project_id()
|
||||
.and_then(|id| {
|
||||
dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_for_project(id)
|
||||
})
|
||||
.cloned();
|
||||
|
||||
let ssh_connection_options = project.read(cx).ssh_connection_options(cx);
|
||||
let host = if let Some(dev_server) = dev_server {
|
||||
Host::DevServerProject(dev_server)
|
||||
} else if let Some(ssh_connection_options) = ssh_connection_options {
|
||||
let host = if let Some(ssh_connection_options) = ssh_connection_options {
|
||||
Host::SshRemoteProject(ssh_connection_options)
|
||||
} else {
|
||||
Host::RemoteProject
|
||||
|
@ -89,9 +73,6 @@ impl DisconnectedOverlay {
|
|||
cx.emit(DismissEvent);
|
||||
|
||||
match &self.host {
|
||||
Host::DevServerProject(dev_server) => {
|
||||
self.reconnect_to_dev_server(dev_server.clone(), cx);
|
||||
}
|
||||
Host::SshRemoteProject(ssh_connection_options) => {
|
||||
self.reconnect_to_ssh_remote(ssh_connection_options.clone(), cx);
|
||||
}
|
||||
|
@ -99,50 +80,6 @@ impl DisconnectedOverlay {
|
|||
}
|
||||
}
|
||||
|
||||
fn reconnect_to_dev_server(&self, dev_server: DevServer, cx: &mut ViewContext<Self>) {
|
||||
let Some(workspace) = self.workspace.upgrade() else {
|
||||
return;
|
||||
};
|
||||
let Some(dev_server_project_id) = workspace
|
||||
.read(cx)
|
||||
.project()
|
||||
.read(cx)
|
||||
.dev_server_project_id()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(project_id) = dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_project(dev_server_project_id)
|
||||
.and_then(|project| project.project_id)
|
||||
{
|
||||
return workspace.update(cx, move |_, cx| {
|
||||
open_dev_server_project(true, dev_server_project_id, project_id, cx)
|
||||
.detach_and_prompt_err("Failed to reconnect", cx, |_, _| None)
|
||||
});
|
||||
}
|
||||
|
||||
if dev_server.ssh_connection_string.is_some() {
|
||||
let task = workspace.update(cx, |_, cx| {
|
||||
reconnect_to_dev_server_project(
|
||||
cx.view().clone(),
|
||||
dev_server,
|
||||
dev_server_project_id,
|
||||
true,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
task.detach_and_prompt_err("Failed to reconnect", cx, |_, _| None);
|
||||
} else {
|
||||
return workspace.update(cx, |workspace, cx| {
|
||||
let handle = cx.view().downgrade();
|
||||
workspace.toggle_modal(cx, |cx| RemoteServerProjects::new(cx, handle))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn reconnect_to_ssh_remote(
|
||||
&self,
|
||||
connection_options: SshConnectionOptions,
|
||||
|
@ -200,13 +137,10 @@ impl DisconnectedOverlay {
|
|||
|
||||
impl Render for DisconnectedOverlay {
|
||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||
let can_reconnect = matches!(
|
||||
self.host,
|
||||
Host::DevServerProject(_) | Host::SshRemoteProject(_)
|
||||
);
|
||||
let can_reconnect = matches!(self.host, Host::SshRemoteProject(_));
|
||||
|
||||
let message = match &self.host {
|
||||
Host::RemoteProject | Host::DevServerProject(_) => {
|
||||
Host::RemoteProject => {
|
||||
"Your connection to the remote project has been lost.".to_string()
|
||||
}
|
||||
Host::SshRemoteProject(options) => {
|
||||
|
|
|
@ -4,7 +4,6 @@ mod ssh_connections;
|
|||
use remote::SshConnectionOptions;
|
||||
pub use ssh_connections::open_ssh_project;
|
||||
|
||||
use client::{DevServerProjectId, ProjectId};
|
||||
use disconnected_overlay::DisconnectedOverlay;
|
||||
use fuzzy::{StringMatch, StringMatchCandidate};
|
||||
use gpui::{
|
||||
|
@ -17,9 +16,7 @@ use picker::{
|
|||
highlighted_match_with_paths::{HighlightedMatchWithPaths, HighlightedText},
|
||||
Picker, PickerDelegate,
|
||||
};
|
||||
use remote_servers::reconnect_to_dev_server_project;
|
||||
pub use remote_servers::RemoteServerProjects;
|
||||
use rpc::proto::DevServerStatus;
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
pub use ssh_connections::SshSettings;
|
||||
|
@ -28,13 +25,12 @@ use std::{
|
|||
sync::Arc,
|
||||
};
|
||||
use ui::{
|
||||
prelude::*, tooltip_container, ButtonLike, IconWithIndicator, Indicator, KeyBinding, ListItem,
|
||||
ListItemSpacing, Tooltip,
|
||||
prelude::*, tooltip_container, ButtonLike, KeyBinding, ListItem, ListItemSpacing, Tooltip,
|
||||
};
|
||||
use util::{paths::PathExt, ResultExt};
|
||||
use workspace::{
|
||||
AppState, CloseIntent, ModalView, OpenOptions, SerializedWorkspaceLocation, Workspace,
|
||||
WorkspaceId, WORKSPACE_DB,
|
||||
CloseIntent, ModalView, OpenOptions, SerializedWorkspaceLocation, Workspace, WorkspaceId,
|
||||
WORKSPACE_DB,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Clone, Deserialize, Default)]
|
||||
|
@ -101,7 +97,7 @@ impl RecentProjects {
|
|||
}
|
||||
}
|
||||
|
||||
fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
||||
fn register(workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>) {
|
||||
workspace.register_action(|workspace, open_recent: &OpenRecent, cx| {
|
||||
let Some(recent_projects) = workspace.active_modal::<Self>(cx) else {
|
||||
Self::open(workspace, open_recent.create_new_window, cx);
|
||||
|
@ -114,20 +110,6 @@ impl RecentProjects {
|
|||
.update(cx, |picker, cx| picker.cycle_selection(cx))
|
||||
});
|
||||
});
|
||||
if workspace
|
||||
.project()
|
||||
.read(cx)
|
||||
.dev_server_project_id()
|
||||
.is_some()
|
||||
{
|
||||
workspace.register_action(|workspace, _: &workspace::Open, cx| {
|
||||
if workspace.active_modal::<Self>(cx).is_some() {
|
||||
cx.propagate();
|
||||
} else {
|
||||
Self::open(workspace, true, cx);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open(
|
||||
|
@ -254,13 +236,6 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
.map(|(_, path)| path.compact().to_string_lossy().into_owned())
|
||||
.collect::<Vec<_>>()
|
||||
.join(""),
|
||||
SerializedWorkspaceLocation::DevServer(dev_server_project) => {
|
||||
format!(
|
||||
"{}{}",
|
||||
dev_server_project.dev_server_name,
|
||||
dev_server_project.paths.join("")
|
||||
)
|
||||
}
|
||||
SerializedWorkspaceLocation::Ssh(ssh_project) => ssh_project
|
||||
.ssh_urls()
|
||||
.iter()
|
||||
|
@ -321,7 +296,10 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
cx.spawn(move |workspace, mut cx| async move {
|
||||
let continue_replacing = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.prepare_to_close(CloseIntent::ReplaceWindow, cx)
|
||||
workspace.prepare_to_close(
|
||||
CloseIntent::ReplaceWindow,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
if continue_replacing {
|
||||
|
@ -339,74 +317,56 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
workspace.open_workspace_for_paths(false, paths, cx)
|
||||
}
|
||||
}
|
||||
SerializedWorkspaceLocation::DevServer(dev_server_project) => {
|
||||
let store = dev_server_projects::Store::global(cx);
|
||||
let Some(project_id) = store.read(cx)
|
||||
.dev_server_project(dev_server_project.id)
|
||||
.and_then(|p| p.project_id)
|
||||
else {
|
||||
let server = store.read(cx).dev_server_for_project(dev_server_project.id);
|
||||
if server.is_some_and(|server| server.ssh_connection_string.is_some()) {
|
||||
return reconnect_to_dev_server_project(cx.view().clone(), server.unwrap().clone(), dev_server_project.id, replace_current_window, cx);
|
||||
} else {
|
||||
let dev_server_name = dev_server_project.dev_server_name.clone();
|
||||
return cx.spawn(|workspace, mut cx| async move {
|
||||
let response =
|
||||
cx.prompt(gpui::PromptLevel::Warning,
|
||||
"Dev Server is offline",
|
||||
Some(format!("Cannot connect to {}. To debug open the remote project settings.", dev_server_name).as_str()),
|
||||
&["Ok", "Open Settings"]
|
||||
).await?;
|
||||
if response == 1 {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
let handle = cx.view().downgrade();
|
||||
workspace.toggle_modal(cx, |cx| RemoteServerProjects::new(cx, handle))
|
||||
})?;
|
||||
} else {
|
||||
workspace.update(&mut cx, |workspace, cx| {
|
||||
RecentProjects::open(workspace, true, cx);
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
SerializedWorkspaceLocation::Ssh(ssh_project) => {
|
||||
let app_state = workspace.app_state().clone();
|
||||
|
||||
let replace_window = if replace_current_window {
|
||||
cx.window_handle().downcast::<Workspace>()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
open_dev_server_project(replace_current_window, dev_server_project.id, project_id, cx)
|
||||
}
|
||||
SerializedWorkspaceLocation::Ssh(ssh_project) => {
|
||||
let app_state = workspace.app_state().clone();
|
||||
|
||||
let replace_window = if replace_current_window {
|
||||
cx.window_handle().downcast::<Workspace>()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let open_options = OpenOptions {
|
||||
replace_window,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let open_options = OpenOptions {
|
||||
replace_window,
|
||||
..Default::default()
|
||||
};
|
||||
let args = SshSettings::get_global(cx).args_for(
|
||||
&ssh_project.host,
|
||||
ssh_project.port,
|
||||
&ssh_project.user,
|
||||
);
|
||||
let nickname = SshSettings::get_global(cx).nickname_for(
|
||||
&ssh_project.host,
|
||||
ssh_project.port,
|
||||
&ssh_project.user,
|
||||
);
|
||||
let connection_options = SshConnectionOptions {
|
||||
host: ssh_project.host.clone(),
|
||||
username: ssh_project.user.clone(),
|
||||
port: ssh_project.port,
|
||||
password: None,
|
||||
args,
|
||||
};
|
||||
|
||||
let args = SshSettings::get_global(cx).args_for(&ssh_project.host, ssh_project.port, &ssh_project.user);
|
||||
let nickname = SshSettings::get_global(cx).nickname_for(&ssh_project.host, ssh_project.port, &ssh_project.user);
|
||||
let connection_options = SshConnectionOptions {
|
||||
host: ssh_project.host.clone(),
|
||||
username: ssh_project.user.clone(),
|
||||
port: ssh_project.port,
|
||||
password: None,
|
||||
args,
|
||||
};
|
||||
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
|
||||
|
||||
let paths = ssh_project.paths.iter().map(PathBuf::from).collect();
|
||||
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
open_ssh_project(connection_options, paths, app_state, open_options, nickname, &mut cx).await
|
||||
})
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
open_ssh_project(
|
||||
connection_options,
|
||||
paths,
|
||||
app_state,
|
||||
open_options,
|
||||
nickname,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach_and_log_err(cx);
|
||||
.detach_and_log_err(cx);
|
||||
cx.emit(DismissEvent);
|
||||
}
|
||||
}
|
||||
|
@ -431,20 +391,6 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
|
||||
let (_, location) = self.workspaces.get(hit.candidate_id)?;
|
||||
|
||||
let dev_server_status =
|
||||
if let SerializedWorkspaceLocation::DevServer(dev_server_project) = location {
|
||||
let store = dev_server_projects::Store::global(cx).read(cx);
|
||||
Some(
|
||||
store
|
||||
.dev_server_project(dev_server_project.id)
|
||||
.and_then(|p| store.dev_server(p.dev_server_id))
|
||||
.map(|s| s.status)
|
||||
.unwrap_or_default(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut path_start_offset = 0;
|
||||
let paths = match location {
|
||||
SerializedWorkspaceLocation::Local(paths, order) => Arc::new(
|
||||
|
@ -457,13 +403,6 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
.collect(),
|
||||
),
|
||||
SerializedWorkspaceLocation::Ssh(ssh_project) => Arc::new(ssh_project.ssh_urls()),
|
||||
SerializedWorkspaceLocation::DevServer(dev_server_project) => {
|
||||
Arc::new(vec![PathBuf::from(format!(
|
||||
"{}:{}",
|
||||
dev_server_project.dev_server_name,
|
||||
dev_server_project.paths.join(", ")
|
||||
))])
|
||||
}
|
||||
};
|
||||
|
||||
let (match_labels, paths): (Vec<_>, Vec<_>) = paths
|
||||
|
@ -478,13 +417,7 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
.unzip();
|
||||
|
||||
let highlighted_match = HighlightedMatchWithPaths {
|
||||
match_label: HighlightedText::join(match_labels.into_iter().flatten(), ", ").color(
|
||||
if matches!(dev_server_status, Some(DevServerStatus::Offline)) {
|
||||
Color::Disabled
|
||||
} else {
|
||||
Color::Default
|
||||
},
|
||||
),
|
||||
match_label: HighlightedText::join(match_labels.into_iter().flatten(), ", "),
|
||||
paths,
|
||||
};
|
||||
|
||||
|
@ -507,24 +440,6 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
SerializedWorkspaceLocation::Ssh(_) => Icon::new(IconName::Server)
|
||||
.color(Color::Muted)
|
||||
.into_any_element(),
|
||||
SerializedWorkspaceLocation::DevServer(_) => {
|
||||
let indicator_color = match dev_server_status {
|
||||
Some(DevServerStatus::Online) => Color::Created,
|
||||
Some(DevServerStatus::Offline) => Color::Hidden,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
IconWithIndicator::new(
|
||||
Icon::new(IconName::Server).color(Color::Muted),
|
||||
Some(Indicator::dot()),
|
||||
)
|
||||
.indicator_color(indicator_color)
|
||||
.indicator_border_color(if selected {
|
||||
Some(cx.theme().colors().element_selected)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.into_any_element()
|
||||
}
|
||||
})
|
||||
})
|
||||
.child({
|
||||
|
@ -597,59 +512,6 @@ impl PickerDelegate for RecentProjectsDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
fn open_dev_server_project(
|
||||
replace_current_window: bool,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
project_id: ProjectId,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<anyhow::Result<()>> {
|
||||
if let Some(app_state) = AppState::global(cx).upgrade() {
|
||||
let handle = if replace_current_window {
|
||||
cx.window_handle().downcast::<Workspace>()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(handle) = handle {
|
||||
cx.spawn(move |workspace, mut cx| async move {
|
||||
let continue_replacing = workspace
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
workspace.prepare_to_close(CloseIntent::ReplaceWindow, cx)
|
||||
})?
|
||||
.await?;
|
||||
if continue_replacing {
|
||||
workspace
|
||||
.update(&mut cx, |_workspace, cx| {
|
||||
workspace::join_dev_server_project(
|
||||
dev_server_project_id,
|
||||
project_id,
|
||||
app_state,
|
||||
Some(handle),
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
} else {
|
||||
let task = workspace::join_dev_server_project(
|
||||
dev_server_project_id,
|
||||
project_id,
|
||||
app_state,
|
||||
None,
|
||||
cx,
|
||||
);
|
||||
cx.spawn(|_, _| async move {
|
||||
task.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
} else {
|
||||
Task::ready(Err(anyhow::anyhow!("App state not found")))
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the highlighted text for the name and path
|
||||
fn highlights_for_path(
|
||||
path: &Path,
|
||||
|
|
|
@ -1,19 +1,12 @@
|
|||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Context;
|
||||
use anyhow::Result;
|
||||
use dev_server_projects::{DevServer, DevServerId, DevServerProjectId};
|
||||
use editor::Editor;
|
||||
use file_finder::OpenPathDelegate;
|
||||
use futures::channel::oneshot;
|
||||
use futures::future::Shared;
|
||||
use futures::FutureExt;
|
||||
use gpui::canvas;
|
||||
use gpui::AsyncWindowContext;
|
||||
use gpui::ClipboardItem;
|
||||
use gpui::Task;
|
||||
use gpui::WeakView;
|
||||
|
@ -22,17 +15,10 @@ use gpui::{
|
|||
PromptLevel, ScrollHandle, View, ViewContext,
|
||||
};
|
||||
use picker::Picker;
|
||||
use project::terminals::wrap_for_ssh;
|
||||
use project::terminals::SshCommand;
|
||||
use project::Project;
|
||||
use remote::SshConnectionOptions;
|
||||
use rpc::proto::DevServerStatus;
|
||||
use settings::update_settings_file;
|
||||
use settings::Settings;
|
||||
use task::HideStrategy;
|
||||
use task::RevealStrategy;
|
||||
use task::SpawnInTerminal;
|
||||
use terminal_view::terminal_panel::TerminalPanel;
|
||||
use ui::{
|
||||
prelude::*, IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Scrollbar,
|
||||
ScrollbarState, Section, Tooltip,
|
||||
|
@ -43,7 +29,6 @@ use workspace::OpenOptions;
|
|||
use workspace::Toast;
|
||||
use workspace::{notifications::DetachAndPromptErr, ModalView, Workspace};
|
||||
|
||||
use crate::open_dev_server_project;
|
||||
use crate::ssh_connections::connect_over_ssh;
|
||||
use crate::ssh_connections::open_ssh_project;
|
||||
use crate::ssh_connections::RemoteSettingsContent;
|
||||
|
@ -1319,146 +1304,3 @@ impl Render for RemoteServerProjects {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reconnect_to_dev_server_project(
|
||||
workspace: View<Workspace>,
|
||||
dev_server: DevServer,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
replace_current_window: bool,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>> {
|
||||
let store = dev_server_projects::Store::global(cx);
|
||||
let reconnect = reconnect_to_dev_server(workspace.clone(), dev_server, cx);
|
||||
cx.spawn(|mut cx| async move {
|
||||
reconnect.await?;
|
||||
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(1000))
|
||||
.await;
|
||||
|
||||
if let Some(project_id) = store.update(&mut cx, |store, _| {
|
||||
store
|
||||
.dev_server_project(dev_server_project_id)
|
||||
.and_then(|p| p.project_id)
|
||||
})? {
|
||||
workspace
|
||||
.update(&mut cx, move |_, cx| {
|
||||
open_dev_server_project(
|
||||
replace_current_window,
|
||||
dev_server_project_id,
|
||||
project_id,
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn reconnect_to_dev_server(
|
||||
workspace: View<Workspace>,
|
||||
dev_server: DevServer,
|
||||
cx: &mut WindowContext,
|
||||
) -> Task<Result<()>> {
|
||||
let Some(ssh_connection_string) = dev_server.ssh_connection_string else {
|
||||
return Task::ready(Err(anyhow!("Can't reconnect, no ssh_connection_string")));
|
||||
};
|
||||
let dev_server_store = dev_server_projects::Store::global(cx);
|
||||
let get_access_token = dev_server_store.update(cx, |store, cx| {
|
||||
store.regenerate_dev_server_token(dev_server.id, cx)
|
||||
});
|
||||
|
||||
cx.spawn(|mut cx| async move {
|
||||
let access_token = get_access_token.await?.access_token;
|
||||
|
||||
spawn_ssh_task(
|
||||
workspace,
|
||||
dev_server_store,
|
||||
dev_server.id,
|
||||
ssh_connection_string.to_string(),
|
||||
access_token,
|
||||
&mut cx,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn spawn_ssh_task(
|
||||
workspace: View<Workspace>,
|
||||
dev_server_store: Model<dev_server_projects::Store>,
|
||||
dev_server_id: DevServerId,
|
||||
ssh_connection_string: String,
|
||||
access_token: String,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
let terminal_panel = workspace
|
||||
.update(cx, |workspace, cx| workspace.panel::<TerminalPanel>(cx))
|
||||
.ok()
|
||||
.flatten()
|
||||
.with_context(|| anyhow!("No terminal panel"))?;
|
||||
|
||||
let command = "sh".to_string();
|
||||
let args = vec![
|
||||
"-x".to_string(),
|
||||
"-c".to_string(),
|
||||
format!(
|
||||
r#"~/.local/bin/zed -v >/dev/stderr || (curl -f https://zed.dev/install.sh || wget -qO- https://zed.dev/install.sh) | sh && ZED_HEADLESS=1 ~/.local/bin/zed --dev-server-token {}"#,
|
||||
access_token
|
||||
),
|
||||
];
|
||||
|
||||
let ssh_connection_string = ssh_connection_string.to_string();
|
||||
let (command, args) = wrap_for_ssh(
|
||||
&SshCommand::DevServer(ssh_connection_string.clone()),
|
||||
Some((&command, &args)),
|
||||
None,
|
||||
HashMap::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
let terminal = terminal_panel
|
||||
.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.spawn_in_new_terminal(
|
||||
SpawnInTerminal {
|
||||
id: task::TaskId("ssh-remote".into()),
|
||||
full_label: "Install zed over ssh".into(),
|
||||
label: "Install zed over ssh".into(),
|
||||
command,
|
||||
args,
|
||||
command_label: ssh_connection_string.clone(),
|
||||
cwd: None,
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: false,
|
||||
reveal: RevealStrategy::Always,
|
||||
hide: HideStrategy::Never,
|
||||
env: Default::default(),
|
||||
shell: Default::default(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
terminal
|
||||
.update(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
|
||||
.await;
|
||||
|
||||
// There's a race-condition between the task completing successfully, and the server sending us the online status. Make it less likely we'll show the error state.
|
||||
if dev_server_store.update(cx, |this, _| this.dev_server_status(dev_server_id))?
|
||||
== DevServerStatus::Offline
|
||||
{
|
||||
cx.background_executor()
|
||||
.timer(Duration::from_millis(200))
|
||||
.await
|
||||
}
|
||||
|
||||
if dev_server_store.update(cx, |this, _| this.dev_server_status(dev_server_id))?
|
||||
== DevServerStatus::Offline
|
||||
{
|
||||
return Err(anyhow!("couldn't reconnect"))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -33,7 +33,6 @@ auto_update.workspace = true
|
|||
call.workspace = true
|
||||
client.workspace = true
|
||||
command_palette.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
extensions_ui.workspace = true
|
||||
feedback.workspace = true
|
||||
feature_flags.workspace = true
|
||||
|
|
|
@ -285,8 +285,7 @@ impl TitleBar {
|
|||
let room = room.read(cx);
|
||||
let project = self.project.read(cx);
|
||||
let is_local = project.is_local() || project.is_via_ssh();
|
||||
let is_dev_server_project = project.dev_server_project_id().is_some();
|
||||
let is_shared = (is_local || is_dev_server_project) && project.is_shared();
|
||||
let is_shared = is_local && project.is_shared();
|
||||
let is_muted = room.is_muted();
|
||||
let is_deafened = room.is_deafened().unwrap_or(false);
|
||||
let is_screen_sharing = room.is_screen_sharing();
|
||||
|
@ -299,7 +298,7 @@ impl TitleBar {
|
|||
|
||||
let mut children = Vec::new();
|
||||
|
||||
if (is_local || is_dev_server_project) && can_share_projects {
|
||||
if is_local && can_share_projects {
|
||||
children.push(
|
||||
Button::new(
|
||||
"toggle_sharing",
|
||||
|
|
|
@ -20,7 +20,7 @@ use gpui::{
|
|||
use project::{Project, RepositoryEntry};
|
||||
use recent_projects::{OpenRemote, RecentProjects, SshSettings};
|
||||
use remote::SshConnectionOptions;
|
||||
use rpc::proto::{self, DevServerStatus};
|
||||
use rpc::proto;
|
||||
use settings::Settings;
|
||||
use smallvec::SmallVec;
|
||||
use std::sync::Arc;
|
||||
|
@ -334,39 +334,6 @@ impl TitleBar {
|
|||
}
|
||||
|
||||
pub fn render_project_host(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
if let Some(dev_server) =
|
||||
self.project
|
||||
.read(cx)
|
||||
.dev_server_project_id()
|
||||
.and_then(|dev_server_project_id| {
|
||||
dev_server_projects::Store::global(cx)
|
||||
.read(cx)
|
||||
.dev_server_for_project(dev_server_project_id)
|
||||
})
|
||||
{
|
||||
return Some(
|
||||
ButtonLike::new("dev_server_trigger")
|
||||
.child(Indicator::dot().color(
|
||||
if dev_server.status == DevServerStatus::Online {
|
||||
Color::Created
|
||||
} else {
|
||||
Color::Disabled
|
||||
},
|
||||
))
|
||||
.child(
|
||||
Label::new(dev_server.name.clone())
|
||||
.size(LabelSize::Small)
|
||||
.line_height_style(LineHeightStyle::UiLabel),
|
||||
)
|
||||
.tooltip(move |cx| Tooltip::text("Project is hosted on a dev server", cx))
|
||||
.on_click(cx.listener(|this, _, cx| {
|
||||
if let Some(workspace) = this.workspace.upgrade() {
|
||||
recent_projects::RemoteServerProjects::open(workspace, cx)
|
||||
}
|
||||
}))
|
||||
.into_any_element(),
|
||||
);
|
||||
}
|
||||
if self.project.read(cx).is_via_ssh() {
|
||||
return self.render_ssh_project_host(cx);
|
||||
}
|
||||
|
|
|
@ -49,7 +49,6 @@ node_runtime.workspace = true
|
|||
parking_lot.workspace = true
|
||||
postage.workspace = true
|
||||
project.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
task.workspace = true
|
||||
release_channel.workspace = true
|
||||
remote.workspace = true
|
||||
|
|
|
@ -24,9 +24,7 @@ use model::{
|
|||
SerializedSshProject, SerializedWorkspace,
|
||||
};
|
||||
|
||||
use self::model::{
|
||||
DockStructure, LocalPathsOrder, SerializedDevServerProject, SerializedWorkspaceLocation,
|
||||
};
|
||||
use self::model::{DockStructure, LocalPathsOrder, SerializedWorkspaceLocation};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub(crate) struct SerializedAxis(pub(crate) gpui::Axis);
|
||||
|
@ -460,89 +458,6 @@ impl WorkspaceDb {
|
|||
})
|
||||
}
|
||||
|
||||
pub(crate) fn workspace_for_dev_server_project(
|
||||
&self,
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
) -> Option<SerializedWorkspace> {
|
||||
// Note that we re-assign the workspace_id here in case it's empty
|
||||
// and we've grabbed the most recent workspace
|
||||
let (
|
||||
workspace_id,
|
||||
dev_server_project_id,
|
||||
window_bounds,
|
||||
display,
|
||||
centered_layout,
|
||||
docks,
|
||||
window_id,
|
||||
): (
|
||||
WorkspaceId,
|
||||
Option<u64>,
|
||||
Option<SerializedWindowBounds>,
|
||||
Option<Uuid>,
|
||||
Option<bool>,
|
||||
DockStructure,
|
||||
Option<u64>,
|
||||
) = self
|
||||
.select_row_bound(sql! {
|
||||
SELECT
|
||||
workspace_id,
|
||||
dev_server_project_id,
|
||||
window_state,
|
||||
window_x,
|
||||
window_y,
|
||||
window_width,
|
||||
window_height,
|
||||
display,
|
||||
centered_layout,
|
||||
left_dock_visible,
|
||||
left_dock_active_panel,
|
||||
left_dock_zoom,
|
||||
right_dock_visible,
|
||||
right_dock_active_panel,
|
||||
right_dock_zoom,
|
||||
bottom_dock_visible,
|
||||
bottom_dock_active_panel,
|
||||
bottom_dock_zoom,
|
||||
window_id
|
||||
FROM workspaces
|
||||
WHERE dev_server_project_id = ?
|
||||
})
|
||||
.and_then(|mut prepared_statement| (prepared_statement)(dev_server_project_id.0))
|
||||
.context("No workspaces found")
|
||||
.warn_on_err()
|
||||
.flatten()?;
|
||||
|
||||
let dev_server_project_id = dev_server_project_id?;
|
||||
|
||||
let dev_server_project: SerializedDevServerProject = self
|
||||
.select_row_bound(sql! {
|
||||
SELECT id, path, dev_server_name
|
||||
FROM dev_server_projects
|
||||
WHERE id = ?
|
||||
})
|
||||
.and_then(|mut prepared_statement| (prepared_statement)(dev_server_project_id))
|
||||
.context("No remote project found")
|
||||
.warn_on_err()
|
||||
.flatten()?;
|
||||
|
||||
let location = SerializedWorkspaceLocation::DevServer(dev_server_project);
|
||||
|
||||
Some(SerializedWorkspace {
|
||||
id: workspace_id,
|
||||
location,
|
||||
center_group: self
|
||||
.get_center_pane_group(workspace_id)
|
||||
.context("Getting center group")
|
||||
.log_err()?,
|
||||
window_bounds,
|
||||
centered_layout: centered_layout.unwrap_or(false),
|
||||
display,
|
||||
docks,
|
||||
session_id: None,
|
||||
window_id,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn workspace_for_ssh_project(
|
||||
&self,
|
||||
ssh_project: &SerializedSshProject,
|
||||
|
@ -659,61 +574,6 @@ impl WorkspaceDb {
|
|||
|
||||
prepared_query(args).context("Updating workspace")?;
|
||||
}
|
||||
SerializedWorkspaceLocation::DevServer(dev_server_project) => {
|
||||
conn.exec_bound(sql!(
|
||||
DELETE FROM workspaces WHERE dev_server_project_id = ? AND workspace_id != ?
|
||||
))?((dev_server_project.id.0, workspace.id))
|
||||
.context("clearing out old locations")?;
|
||||
|
||||
conn.exec_bound(sql!(
|
||||
INSERT INTO dev_server_projects(
|
||||
id,
|
||||
path,
|
||||
dev_server_name
|
||||
) VALUES (?1, ?2, ?3)
|
||||
ON CONFLICT DO
|
||||
UPDATE SET
|
||||
path = ?2,
|
||||
dev_server_name = ?3
|
||||
))?(&dev_server_project)?;
|
||||
|
||||
// Upsert
|
||||
conn.exec_bound(sql!(
|
||||
INSERT INTO workspaces(
|
||||
workspace_id,
|
||||
dev_server_project_id,
|
||||
left_dock_visible,
|
||||
left_dock_active_panel,
|
||||
left_dock_zoom,
|
||||
right_dock_visible,
|
||||
right_dock_active_panel,
|
||||
right_dock_zoom,
|
||||
bottom_dock_visible,
|
||||
bottom_dock_active_panel,
|
||||
bottom_dock_zoom,
|
||||
timestamp
|
||||
)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, CURRENT_TIMESTAMP)
|
||||
ON CONFLICT DO
|
||||
UPDATE SET
|
||||
dev_server_project_id = ?2,
|
||||
left_dock_visible = ?3,
|
||||
left_dock_active_panel = ?4,
|
||||
left_dock_zoom = ?5,
|
||||
right_dock_visible = ?6,
|
||||
right_dock_active_panel = ?7,
|
||||
right_dock_zoom = ?8,
|
||||
bottom_dock_visible = ?9,
|
||||
bottom_dock_active_panel = ?10,
|
||||
bottom_dock_zoom = ?11,
|
||||
timestamp = CURRENT_TIMESTAMP
|
||||
))?((
|
||||
workspace.id,
|
||||
dev_server_project.id.0,
|
||||
workspace.docks,
|
||||
))
|
||||
.context("Updating workspace")?;
|
||||
},
|
||||
SerializedWorkspaceLocation::Ssh(ssh_project) => {
|
||||
conn.exec_bound(sql!(
|
||||
DELETE FROM workspaces WHERE ssh_project_id = ? AND workspace_id != ?
|
||||
|
@ -824,11 +684,10 @@ impl WorkspaceDb {
|
|||
}
|
||||
|
||||
query! {
|
||||
fn recent_workspaces() -> Result<Vec<(WorkspaceId, LocalPaths, LocalPathsOrder, Option<u64>, Option<u64>)>> {
|
||||
SELECT workspace_id, local_paths, local_paths_order, dev_server_project_id, ssh_project_id
|
||||
fn recent_workspaces() -> Result<Vec<(WorkspaceId, LocalPaths, LocalPathsOrder, Option<u64>)>> {
|
||||
SELECT workspace_id, local_paths, local_paths_order, ssh_project_id
|
||||
FROM workspaces
|
||||
WHERE local_paths IS NOT NULL
|
||||
OR dev_server_project_id IS NOT NULL
|
||||
OR ssh_project_id IS NOT NULL
|
||||
ORDER BY timestamp DESC
|
||||
}
|
||||
|
@ -843,13 +702,6 @@ impl WorkspaceDb {
|
|||
}
|
||||
}
|
||||
|
||||
query! {
|
||||
fn dev_server_projects() -> Result<Vec<SerializedDevServerProject>> {
|
||||
SELECT id, path, dev_server_name
|
||||
FROM dev_server_projects
|
||||
}
|
||||
}
|
||||
|
||||
query! {
|
||||
fn ssh_projects() -> Result<Vec<SerializedSshProject>> {
|
||||
SELECT id, host, port, paths, user
|
||||
|
@ -913,24 +765,9 @@ impl WorkspaceDb {
|
|||
) -> Result<Vec<(WorkspaceId, SerializedWorkspaceLocation)>> {
|
||||
let mut result = Vec::new();
|
||||
let mut delete_tasks = Vec::new();
|
||||
let dev_server_projects = self.dev_server_projects()?;
|
||||
let ssh_projects = self.ssh_projects()?;
|
||||
|
||||
for (id, location, order, dev_server_project_id, ssh_project_id) in
|
||||
self.recent_workspaces()?
|
||||
{
|
||||
if let Some(dev_server_project_id) = dev_server_project_id.map(DevServerProjectId) {
|
||||
if let Some(dev_server_project) = dev_server_projects
|
||||
.iter()
|
||||
.find(|rp| rp.id == dev_server_project_id)
|
||||
{
|
||||
result.push((id, dev_server_project.clone().into()));
|
||||
} else {
|
||||
delete_tasks.push(self.delete_workspace_by_id(id));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
for (id, location, order, ssh_project_id) in self.recent_workspaces()? {
|
||||
if let Some(ssh_project_id) = ssh_project_id.map(SshProjectId) {
|
||||
if let Some(ssh_project) = ssh_projects.iter().find(|rp| rp.id == ssh_project_id) {
|
||||
result.push((id, SerializedWorkspaceLocation::Ssh(ssh_project.clone())));
|
||||
|
|
|
@ -4,7 +4,6 @@ use crate::{
|
|||
};
|
||||
use anyhow::{Context, Result};
|
||||
use async_recursion::async_recursion;
|
||||
use client::DevServerProjectId;
|
||||
use db::sqlez::{
|
||||
bindable::{Bind, Column, StaticColumnCount},
|
||||
statement::Statement,
|
||||
|
@ -17,7 +16,6 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use ui::SharedString;
|
||||
use util::ResultExt;
|
||||
use uuid::Uuid;
|
||||
|
||||
|
@ -92,13 +90,6 @@ impl Column for SerializedSshProject {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||
pub struct SerializedDevServerProject {
|
||||
pub id: DevServerProjectId,
|
||||
pub dev_server_name: String,
|
||||
pub paths: Vec<SharedString>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct LocalPaths(Arc<Vec<PathBuf>>);
|
||||
|
||||
|
@ -176,49 +167,10 @@ impl Column for LocalPathsOrder {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<SerializedDevServerProject> for SerializedWorkspaceLocation {
|
||||
fn from(dev_server_project: SerializedDevServerProject) -> Self {
|
||||
Self::DevServer(dev_server_project)
|
||||
}
|
||||
}
|
||||
|
||||
impl StaticColumnCount for SerializedDevServerProject {}
|
||||
impl Bind for &SerializedDevServerProject {
|
||||
fn bind(&self, statement: &Statement, start_index: i32) -> Result<i32> {
|
||||
let next_index = statement.bind(&self.id.0, start_index)?;
|
||||
let next_index = statement.bind(&self.dev_server_name, next_index)?;
|
||||
let paths = serde_json::to_string(&self.paths)?;
|
||||
statement.bind(&paths, next_index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Column for SerializedDevServerProject {
|
||||
fn column(statement: &mut Statement, start_index: i32) -> Result<(Self, i32)> {
|
||||
let id = statement.column_int64(start_index)?;
|
||||
let dev_server_name = statement.column_text(start_index + 1)?.to_string();
|
||||
let paths = statement.column_text(start_index + 2)?.to_string();
|
||||
let paths: Vec<SharedString> = if paths.starts_with('[') {
|
||||
serde_json::from_str(&paths).context("JSON deserialization of paths failed")?
|
||||
} else {
|
||||
vec![paths.into()]
|
||||
};
|
||||
|
||||
Ok((
|
||||
Self {
|
||||
id: DevServerProjectId(id as u64),
|
||||
dev_server_name,
|
||||
paths,
|
||||
},
|
||||
start_index + 3,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum SerializedWorkspaceLocation {
|
||||
Local(LocalPaths, LocalPathsOrder),
|
||||
Ssh(SerializedSshProject),
|
||||
DevServer(SerializedDevServerProject),
|
||||
}
|
||||
|
||||
impl SerializedWorkspaceLocation {
|
||||
|
|
|
@ -16,7 +16,7 @@ use anyhow::{anyhow, Context as _, Result};
|
|||
use call::{call_settings::CallSettings, ActiveCall};
|
||||
use client::{
|
||||
proto::{self, ErrorCode, PanelId, PeerId},
|
||||
ChannelId, Client, DevServerProjectId, ErrorExt, ProjectId, Status, TypedEnvelope, UserStore,
|
||||
ChannelId, Client, ErrorExt, ProjectId, Status, TypedEnvelope, UserStore,
|
||||
};
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
@ -52,7 +52,7 @@ use notifications::{
|
|||
pub use pane::*;
|
||||
pub use pane_group::*;
|
||||
pub use persistence::{
|
||||
model::{ItemId, LocalPaths, SerializedDevServerProject, SerializedWorkspaceLocation},
|
||||
model::{ItemId, LocalPaths, SerializedWorkspaceLocation},
|
||||
WorkspaceDb, DB as WORKSPACE_DB,
|
||||
};
|
||||
use persistence::{
|
||||
|
@ -97,7 +97,7 @@ use ui::{
|
|||
IntoElement, ParentElement as _, Pixels, SharedString, Styled as _, ViewContext,
|
||||
VisualContext as _, WindowContext,
|
||||
};
|
||||
use util::{maybe, ResultExt, TryFutureExt};
|
||||
use util::{ResultExt, TryFutureExt};
|
||||
use uuid::Uuid;
|
||||
pub use workspace_settings::{
|
||||
AutosaveSetting, RestoreOnStartupBehavior, TabBarSettings, WorkspaceSettings,
|
||||
|
@ -2057,7 +2057,7 @@ impl Workspace {
|
|||
|
||||
fn add_folder_to_project(&mut self, _: &AddFolderToProject, cx: &mut ViewContext<Self>) {
|
||||
let project = self.project.read(cx);
|
||||
if project.is_via_collab() && project.dev_server_project_id().is_none() {
|
||||
if project.is_via_collab() {
|
||||
self.show_error(
|
||||
&anyhow!("You cannot add folders to someone else's project"),
|
||||
cx,
|
||||
|
@ -4133,20 +4133,6 @@ impl Workspace {
|
|||
} else {
|
||||
None
|
||||
}
|
||||
} else if let Some(dev_server_project_id) = self.project().read(cx).dev_server_project_id()
|
||||
{
|
||||
let store = dev_server_projects::Store::global(cx).read(cx);
|
||||
maybe!({
|
||||
let project = store.dev_server_project(dev_server_project_id)?;
|
||||
let dev_server = store.dev_server(project.dev_server_id)?;
|
||||
|
||||
let dev_server_project = SerializedDevServerProject {
|
||||
id: dev_server_project_id,
|
||||
dev_server_name: dev_server.name.to_string(),
|
||||
paths: project.paths.to_vec(),
|
||||
};
|
||||
Some(SerializedWorkspaceLocation::DevServer(dev_server_project))
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -5180,13 +5166,12 @@ async fn join_channel_internal(
|
|||
if let Some(workspace) = requesting_window {
|
||||
let project = workspace.update(cx, |workspace, cx| {
|
||||
let project = workspace.project.read(cx);
|
||||
let is_dev_server = project.dev_server_project_id().is_some();
|
||||
|
||||
if !is_dev_server && !CallSettings::get_global(cx).share_on_join {
|
||||
if !CallSettings::get_global(cx).share_on_join {
|
||||
return None;
|
||||
}
|
||||
|
||||
if (project.is_local() || project.is_via_ssh() || is_dev_server)
|
||||
if (project.is_local() || project.is_via_ssh())
|
||||
&& project.visible_worktrees(cx).any(|tree| {
|
||||
tree.read(cx)
|
||||
.root_entry()
|
||||
|
@ -5685,84 +5670,6 @@ fn serialize_ssh_project(
|
|||
})
|
||||
}
|
||||
|
||||
pub fn join_dev_server_project(
|
||||
dev_server_project_id: DevServerProjectId,
|
||||
project_id: ProjectId,
|
||||
app_state: Arc<AppState>,
|
||||
window_to_replace: Option<WindowHandle<Workspace>>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<WindowHandle<Workspace>>> {
|
||||
let windows = cx.windows();
|
||||
cx.spawn(|mut cx| async move {
|
||||
let existing_workspace = windows.into_iter().find_map(|window| {
|
||||
window.downcast::<Workspace>().and_then(|window| {
|
||||
window
|
||||
.update(&mut cx, |workspace, cx| {
|
||||
if workspace.project().read(cx).remote_id() == Some(project_id.0) {
|
||||
Some(window)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or(None)
|
||||
})
|
||||
});
|
||||
|
||||
let serialized_workspace: Option<SerializedWorkspace> =
|
||||
persistence::DB.workspace_for_dev_server_project(dev_server_project_id);
|
||||
|
||||
let workspace = if let Some(existing_workspace) = existing_workspace {
|
||||
existing_workspace
|
||||
} else {
|
||||
let project = Project::remote(
|
||||
project_id.0,
|
||||
app_state.client.clone(),
|
||||
app_state.user_store.clone(),
|
||||
app_state.languages.clone(),
|
||||
app_state.fs.clone(),
|
||||
cx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let workspace_id = if let Some(ref serialized_workspace) = serialized_workspace {
|
||||
serialized_workspace.id
|
||||
} else {
|
||||
persistence::DB.next_id().await?
|
||||
};
|
||||
|
||||
if let Some(window_to_replace) = window_to_replace {
|
||||
cx.update_window(window_to_replace.into(), |_, cx| {
|
||||
cx.replace_root_view(|cx| {
|
||||
Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
|
||||
});
|
||||
})?;
|
||||
window_to_replace
|
||||
} else {
|
||||
let window_bounds_override = window_bounds_env_override();
|
||||
cx.update(|cx| {
|
||||
let mut options = (app_state.build_window_options)(None, cx);
|
||||
options.window_bounds = window_bounds_override.map(WindowBounds::Windowed);
|
||||
cx.open_window(options, |cx| {
|
||||
cx.new_view(|cx| {
|
||||
Workspace::new(Some(workspace_id), project, app_state.clone(), cx)
|
||||
})
|
||||
})
|
||||
})??
|
||||
}
|
||||
};
|
||||
|
||||
workspace
|
||||
.update(&mut cx, |_, cx| {
|
||||
cx.activate(true);
|
||||
cx.activate_window();
|
||||
open_items(serialized_workspace, vec![], app_state, cx)
|
||||
})?
|
||||
.await?;
|
||||
|
||||
anyhow::Ok(workspace)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn join_in_room_project(
|
||||
project_id: u64,
|
||||
follow_user_id: u64,
|
||||
|
|
|
@ -36,7 +36,6 @@ command_palette.workspace = true
|
|||
command_palette_hooks.workspace = true
|
||||
copilot.workspace = true
|
||||
db.workspace = true
|
||||
dev_server_projects.workspace = true
|
||||
diagnostics.workspace = true
|
||||
editor.workspace = true
|
||||
env_logger.workspace = true
|
||||
|
@ -52,7 +51,6 @@ git.workspace = true
|
|||
git_hosting_providers.workspace = true
|
||||
go_to_line.workspace = true
|
||||
gpui = { workspace = true, features = ["wayland", "x11", "font-kit"] }
|
||||
headless.workspace = true
|
||||
http_client.workspace = true
|
||||
image_viewer.workspace = true
|
||||
inline_completion_button.workspace = true
|
||||
|
|
|
@ -11,7 +11,7 @@ use assistant::PromptBuilder;
|
|||
use chrono::Offset;
|
||||
use clap::{command, Parser};
|
||||
use cli::FORCE_CLI_MODE_ENV_VAR_NAME;
|
||||
use client::{parse_zed_link, Client, DevServerToken, ProxySettings, UserStore};
|
||||
use client::{parse_zed_link, Client, ProxySettings, UserStore};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use db::kvp::{GLOBAL_KEY_VALUE_STORE, KEY_VALUE_STORE};
|
||||
use editor::Editor;
|
||||
|
@ -20,8 +20,8 @@ use fs::{Fs, RealFs};
|
|||
use futures::{future, StreamExt};
|
||||
use git::GitHostingProviderRegistry;
|
||||
use gpui::{
|
||||
Action, App, AppContext, AsyncAppContext, Context, DismissEvent, Global, Task,
|
||||
UpdateGlobal as _, VisualContext,
|
||||
Action, App, AppContext, AsyncAppContext, Context, DismissEvent, UpdateGlobal as _,
|
||||
VisualContext,
|
||||
};
|
||||
use http_client::{read_proxy_from_env, Uri};
|
||||
use language::LanguageRegistry;
|
||||
|
@ -136,43 +136,6 @@ fn fail_to_open_window(e: anyhow::Error, _cx: &mut AppContext) {
|
|||
}
|
||||
}
|
||||
|
||||
enum AppMode {
|
||||
Headless(DevServerToken),
|
||||
Ui,
|
||||
}
|
||||
impl Global for AppMode {}
|
||||
|
||||
fn init_headless(
|
||||
dev_server_token: DevServerToken,
|
||||
app_state: Arc<AppState>,
|
||||
cx: &mut AppContext,
|
||||
) -> Task<Result<()>> {
|
||||
match cx.try_global::<AppMode>() {
|
||||
Some(AppMode::Headless(token)) if token == &dev_server_token => return Task::ready(Ok(())),
|
||||
Some(_) => {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"zed is already running. Use `kill {}` to stop it",
|
||||
process::id()
|
||||
)))
|
||||
}
|
||||
None => {
|
||||
cx.set_global(AppMode::Headless(dev_server_token.clone()));
|
||||
}
|
||||
};
|
||||
let client = app_state.client.clone();
|
||||
client.set_dev_server_token(dev_server_token);
|
||||
headless::init(
|
||||
client.clone(),
|
||||
headless::AppState {
|
||||
languages: app_state.languages.clone(),
|
||||
user_store: app_state.user_store.clone(),
|
||||
fs: app_state.fs.clone(),
|
||||
node_runtime: app_state.node_runtime.clone(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
}
|
||||
|
||||
// init_common is called for both headless and normal mode.
|
||||
fn init_common(app_state: Arc<AppState>, cx: &mut AppContext) -> Arc<PromptBuilder> {
|
||||
SystemAppearance::init(cx);
|
||||
|
@ -223,19 +186,6 @@ fn init_ui(
|
|||
prompt_builder: Arc<PromptBuilder>,
|
||||
cx: &mut AppContext,
|
||||
) -> Result<()> {
|
||||
match cx.try_global::<AppMode>() {
|
||||
Some(AppMode::Headless(_)) => {
|
||||
return Err(anyhow!(
|
||||
"zed is already running in headless mode. Use `kill {}` to stop it",
|
||||
process::id()
|
||||
))
|
||||
}
|
||||
Some(AppMode::Ui) => return Ok(()),
|
||||
None => {
|
||||
cx.set_global(AppMode::Ui);
|
||||
}
|
||||
};
|
||||
|
||||
load_embedded_fonts(cx);
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
|
@ -252,7 +202,6 @@ fn init_ui(
|
|||
go_to_line::init(cx);
|
||||
file_finder::init(cx);
|
||||
tab_switcher::init(cx);
|
||||
dev_server_projects::init(app_state.client.clone(), cx);
|
||||
outline::init(cx);
|
||||
project_symbols::init(cx);
|
||||
project_panel::init(Assets, cx);
|
||||
|
@ -426,22 +375,15 @@ fn main() {
|
|||
app.on_reopen(move |cx| {
|
||||
if let Some(app_state) = AppState::try_global(cx).and_then(|app_state| app_state.upgrade())
|
||||
{
|
||||
let ui_has_launched = cx
|
||||
.try_global::<AppMode>()
|
||||
.map(|mode| matches!(mode, AppMode::Ui))
|
||||
.unwrap_or(false);
|
||||
|
||||
if ui_has_launched {
|
||||
cx.spawn({
|
||||
let app_state = app_state.clone();
|
||||
|mut cx| async move {
|
||||
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
|
||||
fail_to_open_window_async(e, &mut cx)
|
||||
}
|
||||
cx.spawn({
|
||||
let app_state = app_state.clone();
|
||||
|mut cx| async move {
|
||||
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
|
||||
fail_to_open_window_async(e, &mut cx)
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -590,30 +532,16 @@ fn main() {
|
|||
handle_open_request(request, app_state.clone(), prompt_builder.clone(), cx);
|
||||
}
|
||||
None => {
|
||||
if let Some(dev_server_token) = args.dev_server_token {
|
||||
let task =
|
||||
init_headless(DevServerToken(dev_server_token), app_state.clone(), cx);
|
||||
cx.spawn(|cx| async move {
|
||||
if let Err(e) = task.await {
|
||||
log::error!("{}", e);
|
||||
cx.update(|cx| cx.quit()).log_err();
|
||||
} else {
|
||||
log::info!("connected!");
|
||||
init_ui(app_state.clone(), prompt_builder.clone(), cx).unwrap();
|
||||
cx.spawn({
|
||||
let app_state = app_state.clone();
|
||||
|mut cx| async move {
|
||||
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
|
||||
fail_to_open_window_async(e, &mut cx)
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
} else {
|
||||
init_ui(app_state.clone(), prompt_builder.clone(), cx).unwrap();
|
||||
cx.spawn({
|
||||
let app_state = app_state.clone();
|
||||
|mut cx| async move {
|
||||
if let Err(e) = restore_or_create_workspace(app_state, &mut cx).await {
|
||||
fail_to_open_window_async(e, &mut cx)
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -927,7 +855,6 @@ async fn restore_or_create_workspace(
|
|||
})
|
||||
.detach();
|
||||
}
|
||||
SerializedWorkspaceLocation::DevServer(_) => {}
|
||||
}
|
||||
}
|
||||
} else if matches!(KEY_VALUE_STORE.read_kvp(FIRST_OPEN), Ok(None)) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::restorable_workspace_locations;
|
||||
use crate::{handle_open_request, init_headless, init_ui};
|
||||
use crate::{handle_open_request, init_ui};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use assistant::PromptBuilder;
|
||||
use cli::{ipc, IpcHandshake};
|
||||
|
@ -21,8 +21,8 @@ use remote::SshConnectionOptions;
|
|||
use settings::Settings;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use std::{process, thread};
|
||||
use util::paths::PathWithPosition;
|
||||
use util::ResultExt;
|
||||
use welcome::{show_welcome_view, FIRST_OPEN};
|
||||
|
@ -262,38 +262,9 @@ pub async fn handle_cli_connection(
|
|||
paths,
|
||||
wait,
|
||||
open_new_workspace,
|
||||
dev_server_token,
|
||||
|
||||
env,
|
||||
} => {
|
||||
if let Some(dev_server_token) = dev_server_token {
|
||||
match cx
|
||||
.update(|cx| {
|
||||
init_headless(client::DevServerToken(dev_server_token), app_state, cx)
|
||||
})
|
||||
.unwrap()
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
responses
|
||||
.send(CliResponse::Stdout {
|
||||
message: format!("zed (pid {}) connected!", process::id()),
|
||||
})
|
||||
.log_err();
|
||||
responses.send(CliResponse::Exit { status: 0 }).log_err();
|
||||
}
|
||||
Err(error) => {
|
||||
responses
|
||||
.send(CliResponse::Stderr {
|
||||
message: format!("{error}"),
|
||||
})
|
||||
.log_err();
|
||||
responses.send(CliResponse::Exit { status: 1 }).log_err();
|
||||
cx.update(|cx| cx.quit()).log_err();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if !urls.is_empty() {
|
||||
cx.update(|cx| {
|
||||
match OpenRequest::parse(urls, cx) {
|
||||
|
@ -459,7 +430,6 @@ async fn open_workspaces(
|
|||
// We don't set `errored` here, because for ssh projects, the
|
||||
// error is displayed in the window.
|
||||
}
|
||||
SerializedWorkspaceLocation::DevServer(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue